sexp_path 0.4.0 → 0.5.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.
- data/Gemfile +8 -0
- data/Gemfile.lock +30 -0
- data/README.rdoc +7 -7
- data/Rakefile +2 -2
- data/TODO +0 -1
- data/VERSION.yml +3 -2
- data/examples/print_methods.rb +6 -8
- data/examples/sexp_grep.rb +4 -4
- data/lib/sexp_path/matcher/remaining.rb +19 -0
- data/lib/sexp_path/ruby_query_builder.rb +62 -0
- data/lib/sexp_path/sexp_collection.rb +1 -1
- data/lib/sexp_path/sexp_query_builder.rb +28 -4
- data/lib/sexp_path/sexp_result.rb +21 -1
- data/lib/sexp_path.rb +9 -4
- data/test/sexp_path_matching_test.rb +8 -1
- data/test/use_case_test.rb +13 -12
- metadata +78 -20
- data/lib/sexp_path/line_numbering_processor.rb +0 -60
- data/test/line_numbering_processor_test.rb +0 -55
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sexp_path (0.4.6)
|
5
|
+
sexp_processor (~> 4.2)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
git (1.2.5)
|
11
|
+
jeweler (1.8.4)
|
12
|
+
bundler (~> 1.0)
|
13
|
+
git (>= 1.2.5)
|
14
|
+
rake
|
15
|
+
rdoc
|
16
|
+
json (1.8.0)
|
17
|
+
rake (10.1.0)
|
18
|
+
rdoc (4.0.1)
|
19
|
+
json (~> 1.4)
|
20
|
+
ruby_parser (3.1.3)
|
21
|
+
sexp_processor (~> 4.1)
|
22
|
+
sexp_processor (4.2.1)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
jeweler
|
29
|
+
ruby_parser (~> 3.1)
|
30
|
+
sexp_path!
|
data/README.rdoc
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
Structural pattern matching against S-Expressions.
|
4
4
|
|
5
5
|
SexpPath allows you to define patterns that can be matched against S-Expressions.
|
6
|
-
|
6
|
+
It draws inspiration from Regular Expressions, XPath, and CSS Selectors.
|
7
7
|
|
8
|
-
I'm still figuring out how
|
8
|
+
I'm still figuring out how this should work so either fork this, or send me
|
9
9
|
some feedback.
|
10
10
|
http://github.com/adamsanderson/sexp_path
|
11
11
|
netghost@gmail.com
|
@@ -14,12 +14,12 @@ some feedback.
|
|
14
14
|
|
15
15
|
SexpPath is distributed as a ruby gem:
|
16
16
|
|
17
|
-
gem install
|
17
|
+
gem install sexp_path
|
18
18
|
|
19
19
|
== Notation
|
20
20
|
|
21
21
|
In ruby you're most likely to come across S-Expressions when dealing with
|
22
|
-
|
22
|
+
Ruby's representation of the abstract syntax tree. An S-Expression is
|
23
23
|
just a set of nested lists. The SexpProcessor library displays them like this:
|
24
24
|
|
25
25
|
s(:a, :b,
|
@@ -129,11 +129,11 @@ a file:
|
|
129
129
|
|
130
130
|
require 'rubygems'
|
131
131
|
require 'sexp_path'
|
132
|
-
require '
|
132
|
+
require 'ruby_parser' # `gem install ruby_parser`
|
133
133
|
|
134
134
|
path = ARGV.shift
|
135
135
|
code = File.read(path)
|
136
|
-
sexp =
|
136
|
+
sexp = RubyParser.new.parse(code, path)
|
137
137
|
|
138
138
|
class_query = Q?{ s(:class, atom % 'class_name', _, _) }
|
139
139
|
method_query = Q?{ s(:defn, atom % 'method_name', _ ) }
|
@@ -160,7 +160,7 @@ Ideas for Hacking on SexpPath:
|
|
160
160
|
|
161
161
|
* More examples
|
162
162
|
* Add new matchers
|
163
|
-
*
|
163
|
+
* Convenience matchers, for instance canned matchers for matching ruby classes, methods, etc
|
164
164
|
|
165
165
|
I'd love to see what people do with this library, let me know if you find it useful.
|
166
166
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rake/testtask'
|
3
|
-
require '
|
3
|
+
require 'rdoc/task'
|
4
4
|
|
5
5
|
begin
|
6
6
|
require 'jeweler'
|
@@ -20,7 +20,7 @@ begin
|
|
20
20
|
|
21
21
|
# Testing
|
22
22
|
s.test_files = FileList["test/**/*_test.rb"]
|
23
|
-
s.add_development_dependency '
|
23
|
+
s.add_development_dependency 'ruby_parser', '~> 2.0'
|
24
24
|
end
|
25
25
|
|
26
26
|
rescue LoadError
|
data/TODO
CHANGED
data/VERSION.yml
CHANGED
data/examples/print_methods.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require File.dirname(__FILE__) + '/../lib/sexp_path'
|
3
|
-
require '
|
3
|
+
require 'ruby_parser'
|
4
4
|
|
5
5
|
path = ARGV.shift
|
6
6
|
if !path
|
@@ -11,18 +11,16 @@ if !path
|
|
11
11
|
end
|
12
12
|
|
13
13
|
code = File.read(path)
|
14
|
-
sexp =
|
14
|
+
sexp = RubyParser.new.parse(code, path)
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
results = sexp / class_query / method_query
|
16
|
+
# Use the ruby pattern matcher:
|
17
|
+
results = sexp / R?{ _class } / R?{ _method }
|
20
18
|
|
21
19
|
puts path
|
22
20
|
puts "-" * 80
|
23
21
|
|
24
22
|
results.each do |sexp_result|
|
25
|
-
class_name = sexp_result['
|
26
|
-
method_name = sexp_result['
|
23
|
+
class_name = sexp_result['class']
|
24
|
+
method_name = sexp_result['method']
|
27
25
|
puts "#{class_name}##{method_name}"
|
28
26
|
end
|
data/examples/sexp_grep.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require File.dirname(__FILE__) + '/../lib/sexp_path'
|
3
|
-
require '
|
3
|
+
require 'ruby_parser'
|
4
4
|
|
5
5
|
# Example program, this will scan a file for anything
|
6
6
|
# matching the Sexp passed in.
|
@@ -14,7 +14,7 @@ if paths.empty? || !pattern
|
|
14
14
|
puts "usage:"
|
15
15
|
puts " ruby sexp_grep.rb <pattern> <path>"
|
16
16
|
puts "example:"
|
17
|
-
puts " ruby sexp_grep.rb t(:defn) *.rb"
|
17
|
+
puts " ruby sexp_grep.rb 't(:defn)' *.rb"
|
18
18
|
exit
|
19
19
|
end
|
20
20
|
|
@@ -31,8 +31,8 @@ end
|
|
31
31
|
|
32
32
|
# For each path the user defined, search for the SexpPath pattern
|
33
33
|
paths.each do |path|
|
34
|
-
# Parse it with
|
35
|
-
sexp =
|
34
|
+
# Parse it with RubyParser, and append line numbers
|
35
|
+
sexp = RubyParser.new.parse(File.read(path), path)
|
36
36
|
found = false
|
37
37
|
|
38
38
|
# Search it with the given pattern, printing any results
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# See SexpQueryBuilder.___
|
2
|
+
#
|
3
|
+
class SexpPath::Matcher::Remaining < SexpPath::Matcher::Base
|
4
|
+
# Creates a Matcher which will match any remaining
|
5
|
+
# Defaults to matching the immediate following sibling.
|
6
|
+
def initialize()
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
# Always satisfied once this is reached. Think of it as a var arg.
|
11
|
+
def satisfy?(o, data={})
|
12
|
+
data
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"___"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module SexpPath
|
2
|
+
class RubyQueryBuilder < SexpQueryBuilder
|
3
|
+
class << self
|
4
|
+
def _method(name=nil, bind='method')
|
5
|
+
s(:defn, v(name, bind), _, _)
|
6
|
+
end
|
7
|
+
|
8
|
+
def _class_method(name=nil, bind='class_method')
|
9
|
+
s(:defs, v(name, bind), _, _)
|
10
|
+
end
|
11
|
+
|
12
|
+
def _call(name=nil, bind='call')
|
13
|
+
s(:call, _, v(name, bind), _)
|
14
|
+
end
|
15
|
+
|
16
|
+
def _class(name=nil, bind='class')
|
17
|
+
s(:class, v(name, bind), _, _)
|
18
|
+
end
|
19
|
+
|
20
|
+
def _variable(name=nil, bind='variable')
|
21
|
+
tag = variable_tag(name, :ivar, :lvar)
|
22
|
+
s(tag, v(name, var))
|
23
|
+
end
|
24
|
+
|
25
|
+
def _assignment(name=nil, bind='assignment')
|
26
|
+
term = v(name, bind)
|
27
|
+
tag = variable_tag(name, :iasgn, :lasgn)
|
28
|
+
|
29
|
+
s(tag, term) | # declaration
|
30
|
+
s(tag, term, _) | # assignment
|
31
|
+
( # block arument
|
32
|
+
t(:args) &
|
33
|
+
( # note this last case is wrong for regexps
|
34
|
+
SexpPath::Matcher::Block.new{|s| s[1..-1].any?{|a| a == name}} % bind
|
35
|
+
)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
# Inserts the appropriate type of value given name.
|
41
|
+
# For instance:
|
42
|
+
# v('/cake/') #=> /cake/ # regular expression match
|
43
|
+
# v('apple') #=> :apple # atom match
|
44
|
+
def v(name, bind)
|
45
|
+
if name.nil?
|
46
|
+
atom % bind
|
47
|
+
else
|
48
|
+
m(name) % bind
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def variable_tag(name, ivar, lvar)
|
53
|
+
if name.nil?
|
54
|
+
atom
|
55
|
+
else
|
56
|
+
name = name.is_a?(Regexp) ? name.inspect[1..-2] : name.to_s
|
57
|
+
name[0..0] == '@' ? ivar : lvar
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -9,7 +9,7 @@ module SexpPath
|
|
9
9
|
class SexpCollection < Array
|
10
10
|
# See Traverse#search
|
11
11
|
def search(pattern)
|
12
|
-
inject(SexpCollection.new){|collection, match| collection.concat match.
|
12
|
+
inject(SexpCollection.new){|collection, match| collection.concat match.search(pattern, match) }
|
13
13
|
end
|
14
14
|
alias_method :/, :search
|
15
15
|
end
|
@@ -44,7 +44,7 @@ module SexpPath
|
|
44
44
|
SexpPath::Matcher::Base.new(*args)
|
45
45
|
end
|
46
46
|
|
47
|
-
# Matches
|
47
|
+
# Matches any single item.
|
48
48
|
#
|
49
49
|
# example:
|
50
50
|
# s(:a) / Q?{ _ } #=> [s(:a)]
|
@@ -57,7 +57,31 @@ module SexpPath
|
|
57
57
|
SexpPath::Matcher::Wild.new
|
58
58
|
end
|
59
59
|
alias_method :_, :wild
|
60
|
-
|
60
|
+
|
61
|
+
# Matches all remaining input.
|
62
|
+
# This is a special case pattern and has a few odd characteristics.
|
63
|
+
#
|
64
|
+
# - You cannot capture with +remaining+
|
65
|
+
# - If remaining comes before any other matchers, they will be ignored.
|
66
|
+
#
|
67
|
+
# example:
|
68
|
+
# s(:a) / Q?{ s(:a, ___ ) } #=> [s(:a)]
|
69
|
+
# s(:a, :b, :c) / Q?{ s(:a, ___ ) } #=> [s(:a, :b, :c)]
|
70
|
+
#
|
71
|
+
# Can also be called with +remaining+
|
72
|
+
# s(:a) / Q?{ s(:a, remaining) } #=> [s(:a)]
|
73
|
+
#
|
74
|
+
def remaining()
|
75
|
+
SexpPath::Matcher::Remaining.new
|
76
|
+
end
|
77
|
+
alias_method :___, :remaining
|
78
|
+
|
79
|
+
# Matches an expression or any expression that includes the child.
|
80
|
+
#
|
81
|
+
# example:
|
82
|
+
# s(:a, :b) / Q?{ include(:b) }
|
83
|
+
# s(s(s(:a))) / Q?{ include(:a) }
|
84
|
+
#
|
61
85
|
def include(child)
|
62
86
|
SexpPath::Matcher::Include.new(child)
|
63
87
|
end
|
@@ -72,7 +96,7 @@ module SexpPath
|
|
72
96
|
SexpPath::Matcher::Atom.new
|
73
97
|
end
|
74
98
|
|
75
|
-
# Matches when any sub
|
99
|
+
# Matches when any of the sub expressions match.
|
76
100
|
#
|
77
101
|
# example:
|
78
102
|
# s(:a) / Q?{ any(s(:a), s(:b)) } #=> [s(:a)]
|
@@ -82,7 +106,7 @@ module SexpPath
|
|
82
106
|
SexpPath::Matcher::Any.new(*args)
|
83
107
|
end
|
84
108
|
|
85
|
-
# Matches when all sub
|
109
|
+
# Matches only when all sub expressions match
|
86
110
|
#
|
87
111
|
# example:
|
88
112
|
# s(:a) / Q?{ all(s(:a), s(:b)) } #=> []
|
@@ -17,5 +17,25 @@ module SexpPath
|
|
17
17
|
@sexp = sexp
|
18
18
|
merge! data
|
19
19
|
end
|
20
|
+
|
21
|
+
# Shortcut for querying directly against a result's
|
22
|
+
# Sexp.
|
23
|
+
def search(pattern, data={})
|
24
|
+
@sexp.search(pattern,data)
|
25
|
+
end
|
26
|
+
alias_method :/, :search
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
if empty?
|
30
|
+
@sexp.to_s
|
31
|
+
else
|
32
|
+
matches = self.map{|k,v| "#{k}:#{v}"}.join(", ")
|
33
|
+
"#{@sexp} [#{matches}]"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"#{@sexp} #{super}"
|
39
|
+
end
|
20
40
|
end
|
21
|
-
end
|
41
|
+
end
|
data/lib/sexp_path.rb
CHANGED
@@ -14,11 +14,10 @@ sexp_path_root = File.dirname(__FILE__)+'/sexp_path/'
|
|
14
14
|
%w[
|
15
15
|
traverse
|
16
16
|
sexp_query_builder
|
17
|
+
ruby_query_builder
|
17
18
|
sexp_result
|
18
19
|
sexp_collection
|
19
|
-
|
20
|
-
line_numbering_processor
|
21
|
-
|
20
|
+
|
22
21
|
matcher/base
|
23
22
|
matcher/any
|
24
23
|
matcher/all
|
@@ -29,6 +28,7 @@ sexp_path_root = File.dirname(__FILE__)+'/sexp_path/'
|
|
29
28
|
matcher/pattern
|
30
29
|
matcher/type
|
31
30
|
matcher/wild
|
31
|
+
matcher/remaining
|
32
32
|
matcher/include
|
33
33
|
matcher/sibling
|
34
34
|
|
@@ -41,6 +41,11 @@ def Q?(&block)
|
|
41
41
|
SexpPath::SexpQueryBuilder.do(&block)
|
42
42
|
end
|
43
43
|
|
44
|
+
# Ruby specific builder
|
45
|
+
def R?(&block)
|
46
|
+
SexpPath::RubyQueryBuilder.do(&block)
|
47
|
+
end
|
48
|
+
|
44
49
|
# SexpPath extends Sexp with Traverse.
|
45
50
|
# This adds support for searching S-Expressions
|
46
51
|
class Sexp
|
@@ -49,7 +54,7 @@ class Sexp
|
|
49
54
|
# Extends Sexp to allow any Sexp to be used as a SexpPath matcher
|
50
55
|
def satisfy?(o, data={})
|
51
56
|
return false unless o.is_a? Sexp
|
52
|
-
return false unless length == o.length
|
57
|
+
return false unless (length == o.length || last.is_a?(SexpPath::Matcher::Remaining) )
|
53
58
|
each_with_index{|c,i| return false unless c.is_a?(Sexp) ? c.satisfy?( o[i], data ) : c == o[i] }
|
54
59
|
|
55
60
|
capture_match(o, data)
|
@@ -147,7 +147,7 @@ class SexpMatchingPathTest < Test::Unit::TestCase
|
|
147
147
|
|
148
148
|
assert Q?{s(:a) >> s(:b)}.satisfy?( s(s(:a), s(:b)) ), "should match s(:a) has an immediate sibling s(:b)"
|
149
149
|
assert Q?{s(:a) >> s(:b)}.satisfy?( s(s(:a), s(:b), s(:c)) ), "should match s(:a) has an immediate sibling s(:b)"
|
150
|
-
assert Q?{s(:a) >> s(:c)}.satisfy?( s(s(:a), s(:b), s(:c)) ), "should match s(:a) a sibling s(:b)"
|
150
|
+
assert Q?{s(:a) >> s(:c)}.satisfy?( s(s(:a), s(:b), s(:c)) ), "should match s(:a) has a sibling s(:b)"
|
151
151
|
assert !Q?{s(:c) >> s(:a)}.satisfy?( s(s(:a), s(:b), s(:c)) ), "should not match s(:a) does not follow s(:c)"
|
152
152
|
assert !Q?{s(:a) >> s(:a)}.satisfy?( s(s(:a)) ), "should not match s(:a) has no siblings"
|
153
153
|
assert Q?{s(:a) >> s(:a)}.satisfy?( s(s(:a), s(:b), s(:a)) ), "should match s(:a) has another sibling s(:a)"
|
@@ -166,6 +166,13 @@ class SexpMatchingPathTest < Test::Unit::TestCase
|
|
166
166
|
assert_search_count @ast_sexp, Q?{s(m(/\w{3}/), :a, :b)}, 2,
|
167
167
|
"Should match s(:add, :a, :b) and s(:sub, :a, :b)"
|
168
168
|
end
|
169
|
+
|
170
|
+
def test_remaining_matcher
|
171
|
+
assert Q?{ ___ }.satisfy?( s(:a) ), "Should match a single atom"
|
172
|
+
assert Q?{ ___ }.satisfy?( s(:a, :b, :c) ), "Should match multiple atoms"
|
173
|
+
assert Q?{ s(:x, ___ ) }.satisfy?( s(:x, :y) ), "Should match the remainder after :x"
|
174
|
+
assert !Q?{ s(:y, ___ ) }.satisfy?( s(:x, :y) ), "Should not match (initial atom doesn't match)"
|
175
|
+
end
|
169
176
|
|
170
177
|
def test_search_method
|
171
178
|
assert_equal 1, @ast_sexp.search( s(:sub, :a, :b)).length
|
data/test/use_case_test.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require File.dirname(__FILE__) + '/../lib/sexp_path'
|
3
|
-
require '
|
3
|
+
require 'ruby_parser'
|
4
4
|
require 'set'
|
5
5
|
|
6
6
|
# Here's a crazy idea, these tests actually use sexp_path on some "real"
|
@@ -13,7 +13,7 @@ class UseCaseTest < Test::Unit::TestCase
|
|
13
13
|
def setup
|
14
14
|
path = File.dirname(__FILE__) + '/sample.rb'
|
15
15
|
sample = File.read(path)
|
16
|
-
@sexp =
|
16
|
+
@sexp = RubyParser.new.parse(sample, path)
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_finding_methods
|
@@ -22,7 +22,7 @@ class UseCaseTest < Test::Unit::TestCase
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def test_finding_classes_and_methods
|
25
|
-
res = @sexp / Q?{ s(:class, atom % 'name',
|
25
|
+
res = @sexp / Q?{ s(:class, atom % 'name', ___ ) }
|
26
26
|
assert_equal 1, res.length
|
27
27
|
assert_equal :ExampleTest, res.first['name']
|
28
28
|
|
@@ -31,23 +31,24 @@ class UseCaseTest < Test::Unit::TestCase
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def test_finding_empty_test_methods
|
34
|
-
|
35
|
-
res = @sexp /
|
34
|
+
empty_test = Q?{ s(:defn, m(/^test_.+/) % 'name', s(:args), s(:nil)) }
|
35
|
+
res = @sexp / empty_test
|
36
|
+
|
36
37
|
assert_equal 1, res.length
|
37
38
|
assert_equal :test_b, res.first['name']
|
38
39
|
end
|
39
40
|
|
40
41
|
def test_finding_duplicate_test_names
|
41
|
-
res = @sexp / Q?{ s(:defn, m(/^test_.+/) % 'name',
|
42
|
-
|
43
|
-
|
42
|
+
res = @sexp / Q?{ s(:defn, m(/^test_.+/) % 'name', ___ ) }
|
43
|
+
counts = Hash.new{|h,k| h[k] = 0}
|
44
|
+
|
44
45
|
res.each do |m|
|
45
|
-
|
46
|
-
|
47
|
-
seen << name
|
46
|
+
method_name = m['name']
|
47
|
+
counts[method_name] += 1
|
48
48
|
end
|
49
49
|
|
50
|
-
assert_equal 1,
|
50
|
+
assert_equal 1, counts[:test_b], "Should have seen test_b once"
|
51
|
+
assert_equal 2, counts[:test_a], "Should have caught test_a being repeated"
|
51
52
|
end
|
52
53
|
|
53
54
|
def test_rewriting_colon2s_oh_man_i_hate_those_in_most_cases_but_i_understand_why_they_are_there
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sexp_path
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 5
|
8
|
+
- 0
|
9
|
+
version: 0.5.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Adam Sanderson
|
@@ -9,29 +14,77 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date:
|
17
|
+
date: 2013-06-26 00:00:00 -07:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
21
|
+
name: sexp_path
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
17
31
|
type: :runtime
|
18
|
-
|
19
|
-
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: ruby_parser
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
20
38
|
requirements:
|
21
39
|
- - ~>
|
22
40
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
41
|
+
segments:
|
42
|
+
- 3
|
43
|
+
- 1
|
44
|
+
version: "3.1"
|
45
|
+
type: :development
|
46
|
+
version_requirements: *id002
|
25
47
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
48
|
+
name: jeweler
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
27
58
|
type: :development
|
28
|
-
|
29
|
-
|
59
|
+
version_requirements: *id003
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: sexp_processor
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
30
65
|
requirements:
|
31
66
|
- - ~>
|
32
67
|
- !ruby/object:Gem::Version
|
33
|
-
|
34
|
-
|
68
|
+
segments:
|
69
|
+
- 3
|
70
|
+
- 0
|
71
|
+
version: "3.0"
|
72
|
+
type: :runtime
|
73
|
+
version_requirements: *id004
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: ruby_parser
|
76
|
+
prerelease: false
|
77
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 2
|
84
|
+
- 0
|
85
|
+
version: "2.0"
|
86
|
+
type: :development
|
87
|
+
version_requirements: *id005
|
35
88
|
description: " Allows you to do example based pattern matching and queries against S Expressions (sexp).\n"
|
36
89
|
email: netghost@gmail.com
|
37
90
|
executables: []
|
@@ -42,6 +95,8 @@ extra_rdoc_files:
|
|
42
95
|
- README.rdoc
|
43
96
|
- TODO
|
44
97
|
files:
|
98
|
+
- Gemfile
|
99
|
+
- Gemfile.lock
|
45
100
|
- README.rdoc
|
46
101
|
- Rakefile
|
47
102
|
- TODO
|
@@ -49,7 +104,6 @@ files:
|
|
49
104
|
- examples/print_methods.rb
|
50
105
|
- examples/sexp_grep.rb
|
51
106
|
- lib/sexp_path.rb
|
52
|
-
- lib/sexp_path/line_numbering_processor.rb
|
53
107
|
- lib/sexp_path/matcher/all.rb
|
54
108
|
- lib/sexp_path/matcher/any.rb
|
55
109
|
- lib/sexp_path/matcher/atom.rb
|
@@ -59,14 +113,15 @@ files:
|
|
59
113
|
- lib/sexp_path/matcher/include.rb
|
60
114
|
- lib/sexp_path/matcher/not.rb
|
61
115
|
- lib/sexp_path/matcher/pattern.rb
|
116
|
+
- lib/sexp_path/matcher/remaining.rb
|
62
117
|
- lib/sexp_path/matcher/sibling.rb
|
63
118
|
- lib/sexp_path/matcher/type.rb
|
64
119
|
- lib/sexp_path/matcher/wild.rb
|
120
|
+
- lib/sexp_path/ruby_query_builder.rb
|
65
121
|
- lib/sexp_path/sexp_collection.rb
|
66
122
|
- lib/sexp_path/sexp_query_builder.rb
|
67
123
|
- lib/sexp_path/sexp_result.rb
|
68
124
|
- lib/sexp_path/traverse.rb
|
69
|
-
- test/line_numbering_processor_test.rb
|
70
125
|
- test/sample.rb
|
71
126
|
- test/sexp_path_capture_test.rb
|
72
127
|
- test/sexp_path_matching_test.rb
|
@@ -77,31 +132,34 @@ homepage: http://github.com/adamsanderson/sexp_path
|
|
77
132
|
licenses: []
|
78
133
|
|
79
134
|
post_install_message:
|
80
|
-
rdoc_options:
|
81
|
-
|
135
|
+
rdoc_options: []
|
136
|
+
|
82
137
|
require_paths:
|
83
138
|
- lib
|
84
139
|
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
85
141
|
requirements:
|
86
142
|
- - ">="
|
87
143
|
- !ruby/object:Gem::Version
|
144
|
+
segments:
|
145
|
+
- 0
|
88
146
|
version: "0"
|
89
|
-
version:
|
90
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
none: false
|
91
149
|
requirements:
|
92
150
|
- - ">="
|
93
151
|
- !ruby/object:Gem::Version
|
152
|
+
segments:
|
153
|
+
- 0
|
94
154
|
version: "0"
|
95
|
-
version:
|
96
155
|
requirements: []
|
97
156
|
|
98
157
|
rubyforge_project:
|
99
|
-
rubygems_version: 1.3.
|
158
|
+
rubygems_version: 1.3.7
|
100
159
|
signing_key:
|
101
160
|
specification_version: 3
|
102
161
|
summary: Pattern matching for S-Expressions (sexp).
|
103
162
|
test_files:
|
104
|
-
- test/line_numbering_processor_test.rb
|
105
163
|
- test/sexp_path_capture_test.rb
|
106
164
|
- test/sexp_path_matching_test.rb
|
107
165
|
- test/sexp_replacement_test.rb
|
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'parse_tree' rescue nil
|
2
|
-
|
3
|
-
# Transforms a Sexp, keeping track of newlines. This uses the internal ruby newline nodes
|
4
|
-
# so they must be included in the Sexp to be transformed. If ParseTree is being used, it should
|
5
|
-
# be configured to include newlines:
|
6
|
-
#
|
7
|
-
# parser = ParseTree.new(true) # true => include_newlines
|
8
|
-
#
|
9
|
-
# LineNumberingProcessor.rewrite_file(path) should be used as a short cut if ParseTree is available.
|
10
|
-
#
|
11
|
-
class LineNumberingProcessor < SexpProcessor
|
12
|
-
# Helper method for generating a Sexp with line numbers from a file at +path+.
|
13
|
-
#
|
14
|
-
# Only available if ParseTree is loaded.
|
15
|
-
def self.rewrite_file(path)
|
16
|
-
raise 'ParseTree must be installed.' unless Object.const_defined? :ParseTree
|
17
|
-
|
18
|
-
code = File.read(path)
|
19
|
-
sexp = Sexp.from_array(ParseTree.new(true).parse_tree_for_string(code, path).first)
|
20
|
-
processor = LineNumberingProcessor.new
|
21
|
-
|
22
|
-
# Fill in the first lines with a value
|
23
|
-
sexp.line = 0
|
24
|
-
sexp.file = path
|
25
|
-
|
26
|
-
# Rewrite the sexp so that everything gets a line number if possible.
|
27
|
-
processor.rewrite sexp
|
28
|
-
end
|
29
|
-
|
30
|
-
# Creates a new LineNumberingProcessor.
|
31
|
-
def initialize()
|
32
|
-
super
|
33
|
-
@unsupported.delete :newline
|
34
|
-
end
|
35
|
-
|
36
|
-
# Rewrites a Sexp using :newline nodes to fill in line and file information.
|
37
|
-
def rewrite exp
|
38
|
-
unless exp.nil?
|
39
|
-
if exp.sexp_type == :newline
|
40
|
-
@line = exp[1]
|
41
|
-
@file = exp[2]
|
42
|
-
end
|
43
|
-
|
44
|
-
exp.file ||= @file
|
45
|
-
exp.line ||= @line
|
46
|
-
end
|
47
|
-
|
48
|
-
super exp
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
# Removes newlines from the expression, they are read inside of rewrite, and used to give
|
53
|
-
# the other nodes a line number and file.
|
54
|
-
def rewrite_newline(exp)
|
55
|
-
# New lines look like:
|
56
|
-
# s(:newline, 21, "test/sample.rb", s(:call, nil, :private, s(:arglist)) )
|
57
|
-
sexp = exp[3]
|
58
|
-
rewrite(sexp)
|
59
|
-
end
|
60
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'test/unit'
|
2
|
-
require File.dirname(__FILE__) + '/../lib/sexp_path'
|
3
|
-
require 'parse_tree'
|
4
|
-
|
5
|
-
class LineNumberingProcessorTest < Test::Unit::TestCase
|
6
|
-
def setup
|
7
|
-
@path = File.dirname(__FILE__) + '/sample.rb'
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_processing_a_file_fills_in_all_the_line_numbers
|
11
|
-
sexp = LineNumberingProcessor.rewrite_file(@path)
|
12
|
-
assert !sexp.empty?
|
13
|
-
sexp.search_each(Q?{_}) do |s|
|
14
|
-
assert !sexp.line.nil?, "Expected a line number for: #{s.sexp.inspect}"
|
15
|
-
assert !sexp.file.nil?, "Expected a file for: #{s.sexp.inspect}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# This test may break if sample.rb changes
|
20
|
-
def test_finding_known_lines
|
21
|
-
sexp = LineNumberingProcessor.rewrite_file(@path)
|
22
|
-
lines = open(@path,'r'){|io| io.readlines}
|
23
|
-
|
24
|
-
assert_line_numbers_equal(
|
25
|
-
lines, 'def test_b()',
|
26
|
-
sexp, Q?{ s(:defn, :test_b, _) }
|
27
|
-
)
|
28
|
-
|
29
|
-
assert_line_numbers_equal(
|
30
|
-
lines, '[apples, oranges, cakes]',
|
31
|
-
sexp, Q?{ s(:array, s(:lvar, :apples), s(:lvar, :oranges), s(:lvar, :cakes)) }
|
32
|
-
)
|
33
|
-
|
34
|
-
assert_line_numbers_equal(
|
35
|
-
lines, "require 'test/unit'",
|
36
|
-
sexp, Q?{ s(:fcall, :require, s(:array, s(:str, "test/unit"))) }
|
37
|
-
)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
def assert_line_numbers_equal(lines, code, sexp, pattern)
|
42
|
-
string_line = find_line(lines, code)
|
43
|
-
sexp_line = (sexp / pattern).first.sexp.line
|
44
|
-
|
45
|
-
assert_equal string_line, sexp_line, "Expected to find #{code} at line #{string_line}"
|
46
|
-
end
|
47
|
-
|
48
|
-
def find_line(lines, code)
|
49
|
-
|
50
|
-
lines.each_with_index do |line,i|
|
51
|
-
return i+1 if line.index(code) # 1 based indexing
|
52
|
-
end
|
53
|
-
return nil
|
54
|
-
end
|
55
|
-
end
|