klenod-build 0.0.20 → 0.0.22

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bbeb7b858f12c3e373b4ddfc2a1d1c097048c84a24bf62babdd9b6abfad0903
4
- data.tar.gz: 743d7dbbb2850cb175e0ba54dafd2989fd66a2c7ad653338d989f7953a6b50e6
3
+ metadata.gz: d7873fce94ba35f8420b6aa896bca1866896d5c9f038102f0d13522fb1fa9a16
4
+ data.tar.gz: 715c13a86148ae750ed6151f05e4fbcb32d1d73bf21d139d1b21d42f7bb405f4
5
5
  SHA512:
6
- metadata.gz: 39651b21e98ac61f0f4f1e2f014f036a815b317d21107b6422d20767a6e0117227f640ad19c1436251a58b8c4a1e7b7d8c2c52b26776b0ce28e6d9a8a0199332
7
- data.tar.gz: 1d4c523e9fff5640b5bf724067c924bfc8f869aca77633e5566388de5365ec6d114cf92972b5737a2067f8ac4d2064911c4df8f9e3d0115bd8839a0829d1a78b
6
+ metadata.gz: 29157efe18cfdd5982f05ef742f07bb56ee64f54c820b152569be703b76a97dc76f71c166b67ff57e8b5124bed59f16ecbcf39415118dba3672a304854d4fbfa
7
+ data.tar.gz: 0a0b9e45f353aa70004c01ed9f78d1fe6ef667c7eb4faa5e7c64d038087ed82501c7f77755ed5e67dd26504ac122ee14a7c5414164b36c5933afae86df89df37
@@ -23,6 +23,21 @@ module Klenod
23
23
  # frozen_string_literal: true
24
24
 
25
25
  module Default
26
+ def self.capture
27
+ captures = (Thread.current[:__klenod_haml_captures__] ||= [])
28
+ output = []
29
+ captures << output
30
+ yield
31
+ output
32
+ ensure
33
+ captures&.pop
34
+ end
35
+
36
+ def self.append_capture(value)
37
+ Thread.current.fetch(:__klenod_haml_captures__).last << value
38
+ value
39
+ end
40
+
26
41
  def self.merge_props(component_class, *sources)
27
42
  result = {}
28
43
  classes = []
@@ -46,12 +61,7 @@ module Klenod
46
61
  end
47
62
 
48
63
  def self.normalize_prop_key(key)
49
- return key if key.is_a?(Symbol)
50
-
51
- name = key.to_s
52
- return key if name.include?("-")
53
-
54
- name.to_sym
64
+ key.to_s.tr("-", "_").to_sym
55
65
  end
56
66
 
57
67
  def self.collect_class_values(classes, value)
@@ -355,7 +355,9 @@ module Klenod
355
355
  def parenthesized_expression(source, line_no: nil)
356
356
  source = rewrite_ruby_source(source, line_no)
357
357
  node = parse_expression(source, context: :parenthesized_expression)
358
- return expression("(#{source})") unless node
358
+ unless node
359
+ raise_ruby_parse_error(source, line_no: line_no, context: "Could not parse Haml output script", syntax_error: true)
360
+ end
359
361
 
360
362
  fragment(Paren(LParen("("), Statements([node])))
361
363
  end
@@ -438,15 +440,18 @@ module Klenod
438
440
 
439
441
  def silent_script_block(source, body, line_no: nil)
440
442
  source = rewrite_ruby_source(source, nil)
441
- ast_silent_script_block(source, body) || raise_ruby_parse_error(source, line_no: line_no, context: "Could not build Ruby block from Haml script")
443
+ ast_silent_script_block(source, body) || raise_ruby_parse_error(source, line_no: line_no, context: "Could not build Ruby block from Haml script", syntax_error: true)
442
444
  end
443
445
 
444
- def silent_script(source)
446
+ def silent_script(source, line_no: nil)
445
447
  source = rewrite_ruby_source(source, nil)
446
- ast_silent_script(source) || raise(ArgumentError, "Could not build Ruby begin block from Haml script: #{source.inspect}")
448
+ fragment = ast_silent_script(source)
449
+ return fragment if fragment
450
+
451
+ raise_ruby_parse_error(source, line_no: line_no, context: "Could not parse Haml silent script", syntax_error: true)
447
452
  end
448
453
 
449
- def silent_script_with_children(source, body)
454
+ def silent_script_with_children(source, body, line_no: nil)
450
455
  source = rewrite_ruby_source(source, nil)
451
456
  return_with_children = false
452
457
 
@@ -464,7 +469,9 @@ module Klenod
464
469
  end
465
470
 
466
471
  statements = parse_statements(source)
467
- return raise(ArgumentError, "Could not parse Haml silent script: #{source.inspect}") unless statements
472
+ unless statements
473
+ raise_ruby_parse_error(source, line_no: line_no, context: "Could not parse Haml silent script", syntax_error: true)
474
+ end
468
475
 
469
476
  return Fragment.new(source, statements) if return_with_children
470
477
 
@@ -472,12 +479,39 @@ module Klenod
472
479
  Fragment.new(["begin", indent(source, 2), indent(to_source(body), 2), "end"].join("\n"), node)
473
480
  end
474
481
 
475
- def branches(branches)
476
- ast_branches(branches) || raise(ArgumentError, "Could not build Ruby branch from Haml scripts: #{branches.map(&:first).inspect}")
482
+ def keyword_script?(source)
483
+ source.start_with?("while ", "until ", "for ")
484
+ end
485
+
486
+ def keyword_script(source, body, line_no: nil)
487
+ source = rewrite_ruby_source(source, nil)
488
+ source = block_source(source, body)
489
+ node = parse_expression(source, context: :keyword_script)
490
+ return Fragment.new(source, node) if node
491
+
492
+ raise_ruby_parse_error(source, line_no: line_no, context: "Could not build Ruby control-flow block from Haml script", syntax_error: true)
477
493
  end
478
494
 
479
- def silent_branches(branches)
480
- ast_silent_branches(branches) || raise(ArgumentError, "Could not build Ruby branch from Haml scripts: #{branches.map(&:first).inspect}")
495
+ def silent_keyword_script(source, body, line_no: nil)
496
+ captured_script = captured_block_source(source, body)
497
+ node = parse_expression(captured_script, context: :keyword_script)
498
+ return Fragment.new(captured_script, node) if node
499
+
500
+ raise_ruby_parse_error(captured_script, line_no: line_no, context: "Could not build Ruby control-flow block from Haml script", syntax_error: true)
501
+ end
502
+
503
+ def branches(branches, line_no: nil)
504
+ fragment = ast_branches(branches)
505
+ return fragment if fragment
506
+
507
+ raise_ruby_parse_error(branch_source(branches), line_no: line_no, context: "Could not parse Haml output branches", syntax_error: true)
508
+ end
509
+
510
+ def silent_branches(branches, line_no: nil)
511
+ fragment = ast_silent_branches(branches)
512
+ return fragment if fragment
513
+
514
+ raise_ruby_parse_error(branch_source(branches), line_no: line_no, context: "Could not parse Haml silent branches", syntax_error: true)
481
515
  end
482
516
 
483
517
  def ruby_filters(nodes)
@@ -631,7 +665,7 @@ module Klenod
631
665
  end
632
666
 
633
667
  def ast_silent_script(source)
634
- statements = parse_statements(source)
668
+ statements = parse_statements(source) || control_flow_statements(source)
635
669
  return nil unless statements
636
670
 
637
671
  node = ast_begin([*statement_body_for(statements), nil_node])
@@ -639,6 +673,17 @@ module Klenod
639
673
  Fragment.new(["begin", indent(source, 2), " nil", "end"].join("\n"), node)
640
674
  end
641
675
 
676
+ # SyntaxTree rejects `next` and `break` at top level even though a
677
+ # silent script can be nested in an iterator or keyword loop. Parse
678
+ # those statements inside a harmless temporary loop, then retain
679
+ # only its body for the surrounding generated block.
680
+ def control_flow_statements(source)
681
+ return unless source.match?(/\A(?:next|break)\b/)
682
+
683
+ wrapper = parse_expression(["loop do", indent(source, 2), "end"].join("\n"), context: :control_flow_statement)
684
+ wrapper&.block&.bodystmt&.statements
685
+ end
686
+
642
687
  def ast_script_block(source, body)
643
688
  node = block_script_node(source, body)
644
689
  return nil unless node
@@ -647,28 +692,30 @@ module Klenod
647
692
  end
648
693
 
649
694
  def ast_silent_script_block(source, body)
650
- node = block_script_node(source, body)
695
+ source = captured_block_source(source, body)
696
+ node = parse_expression(source, context: :block_script)
651
697
  return nil unless node
652
698
 
653
- node = ast_begin([node, nil_node])
654
-
655
- Fragment.new(["begin", indent(block_source(source, body), 2), " nil", "end"].join("\n"), node)
699
+ Fragment.new(source, node)
656
700
  end
657
701
 
658
702
  def ast_branches(branches)
659
- node = branch_node(branches)
703
+ source = branch_source(branches)
704
+ node = parse_expression(source, context: :branches)
660
705
  return nil unless node
661
706
 
662
- Fragment.new(branch_source(branches), node)
707
+ Fragment.new(source, node)
663
708
  end
664
709
 
665
710
  def ast_silent_branches(branches)
666
711
  node = branch_node(branches)
667
712
  return nil unless node
668
713
 
669
- node = ast_begin([node, nil_node])
714
+ Fragment.new(branch_source(branches), node)
715
+ end
670
716
 
671
- Fragment.new(["begin", indent(branch_source(branches), 2), " nil", "end"].join("\n"), node)
717
+ def branch_node(branches)
718
+ parse_expression(branch_source(branches), context: :branches)
672
719
  end
673
720
 
674
721
  def ast_ruby_filters(nodes)
@@ -712,11 +759,24 @@ module Klenod
712
759
  end
713
760
  end
714
761
 
762
+ # Ruby iterators and keyword loops return their control value rather
763
+ # than the values produced by their bodies. Capture each compiled
764
+ # Haml child explicitly, while leaving `return`, `next`, and `break`
765
+ # in their original Ruby block/loop context.
766
+ def captured_block_source(source, body)
767
+ captured_body = "HamlHelper.append_capture(#{argument_source(body)})"
768
+ script = block_source(source, Fragment.new(captured_body, nil))
769
+ ["HamlHelper.capture do", indent(script, 2), "end"].join("\n")
770
+ end
771
+
715
772
  def branch_source(branches)
716
773
  body =
717
774
  branches
718
- .map do |source, body|
719
- if source == "else"
775
+ .each_with_index
776
+ .map do |(source, body), index|
777
+ if index.zero? && source.match?(/\Acase\b/)
778
+ source
779
+ elsif source == "else"
720
780
  ["else", indent(to_source(body), 2)].join("\n")
721
781
  else
722
782
  [source, indent(to_source(body), 2)].join("\n")
@@ -737,78 +797,6 @@ module Klenod
737
797
  )
738
798
  end
739
799
 
740
- def branch_node(branches)
741
- first_source, first_body = branches.fetch(0)
742
- if first_source.match?(/\Acase\b/) && nil_fragment?(first_body)
743
- case_node(first_source, branches.drop(1))
744
- else
745
- if_node(branches)
746
- end
747
- end
748
-
749
- def if_node(branches)
750
- source, body = branches.fetch(0)
751
- return else_node(source, body) if source == "else"
752
-
753
- predicate_source =
754
- case source
755
- when /\Aif\s+(.+)\z/ then $1
756
- when /\Aelsif\s+(.+)\z/ then $1
757
- when /\Aunless\s+(.+)\z/ then "!(#{$1})"
758
- else return nil
759
- end
760
- predicate = parse_expression(predicate_source, context: :branch_predicate)
761
- return nil unless predicate
762
-
763
- consequent =
764
- if branches.length > 1
765
- if_node(branches.drop(1))
766
- end
767
-
768
- if source.start_with?("elsif")
769
- Elsif(predicate, Statements(statement_body_for(body)), consequent)
770
- else
771
- IfNode(predicate, Statements(statement_body_for(body)), consequent)
772
- end
773
- end
774
-
775
- def else_node(source, body)
776
- return nil unless source == "else"
777
-
778
- Else(Kw("else"), Statements(statement_body_for(body)))
779
- end
780
-
781
- def case_node(source, branches)
782
- value_source = source[/\Acase\s*(.*)\z/, 1]
783
- return nil unless value_source
784
-
785
- value = value_source.empty? ? nil : parse_expression(value_source, context: :case_value)
786
- consequent = when_node(branches)
787
- return nil unless consequent
788
-
789
- Case(Kw("case"), value, consequent)
790
- end
791
-
792
- def when_node(branches)
793
- source, body = branches.fetch(0)
794
- return else_node(source, body) if source == "else"
795
- return nil unless source.start_with?("when ")
796
-
797
- arguments =
798
- source
799
- .delete_prefix("when ")
800
- .split(",")
801
- .map { |argument| parse_expression(argument.strip, context: :when_argument) }
802
- return nil if arguments.any?(&:nil?)
803
-
804
- consequent =
805
- if branches.length > 1
806
- when_node(branches.drop(1))
807
- end
808
-
809
- When(Args(arguments), Statements(statement_body_for(body)), consequent)
810
- end
811
-
812
800
  def block_script_node(source, body)
813
801
  node = parse_expression(fix_syntax_by_adding_missing_pairs(source), context: :block_script)
814
802
  return nil unless node.is_a?(SyntaxTree::MethodAddBlock)
@@ -956,10 +944,6 @@ module Klenod
956
944
  Comment(value, false)
957
945
  end
958
946
 
959
- def nil_fragment?(value)
960
- value.is_a?(Fragment) && value.node.is_a?(SyntaxTree::VarRef) && to_source(value) == "nil"
961
- end
962
-
963
947
  def node_for(value)
964
948
  return value.node if value.is_a?(Fragment)
965
949
 
@@ -1060,7 +1044,7 @@ module Klenod
1060
1044
  [source, *left_right.missing].join("\n")
1061
1045
  end
1062
1046
 
1063
- def raise_ruby_parse_error(source, line_no:, context:)
1047
+ def raise_ruby_parse_error(source, line_no:, context:, syntax_error: false)
1064
1048
  parse_error = syntax_tree_parse_error(source)
1065
1049
  explain =
1066
1050
  SyntaxSuggest::ExplainSyntax.new(
@@ -1070,6 +1054,7 @@ module Klenod
1070
1054
  missing = explain.missing.map { |item| explain.why(item) } - errors
1071
1055
 
1072
1056
  message = [context]
1057
+ message[0] += " (Ruby syntax error)" if syntax_error
1073
1058
  message << "Errors:\n #{errors.join("\n ")}" unless errors.empty?
1074
1059
  message << "Missing:\n #{missing.join("\n ")}" unless missing.empty?
1075
1060
 
@@ -352,7 +352,7 @@ module Klenod
352
352
  return compile_node(nodes[0], factory: factory, styleable: styleable, builder: builder, markdown_compiler: markdown_compiler) if nodes.length == 1
353
353
 
354
354
  compile_branches(
355
- nodes.map { |node| [script_source(node, builder: builder), node.children] },
355
+ nodes.map { |node| [script_source(node, builder: builder), node.children, node.line] },
356
356
  factory: factory,
357
357
  styleable: styleable,
358
358
  builder: builder,
@@ -362,7 +362,15 @@ module Klenod
362
362
 
363
363
  def compile_script(node, factory:, builder:, markdown_compiler:, styleable: false)
364
364
  source = script_source(node, builder: builder)
365
- return builder.parenthesized_expression(source) if node.children.empty?
365
+ return builder.parenthesized_expression(source, line_no: node.line) if node.children.empty?
366
+
367
+ if builder.keyword_script?(source)
368
+ return builder.keyword_script(
369
+ source,
370
+ compile_nodes(node.children, factory: factory, styleable: styleable, builder: builder, markdown_compiler: markdown_compiler),
371
+ line_no: node.line
372
+ )
373
+ end
366
374
 
367
375
  builder.script_block(
368
376
  source,
@@ -373,7 +381,7 @@ module Klenod
373
381
 
374
382
  def compile_script_branch(node, factory:, builder:, markdown_compiler:, styleable: false)
375
383
  compile_branches(
376
- [[script_source(node, builder: builder), node.children]],
384
+ [[script_source(node, builder: builder), node.children, node.line]],
377
385
  factory: factory,
378
386
  styleable: styleable,
379
387
  builder: builder,
@@ -383,7 +391,7 @@ module Klenod
383
391
 
384
392
  def compile_silent_script(node, factory:, builder:, markdown_compiler:, styleable: false)
385
393
  source = script_source(node, builder: builder)
386
- return builder.silent_script(source) if node.children.empty?
394
+ return builder.silent_script(source, line_no: node.line) if node.children.empty?
387
395
  if builder.block_script?(source)
388
396
  return builder.silent_script_block(
389
397
  source,
@@ -392,10 +400,19 @@ module Klenod
392
400
  )
393
401
  end
394
402
 
395
- unless source.start_with?("if ", "unless ", "case ")
403
+ if builder.keyword_script?(source)
404
+ return builder.silent_keyword_script(
405
+ source,
406
+ compile_nodes(node.children, factory: factory, styleable: styleable, builder: builder, markdown_compiler: markdown_compiler),
407
+ line_no: node.line
408
+ )
409
+ end
410
+
411
+ unless source.start_with?("if ", "unless ", "case ", "begin")
396
412
  return builder.silent_script_with_children(
397
413
  source,
398
- compile_nodes(node.children, factory: factory, styleable: styleable, builder: builder, markdown_compiler: markdown_compiler)
414
+ compile_nodes(node.children, factory: factory, styleable: styleable, builder: builder, markdown_compiler: markdown_compiler),
415
+ line_no: node.line
399
416
  )
400
417
  end
401
418
 
@@ -410,17 +427,19 @@ module Klenod
410
427
 
411
428
  def compile_branches(branches, factory:, builder:, markdown_compiler:, styleable: false)
412
429
  builder.branches(
413
- branches.map do |source, children|
430
+ branches.map do |source, children, _line_no|
414
431
  [source, compile_nodes(children, factory: factory, styleable: styleable, builder: builder, markdown_compiler: markdown_compiler)]
415
- end
432
+ end,
433
+ line_no: branches.first.fetch(2)
416
434
  )
417
435
  end
418
436
 
419
437
  def compile_silent_branches(branches, factory:, builder:, markdown_compiler:, styleable: false)
420
438
  builder.silent_branches(
421
- branches.map do |source, children|
439
+ branches.map do |source, children, _line_no|
422
440
  [source, compile_nodes(children, factory: factory, styleable: styleable, builder: builder, markdown_compiler: markdown_compiler)]
423
- end
441
+ end,
442
+ line_no: branches.first.fetch(2)
424
443
  )
425
444
  end
426
445
 
@@ -428,18 +447,20 @@ module Klenod
428
447
  branches = []
429
448
  current_source = script_source(node, builder: builder)
430
449
  current_children = []
450
+ current_line = node.line
431
451
 
432
452
  node.children.each do |child|
433
453
  if continuation?(child)
434
- branches << [current_source, current_children]
454
+ branches << [current_source, current_children, current_line]
435
455
  current_source = script_source(child, builder: builder)
436
456
  current_children = child.children.dup
457
+ current_line = child.line
437
458
  else
438
459
  current_children << child
439
460
  end
440
461
  end
441
462
 
442
- branches << [current_source, current_children]
463
+ branches << [current_source, current_children, current_line]
443
464
  end
444
465
 
445
466
  def script_node?(node)
@@ -184,6 +184,7 @@ module Klenod
184
184
 
185
185
  def supported_plus_file?(path)
186
186
  basename = path.basename.to_s
187
+ return true if basename.match?(/\A\+.*\.test\.rb\z/)
187
188
  return true if basename == "+route.rb"
188
189
 
189
190
  ROUTE_FILE_BASENAMES.any? do |route_basename|
@@ -126,8 +126,8 @@ module Klenod
126
126
  Default =
127
127
  SvgRuntime::SvgMetadata.new(
128
128
  src: #{asset.url.inspect},
129
- width: #{asset.metadata[:width].inspect},
130
- height: #{asset.metadata[:height].inspect},
129
+ width: #{rounded_dimension(asset.metadata[:width]).inspect},
130
+ height: #{rounded_dimension(asset.metadata[:height]).inspect},
131
131
  content_type: #{asset.content_type.inspect},
132
132
  aspect_ratio: #{AssetJavaScriptMetadata.aspect_ratio(asset.metadata[:width], asset.metadata[:height]).inspect}
133
133
  )
@@ -152,8 +152,8 @@ module Klenod
152
152
  def javascript_svg_module_source(asset, context)
153
153
  metadata = {
154
154
  src: asset.url,
155
- width: asset.metadata[:width],
156
- height: asset.metadata[:height],
155
+ width: rounded_dimension(asset.metadata[:width]),
156
+ height: rounded_dimension(asset.metadata[:height]),
157
157
  contentType: asset.content_type,
158
158
  aspectRatio: AssetJavaScriptMetadata.aspect_ratio(asset.metadata[:width], asset.metadata[:height])
159
159
  }
@@ -175,6 +175,12 @@ module Klenod
175
175
  view_box_dimensions(attributes["viewBox"] || attributes["viewbox"])
176
176
  end
177
177
 
178
+ # Keep intrinsic dimensions precise in asset metadata so the aspect
179
+ # ratio is accurate, but expose pixel dimensions as whole numbers.
180
+ def rounded_dimension(dimension)
181
+ dimension&.round
182
+ end
183
+
178
184
  # Malformed markup is tolerated: no <svg> match just means unknown
179
185
  # dimensions. Only a file that is not text at all fails here, and
180
186
  # String#match raises without saying which file it was reading.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Klenod
4
4
  module Build
5
- VERSION = "0.0.20"
5
+ VERSION = "0.0.22"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klenod-build
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.20
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrés Alin
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.0.20
18
+ version: 0.0.22
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.0.20
25
+ version: 0.0.22
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: async
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -327,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
327
327
  - !ruby/object:Gem::Version
328
328
  version: '0'
329
329
  requirements: []
330
- rubygems_version: 4.0.16
330
+ rubygems_version: 4.0.20
331
331
  specification_version: 4
332
332
  summary: Build system for Klenod.
333
333
  test_files: []