Module: Docks::Processors

Extended by:
Processors
Included in:
Processors, Tags::Base
Defined in:
lib/docks/processors.rb

Constant Summary collapse

CODE_BLOCK_INDICATOR =
"```"
LIST_ITEM_INDICATOR =
/^(?:\*|\-|\d+\.)/
HEADING_INDICATOR =
/^#+/
HEADING_UNDERLINE_INDICATOR =
/^[=\-]+/

Instance Method Summary collapse

Instance Method Details

#code_block_with_language_and_description(content) ⇒ Object

Public: Processes the passed content by splitting it on commas, spaces, and pipes (and removing associated whitespace).

content - An Array or String representing the parsed result.

Examples

CodeBlockWithLanguage.process([ "html - The markup.",
                                "<div class="foo">Bar</div>",
                                "<div class="bar">Baz</div>" ])
# => {language: "html", description: "The markup", code: "<div class=\"foo\">Bar</div>\n<div class=\"foo\">Bar</div>"}

CodeBlockWithLanguage.process([ "<div class="foo">Bar</div>",
                                "<div class="bar">Baz</div>" ])
# => {code: "<div class=\"foo\">Bar</div>\n<div class=\"foo\">Bar</div>"}

Returns a Hash containing the language, description, and code block.



401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/docks/processors.rb', line 401

def code_block_with_language_and_description(content)
  result = {}
  content = Array(content)
  possible_line_details = content.first.strip.split(/\s*\-\s*/, 2)

  if Docks::Languages.extensions.include?(possible_line_details.first)
    result[:language] = possible_line_details.first
    result[:description] = possible_line_details.last if possible_line_details.length > 1
    content.shift
  end

  result[:code] = content.join("\n")
  result
end

#ensure_valid_demo_type(content) ⇒ Object

Public: Processes the passed content by returning it if it’s a valid demo type, and otherwise returning the default demo type.

content - A string representing the desired demo type.

Examples

ensure_valid_demo_type(Docks::Types::Demo::SELECT)
# => "select"

ensure_valid_demo_type("foo")
# => "none"

Returns the processed string.



378
379
380
381
# File 'lib/docks/processors.rb', line 378

def ensure_valid_demo_type(content)
  @demo_types ||= Docks::Types::Demo.constants.map { |const| Docks::Types::Demo.const_get(const) }
  @demo_types.include?(content) ? content : Docks::Types::Demo::DEFAULT
end

#join_with_smart_line_breaks(content, join = "\n\n") ⇒ Object

Public: Processes the passed content by joining it with line breaks as required. to create markdown-parsable paragraphs and code blocks.

content - An Array representing the parsed result. join - The string with which to join two different paragraphs together.

Examples

join_with_smart_line_breaks(["One paragraph that", "spans two lines.", "And another!"])
# => "One paragraph that spans two lines.\n\nAnd another!"

join_with_smart_line_breaks(["One paragraph", "```html", "<p>A code block</p>", "```", "another paragraph."])
# => "One paragraph.\n\n```html\n<p>A code block</p>\n```\n\nanother paragraph"

Returns the processed string.



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
# File 'lib/docks/processors.rb', line 162

def join_with_smart_line_breaks(content, join = "\n\n")
  return content unless content.kind_of?(Array)

  text = ""
  in_code_block = false
  in_list = false
  at_start_of_paragraph = true

  content.each_with_index do |line, index|
    stripped_line = line.strip

    if stripped_line.start_with?(CODE_BLOCK_INDICATOR)
      # Either the start or end of a code block
      if in_code_block
        in_code_block = false
        at_start_of_paragraph = true

        text << "\n#{line}#{join}"
      else
        in_code_block = true
        text << "#{join}#{line}"
      end

    elsif in_code_block || (in_list && stripped_line =~ LIST_ITEM_INDICATOR)
      # Either:
      # 1. In a code block — just keep appending lines, or
      # 2. Last item was a list item and this item is directly below it,
      #    so just add that line below.
      text << "\n#{line}"

    elsif stripped_line.length == 0
      # Empty line — new paragraph
      at_start_of_paragraph = true

      text << join

    elsif stripped_line =~ LIST_ITEM_INDICATOR && at_start_of_paragraph
      # Line that looks like a list item and we're ready for a new paragraph
      in_list = true
      at_start_of_paragraph = false

      text << line

    elsif stripped_line =~ HEADING_INDICATOR
      # Starts and ends a "## Header"-style heading
      at_start_of_paragraph = true
      text << "\n#{line}\n"

    elsif stripped_line =~ HEADING_UNDERLINE_INDICATOR
      # Ends a "Header\n======"-style heading
      at_start_of_paragraph = true
      text << "#{line}#{join}"

    elsif content[index + 1] && content[index + 1].strip =~ HEADING_UNDERLINE_INDICATOR
      # Start of a "Header\n======"-style heading
      text << "\n#{line}\n"

    elsif at_start_of_paragraph
      # New line at the start of a regular paragraph
      at_start_of_paragraph = false
      in_list = false

      text << line

    else
      # Line that isn't at the start of a paragraph — pin it to the previous line.
      text << " #{stripped_line}"
    end
  end

  text.strip!
  text.gsub!(/\n{3,}/, "\n\n")
  text.length > 0 ? text : nil
end

#multiline_description(content) ⇒ Object

Public: Processes the passed content by yielding to the passed block in order to create a base hash, then adding any subsequent lines to the description (which can start on the first line — just set the description part as the returned hash’s :description key. The description is then joined with smart line breaks.

Yields the first line (item) of content.

content - An Array of Strings representing the multiline description.

Examples

multiline_description(['bar', 'baz']) { |first_line| { foo: first_line } }
# => { foo: 'bar', description: 'baz' }

multiline_description(['bar', 'Baz']) { |first_line| { foo: first_line, description: 'Already started!' } }
# => { foo: 'bar', description: "Already started!\n\nBaz" }

Returns the processed Hash.



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
# File 'lib/docks/processors.rb', line 336

def multiline_description(content)

  content = Array(content)
  description = []
  item = nil

  content.each do |line|
    if item.nil?
      item = yield(line) if block_given?

      if !item.kind_of?(Hash)
        item = Hash.new
        description << line
      elsif !item[:description].nil?
        description << item.delete(:description)
      end
    else
      description << line
    end
  end

  unless item.nil?
    item[:description] = join_with_smart_line_breaks(description)
    item
  end
end

#name_and_parenthetical(content, name_key = :name, default_for_parenthetical = nil) ⇒ Object

Public: parses a string with names and optional parentheticals into an array of hashes with the name as the ‘name_key` and the parsed parenthetical options using the optional `default_for_parenthetical`.

content - The string with the name and optional parenthetical. name_key - An optional Symbol representing the key that should be

used for the non-parenthetical portion. Defaults to :name.

default_key - An optional Symbol to be used as the default key for an

unpaired item in the parentheses.

Examples

name_and_parenthetical(':matches?', :setter)
# => [{setter: ':matches?'}]

name_and_parenthetical(':size (SIZE::LARGE)', :setter, :constant)
# => [{setter: ':size', constant: 'SIZE::LARGE'}]

name_and_parenthetical([':size (SIZE::LARGE)', ':checked?'], :setter, :constant)
# => [{setter: ':size', constant: 'SIZE::LARGE'}, {setter: ':checked?'}]

Returns the parsed hash.



260
261
262
263
264
265
266
267
268
269
270
# File 'lib/docks/processors.rb', line 260

def name_and_parenthetical(content, name_key=:name, default_for_parenthetical=nil)
  result = {}
  match = content.match(/\s*(?<name>[^\(]*)(?:\s*\((?<paren>[^\)]*)\))?/)
  result[name_key] = match[:name].strip

  if (parenthetical = match[:paren]).nil?
    result
  else
    result.merge(parenthetical_options(parenthetical, default_for_parenthetical))
  end
end

#parenthetical_options(content, default = nil) ⇒ Object

Public:

content - A String of the parenthetical. default - An optional Symbol representing the key that should be used if

an un-paired value is provided.

Examples

parenthetical_options('Activate with : foo , active : false')
# => {activate_with: 'foo', active: 'false'}

parenthetical_options('select, active : false, javascript action: $(this).text(1 + 1)', :demo_type)
# => {demo_type: 'select', active: 'false', javascript_action: '$(this).text(1 + 1)'}

Returns the processed Hash.



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
# File 'lib/docks/processors.rb', line 288

def parenthetical_options(content, default=nil)
  return content unless content.kind_of?(String)
  result, default_only = {}, true
  default = default.to_sym unless default.nil?
  # Strip leading and trailing parentheses
  content = content.strip.sub(/^\(/, '').sub(/\)$/, '')

  # Get a leading, un-keyed value. This will be set to the default
  # key (`default`), if it was passed
  content.sub!(/\s*([^,]*),?\s*/) do |match|
    if match.index(': ').nil?
      result[default] = $1.strip unless default.nil?
      ''
    else
      match
    end
  end

  # Cycle over key value pairs and add to the return Hash
  content.scan(/\s*(?<attribute>[^:]*):\s+(?<value>[^,]*),?\s*/).each do |match|
    default_only = false
    # Ensure that the key looks like a regular symbol
    result[match[0].strip.downcase.gsub(/\s+/, '_').to_sym] = match[1].strip
  end

  result
end

#split_on_characters(content, split_on = "\,\s") ⇒ Object

Public: Processes the passed content splitting it on type-delimitting symbols (/ to declare the type, ,/|/s to separate them).

content - The String to break apart. split_on - The characters, passed as a single String or an Array of Strings,

on which the content string should be split. Defaults to "\,\s".

Examples

split_on_characters("String, Array", "\s\,")
# => ["String", "Array"]

Returns an Array with the split pieces.



110
111
112
113
114
115
116
117
# File 'lib/docks/processors.rb', line 110

def split_on_characters(content, split_on="\,\s")
  return content unless content.kind_of?(String)

  split_on = split_on.join("") if split_on.kind_of?(Array)
  return content unless split_on.kind_of?(String)

  content.split(/[#{split_on}]/).select { |piece| piece.length > 0 }
end

#split_on_commas_spaces_and_pipes(content) ⇒ Object

Public: Processes the passed content by splitting it on commas, spaces, and pipes (and removing associated whitespace).

content - An Array or String representing the parsed result.

Examples

split_on_commas_spaces_and_pipes("String, Array | Object")
# => ["String", "Array", "Object"]

Returns an Array of the split results.



17
18
19
# File 'lib/docks/processors.rb', line 17

def split_on_commas_spaces_and_pipes(content)
  split_on_characters(content, [",", "|", "\s"])
end

#split_on_top_level_parens_commas_and_pipes(content) ⇒ Object



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
# File 'lib/docks/processors.rb', line 21

def split_on_top_level_parens_commas_and_pipes(content)
  return content unless content.kind_of?(String)

  indexes = []
  depth = 0

  split_on = /[\|,]/
  content.chars.each_with_index do |char, index|
    if char == "("
      depth += 1
    elsif char == ")"
      new_depth = [0, depth - 1].max

      if new_depth == 0 && depth != 0
        indexes << index
      end

      depth = new_depth
    elsif split_on =~ char && depth == 0
      indexes << index
    end
  end


  result = []
  start_index = 0

  start_strip = /^\s*[,\|]*\s*/
  end_strip = /\s*[,\|]*\s*$/
  indexes.each do |index|
    if index > start_index
      item = content[start_index..index].gsub(start_strip, "").gsub(end_strip, "")
      result << item if item.length > 0
    end

    start_index = index + 1
  end

  if start_index < content.length
    result << content[start_index..-1].gsub(start_strip, "").gsub(end_strip, "")
  end

  result
end

#split_types(content) ⇒ Object

Public: Processes the passed content by splitting it on type-delimitting symbols (/ to declare the type, ,/|/s to separate them).

content - An Array or String representing the parsed result.

Examples

split_types("{String, Number |Object[]}")
# => [
       OpenStruct.new(name: "String", array: false),
       OpenStruct.new(name: "Number", array: false),
       OpenStruct.new(name: "Object", array: true)
     ]

Returns an Array of types.



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/docks/processors.rb', line 82

def split_types(content)
  split_on_commas_spaces_and_pipes(content.gsub(/[\{\}]/, "").strip).map do |type|
    array, type = if /\[\]/ =~ type
      [true, type.split("[").first]
    else
      [false, type]
    end

    type = "Anything" if type == "*"

    OpenStruct.new(name: type, array: array)
  end
end

#stringy_boolean(content) ⇒ Object

Public: Processes the passed String by converting it to a boolean where any string other than one matching /false/ will be considered true.

content - A String representing the parsed result.

Examples

stringy_boolean("  false ")
# => false

stringy_boolean("foo")
# => true

Returns a boolean version of the content.



135
136
137
138
139
# File 'lib/docks/processors.rb', line 135

def stringy_boolean(content)
  return false if content.nil?
  return content unless content.kind_of?(String)
  !content.include?("false")
end