Class: Orgmode::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/org-ruby/parser.rb

Overview

Simple routines for loading / saving an ORG file.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(lines, parser_options = { }) ⇒ Parser

I can construct a parser object either with an array of lines or with a single string that I will split along n boundaries.



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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/org-ruby/parser.rb', line 84

def initialize(lines, parser_options={ })
  if lines.is_a? Array then
    @lines = lines
  elsif lines.is_a? String then
    @lines = lines.split("\n")
  else
    raise "Unsupported type for +lines+: #{lines.class}"
  end

  @custom_keywords = []
  @headlines = Array.new
  @current_headline = nil
  @header_lines = []
  @in_buffer_settings = { }
  @options = { }
  @link_abbrevs = { }
  @parser_options = parser_options

  #
  # Include file feature disabled by default since 
  # it would be dangerous in some environments
  #
  # http://orgmode.org/manual/Include-files.html
  #
  # It will be activated by one of the following:
  #
  # - setting an ORG_RUBY_ENABLE_INCLUDE_FILES env variable to 'true'
  # - setting an ORG_RUBY_INCLUDE_ROOT env variable with the root path
  # - explicitly enabling it by passing it as an option:
  #   e.g. Orgmode::Parser.new(org_text, { :allow_include_files => true })
  #
  # IMPORTANT: To avoid the feature altogether, it can be _explicitly disabled_ as follows:
  #   e.g. Orgmode::Parser.new(org_text, { :allow_include_files => false })
  #
  if @parser_options[:allow_include_files].nil?
    if ENV['ORG_RUBY_ENABLE_INCLUDE_FILES'] == 'true' \
      or not ENV['ORG_RUBY_INCLUDE_ROOT'].nil?
      @parser_options[:allow_include_files] = true
    end
  end

  @parser_options[:offset] ||= 0

  parse_lines @lines
end

Instance Attribute Details

#custom_keywordsObject (readonly)

Array of custom keywords.



28
29
30
# File 'lib/org-ruby/parser.rb', line 28

def custom_keywords
  @custom_keywords
end

#header_linesObject (readonly)

These are any lines before the first headline



18
19
20
# File 'lib/org-ruby/parser.rb', line 18

def header_lines
  @header_lines
end

#headlinesObject (readonly)

All of the headlines in the org file



15
16
17
# File 'lib/org-ruby/parser.rb', line 15

def headlines
  @headlines
end

#in_buffer_settingsObject (readonly)

This contains any in-buffer settings from the org-mode file. See orgmode.org/manual/In_002dbuffer-settings.html#In_002dbuffer-settings



22
23
24
# File 'lib/org-ruby/parser.rb', line 22

def in_buffer_settings
  @in_buffer_settings
end

#linesObject (readonly)

All of the lines of the orgmode file



12
13
14
# File 'lib/org-ruby/parser.rb', line 12

def lines
  @lines
end

#optionsObject (readonly)

This contains in-buffer options; a special case of in-buffer settings.



25
26
27
# File 'lib/org-ruby/parser.rb', line 25

def options
  @options
end

Class Method Details

.load(fname) ⇒ Object

Creates a new parser from the data in a given file



287
288
289
290
# File 'lib/org-ruby/parser.rb', line 287

def self.load(fname)
  lines = IO.readlines(fname)
  return self.new(lines)
end

Instance Method Details

#check_include_file(file_path) ⇒ Object

Check include file availability and permissions



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/org-ruby/parser.rb', line 131

def check_include_file(file_path)
  can_be_included = File.exists? file_path

  if not ENV['ORG_RUBY_INCLUDE_ROOT'].nil?
    # Ensure we have full paths
    root_path = File.expand_path ENV['ORG_RUBY_INCLUDE_ROOT']
    file_path = File.expand_path file_path

    # Check if file is in the defined root path and really exists
    if file_path.slice(0, root_path.length) != root_path
      can_be_included = false
    end
  end

  can_be_included
end

#custom_keyword_regexpObject

Regexp that recognizes words in custom_keywords.



31
32
33
34
# File 'lib/org-ruby/parser.rb', line 31

def custom_keyword_regexp
  return nil if @custom_keywords.empty?
  Regexp.new("^(#{@custom_keywords.join('|')})\$")
end

#export_exclude_tagsObject

A set of tags that, if present on any headlines in the org-file, means that subtree will not get exported.



45
46
47
48
# File 'lib/org-ruby/parser.rb', line 45

def export_exclude_tags
  return Array.new unless @in_buffer_settings["EXPORT_EXCLUDE_TAGS"]
  @in_buffer_settings["EXPORT_EXCLUDE_TAGS"].split
end

#export_footnotes?Boolean

Returns true if we are to export footnotes

Returns:

  • (Boolean)


56
57
58
# File 'lib/org-ruby/parser.rb', line 56

def export_footnotes?
  "t" == @options["f"]
end

#export_heading_number?Boolean

Returns true if we are to export heading numbers.

Returns:

  • (Boolean)


61
62
63
# File 'lib/org-ruby/parser.rb', line 61

def export_heading_number?
  "t" == @options["num"]
end

#export_select_tagsObject

A set of tags that, if present on any headlines in the org-file, means only those headings will get exported.



38
39
40
41
# File 'lib/org-ruby/parser.rb', line 38

def export_select_tags
  return Array.new unless @in_buffer_settings["EXPORT_SELECT_TAGS"]
  @in_buffer_settings["EXPORT_SELECT_TAGS"].split
end

#export_tables?Boolean

Should we export tables? Defaults to true, must be overridden with an explicit “nil”

Returns:

  • (Boolean)


72
73
74
# File 'lib/org-ruby/parser.rb', line 72

def export_tables?
  "nil" != @options["|"]
end

#export_todo?Boolean

Returns true if we are to export todo keywords on headings.

Returns:

  • (Boolean)


51
52
53
# File 'lib/org-ruby/parser.rb', line 51

def export_todo?
  "t" == @options["todo"]
end

#get_include_data(line) ⇒ Object

Get include data, when #+INCLUDE tag is used



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/org-ruby/parser.rb', line 236

def get_include_data(line)
  return IO.read(line.include_file_path) if line.include_file_options.nil?

  case line.include_file_options[0]
  when ':lines'
    # Get options
    include_file_lines = line.include_file_options[1].gsub('"', '').split('-')
    include_file_lines[0] = include_file_lines[0].empty? ? 1 : include_file_lines[0].to_i
    include_file_lines[1] = include_file_lines[1].to_i if !include_file_lines[1].nil?

    # Extract request lines. Note that the second index is excluded, according to the doc
    line_index = 1
    include_data = []
    File.open(line.include_file_path, "r") do |fd|
      while line_data = fd.gets
        if (line_index >= include_file_lines[0] and (include_file_lines[1].nil? or line_index < include_file_lines[1]))
          include_data << line_data.chomp
        end
        line_index += 1
      end
    end

  when 'src', 'example', 'quote'
    # Prepare tags
    begin_tag = '#+BEGIN_%s' % [line.include_file_options[0].upcase]
    if line.include_file_options[0] == 'src' and !line.include_file_options[1].nil?
      begin_tag += ' ' + line.include_file_options[1]
    end
    end_tag = '#+END_%s' % [line.include_file_options[0].upcase]

    # Get lines. Will be transformed into an array at processing
    include_data = "%s\n%s\n%s" % [begin_tag, IO.read(line.include_file_path), end_tag]

  else
    include_data = []
  end
  # @todo: support ":minlevel"

  include_data
end

#parse_lines(lines) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/org-ruby/parser.rb', line 148

def parse_lines(lines)
  mode = :normal
  previous_line = nil
  table_header_set = false
  lines.each do |text|
    line = Line.new text, self

    if @parser_options[:allow_include_files]
      if line.include_file? and not line.include_file_path.nil?
        next if not check_include_file line.include_file_path
        include_data = get_include_data line
        include_lines = Orgmode::Parser.new(include_data, @parser_options).lines
        parse_lines include_lines
      end
    end

    # Store link abbreviations
    if line.link_abbrev?
      link_abbrev_data = line.link_abbrev_data
      @link_abbrevs[link_abbrev_data[0]] = link_abbrev_data[1]
    end

    mode = :normal if line.end_block? and [line.paragraph_type, :comment].include?(mode)
    mode = :normal if line.property_drawer_end_block? and mode == :property_drawer

    case mode
    when :normal, :quote, :center
      if Headline.headline? line.to_s
        line = Headline.new line.to_s, self, @parser_options[:offset]
      elsif line.table_separator?
        if previous_line and previous_line.paragraph_type == :table_row and !table_header_set
          previous_line.assigned_paragraph_type = :table_header
          table_header_set = true
        end
      end
      table_header_set = false if !line.table?

    when :example, :html, :src
      set_mode_for_results_block_contents(previous_line, line) if previous_line

      # As long as we stay in code mode, force lines to be code.
      # Don't try to interpret structural items, like headings and tables.
      line.assigned_paragraph_type = :code
    end

    if mode == :normal
      @headlines << @current_headline = line if Headline.headline? line.to_s
      # If there is a setting on this line, remember it.
      line.in_buffer_setting? do |key, value|
        store_in_buffer_setting key.upcase, value
      end

      mode = line.paragraph_type if line.begin_block?

      if previous_line
        set_mode_for_results_block_contents(previous_line, line)

        mode = :property_drawer if previous_line.property_drawer_begin_block?
      end

      # We treat the results code block differently since the exporting can be omitted
      if line.begin_block?
        if line.results_block_should_be_exported?
          @next_results_block_should_be_exported = true
        else
          @next_results_block_should_be_exported = false
        end
      end
    end

    if mode == :property_drawer and @current_headline
      @current_headline.property_drawer[line.property_drawer_item.first] = line.property_drawer_item.last
    end

    unless mode == :comment
      if @current_headline
        @current_headline.body_lines << line
      else
        @header_lines << line
      end
    end

    previous_line = line
  end                       # lines.each
end

#set_mode_for_results_block_contents(previous_line, line) ⇒ Object



277
278
279
280
281
282
283
284
# File 'lib/org-ruby/parser.rb', line 277

def set_mode_for_results_block_contents(previous_line, line)
  if previous_line.start_of_results_code_block? \
    or previous_line.assigned_paragraph_type == :comment
    unless @next_results_block_should_be_exported or line.paragraph_type == :blank
      line.assigned_paragraph_type = :comment
    end
  end
end

#skip_header_lines?Boolean

Should we skip exporting text before the first heading?

Returns:

  • (Boolean)


66
67
68
# File 'lib/org-ruby/parser.rb', line 66

def skip_header_lines?
  "t" == @options["skip"]
end

#to_htmlObject

Converts the loaded org-mode file to HTML.



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/org-ruby/parser.rb', line 326

def to_html
  mark_trees_for_export
  export_options = {
    :decorate_title        => @in_buffer_settings["TITLE"],
    :export_heading_number => export_heading_number?,
    :export_todo           => export_todo?,
    :use_sub_superscripts  => use_sub_superscripts?,
    :export_footnotes      => export_footnotes?,
    :link_abbrevs          => @link_abbrevs
  }
  export_options[:skip_tables] = true if not export_tables?
  output = ""
  output_buffer = HtmlOutputBuffer.new(output, export_options)

  if @in_buffer_settings["TITLE"]

    # If we're given a new title, then just create a new line
    # for that title.
    title = Line.new(@in_buffer_settings["TITLE"], self, :title)
    translate([title], output_buffer)
  end
  translate(@header_lines, output_buffer) unless skip_header_lines?

  # If we've output anything at all, remove the :decorate_title option.
  export_options.delete(:decorate_title) if (output.length > 0)
  @headlines.each do |headline|
    next if headline.export_state == :exclude
    case headline.export_state
    when :exclude
      # NOTHING
    when :headline_only
      translate(headline.body_lines[0, 1], output_buffer)
    when :all
      translate(headline.body_lines, output_buffer)
    end
  end
  output << "\n"

  rp = RubyPants.new(output)
  rp.to_html
end

#to_markdownObject

Exports the Org mode content into Markdown format



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/org-ruby/parser.rb', line 305

def to_markdown
  mark_trees_for_export
  output = ""
  output_buffer = MarkdownOutputBuffer.new(output)

  translate(@header_lines, output_buffer)
  @headlines.each do |headline|
    next if headline.export_state == :exclude
    case headline.export_state
    when :exclude
      # NOTHING
    when :headline_only
      translate(headline.body_lines[0, 1], output_buffer)
    when :all
      translate(headline.body_lines, output_buffer)
    end
  end
  output
end

#to_textileObject

Saves the loaded orgmode file as a textile file.



293
294
295
296
297
298
299
300
301
302
# File 'lib/org-ruby/parser.rb', line 293

def to_textile
  output = ""
  output_buffer = TextileOutputBuffer.new(output)

  translate(@header_lines, output_buffer)
  @headlines.each do |headline|
    translate(headline.body_lines, output_buffer)
  end
  output
end

#use_sub_superscripts?Boolean

Should we export sub/superscripts? (_foo/^foo) only {} mode is currently supported.

Returns:

  • (Boolean)


78
79
80
# File 'lib/org-ruby/parser.rb', line 78

def use_sub_superscripts?
  @options["^"] != "nil"
end