lxl 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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: []