lilp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +4 -0
- data/Rakefile +10 -0
- data/bin/lilp +9 -0
- data/lib/lilp.rb +12 -0
- data/lib/lilp/base.rb +279 -0
- data/lib/lilp/version.rb +6 -0
- data/lilp.gemspec +20 -0
- metadata +71 -0
data/Gemfile
ADDED
data/Rakefile
ADDED
data/bin/lilp
ADDED
data/lib/lilp.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# This file lists all the the dependencies of lilp.
|
2
|
+
# The `optparse` library is used to handle the command line options.
|
3
|
+
# The redcarpet library is a markdown parser with a very nice API.
|
4
|
+
# We are going to use it in order to create the lilp renderer.
|
5
|
+
require "optparse"
|
6
|
+
require 'redcarpet'
|
7
|
+
|
8
|
+
# Next, let's require our own files. They are located under the lilp/
|
9
|
+
# directory.
|
10
|
+
require 'lilp/version'
|
11
|
+
require 'lilp/base.rb'
|
12
|
+
|
data/lib/lilp/base.rb
ADDED
@@ -0,0 +1,279 @@
|
|
1
|
+
# lilp is a literate programming pre-processor that allows you to write literate programs
|
2
|
+
# using Markdown. In its current state, it is mainly an experiment, a toy.
|
3
|
+
# This file is the main program file of the lilp application. Here is how we are going to
|
4
|
+
# decompose it.
|
5
|
+
# I propose we dive directly into the subject's by specifying how to parse and render source
|
6
|
+
# code from lilp files. After that, we'll see how to handle option parsing for our command
|
7
|
+
# line application. And lastly, we'll tie everything together.
|
8
|
+
# lilp files are simply files that end with a 'md' extension. They are valid markdown files.
|
9
|
+
# In order to simplify the parsing phase, I am using the `redcarpet` library. This library
|
10
|
+
# is easy to extend.
|
11
|
+
# What we want to do is to create a 'render'. A render is an object that outputs a stream.
|
12
|
+
# For that, it defines a number of hooks called by redcarpet's parser. What we need to do
|
13
|
+
# is to specify a valid 'render' object, and make it output a program. Let's do that now.
|
14
|
+
# _Literate Render_
|
15
|
+
# Let's define here our render. It is a class that inherite from Redcarpet::Render::Base
|
16
|
+
# class LiterateRender < Redcarpet::Render::Base
|
17
|
+
# COMMENT_SIGN = "# "
|
18
|
+
#
|
19
|
+
# Now, redcarpet's parser will call a number of hook, depending on what has been found
|
20
|
+
# in the given lilp file. These hooks can be either `header`, `paragraph`, `block_code`
|
21
|
+
# or other. Here is the formal specification:
|
22
|
+
# def preprocess(full_document)
|
23
|
+
# @macro = {}
|
24
|
+
# full_document
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# def header(text, header_level)
|
28
|
+
# if header_level == 3
|
29
|
+
# @macro[$1.to_sym] if text =~ /Call\: (.*)/
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def paragraph(text)
|
34
|
+
# text += "\n"
|
35
|
+
# text.gsub(/^/, COMMENT_SIGN)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# def block_code(code, language)
|
39
|
+
# if @define_macro and @current_macro
|
40
|
+
# @macro[@current_macro] = code += "\n"
|
41
|
+
# code.gsub(/^/, COMMENT_SIGN)
|
42
|
+
# else
|
43
|
+
# code += "\n"
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# def hrule()
|
48
|
+
# @define_macro = ( not @define_macro )
|
49
|
+
# @current_macro = nil if @define_macro
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# def emphasis(text)
|
53
|
+
# @current_macro = text.to_sym if @define_macro
|
54
|
+
# nil
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# The code above lists all the rules our render will live by. If there is anything to
|
60
|
+
# change in our render, it's in this part of the code.
|
61
|
+
# To a regular user, lilp is only a command he can invoke from the terminal. In order
|
62
|
+
# to work with it, we need to get some information from the user, such as the files
|
63
|
+
# he wants to preprocess, or the output directory where he wants the preprocessed files
|
64
|
+
# to go.
|
65
|
+
# For this, we use the `optparse` library. We create an Option object that uses that library's
|
66
|
+
# API.
|
67
|
+
# _The option parser_
|
68
|
+
# class Option
|
69
|
+
# attr_reader :files, :params
|
70
|
+
#
|
71
|
+
# def initialize( args )
|
72
|
+
# @params = {}
|
73
|
+
# @parser = OptionParser.new
|
74
|
+
# @args = args
|
75
|
+
#
|
76
|
+
# @parser.banner = "Usage: lilp file_name.pl [other_file.pl] [-o output_dir]"
|
77
|
+
# @parser.on("-o", "--output D", String, "Output directory") { |val| @params[:output] = File.join('.', "#{val}") }
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# def parse
|
81
|
+
# begin
|
82
|
+
# @files = @parser.parse(@args)
|
83
|
+
# if @files.empty?
|
84
|
+
# puts "Missing file names"
|
85
|
+
# puts @parser
|
86
|
+
# exit
|
87
|
+
# end
|
88
|
+
# rescue OptionParser::InvalidOption => opt
|
89
|
+
# puts "Unknown option #{opt}"
|
90
|
+
# puts @parser
|
91
|
+
# exit
|
92
|
+
# rescue OptionParser::MissingArgument => opt
|
93
|
+
# puts opt
|
94
|
+
# puts @parser
|
95
|
+
# exit
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# We have a lilp render class, an option class that takes care of the command line
|
101
|
+
# options, now we need a way to tie the to together.
|
102
|
+
# To do this, let's create a third object. The class will be "Runner". It's goal
|
103
|
+
# is to render files passed to it while taking into account the options passed by
|
104
|
+
# the command line.
|
105
|
+
# _Runner class_
|
106
|
+
# class Runner
|
107
|
+
#
|
108
|
+
# def run( params, files_path )
|
109
|
+
# lilp_parser = Redcarpet::Markdown.new(LiterateRender, :fenced_code_blocks => true)
|
110
|
+
#
|
111
|
+
# files_path.each do |file_path|
|
112
|
+
# puts "#{file_path}: "
|
113
|
+
#
|
114
|
+
# if File.extname( file_path ) != '.md'
|
115
|
+
# puts 'Skipping (file must have a .lp extension)'
|
116
|
+
# next
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# output_path = String.new
|
120
|
+
# if params[:output]
|
121
|
+
# # Creates the output directory if it doesn't exist
|
122
|
+
# if File.exists?(params[:output])
|
123
|
+
# puts "Folder #{params[:output]} already exists"
|
124
|
+
# else
|
125
|
+
# puts "Creating folder #{params[:output]}"
|
126
|
+
# Dir.mkdir(params[:output])
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# file_name = File.basename(file_path).chomp( File.extname(file_path) )
|
130
|
+
# output_path = File.join(params[:output], file_name)
|
131
|
+
# else
|
132
|
+
# output_path = file_path.chomp( File.extname(file_path) )
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# begin
|
136
|
+
# file = File.open( file_path, 'r' )
|
137
|
+
# out = File.open( output_path, 'w' )
|
138
|
+
#
|
139
|
+
# out.write( lilp_parser.render( file.read ) )
|
140
|
+
#
|
141
|
+
# out.close
|
142
|
+
# file.close
|
143
|
+
#
|
144
|
+
# puts "Wrote #{output_path}"
|
145
|
+
#
|
146
|
+
# rescue
|
147
|
+
# puts "Error while parsing file '#{file_path}': #{$!}"
|
148
|
+
# end
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# end
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# Now, the last thing we need to do is to attach each of the parts in the correct order, under
|
155
|
+
# the Lilp module.
|
156
|
+
module Lilp
|
157
|
+
|
158
|
+
class Option
|
159
|
+
attr_reader :files, :params
|
160
|
+
|
161
|
+
def initialize( args )
|
162
|
+
@params = {}
|
163
|
+
@parser = OptionParser.new
|
164
|
+
@args = args
|
165
|
+
|
166
|
+
@parser.banner = "Usage: lilp file_name.pl [other_file.pl] [-o output_dir]"
|
167
|
+
@parser.on("-o", "--output D", String, "Output directory") { |val| @params[:output] = File.join('.', "#{val}") }
|
168
|
+
end
|
169
|
+
|
170
|
+
def parse
|
171
|
+
begin
|
172
|
+
@files = @parser.parse(@args)
|
173
|
+
if @files.empty?
|
174
|
+
puts "Missing file names"
|
175
|
+
puts @parser
|
176
|
+
exit
|
177
|
+
end
|
178
|
+
rescue OptionParser::InvalidOption => opt
|
179
|
+
puts "Unknown option #{opt}"
|
180
|
+
puts @parser
|
181
|
+
exit
|
182
|
+
rescue OptionParser::MissingArgument => opt
|
183
|
+
puts opt
|
184
|
+
puts @parser
|
185
|
+
exit
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class LiterateRender < Redcarpet::Render::Base
|
191
|
+
COMMENT_SIGN = "# "
|
192
|
+
|
193
|
+
def preprocess(full_document)
|
194
|
+
@macro = {}
|
195
|
+
full_document
|
196
|
+
end
|
197
|
+
|
198
|
+
def header(text, header_level)
|
199
|
+
if header_level == 3
|
200
|
+
@macro[$1.to_sym] if text =~ /Call\: (.*)/
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def paragraph(text)
|
205
|
+
text += "\n"
|
206
|
+
text.gsub(/^/, COMMENT_SIGN)
|
207
|
+
end
|
208
|
+
|
209
|
+
def block_code(code, language)
|
210
|
+
if @define_macro and @current_macro
|
211
|
+
@macro[@current_macro] = code += "\n"
|
212
|
+
code.gsub(/^/, COMMENT_SIGN)
|
213
|
+
else
|
214
|
+
code += "\n"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def hrule()
|
219
|
+
@define_macro = ( not @define_macro )
|
220
|
+
@current_macro = nil if @define_macro
|
221
|
+
end
|
222
|
+
|
223
|
+
def emphasis(text)
|
224
|
+
@current_macro = text.to_sym if @define_macro
|
225
|
+
nil
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
class Runner
|
231
|
+
|
232
|
+
def run( params, files_path )
|
233
|
+
lilp_parser = Redcarpet::Markdown.new(LiterateRender, :fenced_code_blocks => true)
|
234
|
+
|
235
|
+
files_path.each do |file_path|
|
236
|
+
puts "#{file_path}: "
|
237
|
+
|
238
|
+
if File.extname( file_path ) != '.md'
|
239
|
+
puts 'Skipping (file must have a .lp extension)'
|
240
|
+
next
|
241
|
+
end
|
242
|
+
|
243
|
+
output_path = String.new
|
244
|
+
if params[:output]
|
245
|
+
# Creates the output directory if it doesn't exist
|
246
|
+
if File.exists?(params[:output])
|
247
|
+
puts "Folder #{params[:output]} already exists"
|
248
|
+
else
|
249
|
+
puts "Creating folder #{params[:output]}"
|
250
|
+
Dir.mkdir(params[:output])
|
251
|
+
end
|
252
|
+
|
253
|
+
file_name = File.basename(file_path).chomp( File.extname(file_path) )
|
254
|
+
output_path = File.join(params[:output], file_name)
|
255
|
+
else
|
256
|
+
output_path = file_path.chomp( File.extname(file_path) )
|
257
|
+
end
|
258
|
+
|
259
|
+
begin
|
260
|
+
file = File.open( file_path, 'r' )
|
261
|
+
out = File.open( output_path, 'w' )
|
262
|
+
|
263
|
+
out.write( lilp_parser.render( file.read ) )
|
264
|
+
|
265
|
+
out.close
|
266
|
+
file.close
|
267
|
+
|
268
|
+
puts "Wrote #{output_path}"
|
269
|
+
|
270
|
+
rescue
|
271
|
+
puts "Error while parsing file '#{file_path}': #{$!}"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
|
data/lib/lilp/version.rb
ADDED
data/lilp.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "lilp/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "lilp"
|
7
|
+
s.version = Lilp::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Michael Sokol"]
|
10
|
+
s.email = ["mikaa123@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Lilp is lightweight literate programming}
|
13
|
+
|
14
|
+
s.add_dependency 'redcarpet'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lilp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Sokol
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-11-16 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: redcarpet
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
description:
|
27
|
+
email:
|
28
|
+
- mikaa123@gmail.com
|
29
|
+
executables:
|
30
|
+
- lilp
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- Gemfile
|
37
|
+
- Rakefile
|
38
|
+
- bin/lilp
|
39
|
+
- lib/lilp.rb
|
40
|
+
- lib/lilp/base.rb
|
41
|
+
- lib/lilp/version.rb
|
42
|
+
- lilp.gemspec
|
43
|
+
homepage: ""
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.8
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Lilp is lightweight literate programming
|
70
|
+
test_files: []
|
71
|
+
|