decode 0.1.0 → 0.2.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: 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: