Class: Literate::Programming

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

Constant Summary collapse

VERSION =
"1.1.1"

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



24
25
26
27
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
# File 'lib/literate/programming.rb', line 24

def convert
  current_mode = :text
  md = ""
  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 << '```' << $/ << $/
      else
        md << line << $/
      end
    when :code
      if old_mode == :code then
        current_code << line << $/
        md << line << $/
      else
        md << '```ruby:' << current_label
        md << ' append' if operator == :append
        md << $/
      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 << '```' << $/
  end
  table['*'] ||= ''
  return {rb: indent_code(expand(table, '*')), md: indent_text(md)}
end

#expand(table, label) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/literate/programming.rb', line 93

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



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/literate/programming.rb', line 116

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



146
147
148
149
150
151
152
153
# File 'lib/literate/programming.rb', line 146

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_text(text) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/literate/programming.rb', line 155

def indent_text(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

#pretty_print_ruby(buffer, indent_level_stack, line) ⇒ Object



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

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