excel_to_code 0.0.2 → 0.0.4
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.
- data/{README → README.md} +1 -1
- data/TODO +34 -0
- data/bin/excel_to_c +1 -1
- data/bin/excel_to_ruby +60 -6
- data/src/commands/excel_to_c.rb +18 -8
- data/src/commands/excel_to_ruby.rb +103 -88
- data/src/commands/excel_to_x.rb +33 -8
- data/src/compile/c/compile_to_c.rb +0 -1
- data/src/compile/c/compile_to_c_unit_test.rb +1 -1
- data/src/compile/ruby/compile_to_ruby.rb +5 -3
- data/src/compile/ruby/compile_to_ruby_unit_test.rb +6 -5
- data/src/compile/ruby/map_formulae_to_ruby.rb +1 -2
- data/src/rewrite/rewrite_merge_formulae_and_values.rb +8 -0
- data/src/simplify/replace_common_elements_in_formulae.rb +22 -7
- metadata +33 -10
data/{README → README.md}
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Converts some excel spreadsheets (.xlsx, not .xls) into some other programming languages (currently ruby or c).
|
|
4
4
|
This allows the excel spreadsheets to be run programatically, without excel.
|
|
5
5
|
|
|
6
|
-
Its cannonical source is at http://github.com/tamc/
|
|
6
|
+
Its cannonical source is at http://github.com/tamc/excel_to_code
|
|
7
7
|
|
|
8
8
|
# Running excel_to_code
|
|
9
9
|
|
data/TODO
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
|
|
3
|
+
## Parsing bugs
|
|
4
|
+
|
|
5
|
+
See doc/How_to_fix_parsing_bugs.md for help
|
|
6
|
+
|
|
7
|
+
* Whitespace handling in table references
|
|
8
|
+
* Whitespace handling between arguments in a list
|
|
9
|
+
* Doesn't do range unions (e.g., 10:10 C:C == C:10)
|
|
10
|
+
* Doesn't do manually entered arrays {1,2,3;4,5,6}
|
|
11
|
+
|
|
12
|
+
## Missing functions
|
|
13
|
+
|
|
14
|
+
See doc/Which_functions_are_implemented.md
|
|
15
|
+
|
|
16
|
+
See doc/How_to_add_a_missing_function.md
|
|
17
|
+
|
|
18
|
+
## Simplification & optimisation bugs
|
|
19
|
+
|
|
20
|
+
* Optimize IF, CHOOSE, MATCH, VLOOKUP and similar functions so that they don't have to calculate all their arguments
|
|
21
|
+
* Fix it so that cells that are being reported as empty, that excel would give a numeric value of zero, are fixed
|
|
22
|
+
|
|
23
|
+
## Things that are badly written
|
|
24
|
+
|
|
25
|
+
* Rewrite the excel_to_ruby command to use command line options
|
|
26
|
+
* Refactor the common elements of src/commands/excel_to_c.rb and src/commands/excel_to_ruby.rb
|
|
27
|
+
* Refactor excel_to_c_runtime - split the functions? split the tests?
|
|
28
|
+
* Figure out a common test framework for different code output
|
|
29
|
+
* Tool for turning spreadsheets into tests
|
|
30
|
+
* Option to reorder test output to make it easier to localise errors
|
|
31
|
+
* Tool to create minimal failing example out of test run
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
data/bin/excel_to_c
CHANGED
|
@@ -13,7 +13,7 @@ Usage: excel_to_c [options] input_excel_file <output_directory>
|
|
|
13
13
|
input_excel_file the name of a .xlsx excel file (i.e., produced by Excel 2007+, not a .xls file)
|
|
14
14
|
output_directory the name of a folder in which to place the generated code. If not specified will have the same name as the input_excel_file, without the .xlsx
|
|
15
15
|
|
|
16
|
-
Support: http://github.com/tamc/
|
|
16
|
+
Support: http://github.com/tamc/excel_to_code
|
|
17
17
|
END
|
|
18
18
|
|
|
19
19
|
opts.separator ""
|
data/bin/excel_to_ruby
CHANGED
|
@@ -1,9 +1,63 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
2
|
+
require 'optparse'
|
|
3
|
+
require_relative '../src/excel_to_code'
|
|
4
|
+
|
|
3
5
|
command = ExcelToRuby.new
|
|
6
|
+
|
|
7
|
+
opts = OptionParser.new do |opts|
|
|
8
|
+
opts.banner = <<-END
|
|
9
|
+
|
|
10
|
+
A command to approximately translate excel files into c code.
|
|
11
|
+
|
|
12
|
+
Usage: excel_to_ruby [options] input_excel_file <output_directory>
|
|
13
|
+
input_excel_file the name of a .xlsx excel file (i.e., produced by Excel 2007+, not a .xls file)
|
|
14
|
+
output_directory the name of a folder in which to place the generated code. If not specified will have the same name as the input_excel_file, without the .xlsx
|
|
15
|
+
|
|
16
|
+
Support: http://github.com/tamc/excel_to_code
|
|
17
|
+
END
|
|
18
|
+
|
|
19
|
+
opts.separator ""
|
|
20
|
+
opts.separator "Specific options:"
|
|
21
|
+
|
|
22
|
+
opts.on('-s','--settable WORKSHEET','Make it possible to set the values of cells in this worksheet at runtime. By default no values are settable.') do |sheet|
|
|
23
|
+
command.cells_that_can_be_set_at_runtime = { sheet => :all }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
opts.on('-o','--output-name NAME','Filename to give to ruby version of code. Defaults to a folder with the same name as the excel file.') do |name|
|
|
27
|
+
command.output_name = name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
opts.on('-p','--prune-except WORKSHEET',"Remove all cells except those on this worksheet, or that are required to calculate values on that worksheet. By default keeps all cells.") do |sheet|
|
|
31
|
+
command.cells_to_keep = { sheet => :all }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
opts.on('-r','--run-tests',"Run the generated tests") do
|
|
35
|
+
command.actually_run_tests = true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
opts.on('-m','--run-in-memory',"Instead of writing intermediate files to disk, uses memory. Requires a lot of memory") do
|
|
39
|
+
command.run_in_memory = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
opts.on("-h", "--help", "Show this message") do
|
|
43
|
+
puts opts
|
|
44
|
+
exit
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
opts.parse!(ARGV)
|
|
50
|
+
rescue OptionParser::ParseError => e
|
|
51
|
+
STDERR.puts e.message, "\n", opts
|
|
52
|
+
exit(-1)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
unless ARGV.size > 0
|
|
56
|
+
puts opts
|
|
57
|
+
exit(-1)
|
|
58
|
+
end
|
|
59
|
+
|
|
4
60
|
command.excel_file = ARGV[0]
|
|
5
|
-
command.
|
|
6
|
-
|
|
7
|
-
command.
|
|
8
|
-
command.outputs_to_keep = { ARGV[3] => :all } if ARGV[3]
|
|
9
|
-
command.go!
|
|
61
|
+
command.output_directory = ARGV[1] if ARGV[1]
|
|
62
|
+
|
|
63
|
+
command.go!
|
data/src/commands/excel_to_c.rb
CHANGED
|
@@ -3,6 +3,10 @@ require_relative 'excel_to_x'
|
|
|
3
3
|
|
|
4
4
|
class ExcelToC < ExcelToX
|
|
5
5
|
|
|
6
|
+
def language
|
|
7
|
+
"c"
|
|
8
|
+
end
|
|
9
|
+
|
|
6
10
|
# These actually create the code version of the excel
|
|
7
11
|
def write_code
|
|
8
12
|
write_out_excel_as_code
|
|
@@ -18,13 +22,18 @@ class ExcelToC < ExcelToX
|
|
|
18
22
|
number_of_refs = 0
|
|
19
23
|
|
|
20
24
|
# Probably a better way of getting the runtime file to be compiled with the created file
|
|
21
|
-
puts `cp #
|
|
25
|
+
puts `cp #} #{File.join(output_directory,'excel_to_c_runtime.c')}`
|
|
22
26
|
|
|
23
27
|
# Output the workbook preamble
|
|
24
28
|
w = input("worksheet_c_names")
|
|
25
29
|
o = output("#{output_name.downcase}.c")
|
|
26
30
|
o.puts "// #{excel_file} approximately translated into C"
|
|
27
|
-
|
|
31
|
+
|
|
32
|
+
o.puts '// First we have c versions of all the excel functions that we know'
|
|
33
|
+
o.puts IO.readlines(File.join(File.dirname(__FILE__),'..','compile','c','excel_to_c_runtime.c')).join
|
|
34
|
+
o.puts '// End of the generic c functions'
|
|
35
|
+
o.puts
|
|
36
|
+
o.puts '// Start of the file specific functions'
|
|
28
37
|
o.puts
|
|
29
38
|
|
|
30
39
|
# Now we have to put all the initial definitions out
|
|
@@ -109,7 +118,6 @@ class ExcelToC < ExcelToX
|
|
|
109
118
|
c.worksheet = name
|
|
110
119
|
|
|
111
120
|
i = input(name,"formulae_inlined_pruned_replaced.ast")
|
|
112
|
-
ruby_name = c_name_for_worksheet_name(name)
|
|
113
121
|
o.puts "// start #{name}"
|
|
114
122
|
c.rewrite(i,w,o)
|
|
115
123
|
o.puts "// end #{name}"
|
|
@@ -145,12 +153,13 @@ class ExcelToC < ExcelToX
|
|
|
145
153
|
all_formulae = all_formulae('formulae_inlined_pruned_replaced.ast')
|
|
146
154
|
name = output_name.downcase
|
|
147
155
|
o = output("#{name}.rb")
|
|
156
|
+
|
|
148
157
|
code = <<END
|
|
149
158
|
require 'ffi'
|
|
150
159
|
|
|
151
|
-
module #{
|
|
160
|
+
module #{ruby_module_name}
|
|
152
161
|
extend FFI::Library
|
|
153
|
-
ffi_lib File.join(File.dirname(__FILE__),'
|
|
162
|
+
ffi_lib File.join(File.dirname(__FILE__),FFI.map_library_name('#{name}'))
|
|
154
163
|
ExcelType = enum :ExcelEmpty, :ExcelNumber, :ExcelString, :ExcelBoolean, :ExcelError, :ExcelRange
|
|
155
164
|
|
|
156
165
|
class ExcelValue < FFI::Struct
|
|
@@ -202,7 +211,7 @@ END
|
|
|
202
211
|
|
|
203
212
|
def write_tests
|
|
204
213
|
name = output_name.downcase
|
|
205
|
-
o = output("#{name}
|
|
214
|
+
o = output("test_#{name}.rb")
|
|
206
215
|
o.puts "# coding: utf-8"
|
|
207
216
|
o.puts "# Test for #{name}"
|
|
208
217
|
o.puts "require 'rubygems'"
|
|
@@ -212,7 +221,7 @@ END
|
|
|
212
221
|
o.puts
|
|
213
222
|
o.puts "class Test#{name.capitalize} < Test::Unit::TestCase"
|
|
214
223
|
o.puts " def spreadsheet; @spreadsheet ||= init_spreadsheet; end"
|
|
215
|
-
o.puts " def init_spreadsheet; #{
|
|
224
|
+
o.puts " def init_spreadsheet; #{ruby_module_name} end"
|
|
216
225
|
|
|
217
226
|
all_formulae = all_formulae('formulae_inlined_pruned_replaced.ast')
|
|
218
227
|
|
|
@@ -227,6 +236,7 @@ END
|
|
|
227
236
|
refs_to_test = cells_to_keep[name]
|
|
228
237
|
end
|
|
229
238
|
if refs_to_test && !refs_to_test.empty?
|
|
239
|
+
refs_to_test = refs_to_test.map(&:upcase)
|
|
230
240
|
CompileToCUnitTest.rewrite(i, c_name, refs_to_test, o)
|
|
231
241
|
end
|
|
232
242
|
close(i)
|
|
@@ -244,7 +254,7 @@ END
|
|
|
244
254
|
def run_tests
|
|
245
255
|
return unless actually_run_tests
|
|
246
256
|
puts "Running the resulting tests"
|
|
247
|
-
puts `cd #{File.join(output_directory)}; ruby "#{output_name.downcase}
|
|
257
|
+
puts `cd #{File.join(output_directory)}; ruby "test_#{output_name.downcase}.rb"`
|
|
248
258
|
end
|
|
249
259
|
|
|
250
260
|
end
|
|
@@ -3,118 +3,133 @@
|
|
|
3
3
|
require_relative 'excel_to_x'
|
|
4
4
|
|
|
5
5
|
class ExcelToRuby < ExcelToX
|
|
6
|
+
|
|
7
|
+
def language
|
|
8
|
+
"ruby"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Skip this
|
|
12
|
+
def replace_values_with_constants
|
|
6
13
|
|
|
14
|
+
worksheets("Skipping replacing values with constants") do |name,xml_filename|
|
|
15
|
+
i = File.join(intermediate_directory, name, "formulae_inlined_pruned_replaced-1.ast")
|
|
16
|
+
o = File.join(intermediate_directory, name, "formulae_inlined_pruned_replaced.ast")
|
|
17
|
+
if run_in_memory
|
|
18
|
+
@files[o] = @files[i]
|
|
19
|
+
else
|
|
20
|
+
`cp '#{i}' '#{o}'`
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
i = File.join(intermediate_directory,"common-elements-1.ast")
|
|
25
|
+
o = File.join(intermediate_directory,"common-elements.ast")
|
|
26
|
+
if run_in_memory
|
|
27
|
+
@files[o] = @files[i]
|
|
28
|
+
else
|
|
29
|
+
`cp '#{i}' '#{o}'`
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
7
33
|
# These actually create the code version of the excel
|
|
8
34
|
def write_code
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
worksheets("Compiling worksheet") do |name,xml_filename|
|
|
12
|
-
#fork do
|
|
13
|
-
compile_worksheet_code(name,xml_filename)
|
|
14
|
-
compile_worksheet_test(name,xml_filename)
|
|
15
|
-
#end
|
|
16
|
-
end
|
|
35
|
+
write_out_excel_as_code
|
|
36
|
+
write_out_test_as_code
|
|
17
37
|
end
|
|
18
38
|
|
|
19
|
-
def
|
|
20
|
-
w = input("
|
|
21
|
-
o =
|
|
39
|
+
def write_out_excel_as_code
|
|
40
|
+
w = input("worksheet_c_names")
|
|
41
|
+
o = output("#{output_name.downcase}.rb")
|
|
42
|
+
o.puts "# coding: utf-8"
|
|
22
43
|
o.puts "# Compiled version of #{excel_file}"
|
|
23
44
|
o.puts "require '#{File.expand_path(File.join(File.dirname(__FILE__),'../excel/excel_functions'))}'"
|
|
24
45
|
o.puts ""
|
|
25
|
-
o.puts "
|
|
26
|
-
o.puts "class Spreadsheet"
|
|
46
|
+
o.puts "class #{ruby_module_name}"
|
|
27
47
|
o.puts " include ExcelFunctions"
|
|
28
|
-
w.lines do |line|
|
|
29
|
-
name, ruby_name = line.strip.split("\t")
|
|
30
|
-
o.puts " def #{ruby_name}; @#{ruby_name} ||= #{ruby_name.capitalize}.new; end"
|
|
31
|
-
end
|
|
32
48
|
|
|
49
|
+
o.puts
|
|
50
|
+
o.puts " # Starting common elements"
|
|
33
51
|
c = CompileToRuby.new
|
|
34
52
|
i = input("common-elements.ast")
|
|
35
|
-
w.rewind
|
|
53
|
+
w.rewind
|
|
36
54
|
c.rewrite(i,w,o)
|
|
37
|
-
|
|
38
|
-
o.puts "end"
|
|
39
|
-
o.puts 'Dir[File.join(File.dirname(__FILE__),"worksheets/","*.rb")].each {|f| autoload(File.basename(f,".rb").capitalize,f)}'
|
|
40
|
-
o.puts "end"
|
|
41
|
-
close(i,w,o)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def write_out_excel_workbook_test_as_code
|
|
45
|
-
w = input("worksheet_ruby_names")
|
|
46
|
-
o = ruby("test_#{compiled_module_name.downcase}.rb")
|
|
47
|
-
o.puts "# All tests for #{excel_file}"
|
|
48
|
-
o.puts "require 'test/unit'"
|
|
49
|
-
w.lines do |line|
|
|
50
|
-
name, ruby_name = line.strip.split("\t")
|
|
51
|
-
o.puts "require_relative 'tests/test_#{ruby_name.downcase}'"
|
|
52
|
-
end
|
|
53
|
-
close(w,o)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def compile_worksheet_code(name,xml_filename)
|
|
57
|
-
settable_refs = @values_that_can_be_set_at_runtime[name]
|
|
58
|
-
c = CompileToRuby.new
|
|
59
|
-
c.settable =lambda { |ref| (settable_refs == :all) ? true : settable_refs.include?(ref) } if settable_refs
|
|
60
|
-
c.worksheet = name
|
|
61
|
-
i = input(name,"formulae_inlined_pruned_replaced.ast")
|
|
62
|
-
w = input("worksheet_ruby_names")
|
|
63
|
-
ruby_name = ruby_name_for_worksheet_name(name)
|
|
64
|
-
o = ruby('worksheets',"#{ruby_name.downcase}.rb")
|
|
65
|
-
d = output(name,'defaults')
|
|
66
|
-
o.puts "# coding: utf-8"
|
|
67
|
-
o.puts "# #{name}"
|
|
55
|
+
o.puts " # Ending common elements"
|
|
68
56
|
o.puts
|
|
69
|
-
|
|
57
|
+
close(i)
|
|
58
|
+
|
|
59
|
+
d = intermediate('defaults')
|
|
60
|
+
|
|
61
|
+
worksheets("Turning worksheet into code") do |name,xml_filename|
|
|
62
|
+
c.settable = settable(name)
|
|
63
|
+
c.worksheet = name
|
|
64
|
+
i = input(name,"formulae_inlined_pruned_replaced.ast")
|
|
65
|
+
w.rewind
|
|
66
|
+
o.puts " # Start of #{name}"
|
|
67
|
+
c.rewrite(i,w,o,d)
|
|
68
|
+
o.puts " # End of #{name}"
|
|
69
|
+
o.puts ""
|
|
70
|
+
close(i)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
close(d)
|
|
74
|
+
|
|
70
75
|
o.puts
|
|
71
|
-
o.puts "
|
|
72
|
-
o.puts "
|
|
73
|
-
|
|
76
|
+
o.puts " # starting initializer"
|
|
77
|
+
o.puts " def initialize"
|
|
78
|
+
d = input('defaults')
|
|
79
|
+
d.lines do |line|
|
|
80
|
+
o.puts line
|
|
81
|
+
end
|
|
82
|
+
o.puts " end"
|
|
74
83
|
o.puts ""
|
|
75
84
|
close(d)
|
|
76
|
-
|
|
77
|
-
o.puts " def initialize"
|
|
78
|
-
d = input(name,'defaults')
|
|
79
|
-
d.lines do |line|
|
|
80
|
-
o.puts line
|
|
81
|
-
end
|
|
82
|
-
o.puts " end"
|
|
83
|
-
o.puts ""
|
|
84
|
-
close(d)
|
|
85
|
-
end
|
|
86
|
-
o.puts "end"
|
|
85
|
+
|
|
87
86
|
o.puts "end"
|
|
88
|
-
close(
|
|
87
|
+
close(w,o)
|
|
89
88
|
end
|
|
90
89
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
o = ruby('tests',"test_#{ruby_name.downcase}.rb")
|
|
90
|
+
def write_out_test_as_code
|
|
91
|
+
o = output("test_#{output_name.downcase}.rb")
|
|
92
|
+
|
|
95
93
|
o.puts "# coding: utf-8"
|
|
96
|
-
o.puts "#
|
|
97
|
-
o.puts
|
|
98
|
-
o.puts
|
|
94
|
+
o.puts "# All tests for #{excel_file}"
|
|
95
|
+
o.puts "require 'test/unit'"
|
|
96
|
+
o.puts "require_relative '#{output_name.downcase}'"
|
|
99
97
|
o.puts
|
|
100
|
-
o.puts "
|
|
101
|
-
o.puts "
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
98
|
+
o.puts "class Test#{output_name.capitalize} < Test::Unit::TestCase"
|
|
99
|
+
o.puts " def worksheet; @worksheet ||= #{ruby_module_name}.new; end"
|
|
100
|
+
|
|
101
|
+
c = CompileToRubyUnitTest.new
|
|
102
|
+
all_formulae = all_formulae('formulae_inlined_pruned_replaced.ast')
|
|
103
|
+
|
|
104
|
+
worksheets("Compiling worksheet") do |name,xml_filename|
|
|
105
|
+
i = input(name,"values_pruned2.ast")
|
|
106
|
+
o.puts " # Start of #{name}"
|
|
107
|
+
c_name = c_name_for_worksheet_name(name)
|
|
108
|
+
if !cells_to_keep || cells_to_keep.empty? || cells_to_keep[name] == :all
|
|
109
|
+
refs_to_test = all_formulae[name].keys
|
|
110
|
+
else
|
|
111
|
+
refs_to_test = cells_to_keep[name]
|
|
112
|
+
end
|
|
113
|
+
if refs_to_test && !refs_to_test.empty?
|
|
114
|
+
refs_to_test = refs_to_test.map(&:upcase)
|
|
115
|
+
c.rewrite(i, c_name, refs_to_test, o)
|
|
116
|
+
end
|
|
117
|
+
o.puts " # End of #{name}"
|
|
118
|
+
o.puts ""
|
|
119
|
+
close(i)
|
|
120
|
+
end
|
|
121
|
+
o.puts "end"
|
|
122
|
+
close(o)
|
|
108
123
|
end
|
|
109
124
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
def compile_code
|
|
126
|
+
# Not needed
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def run_tests
|
|
130
|
+
return unless actually_run_tests
|
|
131
|
+
puts "Running the resulting tests"
|
|
132
|
+
puts `cd #{File.join(output_directory)}; ruby "test_#{output_name.downcase}.rb"`
|
|
117
133
|
end
|
|
118
|
-
|
|
119
134
|
|
|
120
135
|
end
|
data/src/commands/excel_to_x.rb
CHANGED
|
@@ -14,7 +14,7 @@ class ExcelToX
|
|
|
14
14
|
# If not specified, will be '#{excel_file_name}/c'
|
|
15
15
|
attr_accessor :output_directory
|
|
16
16
|
|
|
17
|
-
# Optional attribute. The name of the resulting c file
|
|
17
|
+
# Optional attribute. The name of the resulting ruby or c file and ruby or ruby ffi module name. Defaults to excelspreadsheet
|
|
18
18
|
attr_accessor :output_name
|
|
19
19
|
|
|
20
20
|
# Optional attribute. The excel file will be translated to xml and stored here.
|
|
@@ -61,7 +61,7 @@ class ExcelToX
|
|
|
61
61
|
def set_defaults
|
|
62
62
|
raise ExcelToCodeException.new("No excel file has been specified") unless excel_file
|
|
63
63
|
|
|
64
|
-
self.output_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),
|
|
64
|
+
self.output_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),language)
|
|
65
65
|
self.xml_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'xml')
|
|
66
66
|
self.intermediate_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'intermediate')
|
|
67
67
|
|
|
@@ -72,14 +72,14 @@ class ExcelToX
|
|
|
72
72
|
# Make sure that all the cell names are downcase and don't have any $ in them
|
|
73
73
|
cells_that_can_be_set_at_runtime.keys.each do |sheet|
|
|
74
74
|
next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
|
|
75
|
-
cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').
|
|
75
|
+
cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').upcase }
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# Make sure that all the cell names are downcase and don't have any $ in them
|
|
79
79
|
if cells_to_keep
|
|
80
80
|
cells_to_keep.keys.each do |sheet|
|
|
81
81
|
next unless cells_to_keep[sheet].is_a?(Array)
|
|
82
|
-
cells_to_keep[sheet] = cells_to_keep[sheet].map { |reference| reference.gsub('$','').
|
|
82
|
+
cells_to_keep[sheet] = cells_to_keep[sheet].map { |reference| reference.gsub('$','').upcase }
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
|
|
@@ -113,6 +113,7 @@ class ExcelToX
|
|
|
113
113
|
remove_any_cells_not_needed_for_outputs
|
|
114
114
|
inline_formulae_that_are_only_used_once
|
|
115
115
|
separate_formulae_elements
|
|
116
|
+
replace_values_with_constants
|
|
116
117
|
|
|
117
118
|
# This actually creates the code (implemented in subclasses)
|
|
118
119
|
write_code
|
|
@@ -272,7 +273,21 @@ class ExcelToX
|
|
|
272
273
|
array_formulae = File.join(name,"array_formulae-expanded.ast")
|
|
273
274
|
simple_formulae = File.join(name,"simple_formulae.ast-nocols")
|
|
274
275
|
output = File.join(name,'formulae.ast')
|
|
275
|
-
|
|
276
|
+
|
|
277
|
+
# This ensures that all gettable and settable values appear in the output
|
|
278
|
+
# even if they are blank in the underlying excel
|
|
279
|
+
required_refs = []
|
|
280
|
+
if @cells_that_can_be_set_at_runtime && @cells_that_can_be_set_at_runtime[name] && @cells_that_can_be_set_at_runtime[name] != :all
|
|
281
|
+
required_refs.concat(@cells_that_can_be_set_at_runtime[name])
|
|
282
|
+
end
|
|
283
|
+
if @cells_to_keep && @cells_to_keep[name] && @cells_to_keep[name] != :all
|
|
284
|
+
required_refs.concat(@cells_to_keep[name])
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
r = RewriteMergeFormulaeAndValues.new
|
|
288
|
+
r.references_to_add_if_they_are_not_already_present = required_refs
|
|
289
|
+
|
|
290
|
+
rewrite r, values, shared_formulae, array_formulae, simple_formulae, output
|
|
276
291
|
end
|
|
277
292
|
|
|
278
293
|
def merge_table_files
|
|
@@ -516,7 +531,9 @@ class ExcelToX
|
|
|
516
531
|
worksheets("Replacing repeated elements") do |name,xml_filename|
|
|
517
532
|
replace ReplaceCommonElementsInFormulae, File.join(name,"formulae_inlined_pruned_with_sheets.ast"), "common-elements-1.ast", File.join(name,"formulae_inlined_pruned_replaced-1.ast")
|
|
518
533
|
end
|
|
519
|
-
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def replace_values_with_constants
|
|
520
537
|
r = ReplaceValuesWithConstants.new
|
|
521
538
|
worksheets("Replacing values with constants") do |name,xml_filename|
|
|
522
539
|
i = input(name,"formulae_inlined_pruned_replaced-1.ast")
|
|
@@ -544,7 +561,7 @@ class ExcelToX
|
|
|
544
561
|
def settable(name)
|
|
545
562
|
settable_refs = @cells_that_can_be_set_at_runtime[name]
|
|
546
563
|
if settable_refs
|
|
547
|
-
lambda { |ref| (settable_refs == :all) ? true : settable_refs.include?(ref) }
|
|
564
|
+
lambda { |ref| (settable_refs == :all) ? true : settable_refs.include?(ref.upcase) }
|
|
548
565
|
else
|
|
549
566
|
lambda { |ref| false }
|
|
550
567
|
end
|
|
@@ -554,7 +571,7 @@ class ExcelToX
|
|
|
554
571
|
if @cells_to_keep
|
|
555
572
|
gettable_refs = @cells_to_keep[name]
|
|
556
573
|
if gettable_refs
|
|
557
|
-
lambda { |ref| (gettable_refs == :all) ? true : gettable_refs.include?(ref) }
|
|
574
|
+
lambda { |ref| (gettable_refs == :all) ? true : gettable_refs.include?(ref.upcase) }
|
|
558
575
|
else
|
|
559
576
|
lambda { |ref| false }
|
|
560
577
|
end
|
|
@@ -657,4 +674,12 @@ class ExcelToX
|
|
|
657
674
|
end
|
|
658
675
|
end
|
|
659
676
|
|
|
677
|
+
def ruby_module_name
|
|
678
|
+
puts output_name
|
|
679
|
+
@ruby_module_name = output_name.sub(/^[a-z\d]*/) { $&.capitalize }
|
|
680
|
+
@ruby_module_name = @ruby_module_name.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
|
|
681
|
+
puts @ruby_module_name
|
|
682
|
+
@ruby_module_name
|
|
683
|
+
end
|
|
684
|
+
|
|
660
685
|
end
|
|
@@ -8,7 +8,7 @@ class CompileToCUnitTest
|
|
|
8
8
|
input.lines do |line|
|
|
9
9
|
begin
|
|
10
10
|
ref, formula = line.split("\t")
|
|
11
|
-
next unless refs_to_test.include?(ref)
|
|
11
|
+
next unless refs_to_test.include?(ref.upcase)
|
|
12
12
|
output.puts "def test_#{c_name}_#{ref.downcase}"
|
|
13
13
|
output.puts " r = spreadsheet.#{c_name}_#{ref.downcase}"
|
|
14
14
|
ast = eval(formula)
|
|
@@ -14,14 +14,16 @@ class CompileToRuby
|
|
|
14
14
|
mapper = MapFormulaeToRuby.new
|
|
15
15
|
mapper.worksheet = worksheet
|
|
16
16
|
mapper.sheet_names = Hash[sheet_names_file.readlines.map { |line| line.strip.split("\t")}]
|
|
17
|
+
c_name = mapper.sheet_names[worksheet]
|
|
17
18
|
input.lines do |line|
|
|
18
19
|
begin
|
|
19
20
|
ref, formula = line.split("\t")
|
|
21
|
+
name = c_name ? "#{c_name}_#{ref.downcase}" : ref.downcase
|
|
20
22
|
if settable.call(ref)
|
|
21
|
-
output.puts " attr_accessor :#{
|
|
22
|
-
defaults.puts " @#{
|
|
23
|
+
output.puts " attr_accessor :#{name} # Default: #{mapper.map(eval(formula))}"
|
|
24
|
+
defaults.puts " @#{name} = #{mapper.map(eval(formula))}" if defaults
|
|
23
25
|
else
|
|
24
|
-
output.puts " def #{
|
|
26
|
+
output.puts " def #{name}; @#{name} ||= #{mapper.map(eval(formula))}; end"
|
|
25
27
|
end
|
|
26
28
|
rescue Exception => e
|
|
27
29
|
puts "Exception at line #{line}"
|
|
@@ -6,21 +6,22 @@ class CompileToRubyUnitTest
|
|
|
6
6
|
self.new.rewrite(*args)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def rewrite(input,output)
|
|
9
|
+
def rewrite(input, c_name, refs_to_test, output)
|
|
10
10
|
mapper = MapValuesToRuby.new
|
|
11
11
|
input.lines do |line|
|
|
12
12
|
ref, formula = line.split("\t")
|
|
13
|
+
next unless refs_to_test.include?(ref.upcase)
|
|
13
14
|
ast = eval(formula)
|
|
14
15
|
value = mapper.map(ast)
|
|
15
|
-
full_reference = "worksheet.#{ref.downcase}"
|
|
16
|
+
full_reference = "worksheet.#{c_name}_#{ref.downcase}"
|
|
16
17
|
if ast.first == :number
|
|
17
18
|
if value == "0" # Need to do a slightly different test, because needs to pass if nil returned, as well as zero
|
|
18
|
-
output.puts " def test_#{ref.downcase}; assert_in_epsilon(#{value},#{full_reference} || 0); end"
|
|
19
|
+
output.puts " def test_#{c_name}_#{ref.downcase}; assert_in_epsilon(#{value},#{full_reference} || 0); end"
|
|
19
20
|
else
|
|
20
|
-
output.puts " def test_#{ref.downcase}; assert_in_epsilon(#{value},#{full_reference}); end"
|
|
21
|
+
output.puts " def test_#{c_name}_#{ref.downcase}; assert_in_epsilon(#{value},#{full_reference}); end"
|
|
21
22
|
end
|
|
22
23
|
else
|
|
23
|
-
output.puts " def test_#{ref.downcase}; assert_equal(#{value},#{full_reference}); end"
|
|
24
|
+
output.puts " def test_#{c_name}_#{ref.downcase}; assert_equal(#{value},#{full_reference}); end"
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
end
|
|
@@ -80,8 +80,7 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def sheet_reference(sheet,reference)
|
|
83
|
-
|
|
84
|
-
"#{sheet_names[sheet]}.#{map(reference)}"
|
|
83
|
+
"#{sheet_names[sheet]}_#{map(reference)}"
|
|
85
84
|
end
|
|
86
85
|
|
|
87
86
|
def array(*rows)
|
|
@@ -3,15 +3,23 @@ class RewriteMergeFormulaeAndValues
|
|
|
3
3
|
new.rewrite(*args)
|
|
4
4
|
end
|
|
5
5
|
|
|
6
|
+
attr_accessor :references_to_add_if_they_are_not_already_present
|
|
7
|
+
|
|
6
8
|
def rewrite(values,shared_formulae,array_formula,simple_formulae,output)
|
|
9
|
+
@references_to_add_if_they_are_not_already_present ||= []
|
|
10
|
+
|
|
7
11
|
shared_formulae = Hash[shared_formulae.readlines.map { |line| [line[/(.*?)\t/,1],line]}]
|
|
8
12
|
array_formula = Hash[array_formula.readlines.map { |line| [line[/(.*?)\t/,1],line]}]
|
|
9
13
|
simple_formulae = Hash[simple_formulae.readlines.map { |line| [line[/(.*?)\t/,1],line]}]
|
|
10
14
|
|
|
11
15
|
values.lines do |line|
|
|
12
16
|
ref = line[/(.*?)\t/,1]
|
|
17
|
+
@references_to_add_if_they_are_not_already_present.delete(ref)
|
|
13
18
|
output.puts simple_formulae[ref] || array_formula[ref] || shared_formulae[ref] || line
|
|
14
19
|
end
|
|
20
|
+
@references_to_add_if_they_are_not_already_present.each do |r|
|
|
21
|
+
output.puts "#{r}\t[:blank]"
|
|
22
|
+
end
|
|
15
23
|
end
|
|
16
24
|
|
|
17
25
|
end
|
|
@@ -4,16 +4,31 @@ class ReplaceCommonElementsInFormulae
|
|
|
4
4
|
self.new.replace(*args)
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
+
attr_accessor :common_elements
|
|
8
|
+
|
|
7
9
|
def replace(input,common,output)
|
|
8
|
-
|
|
10
|
+
@common_elements ||= {}
|
|
11
|
+
common.readlines.map do |a|
|
|
9
12
|
ref, element = a.split("\t")
|
|
10
|
-
[element.strip
|
|
11
|
-
end
|
|
13
|
+
@common_elements[element.strip] = [:cell, ref]
|
|
14
|
+
end
|
|
12
15
|
input.lines do |line|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
output.puts line
|
|
16
|
+
ref, formula = line.split("\t")
|
|
17
|
+
output.puts "#{ref}\t#{replace_repeated_formulae(eval(formula)).inspect}"
|
|
17
18
|
end
|
|
18
19
|
end
|
|
20
|
+
|
|
21
|
+
def replace_repeated_formulae(ast)
|
|
22
|
+
return ast unless ast.is_a?(Array)
|
|
23
|
+
return ast if [:number,:string,:blank,:null,:error,:boolean_true,:boolean_false,:sheet_reference,:cell, :row].include?(ast.first)
|
|
24
|
+
string = ast.inspect
|
|
25
|
+
return ast if string.length < 20
|
|
26
|
+
if @common_elements.has_key?(string)
|
|
27
|
+
return @common_elements[string]
|
|
28
|
+
end
|
|
29
|
+
ast.map do |a|
|
|
30
|
+
replace_repeated_formulae(a)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
19
34
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: excel_to_code
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,11 +9,22 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-04-
|
|
12
|
+
date: 2012-04-24 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: rubypeg
|
|
16
|
+
requirement: &70157840000000 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *70157840000000
|
|
14
25
|
- !ruby/object:Gem::Dependency
|
|
15
26
|
name: nokogiri
|
|
16
|
-
requirement: &
|
|
27
|
+
requirement: &70157839999500 !ruby/object:Gem::Requirement
|
|
17
28
|
none: false
|
|
18
29
|
requirements:
|
|
19
30
|
- - ! '>='
|
|
@@ -21,10 +32,10 @@ dependencies:
|
|
|
21
32
|
version: 1.5.0
|
|
22
33
|
type: :runtime
|
|
23
34
|
prerelease: false
|
|
24
|
-
version_requirements: *
|
|
35
|
+
version_requirements: *70157839999500
|
|
25
36
|
- !ruby/object:Gem::Dependency
|
|
26
37
|
name: rspec
|
|
27
|
-
requirement: &
|
|
38
|
+
requirement: &70157839999000 !ruby/object:Gem::Requirement
|
|
28
39
|
none: false
|
|
29
40
|
requirements:
|
|
30
41
|
- - ! '>='
|
|
@@ -32,13 +43,24 @@ dependencies:
|
|
|
32
43
|
version: 2.7.0
|
|
33
44
|
type: :runtime
|
|
34
45
|
prerelease: false
|
|
35
|
-
version_requirements: *
|
|
46
|
+
version_requirements: *70157839999000
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: ffi
|
|
49
|
+
requirement: &70157839998540 !ruby/object:Gem::Requirement
|
|
50
|
+
none: false
|
|
51
|
+
requirements:
|
|
52
|
+
- - ! '>='
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 1.0.11
|
|
55
|
+
type: :runtime
|
|
56
|
+
prerelease: false
|
|
57
|
+
version_requirements: *70157839998540
|
|
36
58
|
description: ! "# excel_to_code\n\nConverts some excel spreadsheets (.xlsx, not .xls)
|
|
37
59
|
into some other programming languages (currently ruby or c).\nThis allows the excel
|
|
38
60
|
spreadsheets to be run programatically, without excel.\n\nIts cannonical source
|
|
39
|
-
is at http://github.com/tamc/
|
|
40
|
-
a go:\n\n\t./bin/excel_to_c <excel_file_name>\n\t\nNB:For small spreadsheets
|
|
41
|
-
will take a minute or so. For large spreadsheets it is best to run it overnight.\n\t\nfor
|
|
61
|
+
is at http://github.com/tamc/excel_to_code\n\n# Running excel_to_code\n\nTo just
|
|
62
|
+
have a go:\n\n\t./bin/excel_to_c <excel_file_name>\n\t\nNB:For small spreadsheets
|
|
63
|
+
this will take a minute or so. For large spreadsheets it is best to run it overnight.\n\t\nfor
|
|
42
64
|
more detail:\n\t\n\t./bin/excel_to_c --compile --run-tests --settable <name of input
|
|
43
65
|
worksheet> --prune-except <name of output worksheet> <excel file name> \n\t\nthis
|
|
44
66
|
should work:\n\n\t./bin/excel_to_c --help\n\n# Testing excel_to_code\n\n1. Make
|
|
@@ -56,7 +78,8 @@ executables:
|
|
|
56
78
|
extensions: []
|
|
57
79
|
extra_rdoc_files: []
|
|
58
80
|
files:
|
|
59
|
-
- README
|
|
81
|
+
- README.md
|
|
82
|
+
- TODO
|
|
60
83
|
- src/commands/excel_to_c.rb
|
|
61
84
|
- src/commands/excel_to_ruby.rb
|
|
62
85
|
- src/commands/excel_to_x.rb
|