decode 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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