tickly 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,6 +3,8 @@ source "http://rubygems.org"
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
5
 
6
+ gem "bychar", "~> 1.0"
7
+
6
8
  # Add dependencies to develop your gem here.
7
9
  # Include everything needed to run rake, tests, features, etc.
8
10
  group :development do
data/README.rdoc CHANGED
@@ -1,6 +1,69 @@
1
1
  = tickly
2
2
 
3
- A highly simplistic TCL parser and evaluator (primarily designed for parsing Nuke scripts)
3
+ A highly simplistic TCL parser and evaluator (primarily designed for parsing Nuke scripts). It structures
4
+ the passed Nuke scripts into a TCL AST and return it. You can use some cheap tricks to discard the nodes you are not interested in.
5
+
6
+ == Parsing
7
+
8
+ Create a Parser object and pass TCL expressions/scripts to it. You can pass IO obejcts or strings. For example:
9
+
10
+ p = Tickly::Parser.new
11
+
12
+ # One expression
13
+ p.parse '2' #=> ["2"]
14
+
15
+ # TCL command
16
+ p.parse "tail $list" #=> ["tail", "$list"]
17
+
18
+ # Multiple expressions
19
+ p.parse "2\n2" #=> [["2", "2"]]
20
+
21
+ # Expressions in curly braces
22
+ p.parse '{2 2}' #=> [[:c, "2", "2"]]
23
+
24
+ # Expressions in square brackets
25
+ p.parse '{exec cmd [fileName]}' #=> [[:c, "exec", "cmd", [:b, "fileName"]]]
26
+
27
+ The AST is represented by simple arrays. An array is a TCL expression. An array with the :c symbol at the beginning
28
+ element is an expression in curly braces. An array with the :b symbol at the beginning represents an expression with
29
+ string interpolations in it. If you are curious, :c stands for "curlies" and :b for "brackets". All the other array
30
+ elements are guaranteed to be strings.
31
+
32
+ Multiple expressions separated by ; or a newline will be accumulated as multiple arrays.
33
+
34
+ Lots and lots of TCL features are not supported - remember that most Nuke scripts are machine-generated and they do not
35
+ use most of the esoteric language features.
36
+
37
+ == Evaulating
38
+
39
+ Nuke uses the following syntax for it's nodes:
40
+
41
+ SomeNode {
42
+ someknob 15
43
+ anotherknob 3
44
+ andanother {curve x1 12 45 67}
45
+ }
46
+
47
+ and so on. You can use a simple Evaluator object to run through the nodes returned by the parser. To set up
48
+ the evaulation you need to create classes matching the node classes by name. For example, a Blur class:
49
+
50
+ class Blur
51
+ def initialize(string_keyed_knobs_hash)
52
+ end
53
+ end
54
+
55
+ e = Tickly::Evaluator.new
56
+ e.add_node_handler_class Blur
57
+
58
+ some_script_expressions = Tickly::Parser.new.parse(File.open("/mnt/raid/nuke/scripts/HugeShot_123.nk"))
59
+
60
+ some_script_expressions.each do | expression_in_ast |
61
+ # Everytime a Blur node is found in the script it will be instantiated,
62
+ # and the knobs of the node will be passed to the constructor that you define
63
+ e.evaluate(expression_in_ast) do | blur_node |
64
+ # Now a Blur node instance is all yours
65
+ end
66
+ end
4
67
 
5
68
  == Contributing to tickly
6
69
 
data/lib/tickly.rb CHANGED
@@ -4,9 +4,10 @@ require File.dirname(__FILE__) + "/tickly/evaluator"
4
4
  require 'forwardable'
5
5
 
6
6
  module Tickly
7
- VERSION = '0.0.4'
7
+ VERSION = '0.0.5'
8
8
 
9
- # Provides the methods for quickly emitting the LiteralExpr and StringExpr objects
9
+ # Provides the methods for quickly emitting the expression arrays,
10
+ # is used in tests
10
11
  module Emitter
11
12
  def le(*elems)
12
13
  [:c] + elems
@@ -21,11 +22,15 @@ module Tickly
21
22
  end
22
23
  end
23
24
 
25
+ # Converts a passed Array (received from a Parser)
26
+ # into a TCL expression. This is only ever used in tests
24
27
  def self.to_tcl(e)
25
28
  if e.is_a?(Array) && e[0] == :c
26
29
  '{%s}' % e.map{|e| to_tcl(e)}.join(' ')
27
30
  elsif e.is_a?(Array) && e[0] == :b
28
31
  '[%s]' % e.map{|e| to_tcl(e)}.join(' ')
32
+ elsif e.is_a?(Array)
33
+ e.map{|e| to_tcl(e)}.join(" ")
29
34
  elsif e.is_a?(String) && (e.include?('"') || e.include?("'"))
30
35
  e.inspect
31
36
  else
@@ -9,10 +9,19 @@ module Tickly
9
9
  @node_handlers = []
10
10
  end
11
11
 
12
+ # Add a Class object that can instantiate node handlers. The last part of the class name
13
+ # has to match the name of the Nuke node that you want to capture.
14
+ # For example, to capture Tracker3 nodes a name like this will do:
15
+ # Whatever::YourModule::Better::Tracker3
12
16
  def add_node_handler_class(handler_class)
13
17
  @node_handlers << handler_class
14
18
  end
15
19
 
20
+ # Evaluates a single Nuke TCL command, and if it is a node constructor
21
+ # and a class with a corresponding name has been added using add_node_handler_class
22
+ # the class will be instantiated and yielded to the block. The instance will also be returned
23
+ # at the end of the method. This method evaluates one expression at a time
24
+ # (it's more of a pattern matcher really)
16
25
  def evaluate(expr)
17
26
  if multiple_atoms?(expr) && has_subcommand?(expr) && has_handler?(expr)
18
27
  handler_class = @node_handlers.find{|e| unconst_name(e) == expr[0]}
@@ -25,10 +34,16 @@ module Tickly
25
34
  end
26
35
 
27
36
  # Instantiate the handler with the options
28
- handler_class.new(hash_of_args)
37
+ handler_instance = handler_class.new(hash_of_args)
38
+
39
+ # Both return and yield it
40
+ yield handler_instance if block_given?
41
+ handler_instance
29
42
  end
30
43
  end
31
44
 
45
+ private
46
+
32
47
  def multiple_atoms?(expr)
33
48
  expr.length > 1
34
49
  end
data/lib/tickly/parser.rb CHANGED
@@ -1,23 +1,32 @@
1
1
  require 'stringio'
2
+ require 'bychar'
2
3
 
3
4
  module Tickly
4
5
 
6
+ # Simplistic, incomplete and most likely incorrect TCL parser
5
7
  class Parser
6
8
 
7
9
  # Parses a piece of TCL and returns it converted into internal expression
8
- # structures (nested StringExpr or LiteralExpr objects).
10
+ # structures. A basic TCL expression is just an array of Strings. An expression
11
+ # in curly braces will have the symbol :c tacked onto the beginning of the array.
12
+ # An expression in square braces will have :b at the beginning.
9
13
  def parse(io_or_str)
10
- io = io_or_str.respond_to?(:read) ? io_or_str : StringIO.new(io_or_str)
11
- sub_parse(io)
14
+ bare_io = io_or_str.respond_to?(:read) ? io_or_str : StringIO.new(io_or_str)
15
+ # Wrap the IO in a Bychar buffer to read faster
16
+ reader = Bychar::Reader.new(bare_io)
17
+ sub_parse(reader)
12
18
  end
13
19
 
14
- # Override this to remove any unneeded subexpressions
20
+ # Override this to remove any unneeded subexpressions Modify the passed expr
21
+ # array in-place.
15
22
  def expand_subexpr!(expr, at_depth)
16
23
  end
17
24
 
18
25
  private
19
26
 
20
27
  LAST_CHAR = -1..-1 # If we were 1.9 only we could use -1
28
+ TERMINATORS = ["\n", ";"]
29
+ ESC = 92.chr # Backslash (\)
21
30
 
22
31
  # Parse from a passed IO object either until an unescaped stop_char is reached
23
32
  # or until the IO is exhausted. The last argument is the class used to
@@ -29,7 +38,7 @@ module Tickly
29
38
  buf = ''
30
39
  last_char_was_linebreak = false
31
40
  until io.eof?
32
- char = io.read(1)
41
+ char = io.read_one_byte
33
42
 
34
43
  if buf[LAST_CHAR] != ESC
35
44
  if char == stop_char # Bail out of a subexpr
@@ -42,7 +51,7 @@ module Tickly
42
51
  stack << buf
43
52
  buf = ''
44
53
  end
45
- if char == "\n" # Introduce a stack separator! This is a new line
54
+ if TERMINATORS.include?(char) # Introduce a stack separator! This is a new line
46
55
  if stack.any? && !last_char_was_linebreak
47
56
  last_char_was_linebreak = true
48
57
  stack = handle_expr_terminator(stack, stack_depth)
@@ -87,8 +96,6 @@ module Tickly
87
96
  return stack
88
97
  end
89
98
 
90
- ESC = 92.chr # Backslash (\)
91
-
92
99
  def chomp!(stack)
93
100
  stack.delete_at(-1) if stack.any? && stack[-1].nil?
94
101
  end
@@ -118,7 +125,7 @@ module Tickly
118
125
  def parse_str(io, stop_char)
119
126
  buf = ''
120
127
  until io.eof?
121
- c = io.read(1)
128
+ c = io.read_one_byte
122
129
  if c == stop_char && buf[LAST_CHAR] != ESC
123
130
  return buf
124
131
  elsif buf[LAST_CHAR] == ESC # Eat out the escape char
@@ -39,5 +39,22 @@ class TestEvaluator < Test::Unit::TestCase
39
39
  ref_o = {"foo" => "bar", "baz" => "bad"}
40
40
  assert_equal ref_o, node.options
41
41
  end
42
+
43
+ class TargetError < RuntimeError
44
+ end
45
+
46
+ def test_yields_the_handler_instance
47
+ stack = e("SomeNode", le(e("foo", "bar"), e("baz", "bad")))
48
+ e = Tickly::Evaluator.new
49
+ e.add_node_handler_class(SomeNode)
50
+ ref_o = {"foo" => "bar", "baz" => "bad"}
51
+
52
+ assert_raise(TargetError) do
53
+ e.evaluate(stack) do | some_node |
54
+ assert_kind_of SomeNode, some_node
55
+ raise TargetError
56
+ end
57
+ end
58
+ end
42
59
 
43
60
  end
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 = "0.0.4"
8
+ s.version = "0.0.5"
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-12"
12
+ s.date = "2013-03-15"
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 = [
@@ -52,17 +52,20 @@ Gem::Specification.new do |s|
52
52
  s.specification_version = 3
53
53
 
54
54
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
+ s.add_runtime_dependency(%q<bychar>, ["~> 1.0"])
55
56
  s.add_development_dependency(%q<shoulda>, [">= 0"])
56
57
  s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
57
58
  s.add_development_dependency(%q<bundler>, [">= 0"])
58
59
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
59
60
  else
61
+ s.add_dependency(%q<bychar>, ["~> 1.0"])
60
62
  s.add_dependency(%q<shoulda>, [">= 0"])
61
63
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
62
64
  s.add_dependency(%q<bundler>, [">= 0"])
63
65
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
64
66
  end
65
67
  else
68
+ s.add_dependency(%q<bychar>, ["~> 1.0"])
66
69
  s.add_dependency(%q<shoulda>, [">= 0"])
67
70
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
68
71
  s.add_dependency(%q<bundler>, [">= 0"])
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: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-12 00:00:00.000000000 Z
12
+ date: 2013-03-15 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bychar
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: shoulda
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -122,7 +138,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
138
  version: '0'
123
139
  segments:
124
140
  - 0
125
- hash: -2196296843243875248
141
+ hash: -4354134908046767929
126
142
  required_rubygems_version: !ruby/object:Gem::Requirement
127
143
  none: false
128
144
  requirements: