tickly 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -5,9 +5,9 @@ gem "bychar", "~> 1.2"
5
5
  # Add dependencies to develop your gem here.
6
6
  # Include everything needed to run rake, tests, features, etc.
7
7
  group :development do
8
+ gem "rake"
8
9
  gem "shoulda", ">= 0"
9
10
  gem "rdoc", "~> 3.12"
10
- gem "bundler"
11
11
  gem "jeweler", "~> 1.8.3"
12
12
  gem "ruby-prof"
13
13
  end
data/Rakefile CHANGED
@@ -46,3 +46,21 @@ Rake::RDocTask.new do |rdoc|
46
46
  rdoc.rdoc_files.include('README*')
47
47
  rdoc.rdoc_files.include('lib/**/*.rb')
48
48
  end
49
+
50
+ desc "Profiles the parser"
51
+ task :profile do
52
+ require 'ruby-prof'
53
+ p = Tickly::NodeProcessor.new
54
+ f = File.open(File.dirname(__FILE__) + "/test/test-data/huge_nuke_tcl.tcl")
55
+
56
+ RubyProf.start
57
+ p.parse(f) {|_| }
58
+ result = RubyProf.stop
59
+
60
+ # Print a call graph
61
+ File.open("profiler_calls.html", "w") do | f |
62
+ RubyProf::GraphHtmlPrinter.new(result).print(f)
63
+ end
64
+ `open profiler_calls.html`
65
+ end
66
+
@@ -49,7 +49,7 @@ module Tickly
49
49
  # at the end of the method. This method evaluates one expression at a time
50
50
  # (it's more of a pattern matcher really)
51
51
  def evaluate(expr)
52
- if multiple_atoms?(expr) && has_subcommand?(expr) && has_handler?(expr)
52
+ if will_capture?(expr)
53
53
  handler_class = @node_handlers.find{|e| unconst_name(e) == expr[0]}
54
54
  handler_arguments = expr[1]
55
55
  hash_of_args = {}
@@ -68,6 +68,12 @@ module Tickly
68
68
  end
69
69
  end
70
70
 
71
+ # Tells whether this Evaluator will actually instantiate
72
+ # anything from the passed expression
73
+ def will_capture?(expr)
74
+ multiple_atoms?(expr) && has_subcommand?(expr) && has_handler?(expr)
75
+ end
76
+
71
77
  private
72
78
 
73
79
  def multiple_atoms?(expr)
@@ -0,0 +1,81 @@
1
+ module Tickly
2
+ # A combination of a Parser and an Evaluator
3
+ # Evaluates a passed Nuke script without expanding it's inner arguments.
4
+ # The TCL should look like Nuke's node commands:
5
+ #
6
+ # NodeClass {
7
+ # foo bar
8
+ # baz bad
9
+ # }
10
+ #
11
+ # You have to add the Classes that you want to instantiate for nodes using add_node_handler_class
12
+ # and every time the parser encounters that node the node will be instantiated
13
+ # and the node options (actually TCL commands) will be passed to the constructor,
14
+ # as a Ruby Hash with string keys.
15
+ # Every value of the knobs hash will be the AST as returned by the Parser.
16
+ #
17
+ # class Blur
18
+ # def initialize(knobs_hash)
19
+ # puts knobs_hash.inspect
20
+ # end
21
+ # end
22
+ #
23
+ # e = Tickly::NodeProcessor.new
24
+ # e.add_node_handler_class Blur
25
+ # e.parse(File.open("/path/to/script.nk")) do | blur_node |
26
+ # # do whatever you want to the node instance
27
+ # end
28
+ # end
29
+ class NodeProcessor
30
+ def initialize
31
+ @evaluator = Tickly::Evaluator.new
32
+ @parser = Ratchet.new
33
+ @parser.expr_callback = method(:filter_expression)
34
+ end
35
+
36
+ # Add a Class object that can instantiate node handlers. The last part of the class name
37
+ # has to match the name of the Nuke node that you want to capture.
38
+ # For example, to capture Tracker3 nodes a name like this will do:
39
+ # Whatever::YourModule::Better::Tracker3
40
+ def add_node_handler_class(class_object)
41
+ @evaluator.add_node_handler_class(class_object)
42
+ end
43
+
44
+ # Parses from the passed IO or string and yields every node
45
+ # that has been instantiated
46
+ def parse(io_or_str, &nuke_node_callback)
47
+ raise LocalJumpError, "NodeProcesssor#parse totally requires a block" unless block_given?
48
+ @node_handler = nuke_node_callback
49
+ @parser.parse(io_or_str)
50
+ end
51
+
52
+ private
53
+
54
+ class Ratchet < Parser #:nodoc: :all
55
+ attr_accessor :expr_callback
56
+ def compact_subexpr(expr, at_depth)
57
+ expr_callback.call(expr, at_depth)
58
+ end
59
+ end
60
+
61
+ def filter_expression(expression, at_depth)
62
+ # Leave all expressions which are deeper than 1
63
+ # intact
64
+ return expression if at_depth > 1
65
+
66
+ # Skip all nodes which are not interesting for
67
+ # the evaluator to do
68
+ unless @evaluator.will_capture?(expression)
69
+ return nil # Do not even keep it in memory
70
+ end
71
+
72
+ # And immediately evaluate
73
+ # TODO: also yield it!
74
+ node_instance = @evaluator.evaluate(expression)
75
+ @node_handler.call(node_instance)
76
+
77
+ # Still return nil
78
+ return nil
79
+ end
80
+ end
81
+ end
data/lib/tickly/parser.rb CHANGED
@@ -25,9 +25,11 @@ module Tickly
25
25
  sub_parse(reader, stop_char = nil, stack_depth = 0, multiple_expressions = true)
26
26
  end
27
27
 
28
- # Override this to remove any unneeded subexpressions Modify the passed expr
29
- # array in-place.
30
- def expand_subexpr!(expr, at_depth)
28
+ # Override this to remove any unneeded subexpressions.
29
+ # Return the modified expression. If you return nil, the result
30
+ # will not be added to the expression list
31
+ def compact_subexpr(expr, at_depth)
32
+ expr
31
33
  end
32
34
 
33
35
  private
@@ -44,7 +46,6 @@ module Tickly
44
46
  return stack unless multiple_expressions
45
47
 
46
48
  expressions << stack if stack.any?
47
- expressions.each { |expr| expand_subexpr!(expr, stack_depth + 1) }
48
49
 
49
50
  return expressions
50
51
  end
@@ -73,8 +74,13 @@ module Tickly
73
74
  end
74
75
  if TERMINATORS.include?(char) && stack.any? && !last_char_was_linebreak # Introduce a stack separator! This is a new line
75
76
  stack << buf if buf.length > 0
76
- expressions << stack
77
+ # Immediately run this expression through the filter
78
+ filtered_expr = compact_subexpr(stack, stack_depth + 1)
77
79
  stack = []
80
+
81
+ # Only preserve the parsed expression if it's not nil
82
+ expressions << filtered_expr unless filtered_expr.nil?
83
+
78
84
  last_char_was_linebreak = true
79
85
  multiple_expressions = true
80
86
  #puts "Next expression! #{expressions.inspect} #{stack.inspect} #{buf.inspect}"
data/lib/tickly.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  require File.dirname(__FILE__) + "/tickly/parser"
2
- require File.dirname(__FILE__) + "/tickly/node_extractor"
3
2
  require File.dirname(__FILE__) + "/tickly/evaluator"
4
3
  require File.dirname(__FILE__) + "/tickly/curve"
5
- require 'forwardable'
4
+ require File.dirname(__FILE__) + "/tickly/node_processor"
6
5
 
7
6
  module Tickly
8
- VERSION = '1.0.0'
7
+ VERSION = '2.0.0'
9
8
 
10
9
  # Provides the methods for quickly emitting the expression arrays,
11
10
  # is used in tests
@@ -15,7 +15,7 @@ class TestEvaluator < Test::Unit::TestCase
15
15
  end
16
16
  end
17
17
 
18
- should "not send anything to the handler when the expr does not conform to the standard" do
18
+ def test_does_not_send_anything_when_the_expression_passed_does_not_match_pattern
19
19
  stack = e("ShouldNotBeInstantiated")
20
20
  e = Tickly::Evaluator.new
21
21
  e.add_node_handler_class(ShouldNotBeInstantiated)
@@ -56,5 +56,15 @@ class TestEvaluator < Test::Unit::TestCase
56
56
  end
57
57
  end
58
58
  end
59
+
60
+ def test_will_capture
61
+ e = Tickly::Evaluator.new
62
+ e.add_node_handler_class(SomeNode)
63
+
64
+ valid = e("SomeNode", le(e("foo", "bar"), e("baz", "bad")))
65
+ assert e.will_capture?(valid)
66
+ assert !e.will_capture?([])
67
+ assert !e.will_capture?(e("SomeNode"))
68
+ end
59
69
 
60
70
  end
@@ -0,0 +1,36 @@
1
+ require 'helper'
2
+
3
+ class TestParserEvaluator < Test::Unit::TestCase
4
+ include Tickly::Emitter
5
+
6
+ NUKE7_SCRIPT = File.open(File.dirname(__FILE__) + "/test-data/nuke7_tracker_2tracks.nk")
7
+
8
+ class Tracker4
9
+ attr_reader :knobs
10
+ def initialize(knobs)
11
+ @knobs = knobs
12
+ end
13
+ end
14
+
15
+ class NodeCaptured < RuntimeError; end
16
+
17
+ def test_processes_nodes
18
+ pe = Tickly::NodeProcessor.new
19
+ pe.add_node_handler_class(Tracker4)
20
+
21
+ assert_raise(NodeCaptured) do
22
+ pe.parse(NUKE7_SCRIPT) do | node |
23
+
24
+ assert_kind_of Tracker4, node
25
+ assert_equal "Tracker1", node.knobs["name"]
26
+
27
+ raise NodeCaptured
28
+ end
29
+ end
30
+ end
31
+
32
+ def test_raises_without_a_block
33
+ pe = Tickly::NodeProcessor.new
34
+ assert_raise(LocalJumpError) { pe.parse(NUKE7_SCRIPT) }
35
+ end
36
+ end
data/test/test_parser.rb CHANGED
@@ -91,6 +91,31 @@ class TestParser < Test::Unit::TestCase
91
91
  assert_equal blur, p[4]
92
92
  end
93
93
 
94
+ class Discarder < Tickly::Parser
95
+ def compact_subexpr(expr, depth)
96
+ return :discarded
97
+ end
98
+ end
99
+
100
+ class Eater < Tickly::Parser
101
+ def compact_subexpr(e, d)
102
+ nil
103
+ end
104
+ end
105
+
106
+ def test_passes_expressions_via_compact_subexpr
107
+ f = File.open(File.dirname(__FILE__) + "/test-data/three_nodes_and_roto.txt")
108
+ p = Discarder.new.parse(f)
109
+ assert_equal [:discarded, :discarded, :discarded, :discarded, :discarded], p
110
+ end
111
+
112
+ def test_removes_all_the_expressions_compacted_into_nil
113
+ f = File.open(File.dirname(__FILE__) + "/test-data/three_nodes_and_roto.txt")
114
+ p = Eater.new.parse(f)
115
+ assert_equal [], p
116
+ end
117
+
118
+
94
119
  def test_parsing_nuke_script_with_indentations
95
120
  f = File.open(File.dirname(__FILE__) + "/test-data/nuke_group.txt")
96
121
  p = P.parse(f)
data/tickly.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "tickly"
8
- s.version = "1.0.0"
8
+ s.version = "2.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Julik Tarkhanov"]
12
- s.date = "2013-03-17"
12
+ s.date = "2013-03-21"
13
13
  s.description = "Parses the subset of the TCL grammar needed for Nuke scripts"
14
14
  s.email = "me@julik.nl"
15
15
  s.extra_rdoc_files = [
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
26
26
  "lib/tickly.rb",
27
27
  "lib/tickly/curve.rb",
28
28
  "lib/tickly/evaluator.rb",
29
- "lib/tickly/node_extractor.rb",
29
+ "lib/tickly/node_processor.rb",
30
30
  "lib/tickly/parser.rb",
31
31
  "test/helper.rb",
32
32
  "test/test-data/huge_nuke_tcl.tcl",
@@ -41,9 +41,8 @@ Gem::Specification.new do |s|
41
41
  "test/test-data/tracker_with_repeating_gaps.nk",
42
42
  "test/test_curve.rb",
43
43
  "test/test_evaluator.rb",
44
- "test/test_node_extractor.rb",
44
+ "test/test_node_processor.rb",
45
45
  "test/test_parser.rb",
46
- "test/test_profile.rb",
47
46
  "tickly.gemspec"
48
47
  ]
49
48
  s.homepage = "http://github.com/julik/tickly"
@@ -57,24 +56,24 @@ Gem::Specification.new do |s|
57
56
 
58
57
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
58
  s.add_runtime_dependency(%q<bychar>, ["~> 1.2"])
59
+ s.add_development_dependency(%q<rake>, [">= 0"])
60
60
  s.add_development_dependency(%q<shoulda>, [">= 0"])
61
61
  s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
62
- s.add_development_dependency(%q<bundler>, [">= 0"])
63
62
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
64
63
  s.add_development_dependency(%q<ruby-prof>, [">= 0"])
65
64
  else
66
65
  s.add_dependency(%q<bychar>, ["~> 1.2"])
66
+ s.add_dependency(%q<rake>, [">= 0"])
67
67
  s.add_dependency(%q<shoulda>, [">= 0"])
68
68
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
69
- s.add_dependency(%q<bundler>, [">= 0"])
70
69
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
71
70
  s.add_dependency(%q<ruby-prof>, [">= 0"])
72
71
  end
73
72
  else
74
73
  s.add_dependency(%q<bychar>, ["~> 1.2"])
74
+ s.add_dependency(%q<rake>, [">= 0"])
75
75
  s.add_dependency(%q<shoulda>, [">= 0"])
76
76
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
77
- s.add_dependency(%q<bundler>, [">= 0"])
78
77
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
79
78
  s.add_dependency(%q<ruby-prof>, [">= 0"])
80
79
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tickly
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-17 00:00:00.000000000 Z
12
+ date: 2013-03-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bychar
@@ -28,7 +28,7 @@ dependencies:
28
28
  - !ruby/object:Gem::Version
29
29
  version: '1.2'
30
30
  - !ruby/object:Gem::Dependency
31
- name: shoulda
31
+ name: rake
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  none: false
34
34
  requirements:
@@ -44,37 +44,37 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: rdoc
47
+ name: shoulda
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
- - - ~>
51
+ - - ! '>='
52
52
  - !ruby/object:Gem::Version
53
- version: '3.12'
53
+ version: '0'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  none: false
58
58
  requirements:
59
- - - ~>
59
+ - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
- version: '3.12'
61
+ version: '0'
62
62
  - !ruby/object:Gem::Dependency
63
- name: bundler
63
+ name: rdoc
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
67
- - - ! '>='
67
+ - - ~>
68
68
  - !ruby/object:Gem::Version
69
- version: '0'
69
+ version: '3.12'
70
70
  type: :development
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
- - - ! '>='
75
+ - - ~>
76
76
  - !ruby/object:Gem::Version
77
- version: '0'
77
+ version: '3.12'
78
78
  - !ruby/object:Gem::Dependency
79
79
  name: jeweler
80
80
  requirement: !ruby/object:Gem::Requirement
@@ -124,7 +124,7 @@ files:
124
124
  - lib/tickly.rb
125
125
  - lib/tickly/curve.rb
126
126
  - lib/tickly/evaluator.rb
127
- - lib/tickly/node_extractor.rb
127
+ - lib/tickly/node_processor.rb
128
128
  - lib/tickly/parser.rb
129
129
  - test/helper.rb
130
130
  - test/test-data/huge_nuke_tcl.tcl
@@ -139,9 +139,8 @@ files:
139
139
  - test/test-data/tracker_with_repeating_gaps.nk
140
140
  - test/test_curve.rb
141
141
  - test/test_evaluator.rb
142
- - test/test_node_extractor.rb
142
+ - test/test_node_processor.rb
143
143
  - test/test_parser.rb
144
- - test/test_profile.rb
145
144
  - tickly.gemspec
146
145
  homepage: http://github.com/julik/tickly
147
146
  licenses:
@@ -158,7 +157,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
158
157
  version: '0'
159
158
  segments:
160
159
  - 0
161
- hash: 748629881843892716
160
+ hash: 4279008670880210764
162
161
  required_rubygems_version: !ruby/object:Gem::Requirement
163
162
  none: false
164
163
  requirements:
@@ -1,22 +0,0 @@
1
- module Tickly
2
- # A more refined version of the Parser class that can scope itself to the passed Nuke node
3
- # classnames. It will toss all of the node data that is not relevant, keeping only nodes that are
4
- # required.
5
- class NodeExtractor < Parser
6
- def initialize(*interesting_node_class_names)
7
- @nodes = interesting_node_class_names
8
- end
9
-
10
- # Override this to remove any unneeded subexpressions
11
- def expand_subexpr!(expr, at_depth)
12
- if is_node_constructor?(expr, at_depth)
13
- node_class_name = expr[0]
14
- expr.replace([:discarded]) unless @nodes.include?(node_class_name)
15
- end
16
- end
17
-
18
- def is_node_constructor?(expr, depth)
19
- depth == 1 && expr[0].is_a?(String) && expr.length == 2 && expr[1][0] == :c
20
- end
21
- end
22
- end
@@ -1,30 +0,0 @@
1
- require 'helper'
2
-
3
- class TestNodeExtractor < Test::Unit::TestCase
4
-
5
- include Tickly::Emitter
6
-
7
- def test_parsing_nuke_script_with_indentations
8
- f = File.open(File.dirname(__FILE__) + "/test-data/nuke_group.txt")
9
- x = Tickly::NodeExtractor.new("Group")
10
-
11
- p = x.parse(f)
12
- grp = e(
13
- e("set", "cut_paste_input", se("stack", "0")),
14
- e("version", "6.3", "v4"),
15
- e("Group",
16
- le(
17
- e("inputs", "0"),
18
- e("name", "Group1"),
19
- e("selected", "true")
20
- )
21
- ),
22
- e(:discarded),
23
- e(:discarded),
24
- e(:discarded),
25
- e("end_group")
26
- )
27
- assert_equal grp, p
28
- end
29
-
30
- end
data/test/test_profile.rb DELETED
@@ -1,27 +0,0 @@
1
- require 'helper'
2
-
3
- if ENV['USER'] == 'julik'
4
-
5
- require 'ruby-prof'
6
-
7
- class TestProfile < Test::Unit::TestCase
8
- P = Tickly::Parser.new
9
-
10
-
11
- def test_huge_tcl
12
- f = File.open(File.dirname(__FILE__) + "/test-data/huge_nuke_tcl.tcl")
13
-
14
- RubyProf.start
15
- P.parse(f)
16
- result = RubyProf.stop
17
-
18
- # Print a call graph
19
- File.open("profiler_calls.html", "w") do | f |
20
- RubyProf::GraphHtmlPrinter.new(result).print(f)
21
- end
22
- `open profiler_calls.html`
23
- end
24
-
25
- end
26
-
27
- end