开发者用35行Ruby实现文学编程系统Rubylit
来源标题:Rubylit: A literate programming system in 35 lines of Ruby (2025)
一个名为Rubylit的文学编程系统仅用35行Ruby代码实现,README.md文件本身即是可运行程序。该项目采用Donald Knuth提出的文学编程概念,将所有源码缩进处理,使文档同时是合法的Markdown文件,通过一个非文学化的stage1.rb脚本完成初始的tangle处理。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
这个README.md文件的输出是一个程序,它能把文档变成: 这是一个文学编程程序 (wikipedia.org) 概念来自Donald Knuth。 (更新:我写过关于制作这个小程序的感受: literate-programming。) 为了让流程跑起来,有一个非文学化的stage1.rb脚本 负责最初的“tangle”步骤。你马上就会明白那是什么意思。 为了让这套文学编程机制运作,所有源码都缩进。这不仅 让源码形式易于阅读,而且通过选择这种方法, 我还让这份文档本身成为一个合法的Markdown文件,所以README在 通过RepoRat等工具在Web上以HTML输出查看时会被 正确格式化。 下面是它的开始方式。程序把根文件名作为命令行 参数: fname = ARGV[0] 我最初想用“.lit”文件扩展名,但真正的魔法发生在 我意识到可以用“.md”,让README本身成为程序的时候: fname_input = “#{fname}.md” 接下来,我对输入文档进行“tangle”和“weave”: <<Tangle>> <<Weave>> “Tangle”创建程序,“Weave”创建文档。 那些小小的<<括号东西>>是文学编程宏, 用来包含文档其他部分的源码(由 Markdown子标题标识)。 文学编程的妙处在于源码可以以任意顺序呈现, 所以你可以按自己喜欢的方式解释它。tangle过程 把它重新排序,使其真正可运行。 Knuth设想并在其最初的‘WEB’系统中实现的完整文学编程概念 不仅允许你包含代码片段, 甚至允许你定义参数化宏,所以文学文档 实际上是底层编程语言之上的元语言! 我为这个演示只实现了一个粗糙的“include”宏,但 仅此一点就给了我极大的灵活性! 下面是我如何让它工作的。我有一个segments哈希,用来存储 文学程序中一个“segment”的所有行。我让程序 从一个用:start符号标识的segment开始: myseg = :start segments = {} segments[myseg] = [] 然后我遍历源文档中的所有行,只处理 两个特殊情况。(所有其他行都被视为文学程序的“文档” 部分,在这里完全被忽略!) File.open(fname_input).each do |line| <<Handle segments>> <<Handle code lines>> end 在把行收集到segments之后,我递归地跟随 include,把最终程序写入文件: <<Write the program>> Handle segments 当我看到行首的“## ”时,我知道这是一个Markdown 二级标题,我用它来表示新的代码segment。它们 不必包含代码,即使包含,也不必 被使用。 这里你可以看到,我把当前segment设置为 标题名,并初始化一个新数组来存储代码行: if(line.start_with?(‘## ’)) myseg = line[3..].chomp segments[myseg] = [] end Handle code lines 当我看到行首有一个空格时,我知道这是缩进的源代码。 这非常严格、不灵活,只是这个 非工业级演示程序的一个例子。:-) if(line.start_with?(' ')) segments[myseg].push(line) end Write the program 有趣的部分来了!我有个叫put_lines的递归方法,它 接收一个打开的目标文件、一个有名字的代码segment哈希(每个segment 是一个行数组),以及一个要打印的目标segment。 我检查每一行,看它是不是<<宏东西>>(include请求)。 如果是,我就递归进入所请求的segment。否则,我只把 当前行输出到文件: def put_lines(file, segments, sname) segments[sname].each do |line| if(m = /^\s*<<([^>]+)>>\s*$/.match(line)) put_lines(file, segments, m[1]) else file.puts line end end end 为了启动上述递归过程,我打开“.rb”输出文件并请求 :start segment: File.open("#{fname}.rb", 'w') do |out| put_lines(out, segments, :start) end 就是这样! 应用的“weave”部分是文档创建部分。 由于我这个文学编程方案是把它编码为纯Markdown, 我本可以依赖外部工具来创建HTML(事实上,这大概 就是你此刻阅读这个README的方式)。 但既然Ruby的标准库自带Markdown解析器, 我想不妨把它包含进来。解析器和生成器是 RDoc(Ruby文档)模块的一部分: require 'rdoc' Markdown源就是文学程序文档(是的,我们在 Tangle过程中读过它,在Weave中还会再读一次): data = File.read(fname_input) 然后是一些样板代码。RDoc就像一个迷你Pandoc,它能接收输入 并以多种不同格式产生输出,我们为这种 灵活性付出了一些复杂度的代价: formatter = RDoc::Markup::ToHtml.new(RDoc::Options.new, nil) html = RDoc::Markdown.parse(data).accept(formatter) 然后我把它写入一个“.html”文件,前后加上开始和 结束文档标签: File.open("#{fname}.html", 'w') do |out| out.puts("<html><body>") out.print(html) out.puts("</body></html>") end 就是这样! 要把这个README变成程序,从“stage1”开始,它只包含 过程的“tangle”部分(没有文档输出): $ ruby stage1.rb README Found segment 'Tangle' Found segment 'Handle segments' Found segment 'Handle code lines' Found segment 'Write the program' Found segment 'Weave' Found segment 'Comparing this document with its output'
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。