File: line.rb

package info (click to toggle)
ruby-org 0.9.12-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,852 kB
  • sloc: ruby: 3,044; lisp: 50; makefile: 4
file content (402 lines) | stat: -rw-r--r-- 11,326 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
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
129
130
131
132
133
134
135
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
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
233
234
235
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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
module Orgmode

  # Represents a single line of an orgmode file.
  class Line

    # The indent level of this line. this is important to properly translate
    # nested lists from orgmode to textile.
    # TODO 2009-12-20 bdewey: Handle tabs
    attr_reader :indent

    # Backpointer to the parser that owns this line.
    attr_reader :parser

    # Paragraph type determined for the line.
    attr_reader :paragraph_type

    # Major modes associate paragraphs with a table, list and so on.
    attr_reader :major_mode

    # A line can have its type assigned instead of inferred from its
    # content. For example, something that parses as a "table" on its
    # own ("| one | two|\n") may just be a paragraph if it's inside
    # #+BEGIN_EXAMPLE. Set this property on the line to assign its
    # type. This will then affect the value of +paragraph_type+.
    attr_accessor :assigned_paragraph_type

    # In case more contextual info is needed we can put here
    attr_accessor :properties

    def initialize(line, parser=nil, assigned_paragraph_type=nil)
      @parser = parser
      @line = line
      @indent = 0
      @line =~ /\s*/
      @assigned_paragraph_type = assigned_paragraph_type
      @properties = { }
      determine_paragraph_type
      determine_major_mode
      @indent = $&.length unless blank?
    end

    def to_s
      return @line
    end

    # Tests if a line is a comment.
    def comment?
      return @assigned_paragraph_type == :comment if @assigned_paragraph_type
      return block_type.casecmp("COMMENT") if begin_block? or end_block?
      return @line =~ /^[ \t]*?#[ \t]/
    end

    PropertyDrawerRegexp = /^\s*:(PROPERTIES|END):/i

    def property_drawer_begin_block?
      @line =~ PropertyDrawerRegexp && $1 =~ /PROPERTIES/
    end

    def property_drawer_end_block?
      @line =~ PropertyDrawerRegexp && $1 =~ /END/
    end

    def property_drawer?
      check_assignment_or_regexp(:property_drawer, PropertyDrawerRegexp)
    end

    PropertyDrawerItemRegexp = /^\s*:([0-9A-Za-z_\-]+):\s*(.*)$/i

    def property_drawer_item?
      @line =~ PropertyDrawerItemRegexp
    end

    def property_drawer_item
      @line =~ PropertyDrawerItemRegexp

      [$1, $2]
    end

    # Tests if a line contains metadata instead of actual content.
    def metadata?
      check_assignment_or_regexp(:metadata, /^\s*(CLOCK|DEADLINE|START|CLOSED|SCHEDULED):/)
    end

    def nonprinting?
      comment? || metadata? || begin_block? || end_block? || include_file?
    end

    def blank?
      check_assignment_or_regexp(:blank, /^\s*$/)
    end

    def plain_list?
      ordered_list? or unordered_list? or definition_list?
    end

    UnorderedListRegexp = /^\s*(-|\+|\s+[*])\s+/

    def unordered_list?
      check_assignment_or_regexp(:unordered_list, UnorderedListRegexp)
    end

    def strip_unordered_list_tag
      @line.sub(UnorderedListRegexp, "")
    end

    DefinitionListRegexp = /^\s*(-|\+|\s+[*])\s+(.*\s+|)::($|\s+)/

    def definition_list?
      check_assignment_or_regexp(:definition_list, DefinitionListRegexp)
    end

    OrderedListRegexp = /^\s*\d+(\.|\))\s+/

    def ordered_list?
      check_assignment_or_regexp(:ordered_list, OrderedListRegexp)
    end

    def strip_ordered_list_tag
      @line.sub(OrderedListRegexp, "")
    end

    HorizontalRuleRegexp = /^\s*-{5,}\s*$/

    def horizontal_rule?
      check_assignment_or_regexp(:horizontal_rule, HorizontalRuleRegexp)
    end

    # Extracts meaningful text and excludes org-mode markup,
    # like identifiers for lists or headings.
    def output_text
      return strip_ordered_list_tag if ordered_list?
      return strip_unordered_list_tag if unordered_list?
      return @line.sub(InlineExampleRegexp, "") if inline_example?
      return strip_raw_text_tag if raw_text?
      return @line
    end

    def plain_text?
      not metadata? and not blank? and not plain_list?
    end

    def table_row?
      # for an org-mode table, the first non-whitespace character is a
      # | (pipe).
      check_assignment_or_regexp(:table_row, /^\s*\|/)
    end

    def table_separator?
      # an org-mode table separator has the first non-whitespace
      # character as a | (pipe), then consists of nothing else other
      # than pipes, hyphens, and pluses.

      check_assignment_or_regexp(:table_separator, /^\s*\|[-\|\+]*\s*$/)
    end

    # Checks if this line is a table header.
    def table_header?
      @assigned_paragraph_type == :table_header
    end

    def table?
      table_row? or table_separator? or table_header?
    end

    #
    # 1) block delimiters
    # 2) block type (src, example, html...) 
    # 3) switches (e.g. -n -r -l "asdf")
    # 4) header arguments (:hello world)
    #
    BlockRegexp = /^\s*#\+(BEGIN|END)_(\w*)\s*([0-9A-Za-z_\-]*)?\s*([^\":\n]*\"[^\"\n*]*\"[^\":\n]*|[^\":\n]*)?\s*([^\n]*)?/i

    def begin_block?
      @line =~ BlockRegexp && $1 =~ /BEGIN/i
    end

    def end_block?
      @line =~ BlockRegexp && $1 =~ /END/i
    end

    def block_type
      $2 if @line =~ BlockRegexp
    end

    def block_lang
      $3 if @line =~ BlockRegexp
    end

    def code_block?
      block_type =~ /^(EXAMPLE|SRC)$/i
    end

    def block_switches
      $4 if @line =~ BlockRegexp
    end

    def block_header_arguments
      header_arguments = { }

      if @line =~ BlockRegexp
        header_arguments_string = $5
        harray = header_arguments_string.split(' ')
        harray.each_with_index do |arg, i|
          next_argument = harray[i + 1]
          if arg =~ /^:/ and not (next_argument.nil? or next_argument =~ /^:/)
            header_arguments[arg] = next_argument
          end
        end
      end

      header_arguments
    end

    # TODO: COMMENT block should be considered here
    def block_should_be_exported?
      export_state = block_header_arguments[':exports']
      case
      when ['both', 'code', nil, ''].include?(export_state)
        true
      when ['none', 'results'].include?(export_state)
        false
      end
    end

    def results_block_should_be_exported?
      export_state = block_header_arguments[':exports']
      case
      when ['results', 'both'].include?(export_state)
        true
      when ['code', 'none', nil, ''].include?(export_state)
        false
      end
    end

    InlineExampleRegexp = /^\s*:\s/

    # Test if the line matches the "inline example" case:
    # the first character on the line is a colon.
    def inline_example?
      check_assignment_or_regexp(:inline_example, InlineExampleRegexp)
    end

    RawTextRegexp = /^(\s*)#\+(\w+):\s*/

    # Checks if this line is raw text.
    def raw_text?
      check_assignment_or_regexp(:raw_text, RawTextRegexp)
    end

    def raw_text_tag
      $2.upcase if @line =~ RawTextRegexp
    end

    def strip_raw_text_tag
      @line.sub(RawTextRegexp) { |match| $1 }
    end

    InBufferSettingRegexp = /^#\+(\w+):\s*(.*)$/

    # call-seq:
    #     line.in_buffer_setting?         => boolean
    #     line.in_buffer_setting? { |key, value| ... }
    #
    # Called without a block, this method determines if the line
    # contains an in-buffer setting. Called with a block, the block
    # will get called if the line contains an in-buffer setting with
    # the key and value for the setting.
    def in_buffer_setting?
      return false if @assigned_paragraph_type && @assigned_paragraph_type != :comment
      if block_given? then
        if @line =~ InBufferSettingRegexp
          yield $1, $2
        end
      else
        @line =~ InBufferSettingRegexp
      end
    end

    # #+TITLE: is special because even though that it can be
    # written many times in the document, its value will be that of the last one
    def title?
      @assigned_paragraph_type == :title
    end

    ResultsBlockStartsRegexp = /^\s*#\+RESULTS:\s*(.+)?$/i

    def start_of_results_code_block?
      @line =~ ResultsBlockStartsRegexp
    end

    LinkAbbrevRegexp = /^\s*#\+LINK:\s*(\w+)\s+(.+)$/i

    def link_abbrev?
      @line =~ LinkAbbrevRegexp
    end

    def link_abbrev_data
      [$1, $2] if @line =~ LinkAbbrevRegexp
    end

    IncludeFileRegexp = /^\s*#\+INCLUDE:\s*"([^"]+)"(\s+([^\s]+)\s+(.*))?$/i

    def include_file?
      @line =~ IncludeFileRegexp
    end

    def include_file_path
      File.expand_path $1 if @line =~ IncludeFileRegexp
    end

    def include_file_options
      [$3, $4] if @line =~ IncludeFileRegexp and !$2.nil?
    end

    # Determines the paragraph type of the current line.
    def determine_paragraph_type
      @paragraph_type = \
      case
      when blank?
        :blank
      when definition_list? # order is important! A definition_list is also an unordered_list!
        :definition_term
      when (ordered_list? or unordered_list?)
        :list_item
      when property_drawer_begin_block?
        :property_drawer_begin_block
      when property_drawer_end_block?
        :property_drawer_end_block
      when property_drawer_item?
        :property_drawer_item
      when metadata?
        :metadata
      when block_type
        if block_should_be_exported?
          case block_type.downcase.to_sym
          when :center, :comment, :example, :html, :quote, :src
            block_type.downcase.to_sym
          else
            :comment
          end
        else
          :comment
        end
      when title?
        :title
      when raw_text? # order is important! Raw text can be also a comment
        :raw_text
      when comment?
        :comment
      when table_separator?
        :table_separator
      when table_row?
        :table_row
      when table_header?
        :table_header
      when inline_example?
        :inline_example
      when horizontal_rule?
        :horizontal_rule
      else :paragraph
      end
    end

    def determine_major_mode
      @major_mode = \
      case
      when definition_list? # order is important! A definition_list is also an unordered_list!
        :definition_list
      when ordered_list?
        :ordered_list
      when unordered_list?
        :unordered_list
      when table?
        :table
      end
    end

    ######################################################################
    private

    # This function is an internal helper for determining the paragraph
    # type of a line... for instance, if the line is a comment or contains
    # metadata. It's used in routines like blank?, plain_list?, etc.
    #
    # What's tricky is lines can have assigned types, so you need to check
    # the assigned type, if present, or see if the characteristic regexp
    # for the paragraph type matches if not present.
    #
    # call-seq:
    #     check_assignment_or_regexp(assignment, regexp) => boolean
    #
    # assignment:: if the paragraph has an assigned type, it will be
    #              checked to see if it equals +assignment+.
    # regexp::     If the paragraph does not have an assigned type,
    #              the contents of the paragraph will be checked against
    #              this regexp.
    def check_assignment_or_regexp(assignment, regexp)
      return @assigned_paragraph_type == assignment if @assigned_paragraph_type
      return @line =~ regexp
    end
  end                           # class Line
end                             # module Orgmode