sexp_path 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'jeweler'
7
+ end
8
+
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
- SexpPath draws inspiration from Regular Expressions, XPath, and CSS Selectors.
6
+ It draws inspiration from Regular Expressions, XPath, and CSS Selectors.
7
7
 
8
- I'm still figuring out how SexpPath should work so either fork this, or send me
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 adamsanderson-sexp_path
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
- ParseTree's representation of the abstract syntax tree. An S-Expression is
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 'parse_tree'
132
+ require 'ruby_parser' # `gem install ruby_parser`
133
133
 
134
134
  path = ARGV.shift
135
135
  code = File.read(path)
136
- sexp = Sexp.from_array(ParseTree.new.parse_tree_for_string(code, path))
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
- * Connivence matchers, for instance canned matchers for matching ruby classes, methods, etc
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 'rake/rdoctask'
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 'ParseTree', '~> 2.1'
23
+ s.add_development_dependency 'ruby_parser', '~> 2.0'
24
24
  end
25
25
 
26
26
  rescue LoadError
data/TODO CHANGED
@@ -1,4 +1,3 @@
1
- * Clean up class organization
2
1
  * Document extending SexpPath
3
2
  * Add examples of extending matchers
4
3
  * AssertSanity!
data/VERSION.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
- :minor: 4
3
- :patch: 0
4
2
  :major: 0
3
+ :minor: 5
4
+ :patch: 0
5
+ :build:
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
2
  require File.dirname(__FILE__) + '/../lib/sexp_path'
3
- require 'parse_tree'
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 = Sexp.from_array(ParseTree.new.parse_tree_for_string(code, path))
14
+ sexp = RubyParser.new.parse(code, path)
15
15
 
16
- class_query = Q?{ s(:class, atom % 'class_name', _, _) }
17
- method_query = Q?{ s(:defn, atom % 'method_name', _ ) }
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['class_name']
26
- method_name = sexp_result['method_name']
23
+ class_name = sexp_result['class']
24
+ method_name = sexp_result['method']
27
25
  puts "#{class_name}##{method_name}"
28
26
  end
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
2
  require File.dirname(__FILE__) + '/../lib/sexp_path'
3
- require 'parse_tree'
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 ParseTree, and append line numbers
35
- sexp = sexp = LineNumberingProcessor.process_file(path)
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.sexp.search(pattern, 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 anything.
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 expression match
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 expression match
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
@@ -1,6 +1,6 @@
1
1
  require 'test/unit'
2
2
  require File.dirname(__FILE__) + '/../lib/sexp_path'
3
- require 'parse_tree'
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 = Sexp.from_array(ParseTree.new.parse_tree_for_string(sample, path))
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
- empty_body = Q?{ s(:scope, s(:block, t(:args), s(:nil))) }
35
- res = @sexp / Q?{ s(:defn, m(/^test_.+/) % 'name', empty_body ) }
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
- seen = Set.new()
43
- repeated = 0
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
- name = m['name']
46
- repeated += 1 if seen.include? name
47
- seen << name
46
+ method_name = m['name']
47
+ counts[method_name] += 1
48
48
  end
49
49
 
50
- assert_equal 1, repeated, "Should have caught test_a being repeated"
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
- version: 0.4.0
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: 2010-02-12 00:00:00 +01:00
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: sexp_processor
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
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
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
- version: "3.0"
24
- version:
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: ParseTree
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
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
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
- version: "2.1"
34
- version:
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
- - --charset=UTF-8
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.5
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