lxl 0.3.0 → 0.3.1

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.
Files changed (8) hide show
  1. data/CHANGES +7 -0
  2. data/README.en +3 -3
  3. data/VERSION +1 -1
  4. data/lib/lxl.rb +170 -86
  5. data/lxl.gemspec +19 -0
  6. data/lxl.rdoc +114 -0
  7. data/test/lxl_test.rb +2 -1
  8. metadata +7 -4
data/CHANGES CHANGED
@@ -1,3 +1,10 @@
1
+ 0.3.1
2
+
3
+ - LXL::LittleLexer => LXL::Lexer, types is a string again.
4
+ - Range regexp made more explicit.
5
+ - Added the NOT function.
6
+ - Componentized LXL classes into x_class methods
7
+
1
8
  0.3.0
2
9
 
3
10
  - Functions and constants moved into LXL::Namespace (major refactoring)
data/README.en CHANGED
@@ -1,8 +1,8 @@
1
1
  LXL README
2
- ============
2
+ ==========
3
3
 
4
- LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas.
5
- Easily extended with new constants and functions.
4
+ LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas
5
+ and is easily extended with new constants and functions.
6
6
 
7
7
  Usage
8
8
  -----
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.3.1
data/lib/lxl.rb CHANGED
@@ -1,14 +1,66 @@
1
- # LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas.
2
- # Easily extended with new constants and functions using LXL::Namespace.
3
- #
4
- # =Constants
5
- #
6
- # TRUE # true
7
- # FALSE # false
8
- # NULL # nil
9
- #
1
+ # LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas
2
+ # and is easily extended with new constants and functions.
3
+ #
4
+ # =Namespaces
5
+ #
6
+ # Extending the language is as easy as defining new constants
7
+ # and methods on an LXL::Namespace class.
8
+ #
9
+ # class MyLXLNamespace < LXL::Namespace
10
+ # NAME = 'John Doe'
11
+ # def upper(text) text.to_s.upcase end
12
+ # end
13
+ #
14
+ # class MyLXL < LXL::Parser
15
+ # def self.namespace_class() MyLXLNamespace end
16
+ # end
17
+ #
18
+ # MyLXL.eval('=UPPER(NAME)')
19
+ # # => JOHN DOE
20
+ #
21
+ # =Ranges
22
+ #
23
+ # B3:D5 => LXL::Range.new("B3","D5")
24
+ #
25
+ # LXL::Range is a subclass of Range which overrides
26
+ # the #each method to return Excel-style ranges.
27
+ #
28
+ # # Standard Ruby Range
29
+ # Range.new("B3", "D5").collect
30
+ # # => ["B3", "B4", "B5", "B6", "B7", "B8", "B9",
31
+ # "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9",
32
+ # "D0", "D1", "D2", "D3", "D4", "D5"]
33
+ #
34
+ # # LXL Range
35
+ # LXL::Range.new("B3", "D5").collect
36
+ # # => ["B3", "B4", "B5", "C3", "C4", "C5", "D3", "D4", "D5"]
37
+ #
38
+ # =Percentages
39
+ #
40
+ # 25% => .25
41
+ #
42
+ # =Deferred function calls
43
+ #
44
+ # LXL::Deferred snapshots the symbol/arguments of a function call for later use.
45
+ #
46
+ # class MyNamespace < LXL::Namespace
47
+ # register_deferred :foo
48
+ # end
49
+ # LXL.new(MyNamespace.new).eval('=FOO(1, "two", 3)').inspect
50
+ # # => <LXL::Deferred @args=[1, "two", 3] @symbol=:foo>
51
+ #
52
+ # =Symbol registration
53
+ #
54
+ # Register uppercase constants of the same name and value.
55
+ #
56
+ # class MyNamespace < LXL::Namespace
57
+ # register_symbols :foo, :bar
58
+ # end
59
+ # LXL.new(MyNamespace.new).eval('=LIST(FOO, BAR)').inspect
60
+ # # => [:FOO, :BAR]
61
+ #
10
62
  # =Operators
11
- #
63
+ #
12
64
  # a + b # Add
13
65
  # a - b # Subtract
14
66
  # a * b # Multiply
@@ -19,60 +71,74 @@
19
71
  # a > b # Greater than
20
72
  # a <= b # Less than or equal to
21
73
  # a >= b # Greater than or equal to
22
- # a & b # String concentation
23
- # n ^ n # Exponential
24
- #
74
+ # a & b # String concentation
75
+ # n ^ n # Exponential
76
+ #
77
+ # =Constants
78
+ #
79
+ # TRUE # true
80
+ # FALSE # false
81
+ # NULL # nil
82
+ #
25
83
  # =Logical Functions
26
- #
84
+ #
27
85
  # AND (a,b)
28
86
  # OR (a,b)
87
+ # NOT (cond)
29
88
  # IF (cond,true,false)
30
- #
89
+ #
31
90
  # =Date/Time Functions
32
- #
33
- # TODAY () # current date value
34
- # NOW () # current date/time value
35
- # DATE (y,m,d) # date value
36
- # TIME (h,m,s) # time value
37
- # DATETIME (value) # convert a date/time string into a date/time value
38
- #
91
+ #
92
+ # TODAY () # current date value
93
+ # NOW () # current date/time value
94
+ # DATE (y,m,d) # date value
95
+ # TIME (h,m,s) # time value
96
+ # DATETIME (text) # convert a date/time string into a date/time value
97
+ #
39
98
  # =List Functions
40
- #
99
+ #
41
100
  # LIST (a,b,..) # Variable length list
42
- # IN (find,list) # True if find value is found in the given list (works on strings too)
43
- #
101
+ # IN (find,list) # True if find is found in the given list/string
102
+ #
44
103
  # =Notes
45
- #
46
- # * The number zero is interpereted as FALSE
47
- # * Percentages are translated: 25% => 0.25
48
- # * Ranges are translated: A1:D5 => LXL::Range.new("A1", "D5")
49
- # * Return multiple results in an array by separating formulas with a semi-colon (;)
50
- # * Constant and Function names are case-insensitive
51
- # * Values prefixed with = are formulas, anything else is assumed to be text:
52
- #
104
+ #
105
+ # * Values prefixed with = are formulas, anything else is assumed to be text.
106
+ #
53
107
  # LXL.eval("5+5") # => '5+5'
54
108
  # LXL.eval("=5+5") # => 10
109
+ #
110
+ # * Constant and Function names are case-insensitive.
111
+ #
112
+ # * The number zero is interpereted as FALSE.
113
+ #
114
+ # * Separating formulas with a semi-colon returns an Array of results.
55
115
  #
56
116
  module LXL
57
117
 
58
118
  module_function
119
+
120
+ # Default parser class.
121
+ #
122
+ def parser_class
123
+ LXL::Parser
124
+ end
59
125
 
60
- # False if nil, false or zero.
126
+ # Create a new Parser.
61
127
  #
62
- def to_b(value)
63
- [nil,false,0].include?(value) ? false : true
128
+ def new(*args)
129
+ parser_class.new(*args)
64
130
  end
65
131
 
66
132
  # Evaluate a formula.
67
133
  #
68
134
  def eval(formula)
69
- LXL::Parser.eval(formula)
135
+ parser_class.eval(formula)
70
136
  end
71
137
 
72
- # Create a new Parser.
138
+ # False if nil, false or zero.
73
139
  #
74
- def new(*args)
75
- LXL::Parser.new(*args)
140
+ def to_b(obj)
141
+ [nil,false,0].include?(obj) ? false : true
76
142
  end
77
143
 
78
144
  end
@@ -107,30 +173,42 @@ class LXL::Parser
107
173
  self.new.eval(formula)
108
174
  end
109
175
 
176
+ # Default lexer class.
177
+ #
178
+ def self.lexer_class
179
+ LXL::Lexer
180
+ end
181
+
110
182
  # Default namespace class.
111
183
  #
112
184
  def self.namespace_class
113
185
  LXL::Namespace
114
186
  end
115
187
 
188
+ # Default range class.
189
+ #
190
+ def self.range_class
191
+ LXL::Range
192
+ end
193
+
116
194
  # Initialize namespace and parser.
117
195
  #
118
196
  def initialize(namespace=self.class.namespace_class.new)
119
197
  @namespace = namespace
120
198
  ops = EXCEL_OPERATORS.collect { |v| Regexp.quote(v) }.join('|')
121
- @lexer = LXL::LittleLexer.new([
122
- [/\A[\s,]+/, ?w], # Whitespace (includes Commas)
123
- [/\A;/, ?;], # Semi-Colon (statement separator)
124
- [/\A(#{ops})/, ?o], # Operator
125
- [/\A("([^"]|"")*")/m, ?s], # String
126
- [/\A\w+:\w+/, ?r], # Range
127
- [/\A\d+(\.\d+)?%/, ?p], # Percentage
128
- [/\A\d+\.\d+/, ?f], # Float
129
- [/\A\d+/, ?i], # Integer
130
- [/\A\w+/, ?t], # Token
131
- [/\A\(/, ?(], # Open (
132
- [/\A\)/, ?)], # Close )
133
- ], false)
199
+ @lexer = self.class.lexer_class.new([
200
+ [/\A[\s,]+/, ?w], # Whitespace (includes Commas)
201
+ [/\A;/, ?;], # Semi-Colon (statement separator)
202
+ [/\A(#{ops})/, ?o], # Operator
203
+ [/\A("([^"]|"")*")/m, ?s], # String
204
+ [/\A\w+:\w+/, ?r], # Range
205
+ [/\A\d+(\.\d+)?%/, ?p], # Percentage
206
+ [/\A\d+\.\d+/, ?f], # Float
207
+ [/\A\d+/, ?i], # Integer
208
+ [/\A\w+/, ?t], # Token
209
+ [/\A\(/, ?(], # Open (
210
+ [/\A\)/, ?)], # Close )
211
+ ], false)
134
212
  end
135
213
 
136
214
  # Evaluate a formula.
@@ -145,7 +223,7 @@ class LXL::Parser
145
223
  tokens = f.collect { |t| t.last }
146
224
  token = tokens.join.strip
147
225
  if token =~ /^=/
148
- tokens.each_index { |i| tokens[i] = translate_quotes(tokens[i]) if types[i] == :s }
226
+ tokens.each_index { |i| tokens[i] = translate_quotes(tokens[i]) if types[i] == ?s }
149
227
  token = tokens.join.strip.gsub(/^=+/, '')
150
228
  else
151
229
  token = translate_quotes(quote(tokens.join.strip))
@@ -158,7 +236,7 @@ class LXL::Parser
158
236
 
159
237
  protected
160
238
 
161
- # Quote a text value.
239
+ # Quote a string.
162
240
  #
163
241
  # quote('a "quoted" value.')
164
242
  # # => '"a ""quoted"" value."'
@@ -167,7 +245,7 @@ class LXL::Parser
167
245
  '"'+text.to_s.gsub('"','""')+'"'
168
246
  end
169
247
 
170
- # Translate "" to \" in quoted string values.
248
+ # Translate "" to \" in quoted strings.
171
249
  #
172
250
  # translate_quotes('"a ""quoted"" value."')
173
251
  # # => '"a \"quoted\" value."'
@@ -176,7 +254,7 @@ class LXL::Parser
176
254
  text.to_s.gsub(/([^\\])""/,'\1\"')
177
255
  end
178
256
 
179
- # Tokenize a formula into <tt>[TypesArray, TokensArray]</tt>.
257
+ # Tokenize a formula into <tt>[TypesString, TokensArray]</tt>.
180
258
  #
181
259
  def tokenize(formula)
182
260
  ops = Hash[*EXCEL_OPERATORS.zip(RUBY_OPERATORS).flatten]
@@ -189,12 +267,12 @@ class LXL::Parser
189
267
  tokens.each_index do |i|
190
268
  type, token = types[i], tokens[i]
191
269
  tokens[i] = case type
192
- when :o then ops[token]
193
- when :r then "LXL::Range.new(*#{token.split(':').inspect})"
194
- when :p then token.to_f/100
195
- when :f then token.to_f
196
- when :i then token.to_i
197
- when :t then
270
+ when ?o then ops[token]
271
+ when ?r then "self.class.range_class.new(*#{token.split(':').inspect})"
272
+ when ?p then token.to_f/100
273
+ when ?f then token.to_f
274
+ when ?i then token.to_i
275
+ when ?t then
198
276
  upper = token.to_s.upcase.to_sym
199
277
  lower = token.to_s.downcase.to_sym
200
278
  raise NoMethodError, "protected method `#{token}` called for #{self}" if @namespace.const_get(:METHODS).include?(token.to_s)
@@ -202,11 +280,11 @@ class LXL::Parser
202
280
  if tokens[i+1] != '('
203
281
  raise ArgumentError, "wrong number of arguments for #{token}"
204
282
  else
205
- types[i] = :F
283
+ types[i] = ?F
206
284
  "@namespace.method(:#{lower}).call"
207
285
  end
208
286
  elsif @namespace.const_defined?(upper)
209
- types[i] = :C
287
+ types[i] = ?C
210
288
  "@namespace.const_get(:#{upper})"
211
289
  else token
212
290
  end
@@ -237,13 +315,14 @@ end
237
315
  #
238
316
  class LXL::Range < Range
239
317
 
318
+ EXCEL_RANGE = /[A-Z]+[1-9]+/
319
+
240
320
  def each
241
- a,b = self.begin.to_s, self.end.to_s
242
- if a =~ /\D/ and b =~ /\D/
243
- r = [a,b].collect{ |x| x.upcase }.sort
244
- a_col,b_col = r.collect { |x| x.gsub(/\d/, '') }
245
- a_cel,b_cel = r.collect { |x| x.gsub(/\D/, '') }
246
- range = Range.new(a_col,b_col).each{ |col| Range.new(a_cel,b_cel).each { |cel| yield col+cel } }
321
+ range = [self.begin,self.end].collect! { |x| x.to_s.upcase }.sort
322
+ if range.first =~ EXCEL_RANGE and range.last =~ EXCEL_RANGE
323
+ fcol,lcol = range.collect { |x| x.gsub(/\d/, '') }
324
+ fcel,lcel = range.collect { |x| x.gsub(/\D/, '') }
325
+ Range.new(fcol,lcol).each{ |col| Range.new(fcel,lcel).each { |cel| yield col+cel } }
247
326
  else
248
327
  super
249
328
  end
@@ -279,6 +358,12 @@ class LXL::EmptyNamespace
279
358
  METHODS = ['__id__', '__send__', 'constants', 'const_defined?', 'const_get', 'const_set', 'class', 'instance_methods', 'method', 'methods', 'respond_to?', 'to_s']
280
359
  (instance_methods-METHODS).sort.each { |m| undef_method m }
281
360
 
361
+ # Default deferred class.
362
+ #
363
+ def self.deferred_class
364
+ LXL::Deferred
365
+ end
366
+
282
367
  # Ensure all constants are included (inherited, extended, included, etc).
283
368
  #
284
369
  def self.const_defined?(symbol) #:nodoc:
@@ -295,7 +380,7 @@ class LXL::EmptyNamespace
295
380
  #
296
381
  def self.register_deferred(*symbols)
297
382
  symbols.collect! { |s| s.to_s.downcase.to_sym }
298
- symbols.each { |s| define_method(s) { |*args| LXL::Deferred.new(s, *args) } }
383
+ symbols.each { |s| define_method(s) { |*args| self.class.deferred_class.new(s, *args) } }
299
384
  end
300
385
 
301
386
  # Register uppercase constants of the same name and value.
@@ -341,10 +426,14 @@ class LXL::Namespace < LXL::EmptyNamespace
341
426
  LXL.to_b(a) || LXL.to_b(b)
342
427
  end
343
428
 
429
+ def not(c)
430
+ ! c
431
+ end
432
+
344
433
  def if(c,a,b)
345
434
  LXL.to_b(c) ? a : b
346
435
  end
347
-
436
+
348
437
  def list(*items)
349
438
  items
350
439
  end
@@ -375,13 +464,9 @@ class LXL::Namespace < LXL::EmptyNamespace
375
464
 
376
465
  end
377
466
 
378
- # John Carter's LittleLexer.
467
+ # Based on John Carter's LittleLexer (http://littlelexer.rubyforge.org).
379
468
  #
380
- # http://www.rubyforge.org/projects/littlelexer
381
- #
382
- # CHANGE: types are returned as [*Symbol] vs String
383
- #
384
- class LXL::LittleLexer #:nodoc: all
469
+ class LXL::Lexer #:nodoc: all
385
470
 
386
471
  class LexerJammed < Exception; end
387
472
 
@@ -391,29 +476,29 @@ class LXL::LittleLexer #:nodoc: all
391
476
  end
392
477
 
393
478
  def scan(string,string_token_list=nil)
394
- result_list = []
395
- token_list = []
479
+ result_string = String.new
480
+ token_list = Array.new
396
481
  if string_token_list
397
482
  next_token(string) do |t,token, tail|
398
- result_list << t.chr.to_sym
483
+ result_string << t
399
484
  token_list << [string_token_list[0...tail], string[0...tail]]
400
485
  string = string[tail..-1]
401
486
  string_token_list = string_token_list[tail..-1]
402
487
  end
403
488
  else
404
489
  next_token(string) do |t,token, tail|
405
- result_list << t.chr.to_sym
490
+ result_string << t
406
491
  token_list << token
407
492
  end
408
493
  end
409
- return result_list, token_list
494
+ return result_string, token_list
410
495
  end
411
496
 
412
497
  private
413
498
 
414
499
  def next_token( string)
415
500
  match_data = nil
416
- while string != ''
501
+ while ! string.empty?
417
502
  failed = true
418
503
  @regex_to_char.each do |regex,char|
419
504
  match_data = regex.match(string)
@@ -426,7 +511,6 @@ class LXL::LittleLexer #:nodoc: all
426
511
  end
427
512
  raise LexerJammed, string if failed
428
513
  end
429
-
430
514
  rescue RegexpError => details
431
515
  raise RegexpError, "Choked while '#{@scanner}' was trying to match '#{string}' : #{details}"
432
516
  end
data/lxl.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+
3
+ version = File.read('VERSION').strip
4
+ raise "no version" if version.empty?
5
+
6
+ spec = Gem::Specification.new do |s|
7
+ s.name = 'lxl'
8
+ s.version = version
9
+ s.author = "Kevin Howe"
10
+ s.email = "kh@newclear.ca"
11
+ s.homepage = "lxl.rubyforge.org"
12
+ s.platform = Gem::Platform::RUBY
13
+ s.summary = "LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas. Easily extended with new constants and functions."
14
+ s.files = Dir['**/*'].delete_if { |f| f =~ /(cvs|gem|svn)$/i }
15
+ s.require_path = 'lib'
16
+ s.test_file = "test/lxl_test.rb"
17
+ s.has_rdoc = true
18
+ s.rubyforge_project = "lxl"
19
+ end
data/lxl.rdoc ADDED
@@ -0,0 +1,114 @@
1
+ LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas
2
+ and is easily extended with new constants and functions.
3
+
4
+ =Namespaces
5
+
6
+ Extending the language is as easy as defining new constants
7
+ and methods on an LXL::Namespace class.
8
+
9
+ class MyLXLNamespace < LXL::Namespace
10
+ NAME = 'John Doe'
11
+ def upper(text) text.to_s.upcase end
12
+ end
13
+
14
+ class MyLXL < LXL::Parser
15
+ def self.namespace_class() MyLXLNamespace end
16
+ end
17
+
18
+ MyLXL.eval('=UPPER(NAME)')
19
+ # => JOHN DOE
20
+
21
+ =Ranges
22
+
23
+ B3:D5 => LXL::Range.new("B3","D5")
24
+
25
+ LXL::Range is a subclass of Range which overrides
26
+ the #each method to return Excel-style ranges.
27
+
28
+ # Standard Ruby Range
29
+ Range.new("B3", "D5").collect
30
+ # => ["B3", "B4", "B5", "B6", "B7", "B8", "B9",
31
+ "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9",
32
+ "D0", "D1", "D2", "D3", "D4", "D5"]
33
+
34
+ # LXL Range
35
+ LXL::Range.new("B3", "D5").collect
36
+ # => ["B3", "B4", "B5", "C3", "C4", "C5", "D3", "D4", "D5"]
37
+
38
+ =Percentages
39
+
40
+ 25% => .25
41
+
42
+ =Deferred function calls
43
+
44
+ LXL::Deferred snapshots the symbol/arguments of a function call for later use.
45
+
46
+ class MyNamespace < LXL::Namespace
47
+ register_deferred :foo
48
+ end
49
+ LXL.new(MyNamespace.new).eval('=FOO(1, "two", 3)').inspect
50
+ # => <LXL::Deferred @args=[1, "two", 3] @symbol=:foo>
51
+
52
+ =Symbol registration
53
+
54
+ Register uppercase constants of the same name and value.
55
+
56
+ class MyNamespace < LXL::Namespace
57
+ register_symbols :foo, :bar
58
+ end
59
+ LXL.new(MyNamespace.new).eval('=LIST(FOO, BAR)').inspect
60
+ # => [:FOO, :BAR]
61
+
62
+ =Operators
63
+
64
+ a + b # Add
65
+ a - b # Subtract
66
+ a * b # Multiply
67
+ a / b # Divide
68
+ a = b # Equal to
69
+ a <> b # Not equal to
70
+ a < b # Less than
71
+ a > b # Greater than
72
+ a <= b # Less than or equal to
73
+ a >= b # Greater than or equal to
74
+ a & b # String concentation
75
+ n ^ n # Exponential
76
+
77
+ =Constants
78
+
79
+ TRUE # true
80
+ FALSE # false
81
+ NULL # nil
82
+
83
+ =Logical Functions
84
+
85
+ AND (a,b)
86
+ OR (a,b)
87
+ NOT (cond)
88
+ IF (cond,true,false)
89
+
90
+ =Date/Time Functions
91
+
92
+ TODAY () # current date value
93
+ NOW () # current date/time value
94
+ DATE (y,m,d) # date value
95
+ TIME (h,m,s) # time value
96
+ DATETIME (text) # convert a date/time string into a date/time value
97
+
98
+ =List Functions
99
+
100
+ LIST (a,b,..) # Variable length list
101
+ IN (find,list) # True if find is found in the given list/string
102
+
103
+ =Notes
104
+
105
+ * Values prefixed with = are formulas, anything else is assumed to be text.
106
+
107
+ LXL.eval("5+5") # => '5+5'
108
+ LXL.eval("=5+5") # => 10
109
+
110
+ * Constant and Function names are case-insensitive.
111
+
112
+ * The number zero is interpereted as FALSE.
113
+
114
+ * Separating formulas with a semi-colon returns an Array of results.
data/test/lxl_test.rb CHANGED
@@ -23,6 +23,7 @@ class LXLTest < Test::Unit::TestCase
23
23
  =";"=":"
24
24
  =A
25
25
  =B
26
+ =NOT(TRUE)
26
27
  =((1+2)*(10-6))/2
27
28
  =datetime("2004-11-22 11:11:00")=DATE(2004,11,22)+TIME(11,11,00)
28
29
  =IN(" is ", "this is a string")
@@ -39,7 +40,7 @@ class LXLTest < Test::Unit::TestCase
39
40
  ="embedded percentages 25% and semi-colons ; are working properly"
40
41
  }
41
42
  expected = ["This is some text", "\"This is some \"quoted\" text\"", "This is some \"quoted\" text"]
42
- expected += [true, false, :A, :B, 6, true, true, [1, "two", 3.0], true, false, true, "yes", "this and that"]
43
+ expected += [true, false, :A, :B, false, 6, true, true, [1, "two", 3.0], true, false, true, "yes", "this and that"]
43
44
  expected += [8, 0.502, true, true, "embedded percentages 25% and semi-colons ; are working properly"]
44
45
  MyNamespace.register_symbols(:A, :B)
45
46
  lxl = LXL::Parser.new(MyNamespace.new)
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.3
3
3
  specification_version: 1
4
4
  name: lxl
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.0
7
- date: 2005-02-12
6
+ version: 0.3.1
7
+ date: 2005-02-13
8
8
  summary: LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas. Easily extended with new constants and functions.
9
9
  require_paths:
10
10
  - lib
@@ -15,7 +15,7 @@ description:
15
15
  autorequire:
16
16
  default_executable:
17
17
  bindir: bin
18
- has_rdoc: false
18
+ has_rdoc: true
19
19
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
20
  requirements:
21
21
  -
@@ -31,11 +31,14 @@ files:
31
31
  - VERSION
32
32
  - test
33
33
  - CHANGES
34
+ - lxl.gemspec
34
35
  - setup.rb
35
36
  - README.en
37
+ - lxl.rdoc
36
38
  - lib/lxl.rb
37
39
  - test/lxl_test.rb
38
- test_files: []
40
+ test_files:
41
+ - test/lxl_test.rb
39
42
  rdoc_options: []
40
43
  extra_rdoc_files: []
41
44
  executables: []