citrus 1.6.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +30 -7
- data/examples/calc.citrus +2 -0
- data/examples/calc.rb +3 -0
- data/examples/ip.citrus +82 -0
- data/examples/ip.rb +75 -0
- data/extras/citrus.vim +1 -1
- data/lib/citrus.rb +16 -8
- data/lib/citrus/file.rb +24 -26
- data/test/file_test.rb +11 -4
- data/test/rule_test.rb +7 -0
- metadata +5 -3
data/README
CHANGED
@@ -10,7 +10,7 @@ elegance and expressiveness of the language with the simplicity and power of
|
|
10
10
|
parsing expressions.
|
11
11
|
|
12
12
|
|
13
|
-
|
13
|
+
= Installation
|
14
14
|
|
15
15
|
|
16
16
|
Via RubyGems:
|
@@ -24,7 +24,7 @@ From a local copy:
|
|
24
24
|
$ rake package && sudo rake install
|
25
25
|
|
26
26
|
|
27
|
-
|
27
|
+
= Background
|
28
28
|
|
29
29
|
|
30
30
|
In order to be able to use Citrus effectively, you must first understand the
|
@@ -99,7 +99,7 @@ available to them including the text of the match, its position in the input,
|
|
99
99
|
and any submatches.
|
100
100
|
|
101
101
|
|
102
|
-
|
102
|
+
= Syntax
|
103
103
|
|
104
104
|
|
105
105
|
The most straightforward way to compose a Citrus grammar is to use Citrus' own
|
@@ -168,7 +168,7 @@ Note that any operator binds more tightly than the bar.
|
|
168
168
|
== Super
|
169
169
|
|
170
170
|
When including a grammar inside another, all rules in the child that have the
|
171
|
-
same name as a rule in the parent also have access to the super keyword to
|
171
|
+
same name as a rule in the parent also have access to the "super" keyword to
|
172
172
|
invoke the parent rule.
|
173
173
|
|
174
174
|
== Labels
|
@@ -181,8 +181,31 @@ immediately preceding any expression.
|
|
181
181
|
# expression may be referred to as "chars"
|
182
182
|
# in a block method
|
183
183
|
|
184
|
+
== Precedence
|
184
185
|
|
185
|
-
|
186
|
+
The following table contains a list of all operators and their precedence. A
|
187
|
+
higher level of precedence indicates tighter binding.
|
188
|
+
|
189
|
+
Operator | Level of Precedence | Name
|
190
|
+
----------------------------------------------------------------
|
191
|
+
'' | 6 | Literal string
|
192
|
+
"" | 6 | Literal string
|
193
|
+
[] | 6 | Character class
|
194
|
+
. | 6 | Any character (dot)
|
195
|
+
// | 6 | Regular expression
|
196
|
+
() | 6 | Grouping
|
197
|
+
* | 5 | Repetition (arbitrary)
|
198
|
+
+ | 5 | Repetition (one or more)
|
199
|
+
? | 5 | Repetition (zero or one)
|
200
|
+
& | 4 | And predicate
|
201
|
+
! | 4 | Not predicate
|
202
|
+
: | 4 | Label
|
203
|
+
<>, {} | 3 | Extension
|
204
|
+
e1 e2 | 2 | Sequence
|
205
|
+
e1 | e2 | 1 | Ordered choice
|
206
|
+
|
207
|
+
|
208
|
+
= Example
|
186
209
|
|
187
210
|
|
188
211
|
Below is an example of a simple grammar that is able to parse strings of
|
@@ -300,7 +323,7 @@ Take a look at examples/calc.citrus for an example of a calculator that is able
|
|
300
323
|
to parse and evaluate more complex mathematical expressions.
|
301
324
|
|
302
325
|
|
303
|
-
|
326
|
+
= Links
|
304
327
|
|
305
328
|
|
306
329
|
http://mjijackson.com/citrus
|
@@ -309,7 +332,7 @@ http://en.wikipedia.org/wiki/Parsing_expression_grammar
|
|
309
332
|
http://treetop.rubyforge.org/index.html
|
310
333
|
|
311
334
|
|
312
|
-
|
335
|
+
= License
|
313
336
|
|
314
337
|
|
315
338
|
Copyright 2010 Michael Jackson
|
data/examples/calc.citrus
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# A grammar for mathematical formulas that apply the basic four operations to
|
2
2
|
# non-negative numbers (integers and floats), respecting operator precedence and
|
3
3
|
# ignoring whitespace.
|
4
|
+
#
|
5
|
+
# An identical grammar that is written using pure Ruby can be found in calc.rb.
|
4
6
|
grammar Calc
|
5
7
|
rule term
|
6
8
|
additive | factor
|
data/examples/calc.rb
CHANGED
@@ -3,6 +3,9 @@ require 'citrus'
|
|
3
3
|
# A grammar for mathematical formulas that apply the basic four operations to
|
4
4
|
# non-negative numbers (integers and floats), respecting operator precedence and
|
5
5
|
# ignoring whitespace.
|
6
|
+
#
|
7
|
+
# An identical grammar that is written using Citrus' own grammar syntax can be
|
8
|
+
# found in calc.citrus.
|
6
9
|
grammar :Calc do
|
7
10
|
rule :term do
|
8
11
|
any(:additive, :factor)
|
data/examples/ip.citrus
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# The grammars in this file conform to the ABNF given in Appendix A of RFC 3986
|
2
|
+
# Uniform Resource Identifier (URI): Generic Syntax.
|
3
|
+
#
|
4
|
+
# See http://tools.ietf.org/html/rfc3986#appendix-A for more information.
|
5
|
+
|
6
|
+
grammar IPv4Address
|
7
|
+
# A host identified by an IPv4 literal address is represented in
|
8
|
+
# dotted-decimal notation (a sequence of four decimal numbers in the
|
9
|
+
# range 0 to 255, separated by "."), as described in [RFC1123] by
|
10
|
+
# reference to [RFC0952]. Note that other forms of dotted notation may
|
11
|
+
# be interpreted on some platforms, as described in Section 7.4, but
|
12
|
+
# only the dotted-decimal form of four octets is allowed by this
|
13
|
+
# grammar.
|
14
|
+
rule IPv4address
|
15
|
+
(dec-octet '.' dec-octet '.' dec-octet '.' dec-octet) {
|
16
|
+
def version; 4 end
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
rule dec-octet
|
21
|
+
'25' [0-5] # 250-255
|
22
|
+
| '2' [0-4] DIGIT # 200-249
|
23
|
+
| '1' DIGIT DIGIT # 100-199
|
24
|
+
| [1-9] DIGIT # 10-99
|
25
|
+
| DIGIT # 0-9
|
26
|
+
end
|
27
|
+
|
28
|
+
rule DIGIT
|
29
|
+
[0-9]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
grammar IPv6Address
|
34
|
+
include IPv4Address
|
35
|
+
|
36
|
+
# A 128-bit IPv6 address is divided into eight 16-bit pieces. Each
|
37
|
+
# piece is represented numerically in case-insensitive hexadecimal,
|
38
|
+
# using one to four hexadecimal digits (leading zeroes are permitted).
|
39
|
+
# The eight encoded pieces are given most-significant first, separated
|
40
|
+
# by colon characters. Optionally, the least-significant two pieces
|
41
|
+
# may instead be represented in IPv4 address textual format. A
|
42
|
+
# sequence of one or more consecutive zero-valued 16-bit pieces within
|
43
|
+
# the address may be elided, omitting all their digits and leaving
|
44
|
+
# exactly two consecutive colons in their place to mark the elision.
|
45
|
+
rule IPv6address
|
46
|
+
( (h16 ":")6*6 ls32
|
47
|
+
| "::" (h16 ":")5*5 ls32
|
48
|
+
| h16 ? "::" (h16 ":")4*4 ls32
|
49
|
+
| (h16 (":" h16)*1)? "::" (h16 ":")3*3 ls32
|
50
|
+
| (h16 (":" h16)*2)? "::" (h16 ":")2*2 ls32
|
51
|
+
| (h16 (":" h16)*3)? "::" h16 ":" ls32
|
52
|
+
| (h16 (":" h16)*4)? "::" ls32
|
53
|
+
| (h16 (":" h16)*5)? "::" h16
|
54
|
+
| (h16 (":" h16)*6)? "::"
|
55
|
+
) {
|
56
|
+
def version; 6 end
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# 16-bit address represented in hexadecimal.
|
61
|
+
rule h16
|
62
|
+
HEXDIG 1*4
|
63
|
+
end
|
64
|
+
|
65
|
+
# Least-significant 32 bits of address.
|
66
|
+
rule ls32
|
67
|
+
(h16 ":" h16) | IPv4address
|
68
|
+
end
|
69
|
+
|
70
|
+
rule HEXDIG
|
71
|
+
DIGIT | [a-fA-F] # Hexadecimal should be case-insensitive.
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
grammar IPAddress
|
76
|
+
include IPv4Address
|
77
|
+
include IPv6Address
|
78
|
+
|
79
|
+
rule IPaddress
|
80
|
+
IPv4address | IPv6address
|
81
|
+
end
|
82
|
+
end
|
data/examples/ip.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'citrus'
|
2
|
+
|
3
|
+
# This file contains a small suite of tests for the grammars found in ip.citrus.
|
4
|
+
# If this file is run directly (i.e. using `ruby ip.rb') the tests will run.
|
5
|
+
# Otherwise, this file may be required by another that needs access to the IP
|
6
|
+
# address grammars just as any other file would be.
|
7
|
+
|
8
|
+
# Load and evaluate the grammars contained in ip.citrus into the global
|
9
|
+
# namespace.
|
10
|
+
Citrus.load(File.expand_path('../ip.citrus', __FILE__))
|
11
|
+
|
12
|
+
if $0 == __FILE__
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
class IPAddressTest < Test::Unit::TestCase
|
16
|
+
def test_dec_octet
|
17
|
+
match = IPv4Address.parse('0', :root => :'dec-octet')
|
18
|
+
assert(match)
|
19
|
+
|
20
|
+
match = IPv4Address.parse('255', :root => :'dec-octet')
|
21
|
+
assert(match)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_hexdig
|
25
|
+
match = IPv6Address.parse('0', :root => :HEXDIG)
|
26
|
+
assert(match)
|
27
|
+
|
28
|
+
match = IPv6Address.parse('A', :root => :HEXDIG)
|
29
|
+
assert(match)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_v4
|
33
|
+
match = IPv4Address.parse('0.0.0.0')
|
34
|
+
assert(match)
|
35
|
+
|
36
|
+
match = IPv4Address.parse('255.255.255.255')
|
37
|
+
assert(match)
|
38
|
+
|
39
|
+
assert_raise Citrus::ParseError do
|
40
|
+
IPv4Address.parse('255.255.255')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_v6
|
45
|
+
match = IPv6Address.parse('1:2:3:4:5:6:7:8')
|
46
|
+
assert(match)
|
47
|
+
|
48
|
+
match = IPv6Address.parse('12AD:34FC:A453:1922::')
|
49
|
+
assert(match)
|
50
|
+
|
51
|
+
match = IPv6Address.parse('12AD::34FC')
|
52
|
+
assert(match)
|
53
|
+
|
54
|
+
match = IPv6Address.parse('12AD::')
|
55
|
+
assert(match)
|
56
|
+
|
57
|
+
match = IPv6Address.parse('::')
|
58
|
+
assert(match)
|
59
|
+
|
60
|
+
assert_raise Citrus::ParseError do
|
61
|
+
IPv6Address.parse('1:2')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_all
|
66
|
+
match = IPAddress.parse('1.2.3.4')
|
67
|
+
assert(match)
|
68
|
+
assert_equal(4, match.version)
|
69
|
+
|
70
|
+
match = IPAddress.parse('1:2:3:4::')
|
71
|
+
assert(match)
|
72
|
+
assert_equal(6, match.version)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/extras/citrus.vim
CHANGED
@@ -16,7 +16,7 @@ syn case match
|
|
16
16
|
syn match ctDoubleColon "::" contained
|
17
17
|
syn match ctConstant "\u\w*" contained
|
18
18
|
syn match ctModule "\(\(::\)\?\u\w*\)\+" contains=ctDoubleColon,ctConstant contained
|
19
|
-
syn match ctVariable "[a-zA-
|
19
|
+
syn match ctVariable "\a[a-zA-Z0-9_-]*" contained
|
20
20
|
|
21
21
|
" Comments
|
22
22
|
syn match ctComment "#.*" contains=@Spell
|
data/lib/citrus.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
#
|
5
5
|
# http://mjijackson.com/citrus
|
6
6
|
module Citrus
|
7
|
-
VERSION = [1,
|
7
|
+
VERSION = [1, 7, 0]
|
8
8
|
|
9
9
|
Infinity = 1.0 / 0
|
10
10
|
|
@@ -68,7 +68,7 @@ module Citrus
|
|
68
68
|
# created with this method may be assigned a name by being assigned to some
|
69
69
|
# constant, e.g.:
|
70
70
|
#
|
71
|
-
# Calc = Grammar.new {}
|
71
|
+
# Calc = Citrus::Grammar.new {}
|
72
72
|
#
|
73
73
|
def self.new(&block)
|
74
74
|
mod = Module.new { include Grammar }
|
@@ -143,8 +143,8 @@ module Citrus
|
|
143
143
|
end
|
144
144
|
|
145
145
|
# Gets/sets the rule with the given +name+. If +obj+ is given the rule
|
146
|
-
# will be set to the value of +obj+ passed through Rule#
|
147
|
-
#
|
146
|
+
# will be set to the value of +obj+ passed through Rule#new. If a block is
|
147
|
+
# given, its return value will be used for the value of +obj+.
|
148
148
|
#
|
149
149
|
# It is important to note that this method will also check any included
|
150
150
|
# grammars for a rule with the given +name+ if one cannot be found in this
|
@@ -156,7 +156,7 @@ module Citrus
|
|
156
156
|
if obj
|
157
157
|
rule_names << sym unless has_rule?(sym)
|
158
158
|
|
159
|
-
rule = Rule.
|
159
|
+
rule = Rule.new(obj)
|
160
160
|
rule.name = name
|
161
161
|
setup_super(rule, name)
|
162
162
|
rule.grammar = self
|
@@ -239,7 +239,7 @@ module Citrus
|
|
239
239
|
# the given +rule+. A block may also be given that will be used to create
|
240
240
|
# an anonymous module. See Rule#ext=.
|
241
241
|
def ext(rule, mod=nil)
|
242
|
-
rule = Rule.
|
242
|
+
rule = Rule.new(rule)
|
243
243
|
mod = Proc.new if block_given?
|
244
244
|
rule.extension = mod if mod
|
245
245
|
rule
|
@@ -342,7 +342,7 @@ module Citrus
|
|
342
342
|
# Input during parsing.
|
343
343
|
module Rule
|
344
344
|
# Returns a new Rule object depending on the type of object given.
|
345
|
-
def self.
|
345
|
+
def self.new(obj)
|
346
346
|
case obj
|
347
347
|
when Rule then obj
|
348
348
|
when Symbol then Alias.new(obj)
|
@@ -356,6 +356,14 @@ module Citrus
|
|
356
356
|
end
|
357
357
|
end
|
358
358
|
|
359
|
+
# Creates a new rule object from the given expression.
|
360
|
+
#
|
361
|
+
# Citrus::Rule.create('"a" | "b"')
|
362
|
+
#
|
363
|
+
def self.create(expr)
|
364
|
+
File.parse(expr, :root => :rule_body).value
|
365
|
+
end
|
366
|
+
|
359
367
|
@unique_id = 0
|
360
368
|
|
361
369
|
# Generates a new rule id.
|
@@ -587,7 +595,7 @@ module Citrus
|
|
587
595
|
include Rule
|
588
596
|
|
589
597
|
def initialize(rules=[])
|
590
|
-
@rules = rules.map {|r| Rule.
|
598
|
+
@rules = rules.map {|r| Rule.new(r) }
|
591
599
|
end
|
592
600
|
|
593
601
|
# An array of the actual Rule objects this rule uses to match.
|
data/lib/citrus/file.rb
CHANGED
@@ -75,7 +75,7 @@ module Citrus
|
|
75
75
|
end
|
76
76
|
|
77
77
|
rule :sequence do
|
78
|
-
zero_or_more(:
|
78
|
+
zero_or_more(:appendix) {
|
79
79
|
def values
|
80
80
|
matches.map {|m| m.value }
|
81
81
|
end
|
@@ -86,44 +86,44 @@ module Citrus
|
|
86
86
|
}
|
87
87
|
end
|
88
88
|
|
89
|
-
rule :
|
90
|
-
all(
|
89
|
+
rule :appendix do
|
90
|
+
all(:prefix, zero_or_one(:extension)) {
|
91
91
|
def value
|
92
|
-
rule =
|
93
|
-
|
94
|
-
rule =
|
92
|
+
rule = prefix.value
|
93
|
+
extension = matches[1].first
|
94
|
+
rule.extension = extension.value if extension
|
95
95
|
rule
|
96
96
|
end
|
97
97
|
}
|
98
98
|
end
|
99
99
|
|
100
|
-
rule :
|
101
|
-
all(:
|
100
|
+
rule :prefix do
|
101
|
+
all(zero_or_one(:predicate), :suffix) {
|
102
102
|
def value
|
103
103
|
rule = suffix.value
|
104
|
-
|
105
|
-
|
104
|
+
predicate = matches[0].first
|
105
|
+
rule = predicate.wrap(rule) if predicate
|
106
106
|
rule
|
107
107
|
end
|
108
108
|
}
|
109
109
|
end
|
110
110
|
|
111
111
|
rule :suffix do
|
112
|
-
all(:primary, zero_or_one(:
|
112
|
+
all(:primary, zero_or_one(:repeat)) {
|
113
113
|
def value
|
114
114
|
rule = primary.value
|
115
|
-
|
116
|
-
rule =
|
115
|
+
repeat = matches[1].first
|
116
|
+
rule = repeat.wrap(rule) if repeat
|
117
117
|
rule
|
118
118
|
end
|
119
119
|
}
|
120
120
|
end
|
121
121
|
|
122
122
|
rule :primary do
|
123
|
-
any(:super, :alias, :
|
123
|
+
any(:super, :alias, :grouping, :terminal)
|
124
124
|
end
|
125
125
|
|
126
|
-
rule :
|
126
|
+
rule :grouping do
|
127
127
|
all(:lparen, :rule_body, :rparen) {
|
128
128
|
def value
|
129
129
|
rule_body.value
|
@@ -157,8 +157,10 @@ module Citrus
|
|
157
157
|
}
|
158
158
|
end
|
159
159
|
|
160
|
+
# Rule names may contain letters, numbers, underscores, and dashes. They
|
161
|
+
# MUST start with a letter.
|
160
162
|
rule :rule_name do
|
161
|
-
all(/[a-zA-
|
163
|
+
all(/[a-zA-Z][a-zA-Z0-9_-]*/, :space) {
|
162
164
|
def value
|
163
165
|
first.text
|
164
166
|
end
|
@@ -184,7 +186,7 @@ module Citrus
|
|
184
186
|
rule :terminal do
|
185
187
|
any(:quoted_string, :character_class, :anything_symbol, :regular_expression) {
|
186
188
|
def value
|
187
|
-
Rule.
|
189
|
+
Rule.new(super)
|
188
190
|
end
|
189
191
|
}
|
190
192
|
end
|
@@ -221,7 +223,7 @@ module Citrus
|
|
221
223
|
}
|
222
224
|
end
|
223
225
|
|
224
|
-
rule :
|
226
|
+
rule :predicate do
|
225
227
|
any(:and, :not, :label)
|
226
228
|
end
|
227
229
|
|
@@ -254,11 +256,7 @@ module Citrus
|
|
254
256
|
end
|
255
257
|
|
256
258
|
rule :extension do
|
257
|
-
any(:tag, :block)
|
258
|
-
def apply(rule)
|
259
|
-
rule.extension = value
|
260
|
-
end
|
261
|
-
}
|
259
|
+
any(:tag, :block)
|
262
260
|
end
|
263
261
|
|
264
262
|
rule :tag do
|
@@ -277,8 +275,8 @@ module Citrus
|
|
277
275
|
}
|
278
276
|
end
|
279
277
|
|
280
|
-
rule :
|
281
|
-
any(:question, :plus, :
|
278
|
+
rule :repeat do
|
279
|
+
any(:question, :plus, :star_quantity) {
|
282
280
|
def wrap(rule)
|
283
281
|
Repeat.new(min, max, rule)
|
284
282
|
end
|
@@ -299,7 +297,7 @@ module Citrus
|
|
299
297
|
}
|
300
298
|
end
|
301
299
|
|
302
|
-
rule :
|
300
|
+
rule :star_quantity do
|
303
301
|
all(/[0-9]*/, '*', /[0-9]*/, :space) {
|
304
302
|
def min
|
305
303
|
matches[0] == '' ? 0 : matches[0].text.to_i
|
data/test/file_test.rb
CHANGED
@@ -105,6 +105,13 @@ class CitrusFileTest < Test::Unit::TestCase
|
|
105
105
|
assert_kind_of(Rule, match.value)
|
106
106
|
assert_instance_of(Repeat, match.value)
|
107
107
|
|
108
|
+
match = grammar.parse('"a"*')
|
109
|
+
assert(match)
|
110
|
+
assert_kind_of(Rule, match.value)
|
111
|
+
assert_instance_of(Repeat, match.value)
|
112
|
+
assert_equal(0, match.value.min)
|
113
|
+
assert_equal(Infinity, match.value.max)
|
114
|
+
|
108
115
|
match = grammar.parse('("a" "b")*')
|
109
116
|
assert(match)
|
110
117
|
assert_kind_of(Rule, match.value)
|
@@ -469,8 +476,8 @@ class CitrusFileTest < Test::Unit::TestCase
|
|
469
476
|
assert_equal(/a/i, match.value)
|
470
477
|
end
|
471
478
|
|
472
|
-
def
|
473
|
-
grammar = file(:
|
479
|
+
def test_predicate
|
480
|
+
grammar = file(:predicate)
|
474
481
|
|
475
482
|
match = grammar.parse('&')
|
476
483
|
assert(match)
|
@@ -570,8 +577,8 @@ class CitrusFileTest < Test::Unit::TestCase
|
|
570
577
|
assert(match.value)
|
571
578
|
end
|
572
579
|
|
573
|
-
def
|
574
|
-
grammar = file(:
|
580
|
+
def test_repeat
|
581
|
+
grammar = file(:repeat)
|
575
582
|
|
576
583
|
match = grammar.parse('?')
|
577
584
|
assert(match)
|
data/test/rule_test.rb
CHANGED
@@ -20,6 +20,13 @@ class RuleTest < Test::Unit::TestCase
|
|
20
20
|
|
21
21
|
NumericModule = Module.new(&NumericProc)
|
22
22
|
|
23
|
+
def test_create
|
24
|
+
rule = Rule.create('"a"')
|
25
|
+
assert(rule)
|
26
|
+
match = rule.match(input('a'))
|
27
|
+
assert(match)
|
28
|
+
end
|
29
|
+
|
23
30
|
def test_match_module
|
24
31
|
rule = EqualRule.new('a')
|
25
32
|
rule.extension = MatchModule
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
-
-
|
7
|
+
- 7
|
8
8
|
- 0
|
9
|
-
version: 1.
|
9
|
+
version: 1.7.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Michael Jackson
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-08-17 00:00:00 -
|
17
|
+
date: 2010-08-17 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -63,6 +63,8 @@ files:
|
|
63
63
|
- doc/syntax.rdoc
|
64
64
|
- examples/calc.citrus
|
65
65
|
- examples/calc.rb
|
66
|
+
- examples/ip.citrus
|
67
|
+
- examples/ip.rb
|
66
68
|
- extras/citrus.vim
|
67
69
|
- lib/citrus/debug.rb
|
68
70
|
- lib/citrus/file.rb
|