decode 0.13.0 → 0.15.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/bake/decode/index.rb +1 -1
  3. data/lib/decode/comment/attribute.rb +53 -0
  4. data/lib/decode/comment/node.rb +90 -0
  5. data/lib/decode/comment/parameter.rb +58 -0
  6. data/lib/decode/comment/pragma.rb +50 -0
  7. data/lib/decode/comment/raises.rb +32 -0
  8. data/lib/decode/comment/returns.rb +32 -0
  9. data/lib/decode/comment/tag.rb +50 -0
  10. data/lib/decode/comment/tags.rb +80 -0
  11. data/lib/decode/comment/text.rb +37 -0
  12. data/lib/decode/comment/throws.rb +32 -0
  13. data/lib/decode/comment/yields.rb +52 -0
  14. data/lib/decode/definition.rb +26 -19
  15. data/lib/decode/documentation.rb +21 -66
  16. data/lib/decode/index.rb +7 -7
  17. data/lib/decode/language/generic.rb +2 -2
  18. data/lib/decode/language/reference.rb +39 -9
  19. data/lib/decode/language/ruby.rb +40 -6
  20. data/lib/decode/language/ruby/block.rb +1 -1
  21. data/lib/decode/language/ruby/class.rb +2 -2
  22. data/lib/decode/language/ruby/code.rb +85 -0
  23. data/lib/decode/language/ruby/definition.rb +1 -1
  24. data/lib/decode/language/ruby/method.rb +7 -1
  25. data/lib/decode/language/ruby/module.rb +6 -0
  26. data/lib/decode/language/ruby/parser.rb +11 -3
  27. data/lib/decode/language/ruby/reference.rb +31 -0
  28. data/lib/decode/language/ruby/segment.rb +1 -1
  29. data/lib/decode/scope.rb +3 -0
  30. data/lib/decode/segment.rb +4 -4
  31. data/lib/decode/source.rb +24 -8
  32. data/lib/decode/syntax/link.rb +44 -0
  33. data/lib/decode/syntax/match.rb +53 -0
  34. data/lib/decode/syntax/rewriter.rb +70 -0
  35. data/lib/decode/trie.rb +13 -13
  36. data/lib/decode/version.rb +1 -1
  37. metadata +23 -43
  38. data/.github/workflows/development.yml +0 -50
  39. data/.gitignore +0 -13
  40. data/.rspec +0 -2
  41. data/README.md +0 -47
  42. data/decode.gemspec +0 -33
  43. data/gems.rb +0 -3
  44. data/guides/extract-symbols/extract.rb +0 -26
@@ -0,0 +1,37 @@
1
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'node'
22
+
23
+ module Decode
24
+ module Comment
25
+ # A structured comment.
26
+ class Text
27
+ def initialize(line)
28
+ @line = line
29
+ end
30
+
31
+ attr :line
32
+
33
+ def traverse
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'attribute'
22
+
23
+ module Decode
24
+ module Comment
25
+ # Identifies that a method might throw a specific symbol.
26
+ #
27
+ # - `@throws [:skip] To skip recursion.`
28
+ #
29
+ class Throws < Attribute
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,52 @@
1
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'tag'
22
+
23
+ module Decode
24
+ module Comment
25
+ # Describes a block parameter.
26
+ #
27
+ # - `@yields {|person| ... } If a block is given.`
28
+ #
29
+ # Should contain nested parameters.
30
+ class Yields < Tag
31
+ PATTERN = /\A(?<block>{.*?})(\s+(?<details>.*?))?\Z/
32
+
33
+ def self.build(directive, match)
34
+ node = self.new(directive, match[:block])
35
+
36
+ if details = match[:details]
37
+ node.add(Text.new(details))
38
+ end
39
+
40
+ return node
41
+ end
42
+
43
+ def initialize(directive, block)
44
+ super(directive)
45
+
46
+ @block = block
47
+ end
48
+
49
+ attr :block
50
+ end
51
+ end
52
+ end
@@ -22,10 +22,10 @@ module Decode
22
22
  # A symbol with attached documentation.
23
23
  class Definition
24
24
  # Initialize the symbol.
25
- # @param name [Symbol] The name of the definition.
26
- # @param parent [Symbol] The parent lexical scope.
27
- # @param language [Language] The language in which the symbol is defined in.
28
- # @param comments [Array(String)] The comments associated with the definition.
25
+ # @parameter name [Symbol] The name of the definition.
26
+ # @parameter parent [Symbol] The parent lexical scope.
27
+ # @parameter language [Language] The language in which the symbol is defined in.
28
+ # @parameter comments [Array(String)] The comments associated with the definition.
29
29
  def initialize(name, parent: nil, language: parent.language, comments: nil)
30
30
  @name = name
31
31
 
@@ -44,20 +44,23 @@ module Decode
44
44
 
45
45
  # The symbol name.
46
46
  # e.g. `:Decode`.
47
+ # @attribute [Symbol]
47
48
  attr :name
48
49
 
49
- # The parent symbol, defining lexical scope.
50
+ # The parent definition, defining lexical scope.
51
+ # @attribute [Definition | Nil]
50
52
  attr :parent
51
53
 
52
54
  # The language the symbol is defined within.
55
+ # @attribute [Language::Generic]
53
56
  attr :language
54
57
 
55
58
  # The comment lines which directly preceeded the definition.
56
- # @attr [Array(String)]
59
+ # @attribute [Array(String)]
57
60
  attr :comments
58
61
 
59
62
  # The qualified name is an absolute name which includes any and all namespacing.
60
- # @return [String]
63
+ # @returns [String]
61
64
  def qualified_name
62
65
  @qualified_name ||= begin
63
66
  if @parent
@@ -69,21 +72,25 @@ module Decode
69
72
  end
70
73
 
71
74
  # The name of this definition plus the nesting prefix.
72
- # @return [String]
75
+ # @returns [String]
73
76
  def nested_name
74
77
  "::#{@name}"
75
78
  end
76
79
 
80
+ # Does the definition name match the specified prefix?
81
+ # @returns [Boolean]
77
82
  def start_with?(prefix)
78
83
  self.nested_name.start_with?(prefix)
79
84
  end
80
85
 
86
+ # Convert this definition into another kind of definition.
81
87
  def convert(kind)
82
88
  raise ArgumentError, "Unable to convert #{self} into #{kind}!"
83
89
  end
84
90
 
85
- # The lexical scope which is an array of lexical {Key} instances as generated by {key}.
86
- # @return [Array]
91
+ # The lexical scope as an array of names.
92
+ # e.g. `[:Decode, :Definition]`
93
+ # @returns [Array]
87
94
  def path
88
95
  if @path
89
96
  # Cached version:
@@ -102,14 +109,14 @@ module Decode
102
109
  # A short form of the definition.
103
110
  # e.g. `def short_form`.
104
111
  #
105
- # @return [String | nil]
112
+ # @returns [String | nil]
106
113
  def short_form
107
114
  end
108
115
 
109
116
  # A long form of the definition.
110
117
  # e.g. `def initialize(kind, name, comments, **options)`.
111
118
  #
112
- # @return [String | nil]
119
+ # @returns [String | nil]
113
120
  def long_form
114
121
  self.short_form
115
122
  end
@@ -117,44 +124,44 @@ module Decode
117
124
  # A long form which uses the qualified name if possible.
118
125
  # Defaults to {long_form}.
119
126
  #
120
- # @return [String | nil]
127
+ # @returns [String | nil]
121
128
  def qualified_form
122
129
  self.long_form
123
130
  end
124
131
 
125
132
  # Whether the definition spans multiple lines.
126
133
  #
127
- # @return [Boolean]
134
+ # @returns [Boolean]
128
135
  def multiline?
129
136
  false
130
137
  end
131
138
 
132
139
  # The full text of the definition.
133
140
  #
134
- # @return [String | nil]
141
+ # @returns [String | nil]
135
142
  def text
136
143
  end
137
144
 
138
145
  # Whether this definition can contain nested definitions.
139
146
  #
140
- # @return [Boolean]
147
+ # @returns [Boolean]
141
148
  def container?
142
149
  false
143
150
  end
144
151
 
145
152
  # Whether this represents a single entity to be documented (along with it's contents).
146
153
  #
147
- # @return [Boolean]
154
+ # @returns [Boolean]
148
155
  def nested?
149
156
  container?
150
157
  end
151
158
 
152
159
  # Structured access to the definitions comments.
153
160
  #
154
- # @return [Documentation | Nil] A `Documentation` if this definition has comments.
161
+ # @returns [Documentation | Nil] A {Documentation} instance if this definition has comments.
155
162
  def documentation
156
163
  if @comments&.any?
157
- @documentation ||= Documentation.new(@comments)
164
+ @documentation ||= Documentation.new(@comments, @language)
158
165
  end
159
166
  end
160
167
  end
@@ -18,83 +18,38 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative 'comment/node'
22
+
23
+ require_relative 'comment/attribute'
24
+ require_relative 'comment/parameter'
25
+ require_relative 'comment/pragma'
26
+ require_relative 'comment/raises'
27
+ require_relative 'comment/returns'
28
+ require_relative 'comment/throws'
29
+ require_relative 'comment/yields'
30
+
21
31
  module Decode
22
32
  # Structured access to a set of comment lines.
23
- class Documentation
33
+ class Documentation < Comment::Node
24
34
  # Initialize the documentation with an array of comments, within a specific language.
25
35
  #
26
- # @param comments [Array(String)] An array of comment lines.
27
- # @param language [Language] The language in which the comments were extracted.
36
+ # @parameter comments [Array(String)] An array of comment lines.
37
+ # @parameter language [Language] The language in which the comments were extracted.
28
38
  def initialize(comments, language = nil)
29
39
  @comments = comments
30
40
  @language = language
31
- end
32
-
33
- # The language in which the documentation was extracted from.
34
- attr :language
35
-
36
- DESCRIPTION = /\A\s*([^@\s].*)?\z/
37
-
38
- # The text-only lines of the comment block.
39
- #
40
- # @yield [String]
41
- # @return [Enumerable]
42
- def description
43
- return to_enum(:description) unless block_given?
44
-
45
- # We track empty lines and only yield a single empty line when there is another line of text:
46
- gap = false
47
-
48
- @comments.each do |comment|
49
- if match = comment.match(DESCRIPTION)
50
- if match[1]
51
- if gap
52
- yield ""
53
- gap = false
54
- end
55
-
56
- yield match[1]
57
- else
58
- gap = true
59
- end
60
- else
61
- break
62
- end
63
- end
64
- end
65
-
66
- ATTRIBUTE = /\A\s*@(?<name>.*?)\s+(?<value>.*?)\z/
67
-
68
- # The attribute lines of the comment block.
69
- # e.g. `@return [String]`.
70
- #
71
- # @yield [String]
72
- # @return [Enumerable]
73
- def attributes
74
- return to_enum(:attributes) unless block_given?
75
41
 
76
- @comments.each do |comment|
77
- if match = comment.match(ATTRIBUTE)
78
- yield match
79
- end
42
+ language.tags.parse(@comments.dup) do |node|
43
+ self.add(node)
80
44
  end
81
45
  end
82
46
 
83
- PARAMETER = /\A\s*@param\s+(?<name>.*?)\s+\[(?<type>.*?)\]\s+(?<details>.*?)\z/
47
+ # The underlying comments from which the documentation is extracted.
48
+ # @attribute [Array(String)]
49
+ attr :comments
84
50
 
85
- # The parameter lines of the comment block.
86
- # e.g. `@param value [String] The value.`
87
- #
88
- # @yield [String]
89
- # @return [Enumerable]
90
- def parameters
91
- return to_enum(:parameters) unless block_given?
92
-
93
- @comments.each do |comment|
94
- if match = comment.match(PARAMETER)
95
- yield match
96
- end
97
- end
98
- end
51
+ # The language in which the documentation was extracted from.
52
+ # @attribute [Language::Generic]
53
+ attr :language
99
54
  end
100
55
  end
@@ -38,26 +38,26 @@ module Decode
38
38
  end
39
39
 
40
40
  # All supported languages for this index.
41
- # @attr [Languages]
41
+ # @attribute [Languages]
42
42
  attr :languages
43
43
 
44
44
  # All source files that have been parsed.
45
- # @attr [Array(Source)]
45
+ # @attribute [Array(Source)]
46
46
  attr :sources
47
47
 
48
48
  # All definitions which have been parsed.
49
- # @attr [Array(Symbol)]
49
+ # @attribute [Array(Symbol)]
50
50
  attr :definitions
51
51
 
52
52
  # A (prefix) trie of lexically scoped definitions.
53
- # @attr [Trie]
53
+ # @attribute [Trie]
54
54
 
55
55
  attr :trie
56
56
 
57
57
  # Updates the index by parsing the specified files.
58
58
  # All extracted definitions are merged into the existing index.
59
59
  #
60
- # @param paths [Array(String)] The source file paths.
60
+ # @parameter paths [Array(String)] The source file paths.
61
61
  def update(paths)
62
62
  paths.each do |path|
63
63
  if source = @languages.source_for(path)
@@ -75,8 +75,8 @@ module Decode
75
75
 
76
76
  # Lookup the specified reference and return matching definitions.
77
77
  #
78
- # @param reference [Reference] The reference to match.
79
- # @param relative_to [Definition] Lookup the reference relative to the scope of this definition.
78
+ # @parameter reference [Language::Reference] The reference to match.
79
+ # @parameter relative_to [Definition] Lookup the reference relative to the scope of this definition.
80
80
  def lookup(reference, relative_to: nil)
81
81
  if reference.absolute? || relative_to.nil?
82
82
  lexical_path = []
@@ -36,13 +36,13 @@ module Decode
36
36
  end
37
37
 
38
38
  # Parse the input yielding definitions.
39
- # @block `{|definition| ...}`
39
+ # @block {|definition| ... }
40
40
  # @yield definition [Definition]
41
41
  def definitions_for(input, &block)
42
42
  end
43
43
 
44
44
  # Parse the input yielding interleaved comments and code segments.
45
- # @block `{|segment| ...}`
45
+ # @block {|segment| ... }
46
46
  # @yield segment [Segment]
47
47
  def segments_for(input, &block)
48
48
  end
@@ -23,20 +23,29 @@ module Decode
23
23
  # An reference which can be resolved to zero or more definitions.
24
24
  class Reference
25
25
  # Initialize the reference.
26
- # @param identifier [String] The identifier part of the reference.
27
- def initialize(identifier, language)
26
+ # @parameter identifier [String] The identifier part of the reference.
27
+ def initialize(identifier, language, lexical_path = nil)
28
28
  @identifier = identifier
29
29
  @language = language
30
30
 
31
- @lexical_path = nil
31
+ @lexical_path = lexical_path
32
32
  @path = nil
33
33
  end
34
34
 
35
+ def to_s
36
+ "{#{self.language} #{self.identifier}}"
37
+ end
38
+
39
+ def inspect
40
+ "\#<#{self.class} {#{self.identifier}}>"
41
+ end
42
+
35
43
  # The identifier part of the reference.
36
- # @attr [String]
44
+ # @attribute [String]
37
45
  attr :identifier
38
46
 
39
47
  # The language associated with this reference.
48
+ # @attribute [Language::Generic]
40
49
  attr :language
41
50
 
42
51
  # Whether the reference starts at the base of the lexical tree.
@@ -58,18 +67,39 @@ module Decode
58
67
  @lexical_path ||= self.split(@identifier)
59
68
  end
60
69
 
70
+ def priority(definition, prefix)
71
+ if prefix.nil?
72
+ return 1
73
+ elsif definition.start_with?(prefix)
74
+ return 0
75
+ else
76
+ return 2
77
+ end
78
+ end
79
+
61
80
  def best(definitions)
62
81
  prefix, name = lexical_path.last
63
82
 
64
- definitions.select do |definition|
65
- definition.language == @language && (
66
- prefix.nil? || definition.start_with?(prefix)
67
- )
83
+ first = nil
84
+ without_prefix = nil
85
+
86
+ definitions.each do |definition|
87
+ first ||= definition
88
+
89
+ next unless definition.language == @language
90
+
91
+ if prefix.nil?
92
+ without_prefix ||= definition
93
+ elsif definition.start_with?(prefix)
94
+ return definition
95
+ end
68
96
  end
97
+
98
+ return without_prefix || first
69
99
  end
70
100
 
71
101
  # The lexical path of the reference.
72
- # @return [Array(String)]
102
+ # @returns [Array(String)]
73
103
  def path
74
104
  @path ||= self.lexical_path.map{|_, name| name.to_sym}
75
105
  end