Class: Literate::Programming

Inherits:
Object
  • Object
show all
Defined in:
lib/literate/programming.rb,
lib/literate/programming/version.rb

Constant Summary collapse

VERSION =
"1.2.0"

Instance Method Summary collapse

Constructor Details

#initialize(src = "", source: nil, tabstop: 2) ⇒ Programming

Returns a new instance of Programming.



5
6
7
8
9
10
11
12
13
14
# File 'lib/literate/programming.rb', line 5

def initialize(src = "", source: nil, tabstop: 2)
  @source = source || src
  @tabstop = tabstop
  @eval_context = BasicObject.new
  @eval_context.instance_eval <<-EOC
    def gensym
      return (0 .. 20).collect { ('a' .. 'z').to_a[::Random.rand(26)] }.join
    end
  EOC
end

Instance Method Details

#convertObject



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/literate/programming.rb', line 28

def convert
  current_mode = :text
  md = ""
  tex = <<-TEX
\\documentclass{report}
\\usepackage{listings}
\\begin{document}
\\lstset{language=Ruby}
  TEX
  table = {}
  current_code = nil
  current_label = nil
  operator = nil
  @source.split($/).each do |line|
    old_mode = current_mode
    case current_mode
    when :text
      if line.match /^[ \t]*\[\[([^\]]+)\]\][ \t]*(\+?=|<<)[ \t]*$/ then
        current_mode = :code
        current_code = ''
        current_label = $1
        case $2
          when '='
            operator = :assign
          when '+=', '<<'
            operator = :append
        end
      end
    when :code
      if line.match /^[ \t]*$/ then
        current_mode = :text
      else
        line = line.match(/^[ \t]*/).post_match
      end
    end
    case current_mode
    when :text
      if old_mode == :code then
        case operator
          when :assign
            raise if table[current_label]
            table[current_label] = current_code
          when :append
            table[current_label] ||= ''
            table[current_label] << $/ << current_code
        end
        md << '```' << $/ << $/
        tex << '\\end{lstlisting}' << $/ << $/
      else
        md << line << $/
        tex << line << $/
      end
    when :code
      if old_mode == :code then
        current_code << line << $/
        md << line << $/
        tex << line << $/
      else
        md << '```ruby:' << current_label
        md << ' append' if operator == :append
        md << $/
        tex << '\\begin{lstlisting}[frame=lines,caption=' << current_label
        tex << ' append' if operator == :append
        tex << ']' << $/
      end
    end
  end
  if current_mode == :code then
    case operator
      when :assign
        table[current_label] = current_code
      when :append
        table[current_label] ||= ''
        table[current_label] << $/ << current_code
    end
    md << '```' << $/
    tex << '\\end{lstlisting}' << $/
  end
  table['*'] ||= ''
  tex << <<-TEX
\\end{document}
  TEX
  return {rb: indent_code(expand(table, '*')), md: indent_markdown(md), tex: indent_tex(tex)}
end

#expand(table, label) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/literate/programming.rb', line 113

def expand(table, label)
  @eval_context.instance_eval(expand(table, label + 'before*')) if table.member? label + 'before*'
  processed = ''
  processing = "[[#{label}]]"
  while processing.match /^((?:[^\[]|\[[^\[])*)\[\[([^\]]+)\]\]/m
    processed << $1
    expanding = $2
    follow = $~.post_match
    if table.member? expanding then
      processing = expand_template(table[expanding], nil).chomp
    elsif expanding.match /^([^:]+):([^,]+)(,[^,]+)*$/ then
      true_expanding = $1 + ':@'
      args = $~.to_a[2..-2]
      raise 'undefined ' + expanding unless table.member? true_expanding
      processing = expand_template(table[true_expanding], args).chomp
    else
      raise 'undefined ' + expanding
    end
    processing << follow
  end
  return processed << processing
end

#expand_template(string, args) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/literate/programming.rb', line 136

def expand_template(string, args)
  processed = ''
  processing = string
  while processing.match /^((?:[^@]|@[^0-9@])*)@/m
    processed << $1
    follow = $~.post_match
    if follow.match /^(0|[1-9][0-9]*)/m then
      # @num -> args[num]
      processed << args[$1.to_i]
      processing = $~.post_match
    elsif follow.match /^@/m then
      follow = $~.post_match
      if follow.match /^(?<paren>\((?:[^()]|\g<paren>)*\))/m then
        # @@(expr) -> eval(expr)
        post_match = $~.post_match
        expanding = @eval_context.instance_eval(expand_template $1, args)
        processed << expanding.to_s
        processing = post_match
      else
        processed << '@@'
        processing = follow
      end
    else
      processed << '@'
      processing = follow
    end
  end
  return processed << processing
end

#indent_code(code) ⇒ Object



166
167
168
169
170
171
172
173
# File 'lib/literate/programming.rb', line 166

def indent_code(code)
  indent_level_stack = [-@tabstop]
  ret = ''
  code.split($/).each do |line|
    ret, indent_level_stack = pretty_print_ruby ret, indent_level_stack, line
  end
  return ret
end

#indent_markdown(text) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/literate/programming.rb', line 175

def indent_markdown(text)
  mode = :text
  ret = ''
  indent_level_stack = nil
  text.split($/).each do |line|
    if mode == :text and line.match /^```/ then
      mode = :code
      indent_level_stack = [-@tabstop]
      ret << line << $/
    elsif mode == :text then
      ret << line << $/
    elsif mode == :code and line.match /^```/ then
      mode = :text
      ret << line << $/
    else
      ret, indent_level_stack = pretty_print_ruby ret, indent_level_stack, line
    end
  end
  return ret
end

#indent_tex(text) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/literate/programming.rb', line 196

def indent_tex(text)
  mode = :text
  ret = ''
  indent_level_stack = nil
  text.split($/).each do |line|
    if mode == :text and line.match /^\\begin{lstlisting}/ then
      mode = :code
      indent_level_stack = [-@tabstop]
      ret << line << $/
    elsif mode == :text then
      ret << line << $/
    elsif mode == :code and line.match /^\\end{lstlisting}/ then
      mode = :text
      ret << line << $/
    else
      ret, indent_level_stack = pretty_print_ruby ret, indent_level_stack, line
    end
  end
  return ret
end

#pretty_print_ruby(buffer, indent_level_stack, line) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/literate/programming.rb', line 217

def pretty_print_ruby(buffer, indent_level_stack, line)
  if line.match /^[ \t]*(else|elsif|ensure|rescue|when)\b/ then
    current_indent_level = indent_level_stack[-1]
  elsif line.match /\b(end)\b/ then
    current_indent_level = indent_level_stack.pop
  else
    current_indent_level = indent_level_stack[-1] + @tabstop
  end
  if line.match /^[ \t]*$/ then
    buffer << $/
  else
    buffer << ' ' * current_indent_level << line << $/
  end
  if line.match /^[ \t]*(begin|class|def|for|while|module)\b/
    indent_level_stack.push current_indent_level
  end
  return buffer, indent_level_stack
end

#to_mdObject



20
21
22
# File 'lib/literate/programming.rb', line 20

def to_md
  convert[:md]
end

#to_rubyObject



16
17
18
# File 'lib/literate/programming.rb', line 16

def to_ruby
  convert[:rb]
end

#to_texObject



24
25
26
# File 'lib/literate/programming.rb', line 24

def to_tex
  convert[:tex]
end