petit-felix 0.1.4 → 0.1.5

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.
@@ -0,0 +1,502 @@
1
+ require "prawn"
2
+ require 'fileutils'
3
+ require "prawndown-ext"
4
+ require "felix/metadata"
5
+ require "felix/error"
6
+ require "worker/pdf_writer"
7
+ require "worker/template_pdf_calls"
8
+ require "eqn"
9
+
10
+ ## Prawn PDF writer that inputs template files
11
+
12
+ module PetitFelix
13
+ module Worker
14
+
15
+ class TemplatePDFWriter < PetitFelix::Worker::DefaultPDFWriter
16
+
17
+ # error count: 16
18
+
19
+ ERROR_CODES = [
20
+ "OK",
21
+ "Malformed command list.",
22
+ "No command defined. Use \"com\" or \"command\" to define commands.",
23
+ "Command not found.",
24
+ "Stack overflow.",
25
+ "Template ID \"{{arg}}\" not found.",
26
+ "Template method \"{{arg}}\" not found.",
27
+ "\"{{arg}}\" argument not defined.",
28
+ "Expression \"{{arg}}\" does not result in boolean result.",
29
+ "Expression \"{{arg}}\" cannot be evaluated.",
30
+ "File \"{{arg}}\" not found.",
31
+ "Not a valid position.",
32
+ "\"at\" (Array) argument not defined.",
33
+ "\"{{arg}}\" is not an array.",
34
+ "Image \"{{arg}}\" not found.",
35
+ "Template file \"{{arg}}\" not found.",
36
+ "\"template\" option not defined. No template file can be loaded."
37
+ ]
38
+
39
+ SYMBOLIZE = [
40
+ :align,
41
+ :odd_align,
42
+ :even_align,
43
+ :valign,
44
+ :odd_valign,
45
+ :even_valign,
46
+ :direction,
47
+ :mode,
48
+ :style,
49
+ :overflow,
50
+ :rotate_around
51
+ ]
52
+
53
+
54
+ # Highest a stack is allowed to be
55
+ MAX_STACK = 1024
56
+
57
+ ## Functions
58
+
59
+ def init_values options, pdf
60
+ @variables = {}
61
+
62
+ @options = Marshal.load(Marshal.dump(options))
63
+
64
+ # Options to use for method calls and stuff
65
+ # Updated every time a template is loaded
66
+ @metaoptions = {}
67
+ @pdf = pdf
68
+
69
+ # stack referencing current running commands
70
+ @command_stack = []
71
+
72
+ # stack referencing current program counter
73
+ @counter_stack = []
74
+
75
+ # stack referencing current running template
76
+ # currently unused, will use when stitching
77
+ # external templates together.
78
+ @template_stack = []
79
+
80
+ # Templates
81
+ @template = {}
82
+
83
+ # prints errors
84
+ @error_printer = PetitFelix::Error.new
85
+
86
+ # For variables of errors
87
+ @error_param = {}
88
+
89
+ set_variables
90
+ end
91
+
92
+ # Adds a font to the pdf document
93
+ def add_font font, font_name
94
+
95
+ if font.key?(:normal)
96
+
97
+ if font.key?(:italic)
98
+ font[:italic] = font[:normal]
99
+ end
100
+ if font.key?(:bold)
101
+ font[:bold] = font[:normal]
102
+ end
103
+ if font.key?(:bold_italic)
104
+ font[:bold_italic] = font[:normal]
105
+ end
106
+
107
+ font.keys.each do |key|
108
+ font[key] = replace_variable font[key]
109
+ end
110
+
111
+ font_families.update(font_name => font)
112
+
113
+ end
114
+
115
+ end
116
+
117
+ def add_fonts
118
+ font_families.clear
119
+
120
+ fonts = Marshal.load(Marshal.dump(@fonts))
121
+
122
+ fonts.keys.each do |font|
123
+ add_font fonts[font], font.to_s
124
+ end
125
+
126
+ end
127
+
128
+ def set_variables
129
+ @metaoptions.keys.each do |key|
130
+ @variables[key] = @metaoptions[key]
131
+ end
132
+
133
+ if @variables.key? :markdown_metadata
134
+ @variables[:markdown_metadata].keys.each do |meta|
135
+ @variables[meta] = @variables[:markdown_metadata][meta]
136
+ end
137
+ end
138
+
139
+ ## add additional functional stuff
140
+ @variables["cursor"] = @pdf.cursor
141
+ @variables["bounds_height"] = @pdf.bounds.height
142
+ @variables["bounds_width"] = @pdf.bounds.width
143
+ @variables["pages"] = @pdf.page_count
144
+
145
+ # the currently loaded markdown file
146
+ @variables["loaded_markdown"] = {}
147
+
148
+ end
149
+
150
+ def replace_variables args
151
+
152
+ args.keys.each do |arg|
153
+ if args[arg].instance_of? String
154
+ args[arg] = replace_variable args[arg]
155
+ end
156
+ end
157
+
158
+ args
159
+ end
160
+
161
+ def replace_variable string
162
+ @variables.keys.each do |key|
163
+ string = string.gsub("${" + key.to_s + "}", @variables[key].to_s)
164
+ end
165
+
166
+ string
167
+ end
168
+
169
+ ## Parsing template stuff
170
+
171
+ # Reads template from JSON
172
+ # Format: Array of objects where
173
+ # each object has command and set of args
174
+ def read_template
175
+ if @options.key?("template")
176
+ if File.file?(@options["template"])
177
+ obj = JSON.parse(File.read(@options["template"]), symbolize_names: true)
178
+
179
+ default_options = obj[:default_variables].transform_keys!(&:to_s)
180
+
181
+ if default_options.nil?
182
+ default_options = {}
183
+ end
184
+
185
+ @fonts = {}
186
+
187
+ @metaoptions = default_options.merge(@options)
188
+
189
+ set_variables
190
+
191
+ if obj.key?(:fonts)
192
+ @fonts = obj[:fonts]
193
+ end
194
+
195
+ add_fonts
196
+
197
+ @template = {
198
+ "main" => obj[:definition]
199
+ }
200
+
201
+ # Runs the main function as entry point of the template
202
+ @template_stack.push("main")
203
+ result = execute_function "main", "main", self
204
+
205
+ if result[0] != 0
206
+ print_error result[0], result[1]
207
+ end
208
+ else
209
+ @error_param["arg"] = @options["template"].to_s
210
+ print_error 15, -1
211
+
212
+ end
213
+ else
214
+
215
+ print_error 16, -1
216
+
217
+ end
218
+ end
219
+
220
+ def execute_function template_id, function_id, obj
221
+
222
+ definition = nil
223
+
224
+ if !@template.key?(template_id)
225
+ @error_param["arg"] = template_id.to_s
226
+ # Fails because template ID not found.
227
+ return [5, -1]
228
+ else
229
+
230
+ if !@template[template_id].key?(function_id.to_sym )
231
+ @error_param["arg"] = function_id.to_s
232
+ # Fails because template function ID not found.
233
+ return [6, -1]
234
+ else
235
+ definition = Marshal.load(Marshal.dump(@template[template_id][function_id.to_sym]))
236
+ end
237
+
238
+ end
239
+
240
+ if definition.instance_of? Array
241
+
242
+ counter = 0
243
+
244
+ @counter_stack.push(counter)
245
+
246
+ for index in counter ... definition.size
247
+
248
+ # executes the command
249
+ line = definition[counter]
250
+
251
+ set_variables
252
+
253
+ command = ""
254
+
255
+ if line.key?(:cmd)
256
+ command = line[:cmd].to_sym
257
+ end
258
+
259
+ if line.key?(:command)
260
+ command = line[:command].to_sym
261
+ end
262
+
263
+ args = {
264
+ "LINE_NUMBER" => counter
265
+ }
266
+
267
+ if line.key?(:args)
268
+ args = line[:args]
269
+ end
270
+
271
+ begin
272
+ args = replace_variables(args)
273
+
274
+ rescue => error
275
+ print "\n"
276
+ print error
277
+ print "\n"
278
+ print "Error rendering variables!\n"
279
+ end
280
+
281
+ if !command.empty?
282
+
283
+ if COMMAND.keys.include?(command)
284
+ @command_stack.push definition
285
+
286
+ if @command_stack.count > 1024
287
+ # Failed because of stack overflow
288
+ return [4, counter]
289
+ end
290
+
291
+ comm = COMMAND[command].call(obj, args)
292
+
293
+ @counter_stack[-1] = counter
294
+ counter += 1
295
+
296
+ # something about command execution failed
297
+ if comm != 0
298
+ return [comm, counter]
299
+ end
300
+
301
+ @command_stack.pop
302
+ else
303
+ # failed because command not found
304
+ return [3, counter]
305
+
306
+ end
307
+
308
+ else
309
+ # failed because no command defined
310
+ return [2, -1]
311
+
312
+ end
313
+
314
+ end
315
+
316
+ @counter_stack.pop()
317
+
318
+ # Returns 0 if successful
319
+ return [0, counter]
320
+
321
+ end
322
+
323
+ # Failed because malformed command list
324
+ return [1, -1]
325
+ end
326
+
327
+ ## Error display
328
+
329
+ def error_replace_string error
330
+
331
+ @error_param.keys.each do |key|
332
+ error = error.gsub("{{"+ key +"}}", @error_param[key])
333
+ end
334
+
335
+ error
336
+ end
337
+
338
+ def print_error error_code, line
339
+
340
+ if error_code > -1 && error_code < ERROR_CODES.count
341
+ @error_printer.print_err "Error reading template. " + error_replace_string(ERROR_CODES[error_code])
342
+ else
343
+ @error_printer.print_err "Error reading template. General template processing error occured."
344
+ end
345
+
346
+ print "Error code: " + error_code.to_s
347
+
348
+ print "\n\n"
349
+ print "Processing markdown file: " + @metaoptions["filename"]
350
+ print "\n\n"
351
+
352
+ stack_rv = @command_stack.reverse
353
+
354
+ if line >= 0 && !stack_rv.empty?
355
+
356
+ print "\n\nCurrent line:\n"
357
+ print stack_rv[0][line]
358
+
359
+ print "\n\nCurrent Stack:"
360
+ test_arr = *(0..[19,@command_stack.count-1].min)
361
+ test_arr.each do | i |
362
+ command_obj = stack_rv[i]
363
+
364
+ line_edit = command_obj[0..@counter_stack[i]+1]
365
+
366
+ print "\n"
367
+ print "Line: (" + line_edit.count.to_s + "): "
368
+ print line_edit[-1]
369
+ end
370
+ end
371
+
372
+ print "\n"
373
+
374
+ return error_code
375
+ end
376
+
377
+ ## These methods validate parameters
378
+
379
+ def args_has_string arg_name, args
380
+ if !args.key?(arg_name)
381
+ # text not defined
382
+ @error_param["arg"] = arg_name.to_s
383
+ return 7
384
+ end
385
+
386
+ args[arg_name] = replace_variable args[arg_name].to_s
387
+
388
+ return 0
389
+ end
390
+
391
+ def args_has_int arg_name, args
392
+ if !args.key?(arg_name)
393
+ # text not defined
394
+ @error_param["arg"] = arg_name.to_s
395
+ return 7
396
+ end
397
+
398
+ if args[arg_name].instance_of? String
399
+ args[arg_name] = Eqn::Calculator.calc(replace_variable args[arg_name]).to_i
400
+ end
401
+ return 0
402
+ end
403
+
404
+ def args_has_float arg_name, args
405
+
406
+ if !args.key?(arg_name)
407
+ # text not defined
408
+ @error_param["arg"] = arg_name.to_s
409
+ return 7
410
+ end
411
+
412
+ args[arg_name] = Eqn::Calculator.calc(replace_variable args[arg_name]).to_f
413
+
414
+ return 0
415
+ end
416
+
417
+ def args_has_arr (arg_name, args, type, options = {})
418
+
419
+ if !args.key?(arg_name)
420
+ # text not defined
421
+ @error_param["arg"] = arg_name.to_s
422
+ return 7
423
+ end
424
+
425
+ if args[arg_name].instance_of? String
426
+ begin
427
+ set_variables
428
+
429
+ test = replace_variable args[arg_name]
430
+
431
+ args[arg_name] = JSON.parse(test)
432
+
433
+ rescue => error
434
+ print "\nError parsing array: " + args[arg_name] + "\n"
435
+ print error
436
+
437
+ end
438
+
439
+ end
440
+
441
+ if args[arg_name].instance_of? Array
442
+ if type == :float
443
+ args[arg_name].map! {|item| Eqn::Calculator.calc(replace_variable item.to_s).to_f }
444
+ elsif type == :int
445
+ args[arg_name].map! {|item| Eqn::Calculator.calc(replace_variable item.to_s).to_i }
446
+ elsif type == :hash
447
+ args[arg_name].map! {|item| args_correct_hash item, options[:second_type] }
448
+ else
449
+ args[arg_name].map! {|item| replace_variable item.to_s }
450
+ end
451
+
452
+ end
453
+
454
+ return 0
455
+ end
456
+
457
+ def args_correct_hash hash, type
458
+ hash.transform_keys!(&:to_sym)
459
+
460
+ hash.keys.each do |key|
461
+
462
+ if type == :int
463
+ hash[key] = Eqn::Calculator.calc(replace_variable hash[key].to_s).to_i
464
+ elsif type == :float
465
+ hash[key] = Eqn::Calculator.calc(replace_variable hash[key].to_s).to_f
466
+ else
467
+ hash[key] = replace_variable hash[key].to_s
468
+ end
469
+
470
+ end
471
+
472
+ hash
473
+
474
+ end
475
+
476
+ def args_correct_values args
477
+ args_has_int :width, args
478
+ args_has_int :height, args
479
+
480
+ SYMBOLIZE.each do |symbol|
481
+ if args.key?(symbol)
482
+ args[symbol] = args[symbol].to_sym
483
+ end
484
+ end
485
+
486
+ if args.key?(:position)
487
+
488
+ if ["left","center","right"].include? args[:position]
489
+ args[:position] = args[:position].to_sym
490
+ else
491
+ args[:position] = args[:position].to_i
492
+ end
493
+
494
+ end
495
+
496
+ args
497
+
498
+ end
499
+
500
+ end
501
+ end
502
+ end