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