decode 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d37f874bcef0dadaa82d665ae4f27ed182a0d45c55403a280c443dede81d1b97
4
- data.tar.gz: a3394961b5285524ab07be9931aeb436cd78aa94286d6c65c81993389b162a28
3
+ metadata.gz: d1ca1745dcb76b54a61777388aba926d1262967dc51ca7ad633debc369f67d9f
4
+ data.tar.gz: 4164ca61971b6573b8604767cfdf35766605893f8d63cba44a516d705c0d5093
5
5
  SHA512:
6
- metadata.gz: 7df83797d9844a6eb0e4f52f275527327ce7d81564865edcc70d5bce99a2be89b002fbe42cd1440bf666b4197c6dbb85775b4b4097675e525c1100aaf242571a
7
- data.tar.gz: b2303685a56fa40ae3195a21a7a2cf613f5301f6c4520e4e6d8853e218b44363d33d704cd487710af40728f1926832b7bfc8bbd2a90db3555f6ed22c74ec7c45
6
+ metadata.gz: 7b247bcd2d6b0eaacf1bbaa444cd98df314251bca958914793ad455521c9f379233f737e6afeeb31197b76f0ce9876de1887304203eb5487fda113fa4b8eea11
7
+ data.tar.gz: 72b714cf3ad9b0f7969e23be899ca10186994661d6dbc0bb91bd27a200a6ce4ee18815bca5a7cc355316ea5f53b6ed2d818073b28f61a41caca0dbbf52196623
data/README.md CHANGED
@@ -4,10 +4,12 @@ A Ruby code analysis tool and documentation generator.
4
4
 
5
5
  ## Usage
6
6
 
7
- Please see the documentation wiki.
7
+ Please see the <a href="https://ioquatix.github.io/decode">project documentation</a> or run it locally using `bake utopia:project:serve`.
8
8
 
9
9
  ## Contributing
10
10
 
11
+ We welcome contributions to this project.
12
+
11
13
  1. Fork it
12
14
  2. Create your feature branch (`git checkout -b my-new-feature`)
13
15
  3. Commit your changes (`git commit -am 'Add some feature'`)
data/decode.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  # Specify which files should be added to the gem when it is released.
17
17
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
18
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(docs|test|spec|features)/}) }
20
20
  end
21
21
 
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  # This example demonstrates how to extract symbols using the index. An instance of {Decode::Index} is used for loading symbols from source code files. These symbols are available as a flat list and as a trie structure. You can look up specific symbols using a reference using {Decode::Index:lookup}.
4
-
5
4
  require_relative '../../lib/decode/index'
6
5
 
7
6
  # Firstly, construct the index:
data/gems.rb CHANGED
@@ -1,3 +1,5 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ gem 'utopia-project', path: '../../socketry/utopia-project'
@@ -21,6 +21,7 @@
21
21
  require_relative 'symbol'
22
22
 
23
23
  module Decode
24
+ # A symbol with attached documentation.
24
25
  class Definition < Symbol
25
26
  def initialize(kind, name, comments, **options)
26
27
  super(kind, name, **options)
@@ -29,44 +30,56 @@ module Decode
29
30
  @documentation = nil
30
31
  end
31
32
 
33
+ # The comment lines which directly preceeded the definition.
34
+ # @attr [Array(String)]
32
35
  attr :comments
33
36
 
34
- # A short form of the definition, e.g. `def short_form`.
37
+ # A short form of the definition.
38
+ # e.g. `def short_form`.
39
+ #
35
40
  # @return [String | nil]
36
41
  def short_form
37
42
  end
38
43
 
39
- # A long form of the definition, e.g. `def initialize(kind, name, comments, **options)`.
44
+ # A long form of the definition.
45
+ # e.g. `def initialize(kind, name, comments, **options)`.
46
+ #
40
47
  # @return [String | nil]
41
48
  def long_form
42
49
  self.short_form
43
50
  end
44
51
 
45
- # A long form which uses the qualified name if possible. Defaults to the {long_form}.
52
+ # A long form which uses the qualified name if possible.
53
+ # Defaults to {long_form}.
54
+ #
46
55
  # @return [String | nil]
47
56
  def qualified_form
48
57
  self.long_form
49
58
  end
50
59
 
51
60
  # The full text of the definition.
61
+ #
52
62
  # @return [String | nil]
53
63
  def text
54
64
  end
55
65
 
56
66
  # Whether this definition can contain nested definitions.
67
+ #
57
68
  # @return [Boolean]
58
69
  def container?
59
70
  false
60
71
  end
61
72
 
62
73
  # Whether this represents a single entity to be documented (along with it's contents).
74
+ #
63
75
  # @return [Boolean]
64
76
  def nested?
65
77
  container?
66
78
  end
67
79
 
68
- # An interface for accsssing the documentation of the definition.
69
- # @return [Documentation | nil] A `Documentation` if this definition has comments.
80
+ # Structured access to the definitions comments.
81
+ #
82
+ # @return [Documentation | Nil] A `Documentation` if this definition has comments.
70
83
  def documentation
71
84
  if @comments&.any?
72
85
  @documentation ||= Documentation.new(@comments)
@@ -19,13 +19,26 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Decode
22
+ # Structured access to a set of comment lines.
22
23
  class Documentation
23
- def initialize(comments)
24
+ # Initialize the documenation with an array of comments, within a specific language.
25
+ #
26
+ # @param comments [Array(String)] An array of comment lines.
27
+ # @param language [Language] The language in which the comments were extracted.
28
+ def initialize(comments, language = nil)
24
29
  @comments = comments
30
+ @language = language
25
31
  end
26
32
 
33
+ # The language in which the documentation was extracted from.
34
+ attr :language
35
+
27
36
  DESCRIPTION = /\A\s*([^@\s].*)?\z/
28
37
 
38
+ # The text-only lines of the comment block.
39
+ #
40
+ # @yield [String]
41
+ # @return [Enumerable]
29
42
  def description
30
43
  return to_enum(:description) unless block_given?
31
44
 
@@ -52,6 +65,11 @@ module Decode
52
65
 
53
66
  ATTRIBUTE = /\A\s*@(?<name>.*?)\s+(?<value>.*?)\z/
54
67
 
68
+ # The attribute lines of the comment block.
69
+ # e.g. `@return [String]`.
70
+ #
71
+ # @yield [String]
72
+ # @return [Enumerable]
55
73
  def attributes
56
74
  return to_enum(:attributes) unless block_given?
57
75
 
@@ -64,6 +82,11 @@ module Decode
64
82
 
65
83
  PARAMETER = /\A\s*@param\s+(?<name>.*?)\s+\[(?<type>.*?)\]\s+(?<details>.*?)\z/
66
84
 
85
+ # The parameter lines of the comment block.
86
+ # e.g. `@param value [String] The value.`
87
+ #
88
+ # @yield [String]
89
+ # @return [Enumerable]
67
90
  def parameters
68
91
  return to_enum(:parameters) unless block_given?
69
92
 
data/lib/decode/index.rb CHANGED
@@ -22,7 +22,9 @@ require_relative 'source'
22
22
  require_relative 'trie'
23
23
 
24
24
  module Decode
25
+ # A list of symbols organised for quick lookup and lexical enumeration.
25
26
  class Index
27
+ # Initialize an empty index.
26
28
  def initialize
27
29
  @sources = {}
28
30
  @symbols = {}
@@ -31,11 +33,23 @@ module Decode
31
33
  @trie = Trie.new
32
34
  end
33
35
 
36
+ # All source files that have been parsed.
37
+ # @attr [Array(Source)]
34
38
  attr :sources
39
+
40
+ # All symbols which have been parsed.
41
+ # @attr [Array(Symbol)]
35
42
  attr :symbols
36
43
 
44
+ # A (prefix) trie of lexically scoped symbols.
45
+ # @attr [Trie]
46
+
37
47
  attr :trie
38
48
 
49
+ # Updates the index by parsing the specified files.
50
+ # All extracted symbols are merged into the existing index.
51
+ #
52
+ # @param paths [Array(String)] The source file paths.
39
53
  def update(paths)
40
54
  paths.each do |path|
41
55
  source = Source.new(path)
@@ -49,6 +63,10 @@ module Decode
49
63
  end
50
64
  end
51
65
 
66
+ # Lookup the specified reference and return matching symbols.
67
+ #
68
+ # @param reference [Reference] The reference to match.
69
+ # @param relative_to [Symbol] Lookup the reference relative to the scope of this symbol.
52
70
  def lookup(reference, relative_to: nil)
53
71
  if reference.absolute? || relative_to.nil?
54
72
  lexical_path = []
@@ -59,7 +77,7 @@ module Decode
59
77
  path = reference.path
60
78
 
61
79
  while true
62
- node = @trie.match(lexical_path)
80
+ node = @trie.lookup(lexical_path)
63
81
 
64
82
  if node.children[path.first]
65
83
  if target = node.lookup(path)
@@ -21,13 +21,12 @@
21
21
  require_relative 'language/ruby'
22
22
 
23
23
  module Decode
24
+ # Language specific parsers and symbols.
24
25
  module Language
25
26
  def self.detect(path)
26
27
  case File.extname(path)
27
- when '.rb'
28
+ when '.rb', '.ru'
28
29
  return Language::Ruby
29
- else
30
- raise ArgumentError, "Could not determine language for #{path}!"
31
30
  end
32
31
  end
33
32
  end
@@ -23,7 +23,10 @@ require_relative 'ruby/parser'
23
23
 
24
24
  module Decode
25
25
  module Language
26
+ # The Ruby language.
26
27
  module Ruby
28
+ # The canoical name of the language for use in output formatting.
29
+ # e.g. source code highlighting.
27
30
  def self.name
28
31
  "ruby"
29
32
  end
@@ -37,6 +40,7 @@ module Decode
37
40
  defs: '.',
38
41
  }.freeze
39
42
 
43
+ # Generate a language-specific fully qualified name.
40
44
  def self.join(symbols, absolute = true)
41
45
  buffer = String.new
42
46
 
@@ -53,18 +57,21 @@ module Decode
53
57
  return buffer
54
58
  end
55
59
 
60
+ # Generate a language-specific reference.
56
61
  def self.reference(value)
57
62
  Reference.new(value)
58
63
  end
59
64
 
60
65
  # Parse the input yielding symbols.
61
- # @yield [Definition]
66
+ # @block `{|definition| ...}`
67
+ # @yield definition [Definition]
62
68
  def self.symbols_for(input, &block)
63
69
  Parser.new.symbols_for(input, &block)
64
70
  end
65
71
 
66
72
  # Parse the input yielding interleaved comments and code segments.
67
- # @yield [Segment]
73
+ # @block `{|segment| ...}`
74
+ # @yield segment [Segment]
68
75
  def self.segments_for(input, &block)
69
76
  Parser.new.segments_for(input, &block)
70
77
  end
@@ -23,11 +23,16 @@ require_relative 'definition'
23
23
  module Decode
24
24
  module Language
25
25
  module Ruby
26
+ # A Ruby-specific attribute.
26
27
  class Attribute < Definition
28
+ # The keyword that defined the attribute.
29
+ # @return [String]
27
30
  def keyword
28
31
  @node.children[1]
29
32
  end
30
33
 
34
+ # The short form of the attribute.
35
+ # e.g. `attr :value`.
31
36
  def short_form
32
37
  "#{self.keyword} #{@name.inspect}"
33
38
  end
@@ -23,15 +23,21 @@ require_relative 'definition'
23
23
  module Decode
24
24
  module Language
25
25
  module Ruby
26
+ # A Ruby-specific class.
26
27
  class Class < Definition
28
+ # A class is a container for other definitions.
27
29
  def container?
28
30
  true
29
31
  end
30
32
 
33
+ # The short form of the class.
34
+ # e.g. `class Animal`.
31
35
  def short_form
32
36
  "class #{@name}"
33
37
  end
34
38
 
39
+ # The long form of the class.
40
+ # e.g. `class Dog < Animal`.
35
41
  def long_form
36
42
  if super_node = @node.children[1]
37
43
  @node.location.keyword.join(
@@ -42,24 +48,34 @@ module Decode
42
48
  end
43
49
  end
44
50
 
51
+ # The fully qualified name of the class.
52
+ # e.g. `class ::Barnyard::Dog`.
45
53
  def qualified_form
46
54
  "class #{self.qualified_name}"
47
55
  end
48
56
  end
49
57
 
58
+ # A Ruby-specific singleton class.
50
59
  class Singleton < Definition
60
+ # A singleton class is a container for other definitions.
61
+ # @return [Boolean]
51
62
  def container?
52
63
  true
53
64
  end
54
65
 
66
+ # Typically, a singleton class does not contain other definitions.
67
+ # @return [Boolean]
55
68
  def nested?
56
69
  false
57
70
  end
58
71
 
72
+ # The short form of the class.
73
+ # e.g. `class << (self)`.
59
74
  def short_form
60
75
  "class << #{@name}"
61
76
  end
62
77
 
78
+ # The long form is the same as the short form.
63
79
  alias long_form short_form
64
80
  end
65
81
  end
@@ -23,11 +23,16 @@ require_relative 'definition'
23
23
  module Decode
24
24
  module Language
25
25
  module Ruby
26
+ # A Ruby-specific constant.
26
27
  class Constant < Definition
28
+ # The short form of the constant.
29
+ # e.g. `NAME`.
27
30
  def short_form
28
31
  @node.location.name.source
29
32
  end
30
33
 
34
+ # The long form of the constant.
35
+ # e.g. `NAME = "Alice"`.
31
36
  def long_form
32
37
  if @node.location.line == @node.location.last_line
33
38
  @node.location.expression.source
@@ -23,15 +23,20 @@ require_relative '../../definition'
23
23
  module Decode
24
24
  module Language
25
25
  module Ruby
26
+ # A Ruby-specific definition.
26
27
  class Definition < Decode::Definition
28
+ # Initialize the definition from the syntax tree node.
27
29
  def initialize(kind, name, comments, node, **options)
28
30
  super(kind, name, comments, **options)
29
31
 
30
32
  @node = node
31
33
  end
32
34
 
35
+ # The parser syntax tree node.
33
36
  attr :node
34
37
 
38
+ # The source code associated with the definition.
39
+ # @return [String]
35
40
  def text
36
41
  @node.location.expression.source
37
42
  end
@@ -23,7 +23,9 @@ require_relative 'method'
23
23
  module Decode
24
24
  module Language
25
25
  module Ruby
26
+ # A Ruby-specific function.
26
27
  class Function < Method
28
+ # The node which contains the function arguments.
27
29
  def arguments_node
28
30
  if node = @node.children[2]
29
31
  if node.location.expression
@@ -23,11 +23,15 @@ require_relative 'definition'
23
23
  module Decode
24
24
  module Language
25
25
  module Ruby
26
+ # A Ruby-specific method.
26
27
  class Method < Definition
28
+ # The short form of the method.
29
+ # e.g. `def puts`.
27
30
  def short_form
28
31
  @node.location.keyword.join(@node.location.name).source
29
32
  end
30
33
 
34
+ # The node which contains the function arguments.
31
35
  def arguments_node
32
36
  if node = @node.children[1]
33
37
  if node.location.expression
@@ -36,6 +40,8 @@ module Decode
36
40
  end
37
41
  end
38
42
 
43
+ # The long form of the method.
44
+ # e.g. `def puts(*lines, separator: "\n")`.
39
45
  def long_form
40
46
  if arguments_node = self.arguments_node
41
47
  @node.location.keyword.join(
@@ -23,15 +23,20 @@ require_relative 'definition'
23
23
  module Decode
24
24
  module Language
25
25
  module Ruby
26
+ # A Ruby-specific module.
26
27
  class Module < Definition
28
+ # A module is a container for other definitions.
27
29
  def container?
28
30
  true
29
31
  end
30
32
 
33
+ # The short form of the module.
34
+ # e.g. `module Barnyard`.
31
35
  def short_form
32
36
  "module #{@name}"
33
37
  end
34
38
 
39
+ # The long form is the same as the short form.
35
40
  alias long_form short_form
36
41
  end
37
42
  end
@@ -32,16 +32,11 @@ require_relative 'segment'
32
32
  module Decode
33
33
  module Language
34
34
  module Ruby
35
+ # The Ruby source code parser.
35
36
  class Parser
36
- def initialize(parser = ::Parser::CurrentRuby.new)
37
- @parser = parser
38
- end
39
-
37
+ # Extract symbols from the given input file.
40
38
  def symbols_for(input, &block)
41
- buffer = ::Parser::Source::Buffer.new('(input)')
42
- buffer.source = input.read
43
-
44
- top, comments = @parser.parse_with_comments(buffer)
39
+ top, comments = ::Parser::CurrentRuby.parse_with_comments(input.read)
45
40
 
46
41
  if top
47
42
  walk_symbols(top, comments, &block)
@@ -162,11 +157,9 @@ module Decode
162
157
  end
163
158
  end
164
159
 
160
+ # Extract segments from the given input file.
165
161
  def segments_for(input, &block)
166
- buffer = ::Parser::Source::Buffer.new('(input)')
167
- buffer.source = input.read
168
-
169
- top, comments = @parser.parse_with_comments(buffer)
162
+ top, comments = ::Parser::CurrentRuby.parse_with_comments(input.read)
170
163
 
171
164
  # We delete any leading comments:
172
165
  line = 0
@@ -193,11 +186,11 @@ module Decode
193
186
  if segment.nil?
194
187
  segment = Segment.new(
195
188
  extract_comments_for(child, comments),
196
- child
189
+ Ruby, child
197
190
  )
198
191
  elsif next_comments = extract_comments_for(child, comments)
199
192
  yield segment if segment
200
- segment = Segment.new(next_comments, child)
193
+ segment = Segment.new(next_comments, Ruby, child)
201
194
  else
202
195
  segment.expand(child)
203
196
  end
@@ -21,12 +21,15 @@
21
21
  module Decode
22
22
  module Language
23
23
  module Ruby
24
+ # An Ruby-specific reference which can be resolved to zero or more symbols.
24
25
  class Reference
25
26
  KIND = {
26
27
  ':' => :def,
27
28
  '.' => :defs,
28
29
  }.freeze
29
30
 
31
+ # Initialize the reference.
32
+ # @param value [String] The string value of the reference.
30
33
  def initialize(value)
31
34
  @value = value
32
35
 
@@ -34,12 +37,15 @@ module Decode
34
37
  @kind = nil
35
38
  end
36
39
 
40
+ # Whether the reference starts at the base of the lexical tree.
37
41
  def absolute?
38
42
  @value.start_with?('::')
39
43
  end
40
44
 
41
45
  METHOD = /\A(?<scope>.*?)?(?<kind>:|\.)(?<name>.+?)\z/
42
46
 
47
+ # The lexical path of the reference.
48
+ # @return [Array(String)]
43
49
  def path
44
50
  if @path.nil?
45
51
  @path = @value.split(/::/)
@@ -65,6 +71,7 @@ module Decode
65
71
  return @path
66
72
  end
67
73
 
74
+ # The kind of symbol to match.
68
75
  def kind
69
76
  self.path
70
77
 
@@ -23,20 +23,24 @@ require_relative '../../segment'
23
23
  module Decode
24
24
  module Language
25
25
  module Ruby
26
+ # A Ruby specific code segment.
26
27
  class Segment < Decode::Segment
27
- def initialize(comments, node, **options)
28
- super(comments, **options)
28
+ def initialize(comments, language, node, **options)
29
+ super(comments, language, **options)
29
30
 
30
31
  @node = node
31
32
  @expression = node.location.expression
32
33
  end
33
34
 
35
+ # The parser syntax tree node.
34
36
  attr :node
35
37
 
36
38
  def expand(node)
37
39
  @expression = @expression.join(node.location.expression)
38
40
  end
39
41
 
42
+ # The source code trailing the comments.
43
+ # @return [String | nil]
40
44
  def code
41
45
  @expression.source
42
46
  end
@@ -21,18 +21,32 @@
21
21
  require_relative 'documentation'
22
22
 
23
23
  module Decode
24
+ # A chunk of code with an optional preceeding comment block.
25
+ #
26
+ # ~~~ ruby
27
+ # # Get the first segment from a source file:
28
+ # segment = source.segments.first
29
+ # ~~~
30
+ #
24
31
  class Segment
25
- def initialize(comments)
32
+ def initialize(comments, language)
26
33
  @comments = comments
34
+ @language = language
27
35
  end
28
36
 
37
+ # The preceeding comments.
38
+ # @attr [Array(String)]
29
39
  attr :comments
30
40
 
41
+ # The language of the code attached to this segment.
42
+ # @attr [Language]
43
+ attr :language
44
+
31
45
  # An interface for accsssing the documentation of the definition.
32
46
  # @return [Documentation | nil] A `Documentation` if this definition has comments.
33
47
  def documentation
34
48
  if @comments&.any?
35
- @documentation ||= Documentation.new(@comments)
49
+ @documentation ||= Documentation.new(@comments, @language)
36
50
  end
37
51
  end
38
52
 
data/lib/decode/source.rb CHANGED
@@ -22,11 +22,21 @@ require_relative 'language'
22
22
 
23
23
  module Decode
24
24
  class Source
25
+ def self.for?(path)
26
+ if language = Language.detect(path)
27
+ self.new(path, language)
28
+ end
29
+ end
30
+
25
31
  def initialize(path, language = nil)
26
32
  @path = path
27
33
  @language = language || Language.detect(path)
28
34
  end
29
35
 
36
+ attr :path
37
+
38
+ attr :language
39
+
30
40
  def open(&block)
31
41
  File.open(@path, &block)
32
42
  end
data/lib/decode/symbol.rb CHANGED
@@ -21,10 +21,16 @@
21
21
  require_relative 'documentation'
22
22
 
23
23
  module Decode
24
+ # A language agnostic lexical scope.
24
25
  Key = Struct.new(:kind, :name)
25
26
 
26
27
  # A named element which represents some significant element of some kind in a computer program.
27
28
  class Symbol
29
+ # Initialize the symbol.
30
+ # @param kind [Symbol] The kind of symbol.
31
+ # @param name [Symbol] The name of the symbol.
32
+ # @param parent [Symbol] The parent lexical scope.
33
+ # @param language [Language] The language in which the symbol is defined in.
28
34
  def initialize(kind, name, parent: nil, language: parent.language)
29
35
  @kind = kind
30
36
  @name = name
@@ -45,10 +51,12 @@ module Decode
45
51
  "\#<#{self.class} #{@kind} #{qualified_name}>"
46
52
  end
47
53
 
48
- # The kind of symbol, e.g. `:module`.
54
+ # The kind of symbol.
55
+ # e.g. `:module`.
49
56
  attr :kind
50
57
 
51
- # The symbol name, e.g. `:Decode`.
58
+ # The symbol name.
59
+ # e.g. `:Decode`.
52
60
  attr :name
53
61
 
54
62
  # The parent symbol, defining lexical scope.
@@ -62,7 +70,7 @@ module Decode
62
70
  @qualified_name ||= @language.join(self.path).freeze
63
71
  end
64
72
 
65
- # The lexical scope as defined by the {key}.
73
+ # The lexical scope which is an array of lexical {Key} instances as generated by {key}.
66
74
  # @return [Array]
67
75
  def path
68
76
  if @path
data/lib/decode/trie.rb CHANGED
@@ -21,16 +21,27 @@
21
21
  require_relative 'source'
22
22
 
23
23
  module Decode
24
+ # A prefix-trie data structure for fast lexical lookups.
24
25
  class Trie
26
+ # A single node in the trie.
25
27
  class Node
26
28
  def initialize
27
29
  @values = nil
28
30
  @children = Hash.new
29
31
  end
30
32
 
33
+ # A mutable array of all values that terminate at this node.
34
+ # @attr [Array]
31
35
  attr_accessor :values
36
+
37
+ # A hash table of all children nodes, indexed by name.
38
+ # @attr [Hash(String, Node)]
32
39
  attr :children
33
40
 
41
+ # Look up a lexical path starting at this node.
42
+ #
43
+ # @param path [Array(String)] The path to resolve.
44
+ # @return [Node | Nil]
34
45
  def lookup(path, index = 0)
35
46
  if index < path.size
36
47
  if child = @children[path[index]]
@@ -41,6 +52,15 @@ module Decode
41
52
  end
42
53
  end
43
54
 
55
+ # Traverse the trie from this node.
56
+ # Invoke `descend.call` to traverse the children of the current node.
57
+ #
58
+ # @param path [Array(String)] The current lexical path.
59
+ #
60
+ # @block `{|path, node, descend| descend.call}`
61
+ # @yield path [Array(String)] The current lexical path.
62
+ # @yield node [Node] The current node which is being traversed.
63
+ # @yield descend [Proc] The recursive method for traversing children.
44
64
  def traverse(path = [], &block)
45
65
  yield(path, self, ->{
46
66
  @children.each do |name, node|
@@ -50,14 +70,18 @@ module Decode
50
70
  end
51
71
  end
52
72
 
53
- attr_accessor :value
54
-
73
+ # Initialize an empty trie.
55
74
  def initialize
56
75
  @root = Node.new
57
76
  end
58
77
 
78
+ # The root of the trie.
79
+ # @attr [Node]
59
80
  attr :root
60
81
 
82
+ # Insert the specified value at the given path into the trie.
83
+ # @param path [Array(String)] The lexical path where the value will be inserted.
84
+ # @param value [Object] The value to insert.
61
85
  def insert(path, value)
62
86
  node = @root
63
87
 
@@ -68,26 +92,35 @@ module Decode
68
92
  (node.values ||= []) << value
69
93
  end
70
94
 
95
+ # Lookup the values at the specified path.
96
+ #
97
+ # @param path [Array(String)] The lexical path which contains the values.
98
+ # @return [Array(Object) | Nil] The values that existed (or not) at the specified path.
71
99
  def lookup(path)
72
100
  @root.lookup(path)
73
101
  end
74
102
 
75
- # Given a base path, enumerate all paths under that.
76
- # @yield (path, values) pairs
103
+ # Enumerate all lexical scopes under the specified path.
104
+ #
105
+ # @block `{|path, values| ...}`
106
+ # @yield path [Array(String)] The lexical path.
107
+ # @yield values [Array(Object)] The values that exist at the given path.
77
108
  def each(path = [], &block)
78
109
  if node = @root.lookup(path)
79
- node.traverse(&block)
110
+ node.traverse do |path, node, descend|
111
+ yield path, node.values
112
+
113
+ descend.call
114
+ end
80
115
  end
81
116
  end
82
117
 
118
+ # Traverse the trie.
119
+ # See {Node:traverse} for details.
83
120
  def traverse(path = [], &block)
84
121
  if node = @root.lookup(path)
85
122
  node.traverse(&block)
86
123
  end
87
124
  end
88
-
89
- def match(path, &block)
90
- @root.lookup(path)
91
- end
92
125
  end
93
126
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Decode
22
- VERSION = "0.7.0"
22
+ VERSION = "0.8.0"
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-03 00:00:00.000000000 Z
11
+ date: 2020-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -120,10 +120,7 @@ files:
120
120
  - ".rspec"
121
121
  - README.md
122
122
  - decode.gemspec
123
- - docs/config.ru
124
- - docs/gems.locked
125
- - docs/gems.rb
126
- - examples/index/extract.rb
123
+ - examples/decode-index/extract.rb
127
124
  - gems.rb
128
125
  - lib/decode.rb
129
126
  - lib/decode/definition.rb
data/docs/config.ru DELETED
@@ -1,6 +0,0 @@
1
-
2
- require 'utopia/setup'
3
- UTOPIA ||= Utopia.setup
4
-
5
- require 'utopia/project'
6
- Utopia::Project.call(self)
data/docs/gems.locked DELETED
@@ -1,137 +0,0 @@
1
- PATH
2
- remote: ../../../socketry/utopia-project
3
- specs:
4
- utopia-project (0.1.0)
5
- decode
6
- falcon
7
- kramdown
8
- kramdown-parser-gfm
9
- rackula
10
- thread-local
11
- utopia (~> 2.14)
12
- utopia-gallery
13
-
14
- PATH
15
- remote: ..
16
- specs:
17
- decode (0.6.0)
18
- parser
19
-
20
- GEM
21
- remote: https://rubygems.org/
22
- specs:
23
- ast (2.4.0)
24
- async (1.25.2)
25
- console (~> 1.0)
26
- nio4r (~> 2.3)
27
- timers (~> 4.1)
28
- async-container (0.16.4)
29
- async (~> 1.0)
30
- async-io (~> 1.26)
31
- process-group
32
- async-http (0.52.0)
33
- async (~> 1.25)
34
- async-io (~> 1.28)
35
- async-pool (~> 0.2)
36
- protocol-http (~> 0.18.0)
37
- protocol-http1 (~> 0.12.0)
38
- protocol-http2 (~> 0.14.0)
39
- async-http-cache (0.2.0)
40
- async-http (~> 0.51)
41
- async-io (1.29.0)
42
- async (~> 1.14)
43
- async-pool (0.3.0)
44
- async (~> 1.25)
45
- build-environment (1.13.0)
46
- concurrent-ruby (1.1.6)
47
- console (1.8.2)
48
- falcon (0.36.4)
49
- async (~> 1.13)
50
- async-container (~> 0.16.0)
51
- async-http (~> 0.52.0)
52
- async-http-cache (~> 0.2.0)
53
- async-io (~> 1.22)
54
- build-environment (~> 1.13)
55
- localhost (~> 1.1)
56
- process-metrics (~> 0.2.0)
57
- rack (>= 1.0)
58
- samovar (~> 2.1)
59
- ffi (1.12.2)
60
- http-accept (2.1.1)
61
- kramdown (2.2.1)
62
- rexml
63
- kramdown-parser-gfm (1.1.0)
64
- kramdown (~> 2.0)
65
- localhost (1.1.6)
66
- mail (2.7.1)
67
- mini_mime (>= 0.1.1)
68
- mapping (1.1.1)
69
- mime-types (3.3.1)
70
- mime-types-data (~> 3.2015)
71
- mime-types-data (3.2020.0425)
72
- mini_mime (1.0.2)
73
- msgpack (1.3.3)
74
- nio4r (2.5.2)
75
- parser (2.7.1.2)
76
- ast (~> 2.4.0)
77
- process-group (1.2.1)
78
- process-terminal (~> 0.2.0)
79
- process-metrics (0.2.1)
80
- console (~> 1.8)
81
- samovar (~> 2.1)
82
- process-terminal (0.2.0)
83
- ffi
84
- protocol-hpack (1.4.2)
85
- protocol-http (0.18.0)
86
- protocol-http1 (0.12.0)
87
- protocol-http (~> 0.18)
88
- protocol-http2 (0.14.0)
89
- protocol-hpack (~> 1.4)
90
- protocol-http (~> 0.18)
91
- rack (2.2.2)
92
- rackula (1.1.0)
93
- falcon (~> 0.34)
94
- samovar (~> 2.1)
95
- variant
96
- rake (13.0.1)
97
- rake-compiler (1.1.0)
98
- rake
99
- rexml (3.2.4)
100
- samovar (2.1.4)
101
- console (~> 1.0)
102
- mapping (~> 1.0)
103
- thread-local (1.0.0)
104
- timers (4.3.0)
105
- trenni (3.9.0)
106
- rake-compiler
107
- utopia (2.15.1)
108
- concurrent-ruby (~> 1.0)
109
- console (~> 1.0)
110
- http-accept (~> 2.1)
111
- mail (~> 2.6)
112
- mime-types (~> 3.0)
113
- msgpack
114
- rack (~> 2.2)
115
- samovar (~> 2.1)
116
- trenni (~> 3.0)
117
- variant (~> 0.1)
118
- utopia-gallery (2.5.0)
119
- trenni (~> 3.9)
120
- utopia (~> 2.0)
121
- vips-thumbnail (~> 1.6)
122
- variant (0.1.1)
123
- thread-local
124
- vips (8.9.1)
125
- ffi (~> 1.9)
126
- vips-thumbnail (1.6.0)
127
- vips (~> 8.9)
128
-
129
- PLATFORMS
130
- ruby
131
-
132
- DEPENDENCIES
133
- decode!
134
- utopia-project!
135
-
136
- BUNDLED WITH
137
- 2.1.4
data/docs/gems.rb DELETED
@@ -1,7 +0,0 @@
1
-
2
- source "https://rubygems.org"
3
-
4
- group :preload do
5
- gem "utopia-project", path: "../../../socketry/utopia-project"
6
- gem "decode", path: "../"
7
- end