decode 0.1.0 → 0.2.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: 0ee41a6017a7689ecfe8e5d45060f099389f42adef8881dbcc83c379ea0283a0
4
- data.tar.gz: 381f84c1f1ae15d9486c07ac9f58f694169157200ad44c34e3ba5e2006de32d5
3
+ metadata.gz: a6e020e7ce8808abe27ac191ff49baa00f384634d9277c94b6c2cde64a5751c2
4
+ data.tar.gz: f0d8aa8a53d32c81be30df5d23eaa6acc97018c07cab16e82bf44f2f0d9990fd
5
5
  SHA512:
6
- metadata.gz: c1b8322f13b93c5e0e6a64772b57e4dfb2eb6d8682404c69edfb233dc76bdaa795d3b9dfde2567f3240409041116e284790645583e32c9fecf6b0a9a5ea17d18
7
- data.tar.gz: b4c8ab5a9b990107069fb618cc4bc2e64267372c639dc8d42c3de48f5ddb0543b5451e86b58ad016f649298ae84977518a77a7b9c008dc1e0a5974cf70bf4043
6
+ metadata.gz: 9dd39e23835a40d32280c75d3f28ebdc3d944224399d7c0844255ac0b46de31a89c151502361c0852becae448a8cb8b9b9b1f3a1a56489e1c0bbba73a01c774e
7
+ data.tar.gz: cb017f4cc34c7ee444432ba5746d97bbd4fc3791130e0bdbe3ef7eb8dc0a5b26126b1356d007910daae85c6f2f86e3da3d74ff1e9d9c91ee365b313fba834ba0
data/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  A Ruby code analysis tool and documentation generator.
4
4
 
5
+ ## Usage
6
+
7
+ Please see the documentation wiki.
8
+
5
9
  ## Contributing
6
10
 
7
11
  1. Fork it
data/decode.gemspec CHANGED
@@ -22,10 +22,10 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.add_dependency 'build-files'
25
+ spec.add_dependency "parser"
26
26
 
27
+ spec.add_development_dependency 'build-files'
27
28
  spec.add_development_dependency 'bake-bundler'
28
-
29
29
  spec.add_development_dependency 'utopia-wiki'
30
30
  spec.add_development_dependency 'covered'
31
31
  spec.add_development_dependency 'bundler'
data/gems.locked CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decode (0.1.0)
5
- build-files
4
+ decode (0.2.0)
5
+ parser
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
@@ -159,6 +159,7 @@ PLATFORMS
159
159
 
160
160
  DEPENDENCIES
161
161
  bake-bundler
162
+ build-files
162
163
  bundler
163
164
  covered
164
165
  decode!
@@ -0,0 +1,77 @@
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
+ module Decode
22
+ class Documentation
23
+ def initialize(comments)
24
+ @comments = comments
25
+ end
26
+
27
+ DESCRIPTION = /\A\s*([^@\s].*)?\z/
28
+
29
+ def description
30
+ return to_enum(:description) unless block_given?
31
+
32
+ # We track empty lines and only yield a single empty line when there is another line of text:
33
+ gap = false
34
+
35
+ @comments.each do |comment|
36
+ if match = comment.match(DESCRIPTION)
37
+ if match[1]
38
+ if gap
39
+ yield ""
40
+ gap = false
41
+ end
42
+
43
+ yield match[1]
44
+ else
45
+ gap = true
46
+ end
47
+ else
48
+ break
49
+ end
50
+ end
51
+ end
52
+
53
+ ATTRIBUTE = /\A\s*@(?<name>.*?)\s+(?<value>.*?)\z/
54
+
55
+ def attributes
56
+ return to_enum(:attributes) unless block_given?
57
+
58
+ @comments.each do |comment|
59
+ if match = comment.match(ATTRIBUTE)
60
+ yield match
61
+ end
62
+ end
63
+ end
64
+
65
+ PARAMETER = /\A\s*@param\s+(?<name>.*?)\s+\[(?<type>.*?)\]\s+(?<details>.*?)\z/
66
+
67
+ def parameters
68
+ return to_enum(:parameters) unless block_given?
69
+
70
+ @comments.each do |comment|
71
+ if match = comment.match(PARAMETER)
72
+ yield match
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
data/lib/decode/index.rb CHANGED
@@ -18,7 +18,66 @@
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 'source'
22
+ require_relative 'trie'
23
+
21
24
  module Decode
22
25
  class Index
26
+ def initialize(paths)
27
+ @paths = paths
28
+ @sources = {}
29
+ @symbols = {}
30
+
31
+ # This is essentially a prefix tree:
32
+ @trie = Trie.new
33
+ end
34
+
35
+ attr :paths
36
+ attr :sources
37
+ attr :symbols
38
+
39
+ attr :trie
40
+
41
+ def update!
42
+ @paths.each do |path|
43
+ source = Source.new(path)
44
+ @sources[path.relative_path] = Source.new(path)
45
+
46
+ source.parse do |definition|
47
+ @symbols[definition.qualified_name] = definition
48
+
49
+ @trie.insert(definition.lexical_path, definition)
50
+ end
51
+ end
52
+ end
53
+
54
+ def lookup(reference, relative_to: nil)
55
+ if reference.absolute? || relative_to.nil?
56
+ lexical_path = []
57
+ else
58
+ lexical_path = relative_to.lexical_path
59
+ end
60
+
61
+ path = reference.path
62
+
63
+ while true
64
+ node = @trie.match(lexical_path)
65
+
66
+ if node.children[path.first]
67
+ if target = node.lookup(path)
68
+ if reference.kind
69
+ return target.values.select{|symbol| symbol.kind == reference.kind}
70
+ else
71
+ return target.values
72
+ end
73
+ else
74
+ return nil
75
+ end
76
+ end
77
+
78
+ break if lexical_path.empty?
79
+ lexical_path.pop
80
+ end
81
+ end
23
82
  end
24
83
  end
@@ -20,17 +20,70 @@
20
20
 
21
21
  require_relative '../symbol'
22
22
 
23
+ require 'parser/current'
24
+
23
25
  module Decode
24
26
  module Language
25
27
  class Ruby
26
- def initialize
27
- end
28
-
28
+ # The symbol which is used to separate the specified definition from the parent scope.
29
29
  PREFIX = {
30
30
  class: '::',
31
31
  module: '::',
32
- def: '-',
33
- }
32
+ def: ':',
33
+ constant: '::',
34
+ defs: '.',
35
+ }.freeze
36
+
37
+ KIND = {
38
+ ':' => :def,
39
+ '.' => :defs,
40
+ }.freeze
41
+
42
+ class Reference
43
+ def initialize(value)
44
+ @value = value
45
+
46
+ @path = nil
47
+ @kind = nil
48
+ end
49
+
50
+ def absolute?
51
+ @value.start_with?('::')
52
+ end
53
+
54
+ METHOD = /\A(?<scope>.*?)?(?<kind>:|\.)(?<name>.+?)\z/
55
+
56
+ def path
57
+ if @path.nil?
58
+ @path = @value.split(/::/)
59
+
60
+ if last = @path.pop
61
+ if match = last.match(METHOD)
62
+ @kind = KIND[match[:kind]]
63
+
64
+ if scope = match[:scope]
65
+ @path << scope
66
+ end
67
+
68
+ @path << match[:name]
69
+ else
70
+ @path << last
71
+ end
72
+ end
73
+
74
+ @path = @path.map(&:to_sym)
75
+ @path.freeze
76
+ end
77
+
78
+ return @path
79
+ end
80
+
81
+ def kind
82
+ self.path
83
+
84
+ return @kind
85
+ end
86
+ end
34
87
 
35
88
  def join(symbols, absolute = true)
36
89
  buffer = String.new
@@ -42,37 +95,105 @@ module Decode
42
95
  buffer << PREFIX[symbol.kind]
43
96
  end
44
97
 
45
- buffer << symbol.name
98
+ buffer << symbol.name.to_s
46
99
  end
100
+
101
+ return buffer
47
102
  end
48
103
 
49
- COMMENT = /\A\s*\#\s?(.*?)\Z/
50
- DECLARATION = /\A(?<indentation>\s*)(?<text>(?<kind>module|class|def|attr)\s+(?<name>[\w\.\:]+).*)\Z/
104
+ def parse(input, &block)
105
+ parser = ::Parser::CurrentRuby.new
106
+
107
+ buffer = ::Parser::Source::Buffer.new('(input)')
108
+ buffer.source = input.read
109
+
110
+ top, comments = parser.parse_with_comments(buffer)
111
+
112
+ walk(top, comments, &block)
113
+ end
51
114
 
52
- def parse(input)
53
- first = true
54
- comments = []
55
- nesting = []
115
+ def extract_comments_for(node, comments)
116
+ prefix = []
56
117
 
57
- input.each_line do |line|
58
- line.chomp!
118
+ while comment = comments.first
119
+ break if comment.location.line >= node.location.line
59
120
 
60
- if match = line.match(COMMENT)
61
- comments << match[1]
62
- elsif match = line.match(DECLARATION)
63
- level = match[:indentation].size
64
- parent = nesting[level-1]
65
-
66
- declaration = Declaration.new(match[:kind].to_sym, match[:name], match[:text], comments, parent: parent, language: self)
67
-
68
- nesting[level] = declaration
69
-
70
- yield declaration
71
-
72
- comments = []
73
- else
74
- comments.clear
121
+ if last_comment = prefix.last
122
+ if last_comment.location.line != (comment.location.line - 1)
123
+ prefix.clear
124
+ end
75
125
  end
126
+
127
+ prefix << comments.shift
128
+ end
129
+
130
+ # The last comment must butt up against the node:
131
+ if comment = prefix.last
132
+ if comment.location.line == (node.location.line - 1)
133
+ return prefix.map(&:text)
134
+ end
135
+ end
136
+ end
137
+
138
+ # Walk over the syntax tree and extract relevant definitions with their associated comments.
139
+ def walk(node, comments, parent = nil, &block)
140
+ case node.type
141
+ when :begin
142
+ node.children.each do |child|
143
+ walk(child, comments, parent, &block)
144
+ end
145
+ when :class
146
+ definition = Definition.new(
147
+ :class, node.children[0].children[1],
148
+ node, extract_comments_for(node, comments),
149
+ parent: parent, language: self
150
+ )
151
+
152
+ yield definition
153
+
154
+ walk(node.children[2], comments, definition, &block)
155
+ when :module
156
+ definition = Definition.new(
157
+ :module, node.children[0].children[1],
158
+ node, extract_comments_for(node, comments),
159
+ parent: parent, language: self
160
+ )
161
+
162
+ yield definition
163
+
164
+ walk(node.children[1], comments, definition, &block)
165
+ when :def
166
+ definition = Definition.new(
167
+ :def, node.children[0],
168
+ node, extract_comments_for(node, comments),
169
+ parent: parent, language: self
170
+ )
171
+
172
+ yield definition
173
+
174
+ # if body = node.children[2]
175
+ # walk(body, comments, definition, &block)
176
+ # end
177
+ when :defs
178
+ definition = Definition.new(
179
+ :defs, node.children[1],
180
+ node, extract_comments_for(node, comments),
181
+ parent: parent, language: self
182
+ )
183
+
184
+ yield definition
185
+
186
+ # if body = node.children[2]
187
+ # walk(body, comments, definition, &block)
188
+ # end
189
+ when :casgn
190
+ definition = Definition.new(
191
+ :constant, node.children[1],
192
+ node, extract_comments_for(node, comments),
193
+ parent: parent, language: self
194
+ )
195
+
196
+ yield definition
76
197
  end
77
198
  end
78
199
  end
data/lib/decode/symbol.rb CHANGED
@@ -18,13 +18,28 @@
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 'documentation'
22
+
21
23
  module Decode
24
+ Key = Struct.new(:kind, :name)
25
+
22
26
  class Symbol
23
27
  def initialize(kind, name, parent: nil, language: parent.language)
24
28
  @kind = kind
25
29
  @name = name
26
30
  @parent = parent
27
31
  @language = language
32
+
33
+ @path = nil
34
+ @qualified_name = nil
35
+ end
36
+
37
+ def key
38
+ Key.new(@kind, @name)
39
+ end
40
+
41
+ def inspect
42
+ "\#<#{self.class} #{@kind} #{qualified_name}>"
28
43
  end
29
44
 
30
45
  attr :kind
@@ -32,26 +47,44 @@ module Decode
32
47
  attr :parent
33
48
  attr :language
34
49
 
35
- def full_name(parts = [])
36
- if @parent
37
- @parent.full_name(parts)
50
+ def qualified_name
51
+ @qualified_name ||= @language.join(self.path).freeze
52
+ end
53
+
54
+ def path
55
+ if @path
56
+ @path
57
+ elsif @parent
58
+ @path = [*@parent.path, self.key]
59
+ else
60
+ @path = [self.key]
38
61
  end
39
-
40
- parts << self
41
-
42
- @language.join(parts)
62
+ end
63
+
64
+ def lexical_path
65
+ self.path.map(&:name)
43
66
  end
44
67
  end
45
68
 
46
- class Declaration < Symbol
47
- def initialize(kind, name, text, comments, **options)
69
+ class Definition < Symbol
70
+ def initialize(kind, name, node, comments, **options)
48
71
  super(kind, name, **options)
49
72
 
50
- @text = text
73
+ @node = node
51
74
  @comments = comments
75
+ @documentation = nil
76
+ end
77
+
78
+ def text
79
+ @node.location.expression.source
52
80
  end
53
81
 
54
- attr :text
55
82
  attr :comments
83
+
84
+ def documentation
85
+ if @comments.any?
86
+ @documentation ||= Documentation.new(@comments)
87
+ end
88
+ end
56
89
  end
57
90
  end
@@ -0,0 +1,87 @@
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 'source'
22
+
23
+ module Decode
24
+ class Trie
25
+ class Node
26
+ def initialize
27
+ @values = nil
28
+ @children = Hash.new
29
+ end
30
+
31
+ attr_accessor :values
32
+ attr :children
33
+
34
+ def lookup(path, index = 0)
35
+ if index < path.size
36
+ if child = @children[path[index]]
37
+ return child.lookup(path, index+1)
38
+ end
39
+ else
40
+ return self
41
+ end
42
+ end
43
+
44
+ def traverse(path = [], &block)
45
+ yield path, values if values
46
+
47
+ @children.each do |name, node|
48
+ node.traverse([*path, name], &block)
49
+ end
50
+ end
51
+ end
52
+
53
+ attr_accessor :value
54
+
55
+ def initialize
56
+ @root = Node.new
57
+ end
58
+
59
+ attr :root
60
+
61
+ def insert(path, value)
62
+ node = @root
63
+
64
+ path.each do |key|
65
+ node = (node.children[key] ||= Node.new)
66
+ end
67
+
68
+ (node.values ||= []) << value
69
+ end
70
+
71
+ def lookup(path)
72
+ @root.lookup(path).values
73
+ end
74
+
75
+ # Given a base path, enumerate all paths under that.
76
+ # @yield (path, values) pairs
77
+ def each(path, &block)
78
+ if node = @root.lookup(path)
79
+ node.traverse(&block)
80
+ end
81
+ end
82
+
83
+ def match(path, &block)
84
+ @root.lookup(path)
85
+ end
86
+ end
87
+ end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Decode
22
- VERSION = "0.1.0"
22
+ VERSION = "0.2.0"
23
23
  end
data/lib/decode.rb CHANGED
@@ -19,3 +19,4 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative "decode/version"
22
+ require_relative "decode/index"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -11,7 +11,7 @@ cert_chain: []
11
11
  date: 2020-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: build-files
14
+ name: parser
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: build-files
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bake-bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -109,11 +123,13 @@ files:
109
123
  - gems.locked
110
124
  - gems.rb
111
125
  - lib/decode.rb
126
+ - lib/decode/documentation.rb
112
127
  - lib/decode/index.rb
113
128
  - lib/decode/language.rb
114
129
  - lib/decode/language/ruby.rb
115
130
  - lib/decode/source.rb
116
131
  - lib/decode/symbol.rb
132
+ - lib/decode/trie.rb
117
133
  - lib/decode/version.rb
118
134
  homepage: https://github.com/ioquatix/decode
119
135
  licenses: