lilp 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.md +37 -0
- data/lib/lilp/base.rb +66 -241
- data/lib/lilp/version.rb +3 -3
- metadata +3 -1
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Lightweight Literate programming
|
2
|
+
================================
|
3
|
+
|
4
|
+
lilp is a preprocessor that allows you to write literate programming code using the Markdown syntax.
|
5
|
+
|
6
|
+
It is an experiment and shouldn't be used for any serious matter. Or
|
7
|
+
should it?!
|
8
|
+
|
9
|
+
## How it works
|
10
|
+
|
11
|
+
Simply write your source file as if you were writing a normal Markdown document.
|
12
|
+
|
13
|
+
~~~~ ruby
|
14
|
+
puts "code inside code blocks will be extracted, while the rest will
|
15
|
+
be tranformed as comments"
|
16
|
+
~~~~
|
17
|
+
|
18
|
+
Everything but code blocks will be transformed as comments.
|
19
|
+
|
20
|
+
# Compiling
|
21
|
+
lilp comes with a command line tool that takes as an input file names, and a "-o" option to specify an output directory.
|
22
|
+
|
23
|
+
If your files are under the `lib` directory, and you want to compile them into the `tangled` directory, then do:
|
24
|
+
|
25
|
+
~~~~
|
26
|
+
lilp lib/* -o tangled/
|
27
|
+
~~~~
|
28
|
+
|
29
|
+
## License
|
30
|
+
|
31
|
+
Copyright (C) 2011 Michael Sokol
|
32
|
+
|
33
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
34
|
+
|
35
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
36
|
+
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/lilp/base.rb
CHANGED
@@ -1,279 +1,104 @@
|
|
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
1
|
module Lilp
|
157
2
|
|
158
|
-
class Option
|
159
|
-
|
3
|
+
class Option
|
4
|
+
attr_reader :files, :params
|
160
5
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
6
|
+
def initialize( args )
|
7
|
+
@params = {}
|
8
|
+
@parser = OptionParser.new
|
9
|
+
@args = args
|
165
10
|
|
166
|
-
|
167
|
-
|
168
|
-
|
11
|
+
@parser.banner =
|
12
|
+
"Usage: lilp file_name.pl [other_file.pl] [-o output_dir]"
|
13
|
+
|
14
|
+
@parser.on("-o", "--output D", String, "Output directory") do |val|
|
15
|
+
@params[:output] = File.join('.', "#{val}")
|
16
|
+
end
|
17
|
+
end
|
169
18
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
19
|
+
def parse
|
20
|
+
begin
|
21
|
+
@files = @parser.parse(@args)
|
22
|
+
if @files.empty?
|
23
|
+
puts "Missing file names"
|
24
|
+
puts @parser
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
rescue OptionParser::InvalidOption => opt
|
28
|
+
puts "Unknown option #{opt}"
|
29
|
+
puts @parser
|
30
|
+
exit
|
31
|
+
rescue OptionParser::MissingArgument => opt
|
32
|
+
puts opt
|
175
33
|
puts @parser
|
176
34
|
exit
|
177
35
|
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
36
|
end
|
187
37
|
end
|
188
|
-
end
|
189
38
|
|
190
|
-
class LiterateRender < Redcarpet::Render::Base
|
39
|
+
class LiterateRender < Redcarpet::Render::Base
|
191
40
|
COMMENT_SIGN = "# "
|
192
41
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
197
|
-
|
198
|
-
def header(text, header_level)
|
199
|
-
if header_level == 3
|
200
|
-
@macro[$1.to_sym] if text =~ /Call\: (.*)/
|
42
|
+
def paragraph(text)
|
43
|
+
text += "\n"
|
44
|
+
text.gsub(/^/, COMMENT_SIGN)
|
201
45
|
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def paragraph(text)
|
205
|
-
text += "\n"
|
206
|
-
text.gsub(/^/, COMMENT_SIGN)
|
207
|
-
end
|
208
46
|
|
209
|
-
|
210
|
-
if @define_macro and @current_macro
|
211
|
-
@macro[@current_macro] = code += "\n"
|
212
|
-
code.gsub(/^/, COMMENT_SIGN)
|
213
|
-
else
|
47
|
+
def block_code(code, language)
|
214
48
|
code += "\n"
|
215
49
|
end
|
216
|
-
end
|
217
50
|
|
218
|
-
def hrule()
|
219
|
-
@define_macro = ( not @define_macro )
|
220
|
-
@current_macro = nil if @define_macro
|
221
51
|
end
|
222
52
|
|
223
|
-
|
224
|
-
@current_macro = text.to_sym if @define_macro
|
225
|
-
nil
|
226
|
-
end
|
53
|
+
class Runner
|
227
54
|
|
228
|
-
|
55
|
+
def run( params, files_path )
|
56
|
+
lilp_parser = Redcarpet::Markdown.new(LiterateRender,
|
57
|
+
:fenced_code_blocks => true)
|
229
58
|
|
230
|
-
|
59
|
+
files_path.each do |file_path|
|
60
|
+
puts "#{file_path}: "
|
231
61
|
|
232
|
-
|
233
|
-
|
62
|
+
if File.extname( file_path ) != '.md'
|
63
|
+
puts 'Skipping (file must have a .md extension)'
|
64
|
+
next
|
65
|
+
end
|
234
66
|
|
235
|
-
|
236
|
-
|
67
|
+
output_path = String.new
|
68
|
+
if params[:output]
|
69
|
+
# Creates the output directory if it doesn't exist
|
70
|
+
if File.exists?(params[:output])
|
71
|
+
puts "Folder #{params[:output]} already exists"
|
72
|
+
else
|
73
|
+
puts "Creating folder #{params[:output]}"
|
74
|
+
Dir.mkdir(params[:output])
|
75
|
+
end
|
237
76
|
|
238
|
-
|
239
|
-
|
240
|
-
next
|
241
|
-
end
|
77
|
+
file_name = File.basename(file_path).chomp(
|
78
|
+
File.extname(file_path) )
|
242
79
|
|
243
|
-
|
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"
|
80
|
+
output_path = File.join(params[:output], file_name)
|
248
81
|
else
|
249
|
-
|
250
|
-
Dir.mkdir(params[:output])
|
82
|
+
output_path = file_path.chomp( File.extname(file_path) )
|
251
83
|
end
|
252
84
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
output_path = file_path.chomp( File.extname(file_path) )
|
257
|
-
end
|
85
|
+
begin
|
86
|
+
file = File.open( file_path, 'r' )
|
87
|
+
out = File.open( output_path, 'w' )
|
258
88
|
|
259
|
-
|
260
|
-
file = File.open( file_path, 'r' )
|
261
|
-
out = File.open( output_path, 'w' )
|
89
|
+
out.write( lilp_parser.render( file.read ) )
|
262
90
|
|
263
|
-
|
91
|
+
out.close
|
92
|
+
file.close
|
264
93
|
|
265
|
-
|
266
|
-
file.close
|
94
|
+
puts "Wrote #{output_path}"
|
267
95
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
puts "Error while parsing file '#{file_path}': #{$!}"
|
96
|
+
rescue
|
97
|
+
puts "Error while parsing file '#{file_path}': #{$!}"
|
98
|
+
end
|
272
99
|
end
|
273
|
-
end
|
274
100
|
|
101
|
+
end
|
275
102
|
end
|
276
|
-
end
|
277
103
|
|
278
104
|
end
|
279
|
-
|
data/lib/lilp/version.rb
CHANGED
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: lilp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Michael Sokol
|
@@ -33,7 +33,9 @@ extensions: []
|
|
33
33
|
extra_rdoc_files: []
|
34
34
|
|
35
35
|
files:
|
36
|
+
- .gitignore
|
36
37
|
- Gemfile
|
38
|
+
- README.md
|
37
39
|
- Rakefile
|
38
40
|
- bin/lilp
|
39
41
|
- lib/lilp.rb
|