cocoadex 1.4 → 1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/bin/cdex_completion +7 -0
  2. data/bin/cocoadex +5 -3
  3. data/changelog.md +5 -0
  4. data/lib/cocoadex/docset_helper.rb +8 -4
  5. data/lib/cocoadex/extensions.rb +4 -0
  6. data/lib/cocoadex/keyword.rb +30 -103
  7. data/lib/cocoadex/model.rb +56 -0
  8. data/lib/cocoadex/models/callback.rb +12 -0
  9. data/lib/cocoadex/models/class.rb +2 -2
  10. data/lib/cocoadex/models/constant.rb +41 -0
  11. data/lib/cocoadex/models/data_type.rb +47 -0
  12. data/lib/cocoadex/models/element.rb +14 -1
  13. data/lib/cocoadex/models/entity.rb +6 -0
  14. data/lib/cocoadex/models/function.rb +28 -0
  15. data/lib/cocoadex/models/generic_ref.rb +88 -0
  16. data/lib/cocoadex/models/method.rb +21 -70
  17. data/lib/cocoadex/models/nested_node_element.rb +17 -0
  18. data/lib/cocoadex/models/parameter.rb +20 -0
  19. data/lib/cocoadex/models/property.rb +3 -18
  20. data/lib/cocoadex/models/result_code.rb +17 -0
  21. data/lib/cocoadex/models/seq_node_element.rb +53 -0
  22. data/lib/cocoadex/parser.rb +22 -18
  23. data/lib/cocoadex/serializer.rb +18 -8
  24. data/lib/cocoadex/tokenizer.rb +123 -0
  25. data/lib/cocoadex/tools/completion_helper.rb +84 -0
  26. data/lib/cocoadex/version.rb +1 -1
  27. data/lib/cocoadex.rb +12 -10
  28. data/lib/ext/nil.rb +3 -0
  29. data/lib/ext/string.rb +9 -0
  30. data/lib/ext/template_helpers.rb +20 -0
  31. data/lib/ext/xml_element.rb +10 -0
  32. data/readme.md +36 -8
  33. data/views/class.erb +35 -0
  34. data/views/constant.erb +10 -0
  35. data/views/constant_group.erb +28 -0
  36. data/views/data_type.erb +47 -0
  37. data/views/generic_ref.erb +28 -0
  38. data/views/method.erb +34 -0
  39. data/views/multiple.erb +4 -0
  40. data/views/property.erb +14 -0
  41. data/views/result_code.erb +7 -0
  42. metadata +50 -10
  43. data/bin/cocoadex_completion.sh +0 -36
  44. data/lib/cocoadex/templates.rb +0 -114
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # Function to print all keyword tags by scope
3
+ # (Helper for zsh completion)
4
+
5
+ require 'cocoadex'
6
+
7
+ puts Cocoadex::CompletionHelper.new(ARGV[0]).scope_matches.join("\n")
data/bin/cocoadex CHANGED
@@ -18,15 +18,15 @@ main do |query|
18
18
  end
19
19
  if options[:configure]
20
20
  DocSetHelper.search_and_index
21
+ elsif options[:'generate-tags']
22
+ CompletionHelper.generate_tags!
21
23
  elsif options[:'load-docset']
22
24
  paths = options[:'load-docset'].map do |p|
23
25
  File.expand_path(p)
24
26
  end.uniq
25
27
  DocSetHelper.search_and_index(paths)
26
28
  elsif query
27
- if Keyword.loaded?
28
- logger.debug "Loading index..."
29
- Keyword.read
29
+ if Tokenizer.loaded?
30
30
  Cocoadex.search(query, options[:first])
31
31
  else
32
32
  puts "No DocSets loaded. Run `cocodex --configure` to search for existing DocSets."
@@ -43,6 +43,8 @@ arg :query, :optional
43
43
 
44
44
  on("--verbose","Be verbose")
45
45
 
46
+ on("--generate-tags","Generate a tags file from indexed docsets and exit")
47
+
46
48
  on("--configure","Index all DocSets in default locations")
47
49
 
48
50
  on("--first","Load first result when multiple matches exist")
data/changelog.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5
4
+
5
+ - Searching for constants, functions, data types, callbacks, and result codes are now supported!
6
+ - Added smart completion; `Class-method`, `Class+method`, and `Class.property` can now be tab-completed
7
+
3
8
  ## 1.4
4
9
 
5
10
  - Fixed parsing error on non-ASCII characters. Patch submitted by farcaller.
@@ -1,5 +1,7 @@
1
1
 
2
2
  module Cocoadex
3
+
4
+ # Helper for finding and indexing DocSets
3
5
  class DocSetHelper
4
6
 
5
7
  ROOT_PATHS = [
@@ -15,7 +17,9 @@ module Cocoadex
15
17
 
16
18
  def self.docset_paths
17
19
  @paths ||= begin
18
- ROOT_PATHS.map { |path| Dir.glob(File.expand_path(path)+'/*/') }.flatten
20
+ ROOT_PATHS.map do |path|
21
+ Dir.glob(File.expand_path(path)+'/*/')
22
+ end.flatten
19
23
  end
20
24
  end
21
25
 
@@ -28,8 +32,8 @@ module Cocoadex
28
32
  end
29
33
 
30
34
  if docsets.size > 0
31
- Keyword.write(:overwrite)
32
- Keyword.generate_tags!
35
+ Tokenizer.persist
36
+ CompletionHelper.generate_tags!
33
37
  write(docsets)
34
38
  end
35
39
  logger.info "Done! #{docsets.size} DocSet#{docsets.size == 1 ? '':'s'} indexed."
@@ -45,7 +49,7 @@ module Cocoadex
45
49
 
46
50
  def self.write docsets
47
51
  @docsets = docsets
48
- Serializer.write(data_path, docsets, :overwrite)
52
+ Serializer.write_array(data_path, docsets, :overwrite)
49
53
  end
50
54
  end
51
55
  end
@@ -0,0 +1,4 @@
1
+ require '../ext/nil'
2
+ require '../ext/string'
3
+ require '../ext/xml_element'
4
+ require '../ext/template_helpers'
@@ -11,41 +11,45 @@ module Cocoadex
11
11
  CLASS_PROP_DELIM = '.'
12
12
  SCOPE_CHARS = [CLASS_PROP_DELIM,CLASS_METHOD_DELIM,INST_METHOD_DELIM]
13
13
 
14
- def self.datastore
15
- @store ||= []
14
+ # Search the cache for matching text
15
+ def self.find text
16
+ logger.debug "Searching tokens for #{text}"
17
+ if scope = Keyword.get_scope(text)
18
+ class_name, term = text.split(scope)
19
+ find_with_scope(scope, class_name, term)
20
+ else
21
+ keys = Tokenizer.fuzzy_match(text)
22
+ keys.map {|k| k.to_element }
23
+ end
16
24
  end
17
25
 
18
- # Cache storage location
19
- def self.data_path
20
- Cocoadex.config_file("data/store.blob")
26
+ def self.get_scope text
27
+ SCOPE_CHARS.detect {|c| text.include? c}
21
28
  end
22
29
 
23
- def self.tags_path
24
- Cocoadex.config_file("tags")
30
+ def initialize term, type, docset, url
31
+ @term, @type, @docset, @url = term, type, docset, url
25
32
  end
26
33
 
27
- # Search the cache for matching text
28
- def self.find text
29
- if scope = SCOPE_CHARS.detect {|c| text.include? c }
30
- class_name, term = text.split(scope)
31
- logger.debug "Searching scope: #{scope}, #{class_name}, #{term}"
32
- find_with_scope scope, class_name, term
33
- else
34
- logger.debug "Searching Keyword datastore (#{datastore.size}): #{text}"
35
- keys = datastore.select {|k| k.term.start_with? text }
36
- logger.debug "#{keys.size} keys found"
37
- if key = keys.detect {|k| k.term == text }
38
- keys = [key]
39
- logger.debug "Exact match!"
40
- end
41
- untokenize(keys)
42
- end
34
+ def to_element
35
+ Tokenizer.untokenize([self]).first
43
36
  end
44
37
 
38
+ def inspect
39
+ "<Keyword:#{type} @term=\"#{term}\">"
40
+ end
41
+
42
+ def to_s
43
+ "#{type} => #{term}"
44
+ end
45
+
46
+ private
47
+
45
48
  # Find matches for term within a given class
46
49
  def self.find_with_scope scope, class_name, term
47
- if class_key = datastore.detect {|k| k.term == class_name }
48
- klass = untokenize([class_key]).first
50
+ if class_key = Tokenizer.match(class_name)
51
+ return [] unless class_key.type == :class
52
+ klass = class_key.to_element
49
53
  list = case scope
50
54
  when CLASS_PROP_DELIM
51
55
  klass.methods + klass.properties
@@ -59,82 +63,5 @@ module Cocoadex
59
63
  []
60
64
  end
61
65
  end
62
-
63
- # Are any docsets loaded into the cache?
64
- def self.loaded?
65
- File.exists? data_path
66
- end
67
-
68
- # Read a serialized cache file into an Array
69
- def self.read
70
- @store = Serializer.read(data_path)
71
- logger.debug "Loaded #{datastore.size} tokens"
72
- end
73
-
74
- # Write a cache Array as a serialized file
75
- def self.write style
76
- Serializer.write(data_path, datastore, style)
77
- end
78
-
79
- def self.tags
80
- @tags ||= begin
81
- if File.exists? tags_path
82
- IO.read(tags_path).split('\n')
83
- else
84
- []
85
- end
86
- end
87
- end
88
-
89
- # Build a tags file from existing kewords
90
- def self.generate_tags!
91
- text = datastore.map {|k| k.term }.join('\n')
92
- Serializer.write_text tags_path, text
93
- end
94
-
95
- # Create Cocoadex model objects for Keyword references
96
- def self.untokenize keys
97
- keys.map do |key|
98
- case key.type
99
- when :class
100
- Cocoadex::Class.new(key.url)
101
- when :method, :property
102
- if class_key = datastore.detect {|k| k.id == key.fk}
103
- klass = Cocoadex::Class.new(class_key.url)
104
- logger.debug "Searching #{key.type} list of #{klass.name}"
105
- list = key.type == :method ? klass.methods : klass.properties
106
- list.detect {|m| m.name == key.term}
107
- end
108
- end
109
- end
110
- end
111
-
112
- # Find all searchable keywords in a class and add to cache
113
- def self.tokenize_class docset, path, id
114
- klass = Cocoadex::Class.new(path)
115
- class_key = Keyword.new(klass.name, :class, docset, path)
116
- class_key.id = id
117
- datastore << class_key
118
-
119
- {:method => klass.methods, :property => klass.properties}.each do |type,list|
120
- list.each do |item|
121
- item_key = Keyword.new(item.name, type, docset, path)
122
- item_key.fk = id
123
- datastore << item_key
124
- end
125
- end
126
- end
127
-
128
- def initialize term, type, docset, url
129
- @term, @type, @docset, @url = term, type, docset, url
130
- end
131
-
132
- def inspect
133
- "<Keyword#{type} @term=#{term}>"
134
- end
135
-
136
- def to_s
137
- "#{type} => #{term}"
138
- end
139
66
  end
140
- end
67
+ end
@@ -0,0 +1,56 @@
1
+ require 'cocoadex/models/docset'
2
+ require 'cocoadex/models/element'
3
+ require 'cocoadex/models/seq_node_element'
4
+ require 'cocoadex/models/nested_node_element'
5
+ require 'cocoadex/models/entity'
6
+ require 'cocoadex/models/constant'
7
+ require 'cocoadex/models/parameter'
8
+ require 'cocoadex/models/function'
9
+ require 'cocoadex/models/callback'
10
+ require 'cocoadex/models/result_code'
11
+ require 'cocoadex/models/data_type'
12
+ require 'cocoadex/models/generic_ref'
13
+ require 'cocoadex/models/method'
14
+ require 'cocoadex/models/property'
15
+ require 'cocoadex/models/class'
16
+
17
+ # Class Structure -------------
18
+ #
19
+ # DocSet
20
+ # Element
21
+ # Entity
22
+ # Class
23
+ # GenericRef
24
+ # NestedNodeElement
25
+ # Method
26
+ # Property
27
+ # SequentialNodeElement
28
+ # Callback
29
+ # ConstantGroup
30
+ # DataType
31
+ # Function
32
+ # Constant
33
+ # Parameter
34
+ # ResultCode
35
+ #
36
+ # Relationship Tree -----------
37
+ # Class
38
+ # Method
39
+ # Parameter
40
+ # Property
41
+ #
42
+ # GenericRef
43
+ # Callback
44
+ # Parameter
45
+ # ConstantGroup
46
+ # Constant
47
+ # DataType
48
+ # Parameter
49
+ # Function
50
+ # Parameter
51
+ #
52
+ #
53
+ module Cocoadex
54
+ class Model
55
+ end
56
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module Cocoadex
3
+ class Callback < Function
4
+ def type
5
+ "Callback"
6
+ end
7
+
8
+ def discussion
9
+ @discussion
10
+ end
11
+ end
12
+ end
@@ -4,7 +4,7 @@ require 'set'
4
4
  module Cocoadex
5
5
  # A model of a Cocoa API class or protocol
6
6
  class Class < Entity
7
- TEMPLATE=Cocoadex::Templates::CLASS_DESCRIPTION
7
+ TEMPLATE_NAME=:class
8
8
 
9
9
  attr_reader :description, :overview
10
10
 
@@ -48,7 +48,7 @@ module Cocoadex
48
48
  @parents = doc.css("div.zSharedSpecBoxHeadList").first.css('a').map {|node| node.text}
49
49
 
50
50
  parse_properties(doc)
51
- parse_tasks(doc)
51
+ # parse_tasks(doc)
52
52
  parse_methods(doc)
53
53
  end
54
54
 
@@ -0,0 +1,41 @@
1
+
2
+ module Cocoadex
3
+ class ConstantGroup < SequentialNodeElement
4
+ TEMPLATE_NAME=:constant_group
5
+
6
+ attr_reader :abstract, :declaration,
7
+ :discussion, :declared_in
8
+
9
+ def constants
10
+ @constants ||= []
11
+ end
12
+
13
+ def handle_node node
14
+ if node.classes.include? "termdef"
15
+ node.css("dt").each do |field_title_node|
16
+ source = "#{@origin} > #{@name}"
17
+ field_name = field_title_node.css("code").text
18
+ description = field_title_node.next.css("p").map {|p| p.text}.join("\n")
19
+ constants << Constant.new(source,field_name, description)
20
+ end
21
+ elsif node.name == "p" and node.classes.empty?
22
+ @abstract = node.text
23
+ end
24
+ end
25
+ end
26
+
27
+ class Constant < Element
28
+ TEMPLATE_NAME=:constant
29
+
30
+ attr_reader :description
31
+
32
+ def initialize origin, name, description
33
+ @origin = origin
34
+ @name, @description = name, description
35
+ end
36
+
37
+ def origin
38
+ @origin
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+
2
+ module Cocoadex
3
+
4
+ class DataType < SequentialNodeElement
5
+ TEMPLATE_NAME=:data_type
6
+
7
+ class Field < Parameter;end
8
+
9
+ attr_reader :abstract, :declaration, :declared_in,
10
+ :discussion, :availability, :considerations
11
+ attr_accessor :next_termdef
12
+
13
+ def fields
14
+ @fields ||= []
15
+ end
16
+
17
+ def constants
18
+ @constants ||= []
19
+ end
20
+
21
+ def origin
22
+ @origin
23
+ end
24
+
25
+ def handle_node node
26
+ if ["Fields","Constants"].include? node.text
27
+ next_termdef = node.text
28
+ elsif node.classes.include? "termdef" and not next_termdef.nil?
29
+ if list = termdef_to_properties(next_termdef)
30
+ node.css("dt").each do |field_title_node|
31
+ field_name = field_title_node.css("code").text
32
+ description = field_title_node.next.text
33
+ list << Field.new(field_name, description)
34
+ end
35
+ next_termdef = ""
36
+ end
37
+ end
38
+ end
39
+
40
+ def termdef_to_properties termdef
41
+ case termdef
42
+ when "Fields" then fields
43
+ when "Constants" then constants
44
+ end
45
+ end
46
+ end
47
+ end
@@ -12,7 +12,9 @@ module Cocoadex
12
12
  end
13
13
 
14
14
  def print
15
- template = self.class.const_get(:TEMPLATE)
15
+ template_name = self.class.const_get("TEMPLATE_NAME")
16
+ path = Cocoadex.view_path(template_name)
17
+ template = IO.read(path, :mode => 'rb')
16
18
  ERB.new(template, nil, '<>').result(binding)
17
19
  end
18
20
 
@@ -27,5 +29,16 @@ module Cocoadex
27
29
  def type
28
30
  raise "#{self.class}#type is not defined"
29
31
  end
32
+
33
+ def parse_parameters node
34
+ node.css("dt").each do |param|
35
+ name_nodes = param.css("em")
36
+ if name_nodes.size > 0
37
+ name = param.css("em").first.text
38
+ description = param.next.css("p").first.text
39
+ parameters << Parameter.new(name, description)
40
+ end
41
+ end
42
+ end
30
43
  end
31
44
  end
@@ -2,8 +2,10 @@ module Cocoadex
2
2
  # A top level element, roughly equivalent to one
3
3
  # page of documentation
4
4
  class Entity < Element
5
+ attr_reader :path
5
6
 
6
7
  def initialize path
8
+ @path = path
7
9
  text = clean(IO.read(path, :mode => 'rb'))
8
10
  document = Nokogiri::HTML(text)
9
11
  parse(document)
@@ -18,5 +20,9 @@ module Cocoadex
18
20
  def strip text
19
21
  text.gsub("&#xA0;&#xA0;","")
20
22
  end
23
+
24
+ def section_by_title doc, title
25
+ doc.css("section").to_a.detect {|s| s.css("h2.jump").text == title }
26
+ end
21
27
  end
22
28
  end
@@ -0,0 +1,28 @@
1
+
2
+ module Cocoadex
3
+ class Function < SequentialNodeElement
4
+ attr_reader :abstract, :declaration, :declared_in,
5
+ :availability, :return_value
6
+ TEMPLATE_NAME=:method
7
+
8
+ def parameters
9
+ @parameters ||= []
10
+ end
11
+
12
+ def discussion
13
+ ""
14
+ end
15
+
16
+ def handle_node node
17
+ if node.classes.include? "parameters"
18
+ parse_parameters(node)
19
+ else
20
+ logger.debug("Unhandled function property: #{node.classes} => #{node.text}")
21
+ end
22
+ end
23
+
24
+ def type
25
+ "Function"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,88 @@
1
+
2
+ module Cocoadex
3
+ # A non-class reference document containing functions,
4
+ # constants, callbacks, result codes, and data types
5
+ class GenericRef < Entity
6
+ attr_reader :specs, :data_types, :overview,
7
+ :result_codes, :const_groups, :functions,
8
+ :callbacks
9
+ TEMPLATE_NAME=:generic_ref
10
+
11
+ def parse doc
12
+ @name = doc.title.sub("Reference","").strip
13
+ @specs = {}
14
+
15
+ parse_specbox(doc)
16
+ parse_overview(doc)
17
+ parse_data_types(doc)
18
+ parse_result_codes(doc)
19
+ parse_constants(doc)
20
+ parse_functions(doc)
21
+ parse_callbacks(doc)
22
+ end
23
+
24
+ def constants
25
+ @const_groups.map {|g| g.constants}.flatten
26
+ end
27
+
28
+ def parse_overview doc
29
+ if section = section_by_title(doc, "Overview")
30
+ @overview = section.text.sub("Overview","")
31
+ else
32
+ @overview = ""
33
+ end
34
+ end
35
+
36
+ def parse_specbox doc
37
+ specbox = doc.css(".specbox")
38
+ return if specbox.to_a.empty?
39
+ specbox.first.css("tr").each do |row|
40
+ title = row.css("td").first.css("strong").text
41
+ value = row.css("td").to_a[1].text.strip
42
+ @specs[title] = value
43
+ end
44
+ end
45
+
46
+ def parse_callbacks doc
47
+ @callbacks = []
48
+ parse_section(doc, @callbacks, "Callbacks", Callback)
49
+ end
50
+
51
+ def parse_functions doc
52
+ @functions = []
53
+ parse_section(doc, @functions, "Functions", Function)
54
+ end
55
+
56
+ def parse_data_types doc
57
+ @data_types = []
58
+ parse_section(doc, @data_types, "Data Types", DataType)
59
+ end
60
+
61
+ def parse_constants doc
62
+ @const_groups = []
63
+ parse_section(doc, @const_groups, "Constants",ConstantGroup)
64
+ end
65
+
66
+ def parse_section doc, list, title, klass
67
+ if section = section_by_title(doc, title)
68
+ section.css("h3").each do |type_title|
69
+ list << klass.new(@name, type_title)
70
+ end
71
+ end
72
+ end
73
+
74
+ def parse_result_codes doc
75
+ @result_codes = []
76
+ if section = section_by_title(doc, "Result Codes")
77
+ table = section.css("table").first
78
+ table.css("tr").each do |row|
79
+ if cells = row.css("td") and cells.size > 0
80
+ value = cells[1].text
81
+ description = cells[2].css("p").map {|p| p.text}.join("\n\n")
82
+ @result_codes << ResultCode.new(@name, cells.first.text, value, description)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,81 +1,32 @@
1
1
 
2
2
  module Cocoadex
3
- class Class < Entity
4
- # A model of a method in a Cocoa class
5
- class Method < Element
6
- TEMPLATE=Cocoadex::Templates::METHOD_DESCRIPTION
3
+ # A model of a method in a Cocoa class
4
+ class Method < NestedNodeElement
5
+ TEMPLATE_NAME=:method
7
6
 
8
- attr_reader :abstract, :declaration, :discussion,
9
- :declared_in, :availability, :parameters,
10
- :return_value, :scope, :parent
7
+ attr_reader :abstract, :declaration, :discussion,
8
+ :declared_in, :availability, :parameters,
9
+ :return_value, :scope, :parent
11
10
 
12
- class Parameter
13
- include Comparable
14
-
15
- attr_reader :name, :description
16
-
17
- def initialize name, description
18
- @name, @description = name, description
19
- end
20
-
21
- def to_s
22
- "#{name} - #{description}"
23
- end
24
-
25
- def <=> other
26
- name <=> other.name if other.respond_to? :name
27
- end
28
- end
29
-
30
- def parameters
31
- @parameters ||= []
32
- end
33
-
34
- def initialize parent_class, type, node
35
- @parent = parent_class
36
- @scope = type
37
- @name = node.css("h3.#{type}Method").first.text
38
- logger.debug("parsing #{@type} method #{@name}")
39
-
40
- @abstract = node.css(".abstract").first.text
41
- @declaration = node.css(".declaration").first.text
42
-
43
- decl_nodes = node.css(".declaredIn code.HeaderFile")
44
- @declared_in = decl_nodes.first.text if decl_nodes.size > 0
45
-
46
- discussion_node = node.css(".discussion > p")
47
- @discussion = discussion_node.first.text if discussion_node.length > 0
48
-
49
- return_nodes = node.css(".return_value p")
50
- @return_value = return_nodes.first.text if return_nodes.size > 0
51
-
52
- ava_nodes = node.css(".availability > ul > li")
53
- @availability = ava_nodes.first.text if ava_nodes.size > 0
11
+ def parameters
12
+ @parameters ||= []
13
+ end
54
14
 
55
- parse_parameters(node)
56
- end
15
+ def initialize parent_class, type, node
16
+ @parent = parent_class
17
+ @scope = type
18
+ @name = node.css("h3.#{type}Method").first.text
57
19
 
58
- def parse_parameters node
59
- if parameters = node.css(".parameters") and parameters.length > 0
60
- @parameters = []
61
- parameters.first.css("dt").each do |param|
62
- name_nodes = param.css("em")
63
- if name_nodes.size > 0
64
- name = param.css("em").first.text
65
- description = param.next.css("p").first.text
66
- @parameters << Parameter.new(name, description)
67
- end
68
- end
69
- end
70
- end
20
+ parse_properties(node)
21
+ parse_parameters(node.css(".parameters").first)
22
+ end
71
23
 
72
- def type
73
- "#{scope.to_s.capitalize} Method"
74
- end
24
+ def type
25
+ "#{scope.to_s.capitalize} Method"
26
+ end
75
27
 
76
- def origin
77
- parent.to_s
78
- end
28
+ def origin
29
+ parent.to_s
79
30
  end
80
31
  end
81
32
  end