format_engine 0.3.2 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +9 -6
- data/lib/format_engine.rb +6 -6
- data/lib/format_engine/engine.rb +8 -5
- data/lib/format_engine/format_spec.rb +25 -10
- data/lib/format_engine/format_spec/set.rb +55 -0
- data/lib/format_engine/format_spec/variable.rb +6 -0
- data/lib/format_engine/version.rb +1 -1
- data/sire.rb +85 -0
- data/tests/format_spec_tests.rb +37 -0
- data/tests/formatter_engine_tests.rb +4 -0
- data/tests/parser_engine_tests.rb +13 -0
- data/tests/scan_tests.rb +95 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dbd9eddd61c278dea142573e9edc3ff65aa622f9
|
4
|
+
data.tar.gz: 5d4637bb827e30c3230cb44b9f4e02d8640af24e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec88dfaf16365f492b9ab47f15885a476218e7e610a74726b9199d077a5ae061579304d5f6d7aceb8612d39abb8757c3003731d8f223d04fc07aa188158fbe0d
|
7
|
+
data.tar.gz: 8c4926a9a3abba6ac9fc2b36155e08a8f987714201da795891106aab22ade47887b4ed2aa9a731d21158591649272513e742e26739de4b0a22af27bd74dd4d82
|
data/README.md
CHANGED
@@ -88,12 +88,15 @@ puts cust.strfmt('%f %l is %a years old.')
|
|
88
88
|
|
89
89
|
Format String Specification Syntax (BNF):
|
90
90
|
|
91
|
-
* spec
|
92
|
-
* item
|
93
|
-
*
|
94
|
-
*
|
95
|
-
|
96
|
-
|
91
|
+
* spec ::= (text | item | set)+
|
92
|
+
* item ::= "%" flag* sign? (parm ("." parm)? )? command
|
93
|
+
* set ::= "%" flag* parm? "[" chrs "]"
|
94
|
+
* flag ::= "~" | "@" | "#" | "&" | "^" | "&" | "*" | "=" | "?" | "_"
|
95
|
+
| "<" | ">" | "\\" | "/" | "." | "," | "|" | "!"
|
96
|
+
* sign ::= sign = ("+" | "-")
|
97
|
+
* parm ::= ("0" .. "9")+
|
98
|
+
* chrs ::= (not_any("]") | "\\" "]")+
|
99
|
+
* command ::= ("a" .. "z" | "A" .. "Z")
|
97
100
|
|
98
101
|
###Samples:
|
99
102
|
|
data/lib/format_engine.rb
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'English'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
require_relative 'format_engine/format_spec'
|
6
|
+
require_relative 'format_engine/engine'
|
7
|
+
require_relative 'format_engine/spec_info'
|
8
|
+
require_relative 'format_engine/attr_formatter'
|
9
|
+
require_relative 'format_engine/attr_parser'
|
10
10
|
|
11
|
-
|
11
|
+
require_relative 'format_engine/version'
|
data/lib/format_engine/engine.rb
CHANGED
@@ -3,24 +3,27 @@ module FormatEngine
|
|
3
3
|
# The engine class of the format engine.
|
4
4
|
class Engine
|
5
5
|
|
6
|
+
#The parse library
|
7
|
+
attr_reader :library
|
8
|
+
|
6
9
|
#Set up base data structures.
|
7
10
|
def initialize(library)
|
8
|
-
@
|
11
|
+
@library = library
|
9
12
|
|
10
13
|
#Set up defaults for pre and post amble blocks.
|
11
14
|
nop = lambda { }
|
12
|
-
@
|
13
|
-
@
|
15
|
+
@library[:before] ||= nop
|
16
|
+
@library[:after] ||= nop
|
14
17
|
end
|
15
18
|
|
16
19
|
# Get an entry from the library
|
17
20
|
def [](index)
|
18
|
-
@
|
21
|
+
@library[index]
|
19
22
|
end
|
20
23
|
|
21
24
|
# Set an entry in the library
|
22
25
|
def []=(index, value)
|
23
|
-
@
|
26
|
+
@library[index] = value
|
24
27
|
end
|
25
28
|
|
26
29
|
#Do the actual work of building the formatted output.
|
@@ -1,14 +1,20 @@
|
|
1
|
+
#Analysis of format/parse specification strings.
|
2
|
+
|
1
3
|
require_relative 'format_spec/literal'
|
2
4
|
require_relative 'format_spec/variable'
|
5
|
+
require_relative 'format_spec/set'
|
3
6
|
|
4
7
|
# Format String Specification Syntax (BNF):
|
5
|
-
# spec = (text | item)+
|
6
|
-
# item = "%" flag* (parm ("." parm)? )? command
|
8
|
+
# spec = (text | item | set)+
|
9
|
+
# item = "%" flag* sign? (parm ("." parm)? )? command
|
10
|
+
# set = "%" flag* parm? "[" chrs "]"
|
7
11
|
# flag = ( "~" | "@" | "#" | "&" | "^" |
|
8
12
|
# "&" | "*" | "-" | "+" | "=" |
|
9
13
|
# "?" | "_" | "<" | ">" | "\\" |
|
10
14
|
# "/" | "." | "," | "|" | "!" )
|
15
|
+
# sign = ("+" | "-")
|
11
16
|
# parm = ("0" .. "9" )+
|
17
|
+
# chrs = (not_any("]") | "\\" "]")+
|
12
18
|
# command = ("a" .. "z" | "A" .. "Z")
|
13
19
|
#
|
14
20
|
# Sample: x = FormatSpec.get_spec "Elapsed = %*3.1H:%02M!"
|
@@ -18,10 +24,12 @@ module FormatEngine
|
|
18
24
|
#The format string parser.
|
19
25
|
class FormatSpec
|
20
26
|
#The regex used to parse variable specifications.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
REGEX = %r{(?<flags> [~@#$^&*\=?_<>\\\/\.,\|!]*){0}
|
28
|
+
(?<parms> [-+]?(\d+(\.\d+)?)?){0}
|
29
|
+
(?<var> %\g<flags>\g<parms>[a-zA-Z]){0}
|
30
|
+
(?<set> %\g<flags>\d*\[([^\]]|\\\])+\]){0}
|
31
|
+
\g<var> | \g<set>
|
32
|
+
}x
|
25
33
|
|
26
34
|
#Don't use new, use get_spec instead.
|
27
35
|
private_class_method :new
|
@@ -47,10 +55,17 @@ module FormatEngine
|
|
47
55
|
#Scan the format string extracting literals and variables.
|
48
56
|
def scan_spec(fmt_string)
|
49
57
|
until fmt_string.empty?
|
50
|
-
if
|
51
|
-
|
52
|
-
|
53
|
-
|
58
|
+
if (match_data = REGEX.match(fmt_string))
|
59
|
+
mid = match_data.to_s
|
60
|
+
pre = match_data.pre_match
|
61
|
+
|
62
|
+
@specs << FormatLiteral.new(pre) unless pre.empty?
|
63
|
+
@specs << case
|
64
|
+
when match_data[:var] then FormatVariable.new(mid)
|
65
|
+
when match_data[:set] then FormatSet.new(mid)
|
66
|
+
else fail "Impossible case in scan_spec."
|
67
|
+
end
|
68
|
+
fmt_string = match_data.post_match
|
54
69
|
else
|
55
70
|
@specs << FormatLiteral.new(fmt_string)
|
56
71
|
fmt_string = ""
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module FormatEngine
|
2
|
+
|
3
|
+
#A format engine set specification.
|
4
|
+
class FormatSet
|
5
|
+
|
6
|
+
#The fixed part of this set specification.
|
7
|
+
attr_reader :format
|
8
|
+
|
9
|
+
#The regular expression part of this set specification.
|
10
|
+
attr_reader :regex
|
11
|
+
|
12
|
+
#Setup a variable format specification.
|
13
|
+
def initialize(format)
|
14
|
+
@raw = format
|
15
|
+
|
16
|
+
if format =~ /(\d+)(?=\[)/
|
17
|
+
qualifier = "{1,#{$MATCH}}"
|
18
|
+
@format = $PREMATCH + "["
|
19
|
+
set = $POSTMATCH
|
20
|
+
elsif format =~ /\[/
|
21
|
+
qualifier = "+"
|
22
|
+
@format = $PREMATCH + $MATCH
|
23
|
+
set = $MATCH + $POSTMATCH
|
24
|
+
else
|
25
|
+
fail "Invalid set string #{format}"
|
26
|
+
end
|
27
|
+
|
28
|
+
@regex = Regexp.new("#{set}#{qualifier}")
|
29
|
+
end
|
30
|
+
|
31
|
+
#Is this variable supported by the engine?
|
32
|
+
def validate(engine)
|
33
|
+
fail "Unsupported tag = #{format.inspect}" unless engine[format]
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
#Format onto the output string
|
38
|
+
def do_format(spec_info)
|
39
|
+
fail "The tag %{@raw} may not be used in formatting."
|
40
|
+
end
|
41
|
+
|
42
|
+
#Parse from the input string
|
43
|
+
def do_parse(spec_info)
|
44
|
+
spec_info.fmt = self
|
45
|
+
spec_info.instance_exec(&spec_info.engine[self.format])
|
46
|
+
end
|
47
|
+
|
48
|
+
#Inspect for debugging.
|
49
|
+
def inspect
|
50
|
+
"Set(#{format.inspect}, #{regex.inspect})"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -67,6 +67,12 @@ module FormatEngine
|
|
67
67
|
has_prec? ? parms[1] : ""
|
68
68
|
end
|
69
69
|
|
70
|
+
#Build up a regular expression for parsing.
|
71
|
+
def regex(base)
|
72
|
+
qualifier = has_width? ? "{1,#{width}}": "+"
|
73
|
+
Regexp.new("#{base}#{qualifier}")
|
74
|
+
end
|
75
|
+
|
70
76
|
#Format onto the output string
|
71
77
|
def do_format(spec_info)
|
72
78
|
spec_info.fmt = self
|
data/sire.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# A Simple Interactive Ruby Environment
|
3
|
+
|
4
|
+
require_relative 'lib/format_engine'
|
5
|
+
|
6
|
+
$no_alias_read_line_module = true
|
7
|
+
require 'mini_readline'
|
8
|
+
require 'pp'
|
9
|
+
|
10
|
+
class Object
|
11
|
+
#Generate the class lineage of the object.
|
12
|
+
def classes
|
13
|
+
begin
|
14
|
+
result = ""
|
15
|
+
klass = self.instance_of?(Class) ? self : self.class
|
16
|
+
|
17
|
+
begin
|
18
|
+
result << klass.to_s
|
19
|
+
klass = klass.superclass
|
20
|
+
result << " < " if klass
|
21
|
+
end while klass
|
22
|
+
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class SIRE
|
29
|
+
#Set up the interactive session.
|
30
|
+
def initialize
|
31
|
+
@_done = false
|
32
|
+
|
33
|
+
puts "Welcome to a Simple Interactive Ruby Environment\n"
|
34
|
+
puts "FormatEngine version = #{FormatEngine::VERSION}"
|
35
|
+
puts "Use command 'q' to quit.\n\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
#Quit the interactive session.
|
39
|
+
def q
|
40
|
+
@_done = true
|
41
|
+
puts
|
42
|
+
"Bye bye for now!"
|
43
|
+
end
|
44
|
+
|
45
|
+
def fs
|
46
|
+
FormatEngine::FormatSpec
|
47
|
+
end
|
48
|
+
|
49
|
+
#Execute a single line.
|
50
|
+
def exec_line(line)
|
51
|
+
result = eval line
|
52
|
+
pp result unless line.length == 0
|
53
|
+
|
54
|
+
rescue Interrupt => e
|
55
|
+
@_break = true
|
56
|
+
puts "\nExecution Interrupted!"
|
57
|
+
puts "\n#{e.class} detected: #{e}\n"
|
58
|
+
puts e.backtrace
|
59
|
+
puts "\n"
|
60
|
+
|
61
|
+
rescue Exception => e
|
62
|
+
@_break = true
|
63
|
+
puts "\n#{e.class} detected: #{e}\n"
|
64
|
+
puts e.backtrace
|
65
|
+
puts
|
66
|
+
end
|
67
|
+
|
68
|
+
#Run the interactive session.
|
69
|
+
def run_sire
|
70
|
+
until @_done
|
71
|
+
@_break = false
|
72
|
+
exec_line(MiniReadline.readline('SIRE>', true))
|
73
|
+
end
|
74
|
+
|
75
|
+
puts "\n\n"
|
76
|
+
|
77
|
+
rescue Interrupt => e
|
78
|
+
puts "\nInterrupted! Program Terminating."
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
if __FILE__ == $0
|
84
|
+
SIRE.new.run_sire
|
85
|
+
end
|
data/tests/format_spec_tests.rb
CHANGED
@@ -25,6 +25,43 @@ class FormatSpecTester < Minitest::Test
|
|
25
25
|
assert_equal(nil, test.specs[0].parms)
|
26
26
|
end
|
27
27
|
|
28
|
+
def test_that_it_scans_set_formats
|
29
|
+
test = FormatEngine::FormatSpec.get_spec "%[A]"
|
30
|
+
assert_equal(Array, test.specs.class)
|
31
|
+
assert_equal(1, test.specs.length)
|
32
|
+
assert_equal(FormatEngine::FormatSet, test.specs[0].class)
|
33
|
+
assert_equal("%[", test.specs[0].format)
|
34
|
+
assert_equal(/[A]+/, test.specs[0].regex)
|
35
|
+
|
36
|
+
test = FormatEngine::FormatSpec.get_spec "%*[A]"
|
37
|
+
assert_equal(Array, test.specs.class)
|
38
|
+
assert_equal(1, test.specs.length)
|
39
|
+
assert_equal(FormatEngine::FormatSet, test.specs[0].class)
|
40
|
+
assert_equal("%*[", test.specs[0].format)
|
41
|
+
assert_equal(/[A]+/, test.specs[0].regex)
|
42
|
+
|
43
|
+
test = FormatEngine::FormatSpec.get_spec "%7[A]"
|
44
|
+
assert_equal(Array, test.specs.class)
|
45
|
+
assert_equal(1, test.specs.length)
|
46
|
+
assert_equal(FormatEngine::FormatSet, test.specs[0].class)
|
47
|
+
assert_equal("%[", test.specs[0].format)
|
48
|
+
assert_equal(/[A]{1,7}/, test.specs[0].regex)
|
49
|
+
|
50
|
+
test = FormatEngine::FormatSpec.get_spec "%*7[A]"
|
51
|
+
assert_equal(Array, test.specs.class)
|
52
|
+
assert_equal(1, test.specs.length)
|
53
|
+
assert_equal(FormatEngine::FormatSet, test.specs[0].class)
|
54
|
+
assert_equal("%*[", test.specs[0].format)
|
55
|
+
assert_equal(/[A]{1,7}/, test.specs[0].regex)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_a_mixed_set
|
59
|
+
test = FormatEngine::FormatSpec.get_spec "%f %l %[age] %a"
|
60
|
+
assert_equal(Array, test.specs.class)
|
61
|
+
assert_equal(7, test.specs.length)
|
62
|
+
|
63
|
+
end
|
64
|
+
|
28
65
|
def test_that_it_scans_tab_seperators
|
29
66
|
test = FormatEngine::FormatSpec.get_spec "%A\t%B"
|
30
67
|
assert_equal(Array, test.specs.class)
|
@@ -32,6 +32,10 @@ class FormatterTester < Minitest::Test
|
|
32
32
|
[make_formatter, make_person, make_spec(str)]
|
33
33
|
end
|
34
34
|
|
35
|
+
def test_crazy
|
36
|
+
assert_equal("0.4.0", FormatEngine::VERSION)
|
37
|
+
end
|
38
|
+
|
35
39
|
def test_that_it_can_format_normally
|
36
40
|
engine, obj, spec = make_all("Name = %f %l %a")
|
37
41
|
assert_equal("Name = Squidly Jones 21", engine.do_format(obj, spec))
|
@@ -20,6 +20,7 @@ class ParserTester < Minitest::Test
|
|
20
20
|
"%l" => lambda { tmp[:ln] = found if parse(/(\w)+/ ) },
|
21
21
|
"%L" => lambda { tmp[:ln] = found.upcase if parse(/(\w)+/) },
|
22
22
|
"%-L" => lambda { tmp[:ln] = found.capitalize if parse(/(\w)+/) },
|
23
|
+
"%[" => lambda { parse! fmt.regex },
|
23
24
|
"%t" => lambda { parse("\t") },
|
24
25
|
"%!t" => lambda { parse!("\t") },
|
25
26
|
|
@@ -92,6 +93,18 @@ class ParserTester < Minitest::Test
|
|
92
93
|
assert_equal(TestPerson, result.class)
|
93
94
|
assert_equal("Squidly", result.first_name)
|
94
95
|
assert_equal("Jones", result.last_name)
|
96
|
+
assert_equal(55, result.age)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_that_it_can_parse_sets
|
100
|
+
engine = make_parser
|
101
|
+
spec = "%f %l %[age] %a"
|
102
|
+
result = engine.do_parse("Squidly Jones age 55", TestPerson, spec)
|
103
|
+
|
104
|
+
assert_equal(TestPerson, result.class)
|
105
|
+
assert_equal("Squidly", result.first_name)
|
106
|
+
assert_equal("Jones", result.last_name)
|
107
|
+
assert_equal(55, result.age)
|
95
108
|
end
|
96
109
|
|
97
110
|
def test_that_it_can_detect_errors
|
data/tests/scan_tests.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative '../lib/format_engine'
|
2
|
+
gem 'minitest'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest_visible'
|
5
|
+
|
6
|
+
|
7
|
+
# Test the internals of the parser engine. This is not the normal interface.
|
8
|
+
class ScanTester < Minitest::Test
|
9
|
+
|
10
|
+
#Track mini-test progress.
|
11
|
+
MinitestVisible.track self, __FILE__
|
12
|
+
|
13
|
+
def make_parser
|
14
|
+
FormatEngine::Engine.new(
|
15
|
+
"%d" => lambda do
|
16
|
+
dst << found.to_i if parse(fmt.regex("\\d"))
|
17
|
+
end,
|
18
|
+
|
19
|
+
"%*d" => lambda do
|
20
|
+
parse(fmt.regex("\\d"))
|
21
|
+
end,
|
22
|
+
|
23
|
+
"%[" => lambda do
|
24
|
+
dst << found if parse(fmt.regex)
|
25
|
+
end,
|
26
|
+
|
27
|
+
"%*[" => lambda do
|
28
|
+
parse(fmt.regex)
|
29
|
+
end)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_that_it_can_scan
|
33
|
+
engine = make_parser
|
34
|
+
spec = "%d %2d %4d"
|
35
|
+
result = engine.do_parse("12 34 56", [], spec)
|
36
|
+
|
37
|
+
assert_equal(Array, result.class)
|
38
|
+
assert_equal([12, 34, 56] , result)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_missing_data
|
42
|
+
engine = make_parser
|
43
|
+
spec = "%d %2d %4d %d"
|
44
|
+
result = engine.do_parse("12 34 56", [], spec)
|
45
|
+
|
46
|
+
assert_equal(Array, result.class)
|
47
|
+
assert_equal([12, 34, 56] , result)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_excess_data
|
51
|
+
engine = make_parser
|
52
|
+
spec = "%d %2d %4d"
|
53
|
+
result = engine.do_parse("12 34 56 78", [], spec)
|
54
|
+
|
55
|
+
assert_equal(Array, result.class)
|
56
|
+
assert_equal([12, 34, 56] , result)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_skipped_data
|
60
|
+
engine = make_parser
|
61
|
+
spec = "%d %2d %*d %d"
|
62
|
+
result = engine.do_parse("12 34 56 78", [], spec)
|
63
|
+
|
64
|
+
assert_equal(Array, result.class)
|
65
|
+
assert_equal([12, 34, 78] , result)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_packed_data
|
69
|
+
engine = make_parser
|
70
|
+
spec = "%2d %2d %2d"
|
71
|
+
result = engine.do_parse("123456", [], spec)
|
72
|
+
|
73
|
+
assert_equal(Array, result.class)
|
74
|
+
assert_equal([12, 34, 56] , result)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_some_sets
|
78
|
+
engine = make_parser
|
79
|
+
spec = "%5[truefals] %5[truefals] %5[truefals]"
|
80
|
+
result = engine.do_parse("true false true", [], spec)
|
81
|
+
|
82
|
+
assert_equal(Array, result.class)
|
83
|
+
assert_equal(["true", "false", "true"] , result)
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_some_sets_with_skips
|
87
|
+
engine = make_parser
|
88
|
+
spec = "%5[truefals] %*5[truefals] %5[truefals]"
|
89
|
+
result = engine.do_parse("true false true", [], spec)
|
90
|
+
|
91
|
+
assert_equal(Array, result.class)
|
92
|
+
assert_equal(["true", "true"] , result)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: format_engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Camilleri
|
@@ -101,6 +101,7 @@ files:
|
|
101
101
|
- lib/format_engine/engine.rb
|
102
102
|
- lib/format_engine/format_spec.rb
|
103
103
|
- lib/format_engine/format_spec/literal.rb
|
104
|
+
- lib/format_engine/format_spec/set.rb
|
104
105
|
- lib/format_engine/format_spec/variable.rb
|
105
106
|
- lib/format_engine/spec_info.rb
|
106
107
|
- lib/format_engine/version.rb
|
@@ -110,12 +111,14 @@ files:
|
|
110
111
|
- mocks/demo/demo_parse.rb
|
111
112
|
- mocks/test_person_mock.rb
|
112
113
|
- reek.txt
|
114
|
+
- sire.rb
|
113
115
|
- tests/engine_base_tests.rb
|
114
116
|
- tests/format_engine_tests.rb
|
115
117
|
- tests/format_spec_tests.rb
|
116
118
|
- tests/formatter_engine_tests.rb
|
117
119
|
- tests/literal_spec_tests.rb
|
118
120
|
- tests/parser_engine_tests.rb
|
121
|
+
- tests/scan_tests.rb
|
119
122
|
- tests/variable_spec_tests.rb
|
120
123
|
homepage: http://teuthida-technologies.com/
|
121
124
|
licenses:
|