excel_to_code 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|