klenod-build 0.0.23 → 0.0.25
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 +4 -4
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.json +37 -1
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.txt +1 -1
- data/lib/klenod/build/plugins/haml_plugin/parser.rb +21 -1
- data/lib/klenod/build/plugins/haml_plugin/transformer/ruby_builder.rb +6 -6
- data/lib/klenod/build/plugins/haml_plugin/transformer.rb +9 -1
- data/lib/klenod/build/plugins/markdown_compiler.rb +73 -94
- data/lib/klenod/build/plugins/text_interpolation.rb +153 -0
- data/lib/klenod/build/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ceb99aac4328e67769eebf0c137e9e2f8412bbfc65c51c0757906f7e5e1ce88f
|
|
4
|
+
data.tar.gz: 7bd420cb85700ace2a34e6e0460ee5f63f8a9b1530264f8b1f5d4a8f7f4583e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a8225fdd2f9208f17b2feeb832d03ed7bbb0fd5669bfd875fccdff579d05050a846f229996ccdce028e028d47b48cc70f24355b7a9384409d99c77e31a0a4a62
|
|
7
|
+
data.tar.gz: 0c5e2b51e492c0a56772ff6a9ed4309924a2da19e3a973c60782b1182605be59cec2cc3b10cc4c20a1641c21ae8102d9d37c3a6b00549fcf0e6a2b6347e84430
|
|
@@ -1106,6 +1106,15 @@
|
|
|
1106
1106
|
"unitsPerEm": 1000,
|
|
1107
1107
|
"xWidthAvg": 362
|
|
1108
1108
|
},
|
|
1109
|
+
"Asap Sharp": {
|
|
1110
|
+
"familyName": "Asap Sharp",
|
|
1111
|
+
"category": "sans-serif",
|
|
1112
|
+
"ascent": 934,
|
|
1113
|
+
"descent": -212,
|
|
1114
|
+
"lineGap": 0,
|
|
1115
|
+
"unitsPerEm": 1000,
|
|
1116
|
+
"xWidthAvg": 442
|
|
1117
|
+
},
|
|
1109
1118
|
"Asar": {
|
|
1110
1119
|
"familyName": "Asar",
|
|
1111
1120
|
"category": "serif",
|
|
@@ -2537,6 +2546,15 @@
|
|
|
2537
2546
|
"unitsPerEm": 16,
|
|
2538
2547
|
"xWidthAvg": 8
|
|
2539
2548
|
},
|
|
2549
|
+
"Caacupe One": {
|
|
2550
|
+
"familyName": "Caacupe One",
|
|
2551
|
+
"category": "display",
|
|
2552
|
+
"ascent": 960,
|
|
2553
|
+
"descent": -340,
|
|
2554
|
+
"lineGap": 0,
|
|
2555
|
+
"unitsPerEm": 1000,
|
|
2556
|
+
"xWidthAvg": 309
|
|
2557
|
+
},
|
|
2540
2558
|
"Cabin": {
|
|
2541
2559
|
"familyName": "Cabin",
|
|
2542
2560
|
"category": "sans-serif",
|
|
@@ -8187,7 +8205,7 @@
|
|
|
8187
8205
|
"descent": -120,
|
|
8188
8206
|
"lineGap": 0,
|
|
8189
8207
|
"unitsPerEm": 1000,
|
|
8190
|
-
"xWidthAvg":
|
|
8208
|
+
"xWidthAvg": 313
|
|
8191
8209
|
},
|
|
8192
8210
|
"Macondo": {
|
|
8193
8211
|
"familyName": "Macondo",
|
|
@@ -14993,6 +15011,15 @@
|
|
|
14993
15011
|
"unitsPerEm": 1000,
|
|
14994
15012
|
"xWidthAvg": 457
|
|
14995
15013
|
},
|
|
15014
|
+
"Scoutie Sans": {
|
|
15015
|
+
"familyName": "Scoutie Sans",
|
|
15016
|
+
"category": "sans-serif",
|
|
15017
|
+
"ascent": 2000,
|
|
15018
|
+
"descent": -600,
|
|
15019
|
+
"lineGap": 0,
|
|
15020
|
+
"unitsPerEm": 2000,
|
|
15021
|
+
"xWidthAvg": 920
|
|
15022
|
+
},
|
|
14996
15023
|
"Seaweed Script": {
|
|
14997
15024
|
"familyName": "Seaweed Script",
|
|
14998
15025
|
"category": "display",
|
|
@@ -16847,6 +16874,15 @@
|
|
|
16847
16874
|
"unitsPerEm": 1000,
|
|
16848
16875
|
"xWidthAvg": 400
|
|
16849
16876
|
},
|
|
16877
|
+
"Valley Sans": {
|
|
16878
|
+
"familyName": "Valley Sans",
|
|
16879
|
+
"category": "sans-serif",
|
|
16880
|
+
"ascent": 960,
|
|
16881
|
+
"descent": -240,
|
|
16882
|
+
"lineGap": 0,
|
|
16883
|
+
"unitsPerEm": 1000,
|
|
16884
|
+
"xWidthAvg": 487
|
|
16885
|
+
},
|
|
16850
16886
|
"Vampiro One": {
|
|
16851
16887
|
"familyName": "Vampiro One",
|
|
16852
16888
|
"category": "display",
|
|
@@ -5,7 +5,7 @@ Update them to the latest Capsize revision from the Klenod repository root:
|
|
|
5
5
|
bundle exec rake google_fonts:metrics:update
|
|
6
6
|
|
|
7
7
|
Project: https://github.com/seek-oss/capsize
|
|
8
|
-
Commit:
|
|
8
|
+
Commit: 8df97f098adc19d8a6d33df301757d7e9670bae6
|
|
9
9
|
Source: packages/metrics/src/entireMetricsCollection.json
|
|
10
10
|
|
|
11
11
|
MIT License
|
|
@@ -46,6 +46,7 @@ module Klenod
|
|
|
46
46
|
# invalid attribute list. Join the lines of an unclosed list first so
|
|
47
47
|
# both forms work across lines.
|
|
48
48
|
def parse_new_attributes(text)
|
|
49
|
+
tag_prefix = @line.text.strip[/\A[^(]*/]
|
|
49
50
|
joined_lines = 0
|
|
50
51
|
until (close_index = attribute_list_close_index(text)) || @next_line.eod?
|
|
51
52
|
text = "#{text} #{@next_line.text}"
|
|
@@ -56,7 +57,13 @@ module Klenod
|
|
|
56
57
|
pairs = close_index && extended_attribute_pairs(text[1...close_index])
|
|
57
58
|
rest = close_index && text[(close_index + 1)..]
|
|
58
59
|
unless pairs&.any? { |_name, value| value.match?(/[.\[]/) }
|
|
59
|
-
|
|
60
|
+
begin
|
|
61
|
+
attributes, rest, last_line = super
|
|
62
|
+
rescue ::Haml::SyntaxError => error
|
|
63
|
+
raise unless pairs && error.message.start_with?("Invalid attribute list")
|
|
64
|
+
|
|
65
|
+
raise ::Haml::SyntaxError.new("#{error.message}\n\n#{hash_attributes_hint(tag_prefix, pairs)}", error.line)
|
|
66
|
+
end
|
|
60
67
|
return [attributes, rest, last_line + joined_lines]
|
|
61
68
|
end
|
|
62
69
|
|
|
@@ -147,6 +154,19 @@ module Klenod
|
|
|
147
154
|
pairs
|
|
148
155
|
end
|
|
149
156
|
|
|
157
|
+
# Every value is valid Ruby, but some are neither Haml's bare variables
|
|
158
|
+
# nor the member and index expressions handled above. Suggest the
|
|
159
|
+
# equivalent hash attributes, which accept any Ruby expression.
|
|
160
|
+
def hash_attributes_hint(tag_prefix, pairs)
|
|
161
|
+
entries = pairs.map do |name, value|
|
|
162
|
+
key = name.match?(/\A[a-z_]\w*\z/i) ? name : name.inspect
|
|
163
|
+
"#{key}: #{value}"
|
|
164
|
+
end
|
|
165
|
+
tag_prefix = "" unless tag_prefix.match?(/\A[%.#][-:\w.#]*\z/)
|
|
166
|
+
|
|
167
|
+
"Hint:\n Use {} attributes for Ruby expressions: #{tag_prefix}{#{entries.join(", ")}}"
|
|
168
|
+
end
|
|
169
|
+
|
|
150
170
|
# Returns the index of the `)` that closes the list opened at index 0,
|
|
151
171
|
# ignoring brackets inside quoted values.
|
|
152
172
|
def attribute_list_close_index(source)
|
|
@@ -585,7 +585,7 @@ module Klenod
|
|
|
585
585
|
return source unless source.include?("__LINE__")
|
|
586
586
|
|
|
587
587
|
line_offsets = [0]
|
|
588
|
-
source.each_line(chomp: false) { |line| line_offsets << line_offsets.last + line.
|
|
588
|
+
source.each_line(chomp: false) { |line| line_offsets << line_offsets.last + line.bytesize }
|
|
589
589
|
|
|
590
590
|
Ripper
|
|
591
591
|
.lex(source)
|
|
@@ -593,7 +593,7 @@ module Klenod
|
|
|
593
593
|
.reverse_each
|
|
594
594
|
.each_with_object(source.dup) do |((line, column), _type, token, _state), rewritten|
|
|
595
595
|
offset = line_offsets.fetch(line - 1) + column
|
|
596
|
-
rewritten
|
|
596
|
+
rewritten.bytesplice(offset, token.bytesize, line_no.to_s)
|
|
597
597
|
end
|
|
598
598
|
end
|
|
599
599
|
|
|
@@ -607,7 +607,7 @@ module Klenod
|
|
|
607
607
|
return source if @variables.empty?
|
|
608
608
|
|
|
609
609
|
line_offsets = [0]
|
|
610
|
-
source.each_line(chomp: false) { |line| line_offsets << line_offsets.last + line.
|
|
610
|
+
source.each_line(chomp: false) { |line| line_offsets << line_offsets.last + line.bytesize }
|
|
611
611
|
|
|
612
612
|
tokens = Ripper.lex(source)
|
|
613
613
|
|
|
@@ -617,17 +617,17 @@ module Klenod
|
|
|
617
617
|
kind, prefix, pattern = VARIABLE_TOKEN_KINDS[type]
|
|
618
618
|
receiver = @variables[kind]
|
|
619
619
|
if type == :on_gvar && token == "$*" && receiver
|
|
620
|
-
[offset_for(line_offsets, line, column), token.
|
|
620
|
+
[offset_for(line_offsets, line, column), token.bytesize, receiver]
|
|
621
621
|
elsif receiver && token.match?(pattern)
|
|
622
622
|
name = token.delete_prefix(prefix)
|
|
623
623
|
replacement = "(#{receiver})[#{symbol_source(name)}]"
|
|
624
624
|
replacement = "{#{replacement}}" if tokens[index - 1]&.fetch(1) == :on_embvar
|
|
625
|
-
[offset_for(line_offsets, line, column), token.
|
|
625
|
+
[offset_for(line_offsets, line, column), token.bytesize, replacement]
|
|
626
626
|
end
|
|
627
627
|
end
|
|
628
628
|
.reverse_each
|
|
629
629
|
.each_with_object(source.dup) do |(offset, length, replacement), rewritten|
|
|
630
|
-
rewritten
|
|
630
|
+
rewritten.bytesplice(offset, length, replacement)
|
|
631
631
|
end
|
|
632
632
|
end
|
|
633
633
|
|
|
@@ -5,6 +5,7 @@ require "ripper"
|
|
|
5
5
|
|
|
6
6
|
require "klenod/runtime/source_map"
|
|
7
7
|
require_relative "../markdown_compiler"
|
|
8
|
+
require_relative "../text_interpolation"
|
|
8
9
|
require_relative "parser"
|
|
9
10
|
|
|
10
11
|
module Klenod
|
|
@@ -613,11 +614,18 @@ module Klenod
|
|
|
613
614
|
def compile_filter_node(node, builder:, markdown_compiler:)
|
|
614
615
|
return builder.render_ruby_filter(compile_ruby_filter(node, builder: builder)) if ruby_filter?(node)
|
|
615
616
|
return builder.expression(markdown_compiler.compile(node.value.fetch(:text), interpolate: true)) if markdown_filter?(node)
|
|
616
|
-
return
|
|
617
|
+
return compile_plain_filter(node.value.fetch(:text).to_s, builder: builder) if plain_filter?(node)
|
|
617
618
|
|
|
618
619
|
raise ArgumentError, "Only :ruby, :markdown, and :plain Haml filters are supported"
|
|
619
620
|
end
|
|
620
621
|
|
|
622
|
+
def compile_plain_filter(text, builder:)
|
|
623
|
+
source = TextInterpolation::Source.new(text)
|
|
624
|
+
return builder.literal(source.literal(source.text)) unless source.interpolated?
|
|
625
|
+
|
|
626
|
+
builder.expression("[#{source.expressions(source.text).join(", ")}]")
|
|
627
|
+
end
|
|
628
|
+
|
|
621
629
|
def ruby_filter?(node)
|
|
622
630
|
node.type == :filter && node.value.fetch(:name) == "ruby"
|
|
623
631
|
end
|
|
@@ -9,11 +9,13 @@ ensure
|
|
|
9
9
|
$VERBOSE = previous_verbose
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
require_relative "text_interpolation"
|
|
13
|
+
|
|
12
14
|
module Klenod
|
|
13
15
|
module Build
|
|
14
16
|
module Plugins
|
|
15
17
|
class MarkdownCompiler
|
|
16
|
-
|
|
18
|
+
TYPOGRAPHIC_TEXT = Kramdown::Converter::Html::TYPOGRAPHIC_SYMS.transform_values { |entities| entities.map(&:char).join }.freeze
|
|
17
19
|
|
|
18
20
|
def initialize(factory:, components_source: "{}")
|
|
19
21
|
@factory = factory
|
|
@@ -21,69 +23,95 @@ module Klenod
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def compile(source, interpolate: false)
|
|
24
|
-
|
|
25
|
-
document = Kramdown::Document.new(source, input: "GFM", hard_wrap: false)
|
|
26
|
-
compile_children(document.root.children
|
|
26
|
+
@interpolation = TextInterpolation::Source.new(source) if interpolate
|
|
27
|
+
document = Kramdown::Document.new(@interpolation&.text || source, input: "GFM", hard_wrap: false)
|
|
28
|
+
compile_children(document.root.children)
|
|
29
|
+
ensure
|
|
30
|
+
@interpolation = nil
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
private
|
|
30
34
|
|
|
31
|
-
def compile_children(children, parent: nil
|
|
32
|
-
expressions = compile_child_expressions(children, parent: parent
|
|
35
|
+
def compile_children(children, parent: nil)
|
|
36
|
+
expressions = compile_child_expressions(children, parent: parent)
|
|
33
37
|
return "nil" if expressions.empty?
|
|
34
38
|
return expressions.fetch(0) if expressions.length == 1
|
|
35
39
|
|
|
36
40
|
"[#{expressions.join(", ")}]"
|
|
37
41
|
end
|
|
38
42
|
|
|
39
|
-
def compile_child_expressions(children, parent:,
|
|
40
|
-
children.flat_map { |child| compile_node(child, parent: parent, table_section: table_section
|
|
43
|
+
def compile_child_expressions(children, parent:, table_section: nil)
|
|
44
|
+
merge_text_nodes(children).flat_map { |child| compile_node(child, parent: parent, table_section: table_section) }.compact
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Kramdown splits typographic replacements and entities out of text.
|
|
48
|
+
# Fold them back into neighboring text so "it's" stays one string.
|
|
49
|
+
def merge_text_nodes(children)
|
|
50
|
+
children.each_with_object([]) do |child, merged|
|
|
51
|
+
text = inline_text(child)
|
|
52
|
+
if text.nil?
|
|
53
|
+
merged << child
|
|
54
|
+
elsif merged.last&.type == :text
|
|
55
|
+
merged[-1] = Kramdown::Element.new(:text, merged.last.value + text)
|
|
56
|
+
else
|
|
57
|
+
merged << Kramdown::Element.new(:text, text)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def inline_text(node)
|
|
63
|
+
case node.type
|
|
64
|
+
when :text then node.value
|
|
65
|
+
when :smart_quote then Kramdown::Utils::Entities.entity(node.value.to_s).char
|
|
66
|
+
when :typographic_sym then TYPOGRAPHIC_TEXT.fetch(node.value)
|
|
67
|
+
when :entity then node.value.char
|
|
68
|
+
end
|
|
41
69
|
end
|
|
42
70
|
|
|
43
|
-
def compile_node(node, parent: nil, table_section: nil
|
|
71
|
+
def compile_node(node, parent: nil, table_section: nil)
|
|
44
72
|
case node.type
|
|
45
73
|
when :root
|
|
46
|
-
[compile_children(node.children, parent: node
|
|
74
|
+
[compile_children(node.children, parent: node)]
|
|
47
75
|
when :blank
|
|
48
76
|
nil
|
|
49
77
|
when :text
|
|
50
|
-
text_expressions(node.value
|
|
78
|
+
text_expressions(node.value)
|
|
51
79
|
when :p
|
|
52
|
-
return compile_child_expressions(node.children, parent: node
|
|
80
|
+
return compile_child_expressions(node.children, parent: node) if node.options[:transparent]
|
|
53
81
|
|
|
54
|
-
[factory_call(:p, node.children, attrs: node.attr, parent: node
|
|
82
|
+
[factory_call(:p, node.children, attrs: node.attr, parent: node)]
|
|
55
83
|
when :header
|
|
56
|
-
[factory_call(:"h#{node.options.fetch(:level)}", node.children, attrs: node
|
|
84
|
+
[factory_call(:"h#{node.options.fetch(:level)}", node.children, attrs: header_attrs(node), parent: node)]
|
|
57
85
|
when :em, :strong, :a, :blockquote, :ul, :ol, :li, :table
|
|
58
|
-
[factory_call(node.type, node.children, attrs: node.attr, parent: node
|
|
86
|
+
[factory_call(node.type, node.children, attrs: node.attr, parent: node)]
|
|
59
87
|
when :thead, :tbody
|
|
60
|
-
[factory_call(node.type, node.children, attrs: node.attr, parent: node, table_section: node.type
|
|
88
|
+
[factory_call(node.type, node.children, attrs: node.attr, parent: node, table_section: node.type)]
|
|
61
89
|
when :tr
|
|
62
|
-
[factory_call(node.type, node.children, attrs: node.attr, parent: node, table_section: table_section
|
|
90
|
+
[factory_call(node.type, node.children, attrs: node.attr, parent: node, table_section: table_section)]
|
|
63
91
|
when :td
|
|
64
|
-
[factory_call((table_section == :thead) ? :th : :td, node.children, attrs: node.attr, parent: node, table_section: table_section
|
|
92
|
+
[factory_call((table_section == :thead) ? :th : :td, node.children, attrs: node.attr, parent: node, table_section: table_section)]
|
|
65
93
|
when :img, :hr, :br
|
|
66
|
-
[factory_call(node.type, [], attrs: node.attr, parent: node
|
|
94
|
+
[factory_call(node.type, [], attrs: node.attr, parent: node)]
|
|
67
95
|
when :codespan
|
|
68
|
-
[factory_call(:code, [node], raw_text: node.value, attrs: node.attr, parent: node
|
|
96
|
+
[factory_call(:code, [node], raw_text: node.value, attrs: node.attr, parent: node)]
|
|
69
97
|
when :codeblock
|
|
70
|
-
code = factory_call(:code, [node], raw_text: node.value, attrs: node.attr, parent: node
|
|
71
|
-
[factory_call(:pre, [], child_sources: [code], parent: node
|
|
98
|
+
code = factory_call(:code, [node], raw_text: node.value, attrs: node.attr, parent: node)
|
|
99
|
+
[factory_call(:pre, [], child_sources: [code], parent: node)]
|
|
72
100
|
when :html_element
|
|
73
|
-
[factory_call(node.value.to_sym, node.children, attrs: node.attr, parent: node
|
|
101
|
+
[factory_call(node.value.to_sym, node.children, attrs: node.attr, parent: node)]
|
|
74
102
|
when :raw
|
|
75
|
-
[
|
|
103
|
+
[literal_text(node.value.to_s).inspect]
|
|
76
104
|
else
|
|
77
|
-
[compile_children(node.children, parent: node
|
|
105
|
+
[compile_children(node.children, parent: node)]
|
|
78
106
|
end
|
|
79
107
|
end
|
|
80
108
|
|
|
81
|
-
def factory_call(tag, children, parent:, attrs: {}, raw_text: nil, child_sources: nil, table_section: nil
|
|
109
|
+
def factory_call(tag, children, parent:, attrs: {}, raw_text: nil, child_sources: nil, table_section: nil)
|
|
82
110
|
child_sources ||=
|
|
83
111
|
if raw_text.nil?
|
|
84
|
-
compile_child_expressions(children, parent: parent, table_section: table_section
|
|
112
|
+
compile_child_expressions(children, parent: parent, table_section: table_section)
|
|
85
113
|
else
|
|
86
|
-
[
|
|
114
|
+
[literal_text(raw_text).inspect]
|
|
87
115
|
end
|
|
88
116
|
tag_source = tag_source(tag)
|
|
89
117
|
parts = [tag_source, *child_sources]
|
|
@@ -97,82 +125,33 @@ module Klenod
|
|
|
97
125
|
end
|
|
98
126
|
|
|
99
127
|
def attrs_source(attrs)
|
|
100
|
-
attrs.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def text_expressions(text, interpolate:)
|
|
104
|
-
return [restore_escaped_interpolation(text).inspect] unless interpolate && text.include?("\#{")
|
|
105
|
-
|
|
106
|
-
expressions = []
|
|
107
|
-
buffer = +""
|
|
108
|
-
index = 0
|
|
109
|
-
|
|
110
|
-
while index < text.length
|
|
111
|
-
if text[index, 2] == "\#{"
|
|
112
|
-
expression, next_index = read_interpolation(text, index + 2)
|
|
113
|
-
if expression
|
|
114
|
-
expressions << restore_escaped_interpolation(buffer).inspect unless buffer.empty?
|
|
115
|
-
buffer.clear
|
|
116
|
-
expressions << "(#{expression})"
|
|
117
|
-
index = next_index
|
|
118
|
-
else
|
|
119
|
-
buffer << text[index]
|
|
120
|
-
index += 1
|
|
121
|
-
end
|
|
122
|
-
else
|
|
123
|
-
buffer << text[index]
|
|
124
|
-
index += 1
|
|
125
|
-
end
|
|
128
|
+
pairs = attrs.map do |name, value|
|
|
129
|
+
value_source = value.is_a?(String) ? string_source(value) : value.inspect
|
|
130
|
+
"#{name.to_sym.inspect} => #{value_source}"
|
|
126
131
|
end
|
|
127
132
|
|
|
128
|
-
|
|
129
|
-
expressions
|
|
133
|
+
"{#{pairs.join(", ")}}"
|
|
130
134
|
end
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
while index < text.length
|
|
139
|
-
char = text[index]
|
|
140
|
-
|
|
141
|
-
if quote
|
|
142
|
-
expression << char
|
|
143
|
-
if escaped
|
|
144
|
-
escaped = false
|
|
145
|
-
elsif char == "\\"
|
|
146
|
-
escaped = true
|
|
147
|
-
elsif char == quote
|
|
148
|
-
quote = nil
|
|
149
|
-
end
|
|
150
|
-
else
|
|
151
|
-
case char
|
|
152
|
-
when "\"", "'", "`"
|
|
153
|
-
quote = char
|
|
154
|
-
when "{"
|
|
155
|
-
depth += 1
|
|
156
|
-
when "}"
|
|
157
|
-
depth -= 1
|
|
158
|
-
return [expression.strip, index + 1] if depth.zero?
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
expression << char
|
|
162
|
-
end
|
|
136
|
+
# Kramdown derives heading IDs from text that still contains
|
|
137
|
+
# interpolation tokens, which leaves stray separators behind.
|
|
138
|
+
def header_attrs(node)
|
|
139
|
+
id = node.attr["id"]
|
|
140
|
+
return node.attr unless id && @interpolation&.interpolated?(node.options[:raw_text].to_s)
|
|
163
141
|
|
|
164
|
-
|
|
165
|
-
|
|
142
|
+
node.attr.merge("id" => id.gsub(/-{2,}/, "-").delete_prefix("-").delete_suffix("-"))
|
|
143
|
+
end
|
|
166
144
|
|
|
167
|
-
|
|
145
|
+
def text_expressions(text)
|
|
146
|
+
@interpolation ? @interpolation.expressions(text) : [text.inspect]
|
|
168
147
|
end
|
|
169
148
|
|
|
170
|
-
def
|
|
171
|
-
|
|
149
|
+
def string_source(text)
|
|
150
|
+
@interpolation ? @interpolation.string_source(text) : text.inspect
|
|
172
151
|
end
|
|
173
152
|
|
|
174
|
-
def
|
|
175
|
-
|
|
153
|
+
def literal_text(text)
|
|
154
|
+
@interpolation ? @interpolation.literal(text) : text
|
|
176
155
|
end
|
|
177
156
|
end
|
|
178
157
|
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Klenod
|
|
4
|
+
module Build
|
|
5
|
+
module Plugins
|
|
6
|
+
# Compiles Ruby-style `#{...}` interpolation in filter text into Ruby
|
|
7
|
+
# source. `\#{` stays literal.
|
|
8
|
+
#
|
|
9
|
+
# Each interpolation is replaced by an opaque private-use token before
|
|
10
|
+
# the text is parsed further, so parsers such as Markdown never see the
|
|
11
|
+
# Ruby expression. Compile the parsed pieces back with the same Source.
|
|
12
|
+
module TextInterpolation
|
|
13
|
+
TOKEN_START = ""
|
|
14
|
+
TOKEN_END = ""
|
|
15
|
+
ESCAPE_TOKEN = ""
|
|
16
|
+
FIRST_INDEX_CODEPOINT = 0xE100
|
|
17
|
+
LAST_INDEX_CODEPOINT = 0xF8FF
|
|
18
|
+
TOKEN_PATTERN = /#{TOKEN_START}([\u{E100}-\u{F8FF}])#{TOKEN_END}/o
|
|
19
|
+
SEGMENT_PATTERN = /#{TOKEN_START}[\u{E100}-\u{F8FF}]#{TOKEN_END}|#{ESCAPE_TOKEN}/o
|
|
20
|
+
|
|
21
|
+
class Source
|
|
22
|
+
# The source with interpolations and escapes replaced by tokens.
|
|
23
|
+
attr_reader :text
|
|
24
|
+
|
|
25
|
+
def initialize(source)
|
|
26
|
+
@interpolations = []
|
|
27
|
+
@text = tokenize(source)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def interpolated?(text = @text)
|
|
31
|
+
text.match?(TOKEN_PATTERN)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns Ruby source for each literal and interpolated segment of
|
|
35
|
+
# tokenized text.
|
|
36
|
+
def expressions(text)
|
|
37
|
+
segments(text).map { |kind, value| (kind == :ruby) ? "(#{value})" : value.inspect }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns Ruby source for one String built from tokenized text.
|
|
41
|
+
def string_source(text)
|
|
42
|
+
parts = segments(text)
|
|
43
|
+
return literal(text).inspect if parts.none? { |kind, _| kind == :ruby }
|
|
44
|
+
|
|
45
|
+
body = parts.map { |kind, value| (kind == :ruby) ? "\#{#{value}}" : value.inspect[1...-1] }.join
|
|
46
|
+
"\"#{body}\""
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Restores tokenized text to its original characters, keeping
|
|
50
|
+
# interpolations as literal `#{...}` text.
|
|
51
|
+
def literal(text)
|
|
52
|
+
text.gsub(SEGMENT_PATTERN) do |token|
|
|
53
|
+
(token == ESCAPE_TOKEN) ? "\#{" : @interpolations.fetch(token_index(token)).fetch(:raw)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def segments(text)
|
|
60
|
+
segments = []
|
|
61
|
+
buffer = +""
|
|
62
|
+
|
|
63
|
+
text.split(/(#{SEGMENT_PATTERN})/o).each do |part|
|
|
64
|
+
if part == ESCAPE_TOKEN
|
|
65
|
+
buffer << "\#{"
|
|
66
|
+
elsif part.match?(TOKEN_PATTERN)
|
|
67
|
+
segments << [:text, buffer] unless buffer.empty?
|
|
68
|
+
buffer = +""
|
|
69
|
+
segments << [:ruby, @interpolations.fetch(token_index(part)).fetch(:expression)]
|
|
70
|
+
else
|
|
71
|
+
buffer << part
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
segments << [:text, buffer] unless buffer.empty?
|
|
76
|
+
segments
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def token_index(token)
|
|
80
|
+
token[TOKEN_PATTERN, 1].ord - FIRST_INDEX_CODEPOINT
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def tokenize(source)
|
|
84
|
+
output = +""
|
|
85
|
+
index = 0
|
|
86
|
+
|
|
87
|
+
while index < source.length
|
|
88
|
+
if source[index, 3] == "\\\#{"
|
|
89
|
+
output << ESCAPE_TOKEN
|
|
90
|
+
index += 3
|
|
91
|
+
elsif source[index, 2] == "\#{" && (interpolation = read_interpolation(source, index + 2))
|
|
92
|
+
expression, next_index = interpolation
|
|
93
|
+
output << token(expression, raw: source[index...next_index])
|
|
94
|
+
index = next_index
|
|
95
|
+
else
|
|
96
|
+
output << source[index]
|
|
97
|
+
index += 1
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
output
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def token(expression, raw:)
|
|
105
|
+
codepoint = FIRST_INDEX_CODEPOINT + @interpolations.length
|
|
106
|
+
raise ArgumentError, "Too many interpolations in one filter" if codepoint > LAST_INDEX_CODEPOINT
|
|
107
|
+
|
|
108
|
+
@interpolations << {expression: expression, raw: raw}
|
|
109
|
+
"#{TOKEN_START}#{codepoint.chr(Encoding::UTF_8)}#{TOKEN_END}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def read_interpolation(text, index)
|
|
113
|
+
expression = +""
|
|
114
|
+
depth = 1
|
|
115
|
+
quote = nil
|
|
116
|
+
escaped = false
|
|
117
|
+
|
|
118
|
+
while index < text.length
|
|
119
|
+
char = text[index]
|
|
120
|
+
|
|
121
|
+
if quote
|
|
122
|
+
expression << char
|
|
123
|
+
if escaped
|
|
124
|
+
escaped = false
|
|
125
|
+
elsif char == "\\"
|
|
126
|
+
escaped = true
|
|
127
|
+
elsif char == quote
|
|
128
|
+
quote = nil
|
|
129
|
+
end
|
|
130
|
+
else
|
|
131
|
+
case char
|
|
132
|
+
when "\"", "'", "`"
|
|
133
|
+
quote = char
|
|
134
|
+
when "{"
|
|
135
|
+
depth += 1
|
|
136
|
+
when "}"
|
|
137
|
+
depth -= 1
|
|
138
|
+
return [expression.strip, index + 1] if depth.zero?
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
expression << char
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
index += 1
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/klenod/build/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.0.25
|
|
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.
|
|
18
|
+
version: 0.0.25
|
|
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.
|
|
25
|
+
version: 0.0.25
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: async
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -293,6 +293,7 @@ files:
|
|
|
293
293
|
- lib/klenod/build/plugins/ruby_plugin.rb
|
|
294
294
|
- lib/klenod/build/plugins/static_asset_plugin.rb
|
|
295
295
|
- lib/klenod/build/plugins/svg_plugin.rb
|
|
296
|
+
- lib/klenod/build/plugins/text_interpolation.rb
|
|
296
297
|
- lib/klenod/build/profiler.rb
|
|
297
298
|
- lib/klenod/build/resolution_error_formatter.rb
|
|
298
299
|
- lib/klenod/build/resolver.rb
|