squib 0.6.0 → 0.7.0

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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +162 -133
  3. data/Gemfile +4 -4
  4. data/README.md +630 -550
  5. data/RELEASE TODO.md +18 -18
  6. data/Rakefile +99 -99
  7. data/lib/squib.rb +32 -32
  8. data/lib/squib/api/background.rb +20 -19
  9. data/lib/squib/api/data.rb +100 -99
  10. data/lib/squib/api/image.rb +90 -76
  11. data/lib/squib/api/save.rb +149 -103
  12. data/lib/squib/api/settings.rb +35 -37
  13. data/lib/squib/api/shapes.rb +230 -228
  14. data/lib/squib/api/text.rb +65 -66
  15. data/lib/squib/api/text_embed.rb +96 -66
  16. data/lib/squib/args/arg_loader.rb +138 -0
  17. data/lib/squib/args/box.rb +55 -0
  18. data/lib/squib/args/card_range.rb +32 -0
  19. data/lib/squib/args/color_validator.rb +12 -0
  20. data/lib/squib/args/coords.rb +33 -0
  21. data/lib/squib/args/dir_validator.rb +16 -0
  22. data/lib/squib/args/draw.rb +92 -0
  23. data/lib/squib/args/embed_adjust.rb +25 -0
  24. data/lib/squib/args/embed_key.rb +17 -0
  25. data/lib/squib/args/hand_special.rb +37 -0
  26. data/lib/squib/args/input_file.rb +37 -0
  27. data/lib/squib/args/paint.rb +44 -0
  28. data/lib/squib/args/paragraph.rb +115 -0
  29. data/lib/squib/args/save_batch.rb +60 -0
  30. data/lib/squib/args/scale_box.rb +53 -0
  31. data/lib/squib/args/sheet.rb +72 -0
  32. data/lib/squib/args/showcase_special.rb +38 -0
  33. data/lib/squib/args/svg_special.rb +37 -0
  34. data/lib/squib/args/transform.rb +25 -0
  35. data/lib/squib/args/typographer.rb +117 -117
  36. data/lib/squib/card.rb +67 -67
  37. data/lib/squib/conf.rb +117 -111
  38. data/lib/squib/constants.rb +178 -178
  39. data/lib/squib/deck.rb +113 -111
  40. data/lib/squib/graphics/cairo_context_wrapper.rb +99 -53
  41. data/lib/squib/graphics/gradient_regex.rb +46 -46
  42. data/lib/squib/graphics/hand.rb +42 -43
  43. data/lib/squib/graphics/image.rb +76 -73
  44. data/lib/squib/graphics/save_doc.rb +103 -137
  45. data/lib/squib/graphics/save_images.rb +33 -33
  46. data/lib/squib/graphics/shapes.rb +119 -152
  47. data/lib/squib/graphics/showcase.rb +85 -88
  48. data/lib/squib/graphics/text.rb +176 -216
  49. data/lib/squib/layout_parser.rb +91 -89
  50. data/lib/squib/layouts/economy.yml +85 -0
  51. data/lib/squib/layouts/fantasy.yml +101 -0
  52. data/lib/squib/layouts/hand.yml +62 -46
  53. data/lib/squib/layouts/playing-card.yml +35 -18
  54. data/lib/squib/project_template/config.yml +45 -40
  55. data/lib/squib/version.rb +10 -10
  56. data/samples/color_shortcuts.rb +6 -0
  57. data/samples/csv_import.rb +18 -18
  58. data/samples/custom-config.yml +5 -5
  59. data/samples/custom_config.rb +18 -18
  60. data/samples/draw_shapes.rb +45 -35
  61. data/samples/embed_text.rb +88 -90
  62. data/samples/hand.rb +24 -24
  63. data/samples/layouts.rb +62 -61
  64. data/samples/layouts_builtin.rb +51 -0
  65. data/samples/load_images.rb +78 -64
  66. data/samples/ranges.rb +64 -53
  67. data/samples/sample.csv +2 -2
  68. data/samples/text_options.rb +102 -94
  69. data/spec/api/api_data_spec.rb +57 -50
  70. data/spec/api/api_settings_spec.rb +37 -17
  71. data/spec/args/box_spec.rb +127 -0
  72. data/spec/args/draw_spec.rb +95 -0
  73. data/spec/args/embed_key_spec.rb +13 -0
  74. data/spec/args/input_file_spec.rb +21 -0
  75. data/spec/args/paint_spec.rb +22 -0
  76. data/spec/args/paragraph_spec.rb +153 -0
  77. data/spec/args/range_spec.rb +36 -0
  78. data/spec/args/save_batch_spec.rb +51 -0
  79. data/spec/args/scale_box_spec.rb +71 -0
  80. data/spec/args/sheet_spec.rb +58 -0
  81. data/spec/args/showcase_special_spec.rb +15 -0
  82. data/spec/data/samples/autoscale_font.rb.txt +84 -87
  83. data/spec/data/samples/basic.rb.txt +209 -203
  84. data/spec/data/samples/cairo_access.rb.txt +2 -2
  85. data/spec/data/samples/config_text_markup.rb.txt +72 -75
  86. data/spec/data/samples/csv_import.rb.txt +76 -80
  87. data/spec/data/samples/custom_config.rb.txt +48 -49
  88. data/spec/data/samples/draw_shapes.rb.txt +100 -42
  89. data/spec/data/samples/embed_text.rb.txt +283 -295
  90. data/spec/data/samples/excel.rb.txt +162 -171
  91. data/spec/data/samples/gradients.rb.txt +79 -67
  92. data/spec/data/samples/hand.rb.txt +538 -514
  93. data/spec/data/samples/hello_world.rb.txt +36 -38
  94. data/spec/data/samples/load_images.rb.txt +41 -5
  95. data/spec/data/samples/portrait-landscape.rb.txt +49 -51
  96. data/spec/data/samples/ranges.rb.txt +460 -429
  97. data/spec/data/samples/saves.rb.txt +801 -785
  98. data/spec/data/samples/showcase.rb.txt +5910 -5906
  99. data/spec/data/samples/text_options.rb.txt +1125 -981
  100. data/spec/data/samples/tgc_proofs.rb.txt +81 -79
  101. data/spec/data/samples/units.rb.txt +18 -12
  102. data/spec/data/xlsx/with_macros.xlsm +0 -0
  103. data/spec/graphics/cairo_context_wrapper_spec.rb +84 -75
  104. data/spec/graphics/graphics_images_spec.rb +94 -85
  105. data/spec/graphics/graphics_save_doc_spec.rb +67 -65
  106. data/spec/samples/expected/hand.png +0 -0
  107. data/spec/samples/expected/hand_pretty.png +0 -0
  108. data/spec/samples/expected/layout_00.png +0 -0
  109. data/spec/samples/expected/load_images_00.png +0 -0
  110. data/spec/samples/expected/ranges_00.png +0 -0
  111. data/spec/samples/expected/shape_00.png +0 -0
  112. data/spec/samples/expected/showcase.png +0 -0
  113. data/spec/samples/expected/showcase2.png +0 -0
  114. data/spec/samples/expected/showcase_individual_00.png +0 -0
  115. data/spec/samples/expected/showcase_individual_01.png +0 -0
  116. data/spec/samples/expected/showcase_individual_02.png +0 -0
  117. data/spec/samples/expected/showcase_individual_03.png +0 -0
  118. data/spec/samples/expected/text_00.png +0 -0
  119. data/spec/samples/expected/text_01.png +0 -0
  120. data/spec/samples/expected/text_02.png +0 -0
  121. data/spec/samples/samples_regression_spec.rb +82 -82
  122. data/spec/spec_helper.rb +3 -2
  123. data/squib.gemspec +48 -48
  124. data/squib.sublime-project +42 -36
  125. metadata +61 -33
  126. data/lib/squib/input_helpers.rb +0 -238
  127. data/spec/api/api_image_spec.rb +0 -38
  128. data/spec/api/api_text_spec.rb +0 -37
  129. data/spec/graphics/graphics_shapes_spec.rb +0 -85
  130. data/spec/graphics/graphics_text_spec.rb +0 -164
  131. data/spec/input_helpers_spec.rb +0 -238
  132. data/spec/samples/expected/embed_multi_00.png +0 -0
  133. data/spec/samples/expected/embed_multi_01.png +0 -0
  134. data/spec/samples/expected/embed_multi_02.png +0 -0
  135. data/spec/samples/expected/ranges_01.png +0 -0
  136. data/spec/samples/expected/ranges_02.png +0 -0
@@ -1,216 +1,176 @@
1
- require 'pango'
2
- require 'squib/args/typographer'
3
-
4
- module Squib
5
- class Card
6
-
7
- # :nodoc:
8
- # @api private
9
- def draw_text_hint(cc, x, y, layout, color)
10
- color = @deck.text_hint if color.to_s.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
11
- return if color.to_s.eql? 'off' or color.nil?
12
- # when w,h < 0, it was never set. extents[1] are ink extents
13
- w = layout.width / Pango::SCALE
14
- w = layout.extents[1].width / Pango::SCALE if w < 0
15
- h = layout.height / Pango::SCALE
16
- h = layout.extents[1].height / Pango::SCALE if h < 0
17
- cc.rounded_rectangle(0, 0, w, h, 0, 0)
18
- cc.set_source_color(color)
19
- cc.set_line_width(2.0)
20
- cc.stroke
21
- end
22
-
23
- # :nodoc:
24
- # @api private
25
- def set_ellipsize!(layout, ellipsize)
26
- case ellipsize.to_s.downcase
27
- when 'none', 'false'
28
- layout.ellipsize = Pango::Layout::ELLIPSIZE_NONE
29
- when 'start'
30
- layout.ellipsize = Pango::Layout::ELLIPSIZE_START
31
- when 'middle'
32
- layout.ellipsize = Pango::Layout::ELLIPSIZE_MIDDLE
33
- when 'end', 'true'
34
- layout.ellipsize = Pango::Layout::ELLIPSIZE_END
35
- end
36
- end
37
-
38
- # :nodoc:
39
- # @api private
40
- def set_wrap!(layout, wrap)
41
- case wrap.to_s.downcase
42
- when 'word'
43
- layout.wrap = Pango::Layout::WRAP_WORD
44
- when 'char'
45
- layout.wrap = Pango::Layout::WRAP_CHAR
46
- when 'word_char', 'true'
47
- layout.wrap = Pango::Layout::WRAP_WORD_CHAR
48
- end
49
- end
50
-
51
- # :nodoc:
52
- # @api private
53
- def set_align!(layout, align)
54
- case align.to_s.downcase
55
- when 'left'
56
- layout.alignment = Pango::ALIGN_LEFT
57
- when 'right'
58
- layout.alignment = Pango::ALIGN_RIGHT
59
- when 'center'
60
- layout.alignment = Pango::ALIGN_CENTER
61
- end
62
- end
63
-
64
- # :nodoc:
65
- # @api private
66
- def compute_valign(layout, valign)
67
- return 0 unless layout.height > 0
68
- ink_extents = layout.extents[1]
69
- case valign.to_s.downcase
70
- when 'middle'
71
- Pango.pixels( (layout.height - ink_extents.height) / 2)
72
- when 'bottom'
73
- Pango.pixels(layout.height - ink_extents.height)
74
- else
75
- 0
76
- end
77
- end
78
-
79
- def set_font_rendering_opts!(layout)
80
- font_options = Cairo::FontOptions.new
81
- font_options.antialias = Conf::ANTIALIAS_OPTS[(@deck.antialias || 'gray').downcase]
82
- font_options.hint_metrics = 'on' # TODO make this configurable
83
- font_options.hint_style = 'full' # TODO make this configurable
84
- layout.context.font_options = font_options
85
- end
86
-
87
- # :nodoc:
88
- # @api private
89
- def set_wh!(layout, width, height)
90
- layout.width = width * Pango::SCALE unless width.nil? || width == :native
91
- layout.height = height * Pango::SCALE unless height.nil? || height == :native
92
- end
93
-
94
- # :nodoc:
95
- # @api private
96
- def next_embed(keys, str)
97
- ret = nil
98
- ret_key = nil
99
- keys.each do |key|
100
- i = str.index(key)
101
- ret ||= i
102
- unless i.nil? || i > ret
103
- ret = i
104
- ret_key = key
105
- end
106
- end
107
- ret_key
108
- end
109
-
110
- # When we do embedded icons, we create a 3-character string that has a font
111
- # size of zero and a letter-spacing that fits the icon we need. That gives
112
- # us the wrapping behavior we need but no clutter beneath. On most
113
- # platforms, this works fine. On Linux, this creates
114
- # a Cairo transformation matrix that
115
- ZERO_WIDTH_CHAR_SIZE = 0 # this works on most platforms
116
- # some platforms make this Pango pixels (1/1024), others a 1 pt font
117
- ZERO_WIDTH_CHAR_SIZE = 1 if RbConfig::CONFIG['host_os'] === 'linux-gnu'
118
-
119
- # :nodoc:
120
- # @api private
121
- def process_embeds(embed, str, layout)
122
- return [] unless embed.rules.any?
123
- layout.markup = str
124
- clean_str = layout.text
125
- draw_calls = []
126
- searches = []
127
- while (key = next_embed(embed.rules.keys, clean_str)) != nil
128
- rule = embed.rules[key]
129
- spacing = rule[:width] * Pango::SCALE
130
- index = clean_str.index(key)
131
- index = clean_str[0..index].bytesize #convert to byte index (bug #57)
132
- str = str.sub(key, "<span size=\"#{ZERO_WIDTH_CHAR_SIZE}\">a<span letter_spacing=\"#{spacing.to_i}\">a</span>a</span>")
133
- layout.markup = str
134
- clean_str = layout.text
135
- searches << { index: index, rule: rule }
136
- end
137
- searches.each do |search|
138
- rect = layout.index_to_pos(search[:index])
139
- x = Pango.pixels(rect.x) + search[:rule][:dx]
140
- y = Pango.pixels(rect.y) + search[:rule][:dy]
141
- draw_calls << {x: x, y: y, draw: search[:rule][:draw]} # defer drawing until we've valigned
142
- end
143
- return draw_calls
144
- end
145
-
146
- def stroke_outline!(cc, layout, stroke_width, stroke_color)
147
- if stroke_width > 0
148
- cc.set_source_squibcolor(stroke_color)
149
- cc.set_line_width(stroke_width)
150
- cc.pango_layout_path(layout)
151
- cc.stroke
152
- end
153
- end
154
-
155
- # :nodoc:
156
- # @api private
157
- def text(embed,str, font, font_size, color,
158
- x, y, width, height,
159
- markup, justify, wrap, ellipsize,
160
- spacing, align, valign, hint, angle,
161
- stroke_color, stroke_width)
162
- Squib.logger.debug {"Placing '#{str}'' with font '#{font}' @ #{x}, #{y}, color: #{color}, angle: #{angle} etc."}
163
- extents = nil
164
- str = str.to_s
165
- use_cairo do |cc|
166
- cc.set_source_squibcolor(color)
167
- cc.translate(x,y)
168
- cc.rotate(angle)
169
- cc.move_to(0, 0)
170
-
171
- font_desc = Pango::FontDescription.new(font)
172
- font_desc.size = font_size * Pango::SCALE unless font_size.nil?
173
- layout = cc.create_pango_layout
174
- layout.font_description = font_desc
175
- layout.text = str
176
- if markup
177
- str = @deck.typographer.process(layout.text)
178
- layout.markup = str
179
- end
180
-
181
- set_font_rendering_opts!(layout)
182
- set_wh!(layout, width, height)
183
- set_wrap!(layout, wrap)
184
- set_ellipsize!(layout, ellipsize)
185
- set_align!(layout, align)
186
-
187
- layout.justify = justify unless justify.nil?
188
- layout.spacing = spacing * Pango::SCALE unless spacing.nil?
189
- cc.update_pango_layout(layout)
190
-
191
- embed_draws = process_embeds(embed, str, layout)
192
-
193
- vertical_start = compute_valign(layout, valign)
194
- cc.move_to(0, vertical_start)
195
-
196
- cc.update_pango_layout(layout)
197
- cc.show_pango_layout(layout)
198
- stroke_outline!(cc, layout, stroke_width, stroke_color)
199
- begin
200
- embed_draws.each { |ed| ed[:draw].call(self, ed[:x], ed[:y] + vertical_start) }
201
- rescue Exception => e
202
- puts "====EXCEPTION!===="
203
- puts e
204
- puts "If this was a non-invertible matrix error, this is a known issue with a potential workaround. Please report it at: https://github.com/andymeneely/squib/issues/55"
205
- puts "=================="
206
- raise e
207
- end
208
- draw_text_hint(cc, x, y, layout, hint)
209
- extents = { width: layout.extents[1].width / Pango::SCALE,
210
- height: layout.extents[1].height / Pango::SCALE }
211
- end
212
- return extents
213
- end
214
-
215
- end
216
- end
1
+ require 'pango'
2
+ require 'squib/args/typographer'
3
+
4
+ module Squib
5
+ class Card
6
+
7
+ # :nodoc:
8
+ # @api private
9
+ def draw_text_hint(cc, x, y, layout, color)
10
+ color = @deck.text_hint if color.to_s.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
11
+ return if color.to_s.eql? 'off' or color.nil?
12
+ # when w,h < 0, it was never set. extents[1] are ink extents
13
+ w = layout.width / Pango::SCALE
14
+ w = layout.extents[1].width / Pango::SCALE if w < 0
15
+ h = layout.height / Pango::SCALE
16
+ h = layout.extents[1].height / Pango::SCALE if h < 0
17
+ cc.rounded_rectangle(0, 0, w, h, 0, 0)
18
+ cc.set_source_color(color)
19
+ cc.set_line_width(2.0)
20
+ cc.stroke
21
+ end
22
+
23
+ # :nodoc:
24
+ # @api private
25
+ def compute_valign(layout, valign)
26
+ return 0 unless layout.height > 0
27
+ ink_extents = layout.extents[1]
28
+ case valign.to_s.downcase
29
+ when 'middle'
30
+ Pango.pixels( (layout.height - ink_extents.height) / 2)
31
+ when 'bottom'
32
+ Pango.pixels(layout.height - ink_extents.height)
33
+ else
34
+ 0
35
+ end
36
+ end
37
+
38
+ def set_font_rendering_opts!(layout)
39
+ font_options = Cairo::FontOptions.new
40
+ font_options.antialias = Conf::ANTIALIAS_OPTS[(@deck.antialias || 'gray').downcase]
41
+ font_options.hint_metrics = 'on' # TODO make this configurable
42
+ font_options.hint_style = 'full' # TODO make this configurable
43
+ layout.context.font_options = font_options
44
+ end
45
+
46
+ # :nodoc:
47
+ # @api private
48
+ def set_wh!(layout, width, height)
49
+ layout.width = width * Pango::SCALE unless width.nil? || width == :auto
50
+ layout.height = height * Pango::SCALE unless height.nil? || height == :auto
51
+ end
52
+
53
+ # :nodoc:
54
+ # @api private
55
+ def next_embed(keys, str)
56
+ ret = nil
57
+ ret_key = nil
58
+ keys.each do |key|
59
+ i = str.index(key)
60
+ ret ||= i
61
+ unless i.nil? || i > ret
62
+ ret = i
63
+ ret_key = key
64
+ end
65
+ end
66
+ ret_key
67
+ end
68
+
69
+ # When we do embedded icons, we create a 3-character string that has a font
70
+ # size of zero and a letter-spacing that fits the icon we need. That gives
71
+ # us the wrapping behavior we need but no clutter beneath. On most
72
+ # platforms, this works fine. On Linux, this creates
73
+ # a Cairo transformation matrix that
74
+ ZERO_WIDTH_CHAR_SIZE = 0 # this works on most platforms
75
+ # some platforms make this Pango pixels (1/1024), others a 1 pt font
76
+ ZERO_WIDTH_CHAR_SIZE = 1 if RbConfig::CONFIG['host_os'] === 'linux-gnu'
77
+
78
+ # :nodoc:
79
+ # @api private
80
+ def process_embeds(embed, str, layout)
81
+ return [] unless embed.rules.any?
82
+ layout.markup = str
83
+ clean_str = layout.text
84
+ draw_calls = []
85
+ searches = []
86
+ while (key = next_embed(embed.rules.keys, clean_str)) != nil
87
+ rule = embed.rules[key]
88
+ spacing = rule[:box].width[@index] * Pango::SCALE
89
+ kindex = clean_str.index(key)
90
+ kindex = clean_str[0..kindex].bytesize #convert to byte index (bug #57)
91
+ str = str.sub(key, "<span size=\"#{ZERO_WIDTH_CHAR_SIZE}\">a<span letter_spacing=\"#{spacing.to_i}\">a</span>a</span>")
92
+ layout.markup = str
93
+ clean_str = layout.text
94
+ searches << { index: kindex, rule: rule }
95
+ end
96
+ searches.each do |search|
97
+ rect = layout.index_to_pos(search[:index])
98
+ x = Pango.pixels(rect.x) + search[:rule][:adjust].dx[@index]
99
+ y = Pango.pixels(rect.y) + search[:rule][:adjust].dy[@index]
100
+ draw_calls << {x: x, y: y, draw: search[:rule][:draw]} # defer drawing until we've valigned
101
+ end
102
+ return draw_calls
103
+ end
104
+
105
+ def stroke_outline!(cc, layout, draw)
106
+ if draw.stroke_width > 0
107
+ cc.pango_layout_path(layout)
108
+ cc.fancy_stroke draw
109
+ cc.set_source_squibcolor(draw.color)
110
+ end
111
+ end
112
+
113
+ def warn_if_ellipsized(layout)
114
+ if @deck.conf.warn_ellipsize? && layout.ellipsized?
115
+ Squib.logger.warn { "Ellipsized (too much text). Card \##{@index}. Text: \"#{layout.text}\". \n (To disable this warning, set warn_ellipsize: false in config.yml)" }
116
+ end
117
+ end
118
+
119
+ # :nodoc:
120
+ # @api private
121
+ def text(embed, para, box, trans, draw)
122
+ Squib.logger.debug {"Rendering text with: \n#{para} \nat:\n #{box} \ndraw:\n #{draw} \ntransform: #{trans}"}
123
+ extents = nil
124
+ use_cairo do |cc|
125
+ cc.set_source_squibcolor(draw.color)
126
+ cc.translate(box.x, box.y)
127
+ cc.rotate(trans.angle)
128
+ cc.move_to(0, 0)
129
+
130
+ font_desc = Pango::FontDescription.new(para.font)
131
+ font_desc.size = para.font_size * Pango::SCALE unless para.font_size.nil?
132
+ layout = cc.create_pango_layout
133
+ layout.font_description = font_desc
134
+ layout.text = para.str
135
+ if para.markup
136
+ para.str = @deck.typographer.process(layout.text)
137
+ layout.markup = para.str
138
+ end
139
+
140
+ set_font_rendering_opts!(layout)
141
+ set_wh!(layout, box.width, box.height)
142
+ layout.wrap = para.wrap
143
+ layout.ellipsize = para.ellipsize
144
+ layout.alignment = para.align
145
+
146
+ layout.justify = para.justify unless para.justify.nil?
147
+ layout.spacing = para.spacing unless para.spacing.nil?
148
+
149
+ embed_draws = process_embeds(embed, para.str, layout)
150
+
151
+ vertical_start = compute_valign(layout, para.valign)
152
+ cc.move_to(0, vertical_start) #TODO clean this up a bit
153
+
154
+ stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :stroke_first
155
+ cc.move_to(0, vertical_start)
156
+ cc.show_pango_layout(layout)
157
+ stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :fill_first
158
+ begin
159
+ embed_draws.each { |ed| ed[:draw].call(self, ed[:x], ed[:y] + vertical_start) }
160
+ rescue Exception => e
161
+ puts "====EXCEPTION!===="
162
+ puts e
163
+ puts "If this was a non-invertible matrix error, this is a known issue with a potential workaround. Please report it at: https://github.com/andymeneely/squib/issues/55"
164
+ puts "=================="
165
+ raise e
166
+ end
167
+ draw_text_hint(cc, box.x, box.y, layout, para.hint)
168
+ extents = { width: layout.extents[1].width / Pango::SCALE,
169
+ height: layout.extents[1].height / Pango::SCALE }
170
+ warn_if_ellipsized layout
171
+ end
172
+ return extents
173
+ end
174
+
175
+ end
176
+ end
@@ -1,89 +1,91 @@
1
- module Squib
2
- # Internal class for handling layouts
3
- #@api private
4
- class LayoutParser
5
-
6
- # Load the layout file(s), if exists
7
- # @api private
8
- def self.load_layout(files)
9
- layout = {}
10
- Squib::logger.info { " using layout(s): #{files}" }
11
- Array(files).each do |file|
12
- thefile = file
13
- thefile = builtin(file) unless File.exists?(file)
14
- if File.exists? thefile
15
- yml = layout.merge(YAML.load_file(thefile) || {}) #load_file returns false on empty file
16
- yml.each do |key, value|
17
- layout[key] = recurse_extends(yml, key, {})
18
- end
19
- else
20
- Squib::logger.error { "Layout file not found: #{file}. Skipping..." }
21
- end
22
- end
23
- return layout
24
- end
25
-
26
- # Determine the file path of the built-in layout file
27
- def self.builtin(file)
28
- "#{File.dirname(__FILE__)}/layouts/#{file}"
29
- end
30
-
31
- # Process the extends recursively
32
- # :nodoc:
33
- # @api private
34
- def self.recurse_extends(yml, key, visited )
35
- assert_not_visited(key, visited)
36
- return yml[key] unless has_extends?(yml, key)
37
- return yml[key] unless parents_exist?(yml, key)
38
- visited[key] = key
39
- parent_keys = [yml[key]['extends']].flatten
40
- h = {}
41
- parent_keys.each do |parent_key|
42
- from_extends = yml[key].merge(recurse_extends(yml, parent_key, visited)) do |key, child_val, parent_val|
43
- if child_val.to_s.strip.start_with?('+=')
44
- parent_val + child_val.sub('+=','').strip.to_f
45
- elsif child_val.to_s.strip.start_with?('-=')
46
- parent_val - child_val.sub('-=','').strip.to_f
47
- else
48
- child_val #child overrides parent when merging, no +=
49
- end
50
- end
51
- h = h.merge(from_extends) do |key, older_sibling, younger_sibling|
52
- younger_sibling #when two siblings have the same entry, the "younger" (lower one) overrides
53
- end
54
- end
55
- return h
56
- end
57
-
58
- # Does this layout entry have an extends field?
59
- # i.e. is it a base-case or will it need recursion?
60
- # :nodoc:
61
- # @api private
62
- def self.has_extends?(yml, key)
63
- !!yml[key] && yml[key].key?('extends')
64
- end
65
-
66
- # Checks if we have any absentee parents
67
- # @api private
68
- def self.parents_exist?(yml, key)
69
- exists = true
70
- Array(yml[key]['extends']).each do |parent|
71
- unless yml.key?(parent)
72
- exists = false unless
73
- Squib.logger.error "Processing layout: '#{key}' attempts to extend a missing '#{yml[key]['extends']}'"
74
- end
75
- end
76
- return exists
77
- end
78
-
79
- # Safeguard against malformed circular extends
80
- # :nodoc:
81
- # @api private
82
- def self.assert_not_visited(key, visited)
83
- if visited.key? key
84
- raise "Invalid layout: circular extends with '#{key}'"
85
- end
86
- end
87
-
88
- end
89
- end
1
+ require 'yaml'
2
+
3
+ module Squib
4
+ # Internal class for handling layouts
5
+ #@api private
6
+ class LayoutParser
7
+
8
+ # Load the layout file(s), if exists
9
+ # @api private
10
+ def self.load_layout(files)
11
+ layout = {}
12
+ Squib::logger.info { " using layout(s): #{files}" }
13
+ Array(files).each do |file|
14
+ thefile = file
15
+ thefile = builtin(file) unless File.exists?(file)
16
+ if File.exists? thefile
17
+ yml = layout.merge(YAML.load_file(thefile) || {}) #load_file returns false on empty file
18
+ yml.each do |key, value|
19
+ layout[key] = recurse_extends(yml, key, {})
20
+ end
21
+ else
22
+ Squib::logger.error { "Layout file not found: #{file}. Skipping..." }
23
+ end
24
+ end
25
+ return layout
26
+ end
27
+
28
+ # Determine the file path of the built-in layout file
29
+ def self.builtin(file)
30
+ "#{File.dirname(__FILE__)}/layouts/#{file}"
31
+ end
32
+
33
+ # Process the extends recursively
34
+ # :nodoc:
35
+ # @api private
36
+ def self.recurse_extends(yml, key, visited )
37
+ assert_not_visited(key, visited)
38
+ return yml[key] unless has_extends?(yml, key)
39
+ return yml[key] unless parents_exist?(yml, key)
40
+ visited[key] = key
41
+ parent_keys = [yml[key]['extends']].flatten
42
+ h = {}
43
+ parent_keys.each do |parent_key|
44
+ from_extends = yml[key].merge(recurse_extends(yml, parent_key, visited)) do |key, child_val, parent_val|
45
+ if child_val.to_s.strip.start_with?('+=')
46
+ parent_val + child_val.sub('+=','').strip.to_f
47
+ elsif child_val.to_s.strip.start_with?('-=')
48
+ parent_val - child_val.sub('-=','').strip.to_f
49
+ else
50
+ child_val #child overrides parent when merging, no +=
51
+ end
52
+ end
53
+ h = h.merge(from_extends) do |key, older_sibling, younger_sibling|
54
+ younger_sibling #when two siblings have the same entry, the "younger" (lower one) overrides
55
+ end
56
+ end
57
+ return h
58
+ end
59
+
60
+ # Does this layout entry have an extends field?
61
+ # i.e. is it a base-case or will it need recursion?
62
+ # :nodoc:
63
+ # @api private
64
+ def self.has_extends?(yml, key)
65
+ !!yml[key] && yml[key].key?('extends')
66
+ end
67
+
68
+ # Checks if we have any absentee parents
69
+ # @api private
70
+ def self.parents_exist?(yml, key)
71
+ exists = true
72
+ Array(yml[key]['extends']).each do |parent|
73
+ unless yml.key?(parent)
74
+ exists = false unless
75
+ Squib.logger.error "Processing layout: '#{key}' attempts to extend a missing '#{yml[key]['extends']}'"
76
+ end
77
+ end
78
+ return exists
79
+ end
80
+
81
+ # Safeguard against malformed circular extends
82
+ # :nodoc:
83
+ # @api private
84
+ def self.assert_not_visited(key, visited)
85
+ if visited.key? key
86
+ raise "Invalid layout: circular extends with '#{key}'"
87
+ end
88
+ end
89
+
90
+ end
91
+ end