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 +0 -3
- data/README.rdoc +10 -5
- data/lib/tickly.rb +3 -19
- data/lib/tickly/curve.rb +59 -0
- data/lib/tickly/evaluator.rb +28 -2
- data/lib/tickly/parser.rb +12 -6
- data/test/test_curve.rb +31 -0
- data/test/test_parser.rb +10 -10
- data/tickly.gemspec +4 -2
- metadata +5 -3
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -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.
|
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.
|
data/lib/tickly.rb
CHANGED
@@ -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
|
+
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
|
data/lib/tickly/curve.rb
ADDED
@@ -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
|
data/lib/tickly/evaluator.rb
CHANGED
@@ -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
|
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 = []
|
data/lib/tickly/parser.rb
CHANGED
@@ -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
|
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
|
-
|
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!
|
data/test/test_curve.rb
ADDED
@@ -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
|
data/test/test_parser.rb
CHANGED
@@ -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
|
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
|
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-
|
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
|
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-
|
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:
|
161
|
+
hash: 748629881843892716
|
160
162
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
163
|
none: false
|
162
164
|
requirements:
|