tickly 0.0.8 → 1.0.0

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