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
@@ -20,10 +20,16 @@
20
20
 
21
21
  require_relative 'ruby/reference'
22
22
  require_relative 'ruby/parser'
23
+ require_relative 'ruby/code'
24
+
25
+ require_relative '../comment/tags'
26
+ require_relative '../comment/parameter'
27
+ require_relative '../comment/yields'
28
+ require_relative '../comment/returns'
23
29
 
24
30
  module Decode
25
31
  module Language
26
- # The Ruby language.
32
+ # An interface for extracting information from Ruby source code.
27
33
  module Ruby
28
34
  # The canoical name of the language for use in output formatting.
29
35
  # e.g. source code highlighting.
@@ -39,24 +45,52 @@ module Decode
39
45
  ['.rb', '.ru']
40
46
  end
41
47
 
48
+ TAGS = Comment::Tags.build do |tags|
49
+ tags['attribute'] = Comment::Attribute
50
+ tags['parameter'] = Comment::Parameter
51
+ tags['yields'] = Comment::Yields
52
+ tags['returns'] = Comment::Returns
53
+ tags['raises'] = Comment::Raises
54
+ tags['throws'] = Comment::Throws
55
+
56
+ tags['reentrant'] = Comment::Pragma
57
+ tags['deprecated'] = Comment::Pragma
58
+ tags['blocking'] = Comment::Pragma
59
+ tags['asynchronous'] = Comment::Pragma
60
+ end
61
+
62
+ def self.tags
63
+ TAGS
64
+ end
65
+
42
66
  # Generate a language-specific reference.
67
+ # @parameter identifier [String] A valid identifier.
43
68
  def self.reference_for(identifier)
44
69
  Reference.new(identifier, self)
45
70
  end
46
71
 
47
72
  # Parse the input yielding definitions.
48
- # @block `{|definition| ...}`
49
- # @yield definition [Definition]
73
+ # @parameter input [File] The input file which contains the source code.
74
+ # @yields {|definition| ...} Receives the definitions extracted from the source code.
75
+ # @parameter definition [Definition] The source code definition including methods, classes, etc.
76
+ # @returns [Enumerator(Segment)] If no block given.
50
77
  def self.definitions_for(input, &block)
51
78
  Parser.new.definitions_for(input, &block)
52
79
  end
53
80
 
54
- # Parse the input yielding interleaved comments and code segments.
55
- # @block `{|segment| ...}`
56
- # @yield segment [Segment]
81
+ # Parse the input yielding segments.
82
+ # Segments are constructed from a block of top level comments followed by a block of code.
83
+ # @parameter input [File] The input file which contains the source code.
84
+ # @yields {|segment| ...}
85
+ # @parameter segment [Segment]
86
+ # @returns [Enumerator(Segment)] If no block given.
57
87
  def self.segments_for(input, &block)
58
88
  Parser.new.segments_for(input, &block)
59
89
  end
90
+
91
+ def self.code_for(text, index, relative_to: nil)
92
+ Code.new(text, index, relative_to: relative_to, language: self)
93
+ end
60
94
  end
61
95
  end
62
96
  end
@@ -58,7 +58,7 @@ module Decode
58
58
 
59
59
  def convert(kind)
60
60
  case kind
61
- when :attr
61
+ when :attribute
62
62
  Attribute.new(@node, @name,
63
63
  comments: @comments, parent: @parent, language: @language
64
64
  )
@@ -62,13 +62,13 @@ module Decode
62
62
  # A Ruby-specific singleton class.
63
63
  class Singleton < Definition
64
64
  # A singleton class is a container for other definitions.
65
- # @return [Boolean]
65
+ # @returns [Boolean]
66
66
  def container?
67
67
  true
68
68
  end
69
69
 
70
70
  # Typically, a singleton class does not contain other definitions.
71
- # @return [Boolean]
71
+ # @returns [Boolean]
72
72
  def nested?
73
73
  false
74
74
  end
@@ -0,0 +1,85 @@
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 'definition'
22
+ require_relative '../../syntax/link'
23
+
24
+ require 'parser/current'
25
+
26
+ module Decode
27
+ module Language
28
+ module Ruby
29
+ # A Ruby-specific block of code.
30
+ class Code
31
+ def initialize(text, index, relative_to: nil, language: relative_to&.language)
32
+ @text = text
33
+ @root = ::Parser::CurrentRuby.parse(text)
34
+ @index = index
35
+ @relative_to = relative_to
36
+ @language = language
37
+ end
38
+
39
+ attr :text
40
+
41
+ attr :language
42
+
43
+ def extract(into = [])
44
+ if @index
45
+ traverse(@root, into)
46
+ end
47
+
48
+ return into
49
+ end
50
+
51
+ private
52
+
53
+ def traverse(node, into)
54
+ case node&.type
55
+ when :send
56
+ if reference = Reference.from_const(node, @language)
57
+ if definition = @index.lookup(reference, relative_to: @relative_to)
58
+ expression = node.location.selector
59
+ range = expression.begin_pos...expression.end_pos
60
+ into << Syntax::Link.new(range, definition)
61
+ end
62
+ end
63
+
64
+ # Extract constants from arguments:
65
+ children = node.children[2..-1].each do |node|
66
+ traverse(node, into)
67
+ end
68
+ when :const
69
+ if reference = Reference.from_const(node, @language)
70
+ if definition = @index.lookup(reference, relative_to: @relative_to)
71
+ expression = node.location.name
72
+ range = expression.begin_pos...expression.end_pos
73
+ into << Syntax::Link.new(range, definition)
74
+ end
75
+ end
76
+ when :begin
77
+ node.children.each do |child|
78
+ traverse(child, into)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -44,7 +44,7 @@ module Decode
44
44
  end
45
45
 
46
46
  # The source code associated with the definition.
47
- # @return [String]
47
+ # @returns [String]
48
48
  def text
49
49
  expression = @node.location.expression
50
50
  lines = expression.source.lines
@@ -52,9 +52,15 @@ module Decode
52
52
  end
53
53
  end
54
54
 
55
+ # The fully qualified name of the block.
56
+ # e.g. `::Barnyard#foo`.
57
+ def qualified_form
58
+ self.qualified_name
59
+ end
60
+
55
61
  def convert(kind)
56
62
  case kind
57
- when :attr
63
+ when :attribute
58
64
  Attribute.new(@node, @name,
59
65
  comments: @comments, parent: @parent, language: @language
60
66
  )
@@ -42,6 +42,12 @@ module Decode
42
42
 
43
43
  # The long form is the same as the short form.
44
44
  alias long_form short_form
45
+
46
+ # The fully qualified name of the class.
47
+ # e.g. `module ::Barnyard::Dog`.
48
+ def qualified_form
49
+ "module #{self.qualified_name}"
50
+ end
45
51
  end
46
52
  end
47
53
  end
@@ -40,7 +40,7 @@ module Decode
40
40
  class Parser
41
41
  # Extract definitions from the given input file.
42
42
  def definitions_for(input, &block)
43
- top, comments = ::Parser::CurrentRuby.parse_with_comments(input.read)
43
+ top, comments = ::Parser::CurrentRuby.parse_with_comments(input)
44
44
 
45
45
  if top
46
46
  walk_definitions(top, comments, &block)
@@ -207,7 +207,7 @@ module Decode
207
207
  end
208
208
 
209
209
  KIND_ATTRIBUTE = /\A
210
- (@(?<kind>attr)\s+(?<value>.*?))|
210
+ (@(?<kind>attribute)\s+(?<value>.*?))|
211
211
  (@define\s+(?<kind>)\s+(?<value>.*?))
212
212
  \Z/x
213
213
 
@@ -241,7 +241,7 @@ module Decode
241
241
 
242
242
  # Extract segments from the given input file.
243
243
  def segments_for(input, &block)
244
- top, comments = ::Parser::CurrentRuby.parse_with_comments(input.read)
244
+ top, comments = ::Parser::CurrentRuby.parse_with_comments(input)
245
245
 
246
246
  # We delete any leading comments:
247
247
  line = 0
@@ -279,6 +279,14 @@ module Decode
279
279
  end
280
280
 
281
281
  yield segment if segment
282
+ else
283
+ # One top level segment:
284
+ segment = Segment.new(
285
+ extract_comments_for(node, comments),
286
+ Ruby, node
287
+ )
288
+
289
+ yield segment
282
290
  end
283
291
  end
284
292
  end
@@ -25,6 +25,37 @@ module Decode
25
25
  module Ruby
26
26
  # An Ruby-specific reference which can be resolved to zero or more definitions.
27
27
  class Reference < Language::Reference
28
+ def self.from_const(node, language)
29
+ lexical_path = append_const(node)
30
+
31
+ return self.new(node.location.expression.source, language, lexical_path)
32
+ end
33
+
34
+ def self.append_const(node, path = [])
35
+ parent, name = node.children
36
+
37
+ if parent and parent.type != :cbase
38
+ append_const(parent, path)
39
+ end
40
+
41
+ case node.type
42
+ when :const
43
+ if parent && parent.type != :cbase
44
+ path << ['::', name]
45
+ else
46
+ path << [nil, name]
47
+ end
48
+ when :send
49
+ path << ['#', name]
50
+ when :cbase
51
+ # Ignore.
52
+ else
53
+ raise ArgumentError, "Could not determine reference for #{node}!"
54
+ end
55
+
56
+ return path
57
+ end
58
+
28
59
  def split(text)
29
60
  text.scan(/(::|\.|#|:)?([^:.#]+)/)
30
61
  end
@@ -40,7 +40,7 @@ module Decode
40
40
  end
41
41
 
42
42
  # The source code trailing the comments.
43
- # @return [String | nil]
43
+ # @returns [String | nil]
44
44
  def code
45
45
  @expression.source
46
46
  end
@@ -23,10 +23,13 @@ require_relative 'definition'
23
23
  module Decode
24
24
  # An abstract namespace for nesting definitions.
25
25
  class Scope < Definition
26
+ # @returns [String] The name of the scope.
26
27
  def short_form
27
28
  @name
28
29
  end
29
30
 
31
+ # Scopes are always containers.
32
+ # @returns [Boolean] Always `true`.
30
33
  def container?
31
34
  true
32
35
  end
@@ -35,15 +35,15 @@ module Decode
35
35
  end
36
36
 
37
37
  # The preceeding comments.
38
- # @attr [Array(String)]
38
+ # @attribute [Array(String)]
39
39
  attr :comments
40
40
 
41
41
  # The language of the code attached to this segment.
42
- # @attr [Language]
42
+ # @attribute [Language::Generic]
43
43
  attr :language
44
44
 
45
45
  # An interface for accsssing the documentation of the definition.
46
- # @return [Documentation | nil] A `Documentation` if this definition has comments.
46
+ # @returns [Documentation | nil] A {Documentation} instance if this definition has comments.
47
47
  def documentation
48
48
  if @comments&.any?
49
49
  @documentation ||= Documentation.new(@comments, @language)
@@ -51,7 +51,7 @@ module Decode
51
51
  end
52
52
 
53
53
  # The source code trailing the comments.
54
- # @return [String | nil]
54
+ # @returns [String | nil]
55
55
  def code
56
56
  end
57
57
  end
@@ -21,34 +21,50 @@
21
21
  require_relative 'language'
22
22
 
23
23
  module Decode
24
+ # Represents a source file in a specific language.
24
25
  class Source
25
26
  def initialize(path, language)
26
27
  @path = path
28
+ @buffer = nil
27
29
  @language = language
28
30
  end
29
31
 
32
+ # The path of the source file.
33
+ # @attribute [String] A file-system path.
30
34
  attr :path
31
35
 
36
+ # The language of the source file.
37
+ # @attribute [Language::Generic]
32
38
  attr :language
33
39
 
34
- def open(&block)
35
- File.open(@path, &block)
40
+ # Read the source file into an internal buffer/cache.
41
+ # @returns [String]
42
+ def read
43
+ @buffer ||= File.read(@path).freeze
36
44
  end
37
45
 
46
+ # Open the source file and read all definitions.
47
+ # @yields {|definition| ...} All definitions from the source file.
48
+ # @parameter definition [Definition]
49
+ # @returns [Enumerator(Definition)] If no block given.
38
50
  def definitions(&block)
39
51
  return to_enum(:definitions) unless block_given?
40
52
 
41
- self.open do |file|
42
- @language.definitions_for(file, &block)
43
- end
53
+ @language.definitions_for(self.read, &block)
44
54
  end
45
55
 
56
+ # Open the source file and read all segments.
57
+ # @yields {|segment| ...} All segments from the source file.
58
+ # @parameter segment [Segment]
59
+ # @returns [Enumerator(Segment)] If no block given.
46
60
  def segments(&block)
47
61
  return to_enum(:segments) unless block_given?
48
62
 
49
- self.open do |file|
50
- @language.segments_for(file, &block)
51
- end
63
+ @language.segments_for(self.read, &block)
64
+ end
65
+
66
+ def code(index = nil, relative_to: nil)
67
+ @language.code_for(self.read, index, relative_to: relative_to)
52
68
  end
53
69
  end
54
70
  end
@@ -0,0 +1,44 @@
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 'match'
22
+
23
+ module Decode
24
+ module Syntax
25
+ class Link < Match
26
+ def initialize(range, definition)
27
+ @definition = definition
28
+
29
+ super(range)
30
+ end
31
+
32
+ attr :definition
33
+
34
+ def apply(output, rewriter)
35
+ output << rewriter.link_to(
36
+ @definition,
37
+ rewriter.text_for(@range)
38
+ )
39
+
40
+ return self.size
41
+ end
42
+ end
43
+ end
44
+ end