tickly 0.0.4 → 0.0.5

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 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: