tickly 0.0.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,7 +1,4 @@
1
1
  source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
5
2
 
6
3
  gem "bychar", "~> 1.2"
7
4
 
@@ -5,18 +5,19 @@ the passed Nuke scripts into a TCL AST and return it. You can use some cheap tri
5
5
 
6
6
  == Parsing
7
7
 
8
- Create a Parser object and pass TCL expressions/scripts to it. You can pass IO obejcts or strings. For example:
8
+ Create a Parser object and pass TCL expressions/scripts to it. You can pass IO obejcts or strings. Note that parse()
9
+ will always return an Array of expressions, even if you only fed it one expression line. For example:
9
10
 
10
11
  p = Tickly::Parser.new
11
12
 
12
- # One expression
13
- p.parse '2' #=> ["2"]
13
+ # One expression, even if it's invalid (2 is not a valid TCL bareword)- doesn't matter
14
+ p.parse '2' #=> [["2"]]
14
15
 
15
16
  # TCL command
16
- p.parse "tail $list" #=> ["tail", "$list"]
17
+ p.parse "tail $list" #=> [["tail", "$list"]]
17
18
 
18
19
  # Multiple expressions
19
- p.parse "2\n2" #=> [["2", "2"]]
20
+ p.parse "2\n2" #=> [["2"], ["2"]]
20
21
 
21
22
  # Expressions in curly braces
22
23
  p.parse '{2 2}' #=> [[:c, "2", "2"]]
@@ -65,6 +66,10 @@ the evaulation you need to create classes matching the node classes by name. For
65
66
  end
66
67
  end
67
68
 
69
+ == Animation curves
70
+
71
+ You can parse Nuke's animation curves using Tickly::Curve. This will give you a way to iterate over every defined keyframe.
72
+
68
73
  == Contributing to tickly
69
74
 
70
75
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
@@ -1,14 +1,15 @@
1
1
  require File.dirname(__FILE__) + "/tickly/parser"
2
2
  require File.dirname(__FILE__) + "/tickly/node_extractor"
3
3
  require File.dirname(__FILE__) + "/tickly/evaluator"
4
+ require File.dirname(__FILE__) + "/tickly/curve"
4
5
  require 'forwardable'
5
6
 
6
7
  module Tickly
7
- VERSION = '0.0.8'
8
+ VERSION = '1.0.0'
8
9
 
9
10
  # Provides the methods for quickly emitting the expression arrays,
10
11
  # is used in tests
11
- module Emitter
12
+ module Emitter #:nodoc :all
12
13
  def le(*elems)
13
14
  [:c] + elems
14
15
  end
@@ -22,21 +23,4 @@ module Tickly
22
23
  end
23
24
  end
24
25
 
25
- # Converts a passed Array (received from a Parser)
26
- # into a TCL expression. This is only ever used in tests
27
- def self.to_tcl(e)
28
- if e.is_a?(Array) && e[0] == :c
29
- '{%s}' % e.map{|e| to_tcl(e)}.join(' ')
30
- elsif e.is_a?(Array) && e[0] == :b
31
- '[%s]' % e.map{|e| to_tcl(e)}.join(' ')
32
- elsif e.is_a?(Array)
33
- e.map{|e| to_tcl(e)}.join(" ")
34
- elsif e.is_a?(String) && (e.include?('"') || e.include?("'"))
35
- e.inspect
36
- else
37
- e.to_s
38
- end
39
- end
40
-
41
-
42
26
  end
@@ -0,0 +1,59 @@
1
+ module Tickly
2
+ # A shorthand class for Nuke's animation curves.
3
+ # Will convert a passed Curve expression into a set of values,
4
+ # where all the values are baked per integer frames on the whole
5
+ # stretch of time where curve is defined
6
+ class Curve
7
+
8
+ class InvalidCurveError < RuntimeError; end
9
+
10
+ include Enumerable
11
+
12
+ SECTION_START = /^x(\d+)$/
13
+ KEYFRAME = /^([-\d\.]+)$/
14
+
15
+ # The constructor accepts a Curve expression as returned by the Parser
16
+ # Normally it looks like this
17
+ # [:c, "curve", "x1", "123", "456", ...]
18
+ def initialize(curve_expression)
19
+ raise InvalidCurveError, "A curve expression should have :c as it's first symbol" unless curve_expression[0] == :c
20
+ raise InvalidCurveError, "A curve expression should start with a `curve' command" unless curve_expression[1] == "curve"
21
+ raise InvalidCurveError, "Curve expression contained no values" unless curve_expression[2]
22
+
23
+ expand_curve(curve_expression)
24
+ end
25
+
26
+ # Returns each defined keyframe as a pair of a frame number and a value
27
+ def each(&blk)
28
+ @tuples.each(&blk)
29
+ end
30
+
31
+ private
32
+
33
+ def expand_curve(curve_expression)
34
+ # Replace the closing curly brace with a curly brace with space so that it gets caught by split
35
+ atoms = curve_expression[2..-1] # remove the :c curly designator and the "curve" keyword
36
+
37
+ @tuples = []
38
+ # Nuke saves curves very efficiently. x(keyframe_number) means that an uninterrupted sequence of values will start,
39
+ # after which values follow. When the curve is interrupted in some way a new x(keyframe_number) will signifu that we
40
+ # skip to that specified keyframe and the curve continues from there, in gap size defined by the last fragment.
41
+ # That is, x1 1 x3 2 3 4 will place 2, 3 and 4 at 2-frame increments.
42
+ # Thanks to Michael Lester for explaining this.
43
+ last_processed_keyframe = 1
44
+ intraframe_gap_size = 1
45
+ while atom = atoms.shift
46
+ if atom =~ SECTION_START
47
+ last_processed_keyframe = $1.to_i
48
+ if @tuples.any?
49
+ last_captured_frame = @tuples[-1][0]
50
+ intraframe_gap_size = last_processed_keyframe - last_captured_frame
51
+ end
52
+ elsif atom =~ KEYFRAME
53
+ @tuples << [last_processed_keyframe, $1.to_f]
54
+ last_processed_keyframe += intraframe_gap_size
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,9 +1,35 @@
1
1
  module Tickly
2
2
  # Evaluates a passed TCL expression without expanding it's inner arguments.
3
- # The TCL should look like Nuke's node commands (i.e. NodeClass { foo bar; baz bad; } and so on)
3
+ # The TCL should look like Nuke's node commands:
4
+ #
5
+ # NodeClass {
6
+ # foo bar
7
+ # baz bad
8
+ # }
9
+ #
4
10
  # You have to add the Classes that you want to instantiate for nodes using add_node_handler_class
5
11
  # and the evaluator will instantiate the classes it finds in the passed expression and pass the
6
- # node options (actually TCL commands) to the constructor, as a Ruby Hash with string keys.
12
+ # node options (actually TCL commands) to the constructor, as a Ruby Hash with string keys.
13
+ # Every value of the knobs hash will be the AST as returned by the Parser.
14
+ # You have to pass every expression returned by Tickly::Parser#parse separately.
15
+ #
16
+ # class Blur
17
+ # def initialize(knobs_hash)
18
+ # puts knobs_hash.inspect
19
+ # end
20
+ # end
21
+ #
22
+ # e = Tickly::Evaluator.new
23
+ # e.add_node_handler_class Blur
24
+ # p = Tickly::Parser.new
25
+ #
26
+ # expressions = p.parse(some_nuke_script)
27
+ # expressions.each do | expr |
28
+ # # If expr is a Nuke node constructor, a new Blur will be created and yielded
29
+ # e.evaluate(expr) do | node_instance|
30
+ # # do whatever you want to the node instance
31
+ # end
32
+ # end
7
33
  class Evaluator
8
34
  def initialize
9
35
  @node_handlers = []
@@ -2,20 +2,27 @@ require 'stringio'
2
2
  require 'bychar'
3
3
 
4
4
  module Tickly
5
-
6
-
7
5
  # Simplistic, incomplete and most likely incorrect TCL parser
8
6
  class Parser
9
7
 
10
8
  # Parses a piece of TCL and returns it converted into internal expression
11
9
  # structures. A basic TCL expression is just an array of Strings. An expression
12
10
  # in curly braces will have the symbol :c tacked onto the beginning of the array.
13
- # An expression in square braces will have :b at the beginning.
11
+ # An expression in square braces will have the symbol :b tacked onto the beginning.
12
+ # This method always returns a Array of expressions. If you only fed it one expression,
13
+ # this expression will be the only element of the array.
14
+ # The correct way to use the returned results is thusly:
15
+ #
16
+ # p = Tickly::Parser.new
17
+ # expressions = p.parse("2 + 2") #=> [["2", "+", "2"]]
18
+ # expression = expressions[0] #=> ["2", "2"]
14
19
  def parse(io_or_str)
15
20
  bare_io = io_or_str.respond_to?(:read) ? io_or_str : StringIO.new(io_or_str)
16
21
  # Wrap the IO in a Bychar buffer to read faster
17
22
  reader = Bychar::Reader.new(bare_io)
18
- sub_parse(reader)
23
+ # Use multiple_expressions = true so that the top-level parsed script is always an array
24
+ # of expressions
25
+ sub_parse(reader, stop_char = nil, stack_depth = 0, multiple_expressions = true)
19
26
  end
20
27
 
21
28
  # Override this to remove any unneeded subexpressions Modify the passed expr
@@ -46,13 +53,12 @@ module Tickly
46
53
  # or until the IO is exhausted. The last argument is the class used to
47
54
  # compose the subexpression being parsed. The subparser is reentrant and not
48
55
  # destructive for the object containing it.
49
- def sub_parse(io, stop_char = nil, stack_depth = 0)
56
+ def sub_parse(io, stop_char = nil, stack_depth = 0, multiple_expressions = false)
50
57
  # A standard stack is an expression that does not evaluate to a string
51
58
  expressions = []
52
59
  stack = []
53
60
  buf = ''
54
61
  last_char_was_linebreak = false
55
- multiple_expressions = false
56
62
 
57
63
  no_eof do
58
64
  char = io.read_one_byte!
@@ -0,0 +1,31 @@
1
+ require "helper"
2
+
3
+ class TestCurve < Test::Unit::TestCase
4
+ def test_parsing_nuke_curve
5
+ curve = [:c] + %w( curve x742 888 890.2463989 891.6602783
6
+ 893.5056763 895.6155396 s95 897.2791748 899.1762695
7
+ x754 912.0731812 x755 913.7190552 916.0959473 918.1025391 920.0751953 922.1898804 )
8
+
9
+ p = Tickly::Curve.new(curve)
10
+ result = p.to_a
11
+
12
+ assert_kind_of Array, result
13
+ assert_equal 13, result.length
14
+ assert_equal 742, result[0][0]
15
+ assert_equal 754, result[7][0]
16
+ end
17
+
18
+ def test_invalid_curves
19
+ assert_raise Tickly::Curve::InvalidCurveError do
20
+ Tickly::Curve.new([])
21
+ end
22
+
23
+ assert_raise Tickly::Curve::InvalidCurveError do
24
+ Tickly::Curve.new([:c])
25
+ end
26
+
27
+ assert_raise Tickly::Curve::InvalidCurveError do
28
+ Tickly::Curve.new([:c, "curve"])
29
+ end
30
+ end
31
+ end
@@ -5,33 +5,33 @@ class TestParser < Test::Unit::TestCase
5
5
  include Tickly::Emitter
6
6
 
7
7
  should "parse a single int as a stack with a string" do
8
- assert_equal e("2"), P.parse('2')
8
+ assert_equal e(e("2")), P.parse('2')
9
9
  end
10
10
 
11
11
  should "parse a single int and discard whitespace" do
12
12
  p = P.parse(' 2 ')
13
- assert_equal e("2"), p
13
+ assert_equal e(e("2")), p
14
14
  end
15
15
 
16
16
  should "parse multiple ints and strings as a stack of subexpressions" do
17
- assert_equal e("2", "foo", "bar", "baz"), P.parse('2 foo bar baz')
17
+ assert_equal e(e("2", "foo", "bar", "baz")), P.parse('2 foo bar baz')
18
18
  end
19
19
 
20
20
  should "parse and expand a string in double quotes" do
21
21
  p = P.parse('"This is a string literal with spaces"')
22
- assert_equal e("This is a string literal with spaces"), p
22
+ assert_equal e(e("This is a string literal with spaces")), p
23
23
 
24
24
  p = P.parse('"This is a string literal \"escaped\" with spaces"')
25
- assert_equal e("This is a string literal \"escaped\" with spaces"), p
25
+ assert_equal e(e("This is a string literal \"escaped\" with spaces")), p
26
26
  end
27
27
 
28
28
  should "parse a string expression" do
29
- assert_equal e(se("1", "2", "3")), P.parse("[1 2 3]")
29
+ assert_equal e(e(se("1", "2", "3"))), P.parse("[1 2 3]")
30
30
  end
31
31
 
32
32
  should "parse multiple string expressions" do
33
33
  p = P.parse("[1 2 3] [3 4 5 foo]")
34
- assert_equal e(se("1", "2", "3"), se("3", "4", "5", "foo")), p
34
+ assert_equal e(e(se("1", "2", "3"), se("3", "4", "5", "foo"))), p
35
35
  end
36
36
 
37
37
  def test_parse_multiline_statements_as_literal_expressions
@@ -42,7 +42,7 @@ class TestParser < Test::Unit::TestCase
42
42
  def test_parse_expr
43
43
  expr = '{4 + 5}'
44
44
  p = P.parse(expr)
45
- assert_equal e(le("4", "+", "5")), p
45
+ assert_equal e(e(le("4", "+", "5"))), p
46
46
  end
47
47
 
48
48
  def test_parsing_a_nuke_node
@@ -129,11 +129,11 @@ class TestParser < Test::Unit::TestCase
129
129
  def test_one_node_parsing
130
130
  f = File.open(File.dirname(__FILE__) + "/test-data/one_node_with_one_param.txt")
131
131
  p = P.parse(f)
132
- ref = e("SomeNode",
132
+ ref = e(e("SomeNode",
133
133
  le(
134
134
  e("foo", "bar")
135
135
  )
136
- )
136
+ ))
137
137
 
138
138
  assert_equal ref, p
139
139
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "tickly"
8
- s.version = "0.0.8"
8
+ s.version = "1.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-15"
12
+ s.date = "2013-03-17"
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 = [
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  "README.rdoc",
25
25
  "Rakefile",
26
26
  "lib/tickly.rb",
27
+ "lib/tickly/curve.rb",
27
28
  "lib/tickly/evaluator.rb",
28
29
  "lib/tickly/node_extractor.rb",
29
30
  "lib/tickly/parser.rb",
@@ -38,6 +39,7 @@ Gem::Specification.new do |s|
38
39
  "test/test-data/three_nodes_and_roto.txt",
39
40
  "test/test-data/tracker_with_differing_gaps.nk",
40
41
  "test/test-data/tracker_with_repeating_gaps.nk",
42
+ "test/test_curve.rb",
41
43
  "test/test_evaluator.rb",
42
44
  "test/test_node_extractor.rb",
43
45
  "test/test_parser.rb",
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.8
4
+ version: 1.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-15 00:00:00.000000000 Z
12
+ date: 2013-03-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bychar
@@ -122,6 +122,7 @@ files:
122
122
  - README.rdoc
123
123
  - Rakefile
124
124
  - lib/tickly.rb
125
+ - lib/tickly/curve.rb
125
126
  - lib/tickly/evaluator.rb
126
127
  - lib/tickly/node_extractor.rb
127
128
  - lib/tickly/parser.rb
@@ -136,6 +137,7 @@ files:
136
137
  - test/test-data/three_nodes_and_roto.txt
137
138
  - test/test-data/tracker_with_differing_gaps.nk
138
139
  - test/test-data/tracker_with_repeating_gaps.nk
140
+ - test/test_curve.rb
139
141
  - test/test_evaluator.rb
140
142
  - test/test_node_extractor.rb
141
143
  - test/test_parser.rb
@@ -156,7 +158,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
156
158
  version: '0'
157
159
  segments:
158
160
  - 0
159
- hash: -3261881245436907150
161
+ hash: 748629881843892716
160
162
  required_rubygems_version: !ruby/object:Gem::Requirement
161
163
  none: false
162
164
  requirements: