有趣的Code Poster

简介: 有趣的Code Poster

Pete Corey的博客Build your own code poster with Elixir》展示了如何通过Elixir实现一个类似Commits.io一样的功能,即可以将自己的代码融合到一张LOGO图片中,例如这样的效果:

image.png

左侧是原图,右侧则将代码重叠到图中有颜色的部分,形成一种颇有极客范儿的图片。这很有趣。

实现代码并不复杂,不到200行代码。虽然简单,代码却很好地体现了Elixir编程的风格。这种风格提倡运用|>管道符以流的形式传递数据,体现函数组合子的强大威力。针对问题域,我们的解决方案是思考数据流动的方向,以及处理数据的各个阶段,并将其组合成一个顺序的执行流程。

以Code Poster为例,其执行流程如下所示:

  • 读取代码文件
  • 读取图片文件
  • 生成Text元素集(这个过程会完成代码字符与颜色的融合)
  • 生成SVG
  • 保存SVG文件

Elixir的管道运算符,会自然地让我们想起Unix中实现的Pipe and Filter模式,每个Filter均被定义为一个统一的接口:输入为stdin,输出为stdout或者stderr。

image.png

在Unix中,这种统一接口可以被隐喻为文件(file),上图中的stdin、stdout与stderr都属于file descriptor,可以像操作文件那样读或写字节流(stream of bytes)。这里所谓的文件是一个宽泛的概念,可以是一个文件系统,也可以通过管道(pipe)将字节流传递到另一个进程,可以是Unix Socket,设备驱动,内核API以及TCP连接。

那么,在Code Poster场景下,这个统一接口是什么呢?或者说我们需要抽象的数据究竟是什么呢?分析前面的执行流程,读取一个code文件与读取一个image文件,返回的结果其实完全不同。那么,如这般不同的数据结构如何才能够像Stream一样通过管道连接起来呢?

在Elixir中,我们通常通过定义一个struct来完成对数据的抽象。整个管道处理中需要的数据会作为一个“并集”被定义到struct的属性中。例如,load code file操作需要一个code path,返回文件中的代码code;load image file操作则需要一个image path,返回一个image结构体。image结构体包含了图片的width和所有像素(pixels)。这些数据都是construct text element步骤需要的,在对这些数据进行处理后,该步骤又会返回生成的text elements集合;然后再交给construct svg,最后生成一个svg对象并保存。这个过程如下图所示:

image.png

整个过程传递的数据为PosterData,它好像是一个数据容器一般,各个执行步骤都会对该数据容器传来的相关数据进行处理,然后再将获得的结果塞到该容器中,再接力传递给下一个。PosterData的定义如下:

defstruct code_path: nil,
            image_path: nil,
            ratio: nil,
            final_width: nil,
            final_height: nil,
            out_path: nil,
            code: nil,
            image: nil,
            pixels: nil,
            text_elements: nil,
            svg: nil

Elixir使用管道操作符|>非常好地改善了代码的阅读体验。阅读如下代码,与阅读自然语言没有太大区别:

def go(ratio, final_width, final_height, code_path, image_path, out_path) do
    %PosterData{
      ratio: ratio,
      final_width: final_width,
      final_height: final_height,
      code_path: code_path,
      image_path: image_path,
      out_path: out_path
    }
    |> load_code
    |> load_image
    |> construct_text_elements
    |> construct_svg
    |> save_svg
  end

在编写这样的方法时,我们一定要注意方法的抽象层次,即遵循所谓的单一抽象层次原则。该原则要求一个函数中的所有操作都处于相同的抽象层。只有如此,才不会让函数表达的意思失衡,有的隐藏了细节,有的又暴露了不必要的内容。

我们可以通过对需求的任务逐层拆分来保证这一点。前面提到的流程是最高抽象层的任务分解,而针对load code file,又可以拆分为如下的子任务:

  • 读取代码文件
  • 去除文件内容中的空格、tab、换行符(表现的业务含义就是拼接这些代码字符)
  • 转换为字符集合

load_code函数体现了这样的流程:

def load_code(data = %PosterData{code_path: code_path}) do
    Logger.debug("Loading code from '#{code_path}'...")
    code = code_path
    |> File.read!
    |> join_code
    |> String.codepoints
    %{data | code: code}
  end

其中join_code接收的是File.read!函数读取的文件内容,它又可以继续拆分子任务:

def join_code(code) do
    Logger.debug("Joining code...")
    code
    |> String.trim
    |> String.replace(~r/\s*\n+\s*/, " ")
    |> String.replace(~r/\s/," ")
  end

多么美妙的代码表现力!又是多么清晰的任务分解层次!管道操作符将整个业务盘活了,就好像赋予了代码灵魂一般。

✟ 本文的代码全部来自Pete Corey在Github的Repository

相关文章
|
7月前
|
小程序 前端开发
【微信小程序】-- 常用的基础内容组件介绍 -- text & rich-text & progress & icon(七)
【微信小程序】-- 常用的基础内容组件介绍 -- text & rich-text & progress & icon(七)
|
6月前
QR Code
【6月更文挑战第12天】
255 1
|
5月前
|
Web App开发 前端开发
canvas保存图片时,谷歌浏览器Chrome报错【解决方案】Not allowed to navigate top frame to data URL
canvas保存图片时,谷歌浏览器Chrome报错【解决方案】Not allowed to navigate top frame to data URL
162 0
|
7月前
|
前端开发 JavaScript
screenshot-to-code
screenshot-to-code
260 0
|
缓存 前端开发 JavaScript
实现一个 Code Pen:(二)在 Next.js 中使用 Monaco Editor
Monaco Editor 是 VS Code 中使用的开源代码编辑器, 拥有代码高亮和代码自动补全的功能,本文介绍 Monaco Editor 的加载方式和 React 组件封装
961 0
实现一个 Code Pen:(二)在 Next.js 中使用 Monaco Editor
EasyUI format 图片的问题
需求:在easyUI的datagrid列表里有一个字段里面的图片存的是***.jpg,***.jpg 等格式的图片,现在要实现在列表点击查看每一张图片,效果实现是这样的。
115 0
EasyUI format 图片的问题
|
Web App开发 安全
Not allowed to navigate top frame to data URL
Not allowed to navigate top frame to data URL
332 0
elementUI:Upload组件list-type: picture-card上传闪动
elementUI:Upload组件list-type: picture-card上传闪动
581 0
|
JSON 前端开发 数据格式
前端上传前预览文件image,text,json,video,audio
前天有个需求,上传前需要校验视频长度,然后让我出个 Demo。 预览文件 demo 其实预览功能实现上都差不多,所以今天我们都来实现一下咯。
284 0
前端上传前预览文件image,text,json,video,audio
|
JSON Java 程序员
code S: code style & code standard
关于代码风格与代码规范的二三事
338 0