ruby-latex 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ruby-latex.rb +411 -0
  3. metadata +64 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c77fd912c5a1fd4c97cd6c3df883bb8e0241df501f1a18cc7dfb1d4029ee161d
4
+ data.tar.gz: e4b334a03b8ea96a2af93d09196732c09e5f4ded4971a5070ad2a4b27224dea3
5
+ SHA512:
6
+ metadata.gz: 1ca80bf37a1866eb4ee26555d209f2cbeb79600bc9a146b41f7edfbc9bacc0deb1eab09b15c310c23a57febea4a59bea680673f6d48049f32e95f5955bd32f6a
7
+ data.tar.gz: 86cb4a25cfb183d97ee1f0b3c1318c05d66cbfb588242da37bddb2c7956fdcda0ab40777cb92ab1f836f3605d54a0b0978d80057028bfbbd4cec8a3db41fc72a
data/lib/ruby-latex.rb ADDED
@@ -0,0 +1,411 @@
1
+ require 'code-assertions'
2
+
3
+ module Latex
4
+ class Table
5
+ attr_accessor :caption
6
+ attr_accessor :label
7
+
8
+ def initialize(caption = nil, label = nil)
9
+ @caption = caption
10
+ @label = label
11
+ @tabulars = []
12
+ end
13
+
14
+ def auto_resize
15
+ @resize = true
16
+
17
+ return self
18
+ end
19
+
20
+ def add_tabular(tabular)
21
+ raise TypeError, "No implicit conversion from #{tabular.class} to Latex::Tabular" unless tabular.is_a?(Tabular)
22
+
23
+ @tabulars << tabular
24
+
25
+ return self
26
+ end
27
+
28
+ def tabular=(tabular)
29
+ @tabulars.clear
30
+ add_tabular(tabular)
31
+
32
+ return nil
33
+ end
34
+
35
+ def to_tex
36
+ out = []
37
+ out << "\\begin{table}"
38
+ out << self.caption_string
39
+ @tabulars.each do |tabular|
40
+ out << tabular.to_tex
41
+ end
42
+ out << "\\end{table}"
43
+
44
+ return out.join("\n")
45
+ end
46
+
47
+ private
48
+ def caption_string
49
+ out = []
50
+ out << " \\caption{#@caption}" if @caption
51
+ out << " \\label{#@label}" if @caption && @label
52
+
53
+ return out.join("\n")
54
+ end
55
+ end
56
+
57
+ class Tabular
58
+ def initialize(cols)
59
+ @header = nil
60
+
61
+ if cols.is_a? Array
62
+ @columns = cols.map { |e| e.to_sym }
63
+ @columns_info = cols.map { |e| [e, Column.new] }.to_h
64
+
65
+ elsif cols.is_a? Hash
66
+ keys = cols.keys
67
+ @columns_info = cols.clone
68
+ @columns = keys.map { |k| k.to_sym }
69
+
70
+ if @columns_info.values.any? { |c| c.header }
71
+ headers_row = @columns.map { |c| [c, @columns_info[c].header.to_s] }.to_h
72
+ @header = HeaderRowFamily.new(@columns_info, Row.new(headers_row))
73
+ end
74
+
75
+ else
76
+ raise "Invalid argument: expected Array or Hash"
77
+ end
78
+
79
+ @rows = RowFamily.new(@columns_info)
80
+ end
81
+
82
+ def header_style(&block)
83
+ raise "Block expected" unless block_given?
84
+ raise "No header to style" unless @header
85
+ @header.styler = block
86
+
87
+ return self
88
+ end
89
+
90
+ def content_style(&block)
91
+ raise "Block expected" unless block_given?
92
+ @rows.styler = block
93
+
94
+ return self
95
+ end
96
+
97
+ def header=(row)
98
+ @header = HeaderRowFamily.new(@columns_info, Row.new(row))
99
+ end
100
+
101
+ def add_row(row)
102
+ @rows << Row.new(row)
103
+ end
104
+ alias :<< :add_row
105
+
106
+ def add_separator(type="\\midrule")
107
+ @rows << RowSeparator.new(type)
108
+ end
109
+
110
+ def to_tex
111
+ sizes = self.column_sizes
112
+
113
+ out = []
114
+ out << " \\begin{tabular}{#{self.align_string}}"
115
+ out << " \\toprule"
116
+ if @header
117
+ out << " " + @header.row.to_tex(sizes)
118
+ out << " \\midrule"
119
+ end
120
+ out += @rows.rows.map { |row| " " + row.to_tex(sizes) }
121
+ out << " \\bottomrule"
122
+ out << " \\end{tabular}"
123
+
124
+ return out.join("\n")
125
+ end
126
+
127
+ def column_sizes
128
+ header_sizes = @header ? [ @header.row.sizes ] : []
129
+ sizes = @rows.rows.map { |row| row.sizes } + header_sizes
130
+ @columns.map { |col| [col, (sizes.map { |e| e[col] }).max] }.to_h
131
+ end
132
+
133
+ def align_string
134
+ @columns.map { |col| @columns_info[col].is_a?(Column) ? @columns_info[col].align : auto_align(col) }.join(" ")
135
+ end
136
+
137
+ def auto_align(col)
138
+ @rows.rows.map { |row| row[col] }.all? { |e| e.is_a?(Numeric) } ? 'r' : 'l'
139
+ end
140
+ end
141
+
142
+ class RowFamily
143
+ attr_reader :rows
144
+ attr_reader :columns
145
+
146
+ def initialize(columns)
147
+ @columns = columns
148
+ @rows = []
149
+
150
+ @styler = proc { |e| e }
151
+ end
152
+
153
+ def columns
154
+ @columns.keys
155
+ end
156
+
157
+ def format(element, col)
158
+ @columns[col].formatter.call(element)
159
+ end
160
+
161
+ def style(element)
162
+ @styler.call(element)
163
+ end
164
+
165
+ def <<(row)
166
+ row.family = self
167
+
168
+ @rows << row
169
+ end
170
+
171
+ def formatter=(formatter)
172
+ @formatter = formatter
173
+ end
174
+
175
+ def styler=(styler)
176
+ @styler = styler
177
+ end
178
+ end
179
+
180
+ class SingleRowFamily < RowFamily
181
+ def initialize(columns, row)
182
+ super(columns)
183
+
184
+ row.family = self
185
+ @rows = [row]
186
+ end
187
+
188
+ def row
189
+ @rows.first
190
+ end
191
+
192
+ def <<(row)
193
+ raise "Unable to add new rows to a SingleRowFamily"
194
+ end
195
+ end
196
+
197
+ class HeaderRowFamily < SingleRowFamily
198
+ def initialize(columns, row)
199
+ super(columns, row)
200
+
201
+ @formatter = Formatters::StringFormatter.new
202
+ end
203
+
204
+ def format(element, col)
205
+ @formatter.call(element)
206
+ end
207
+ end
208
+
209
+ class Row
210
+ attr_accessor :family
211
+
212
+ def initialize(hash)
213
+ raise(WrongRowException, "Hash expected") unless hash.is_a?(Hash)
214
+
215
+ @row = hash
216
+ end
217
+
218
+ def family=(family)
219
+ raise(WrongRowException, "The row must have the following headers: #{family.columns}") unless @row.keys.sort == family.columns.sort
220
+
221
+ @family = family
222
+ end
223
+
224
+ def row
225
+ @row
226
+ end
227
+
228
+ def [](col)
229
+ @row[col]
230
+ end
231
+
232
+ def sizes
233
+ styled = self.styled
234
+ @family.columns.map { |col| [col, styled[col].length] }.to_h
235
+ end
236
+
237
+ def formatted
238
+ @family.columns.map { |col| [col, @family.format(row[col], col) ] }.to_h
239
+ end
240
+
241
+ def styled
242
+ self.formatted.map { |col, content| [col, @family.style(content) ] }.to_h
243
+ end
244
+
245
+ def to_tex(column_sizes)
246
+ self.styled.map { |col, content| (" " * (column_sizes[col] - content.length)) + content }.join(" & ") + " \\\\"
247
+ end
248
+
249
+ class WrongRowException < Exception
250
+ end
251
+ end
252
+
253
+ class Column
254
+ attr_reader :align
255
+ attr_reader :formatter
256
+ attr_reader :header
257
+
258
+ def initialize
259
+ @formatter = Formatters::StringFormatter.new
260
+
261
+ self.with_left_align
262
+ end
263
+
264
+ def with_left_align
265
+ self.with_align('l')
266
+ end
267
+
268
+ def with_right_align
269
+ self.with_align('r')
270
+ end
271
+
272
+ def with_center_align
273
+ self.with_align('c')
274
+ end
275
+
276
+ def with_integer_formatter(comma=true)
277
+ self.with_formatter(Formatters::IntegerFormatter.new(comma))
278
+ end
279
+
280
+ def with_decimal_formatter(n=2, comma=true)
281
+ self.with_formatter(Formatters::DecimalFormatter.new(n, comma))
282
+ end
283
+
284
+ def with_align(align)
285
+ @align = align
286
+ return self
287
+ end
288
+
289
+ def with_header(header)
290
+ @header = header
291
+ return self
292
+ end
293
+
294
+ def with_formatter(formatter)
295
+ @formatter = formatter
296
+ return self
297
+ end
298
+ end
299
+
300
+ class RowSeparator
301
+ def initialize(type)
302
+ @content = type
303
+ end
304
+
305
+ def family=(family)
306
+ @columns = family.columns
307
+ end
308
+
309
+ def row
310
+ nil
311
+ end
312
+
313
+ def sizes
314
+ @columns.map { |e| [e, 0] }.to_h
315
+ end
316
+
317
+ def formatted
318
+ @content
319
+ end
320
+
321
+ def styled
322
+ @content
323
+ end
324
+
325
+ def to_tex(*args)
326
+ @content
327
+ end
328
+ end
329
+
330
+ module Parsers
331
+ class TableParser
332
+ def self.parse(text)
333
+ table_space = ['\\begin{table}', '\\end{table}'].map { |s| text.index(s) }
334
+ table_space[0] += 13 # length of '\begin{table}'
335
+
336
+ table_text = text[*table_space]
337
+
338
+ caption = table_text.scan(/\\caption\{([^}]*)\}/).flatten[0]
339
+ label = table_text.scan(/\\label\{([^}]*)\}/).flatten[0]
340
+
341
+ self.parse_tabular(table_text)
342
+ end
343
+
344
+ def self.parse_tabular(text)
345
+ tabular_space = ['\\begin{tabular}', '\\end{tabular}'].map { |s| text.index(s) }
346
+ tabular_space[0] += 15 # length of '\begin{tabular}'
347
+
348
+ tabular_text = text[*tabular_space]
349
+
350
+ align_string = tabular_text.scan(/\{([^}]*)\}/).flatten[0]
351
+ align = self.parse_align_string(align_string)
352
+
353
+ content_text = tabular_text[tabular_text.index('}') + 1, content_text.length]
354
+ content_text.lines.each do |line|
355
+
356
+ end
357
+ end
358
+
359
+ def self.parse_align_string(text)
360
+ # TODO implement
361
+ warn "Ignoring align string #{text}"
362
+ return nil
363
+ end
364
+ end
365
+ end
366
+
367
+ module Formatters
368
+ class StringFormatter
369
+ def call(object)
370
+ object.to_s
371
+ end
372
+ end
373
+
374
+ class IntegerFormatter
375
+ def initialize(comma=true, nums=nil)
376
+ @format = "%#{nums}d"
377
+ @comma = comma
378
+ end
379
+
380
+ def call(object)
381
+ result = @format % [object.to_i]
382
+ result = result.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,") if @comma
383
+ return result
384
+ end
385
+ end
386
+
387
+ class DecimalFormatter
388
+ def initialize(decimals=2, comma=true, nums=nil)
389
+ @format = "%#{nums}.#{decimals}f"
390
+ @comma = comma
391
+ end
392
+
393
+ def call(object)
394
+ result = @format % [object.to_f]
395
+ result = result.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,") if @comma
396
+ return result
397
+ end
398
+ end
399
+
400
+ class CustomFormatter
401
+ def initialize(&block)
402
+ raise "Block required" unless block_given?
403
+ @block = block
404
+ end
405
+
406
+ def call(object)
407
+ @block.call(object)
408
+ end
409
+ end
410
+ end
411
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-latex
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Simone Scalabrino
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: code-assertions
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.1.2
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.2
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.2
33
+ description: This library allows to easily generate LaTeX content (e.g., tables) directly
34
+ from Ruby.
35
+ email: s.scalabrino9@gmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/ruby-latex.rb
41
+ homepage: https://github.com/intersimone999/ruby-latex
42
+ licenses:
43
+ - GPL-3.0-only
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.1.4
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Generate LaTeX content from Ruby
64
+ test_files: []