temple 0.10.4 → 0.10.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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +46 -0
- data/.github/workflows/test.yml +1 -1
- data/CHANGES +4 -0
- data/lib/temple/filters/static_analyzer.rb +1 -1
- data/lib/temple/filters/string_splitter.rb +61 -5
- data/lib/temple/parser_engine.rb +34 -0
- data/lib/temple/static_analyzer.rb +102 -56
- data/lib/temple/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 991a0b24405b8214645f3587f421886aa2f70d5e1dcb7a60e63174e8314bf0cd
|
|
4
|
+
data.tar.gz: 04ab064c8aa17d4baf2457cc6f97ae4f71ee801eeaa50ad0e2a1b4537e953bbc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba9e9141c61962b62b87247a77e70415145681ed35b590ba649781750098813597c40a334d45cfe7e3ae24a15cfe1d09ee32f6788c1aa600851432a8b6fe2c60
|
|
7
|
+
data.tar.gz: 88b97523630a72fa8d61d46a8e857cb3aaa26db025f0f919a52afef3b89fb372f89f3dafdfa4f2ce18a3c823003fad985f1a5d6516bbf8c63223f26a0159e302
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
push:
|
|
13
|
+
if: github.repository_owner == 'judofyr'
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
environment:
|
|
17
|
+
name: rubygems.org
|
|
18
|
+
url: https://rubygems.org/gems/temple
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: write
|
|
22
|
+
id-token: write
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Harden Runner
|
|
26
|
+
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
27
|
+
with:
|
|
28
|
+
egress-policy: audit
|
|
29
|
+
|
|
30
|
+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
31
|
+
|
|
32
|
+
- name: Set up Ruby
|
|
33
|
+
uses: ruby/setup-ruby@eaecf785f6a34567a6d97f686bbb7bccc1ac1e5c # v1.237.0
|
|
34
|
+
with:
|
|
35
|
+
bundler-cache: true
|
|
36
|
+
ruby-version: ruby
|
|
37
|
+
|
|
38
|
+
- name: Publish to RubyGems
|
|
39
|
+
uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1
|
|
40
|
+
|
|
41
|
+
- name: Create GitHub release
|
|
42
|
+
run: |
|
|
43
|
+
tag_name="$(git describe --tags --abbrev=0)"
|
|
44
|
+
gh release create "${tag_name}" --verify-tag --generate-notes
|
|
45
|
+
env:
|
|
46
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/.github/workflows/test.yml
CHANGED
data/CHANGES
CHANGED
|
@@ -4,7 +4,7 @@ module Temple
|
|
|
4
4
|
# Convert [:dynamic, code] to [:static, text] if code is static Ruby expression.
|
|
5
5
|
class StaticAnalyzer < Filter
|
|
6
6
|
def call(exp)
|
|
7
|
-
# Optimize only when
|
|
7
|
+
# Optimize only when a parser engine is available.
|
|
8
8
|
if ::Temple::StaticAnalyzer.available?
|
|
9
9
|
super
|
|
10
10
|
else
|
|
@@ -1,14 +1,70 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'ripper'
|
|
4
|
-
rescue LoadError
|
|
5
|
-
end
|
|
2
|
+
require 'temple/parser_engine'
|
|
6
3
|
|
|
7
4
|
module Temple
|
|
8
5
|
module Filters
|
|
9
6
|
# Compile [:dynamic, "foo#{bar}"] to [:multi, [:static, 'foo'], [:dynamic, 'bar']]
|
|
10
7
|
class StringSplitter < Filter
|
|
11
|
-
if
|
|
8
|
+
if PARSER_ENGINE == :prism
|
|
9
|
+
class << self
|
|
10
|
+
# `code` param must be valid string literal
|
|
11
|
+
def compile(code)
|
|
12
|
+
compiled = try_compile(code)
|
|
13
|
+
raise(FilterError, "Expected string literal") unless compiled
|
|
14
|
+
compiled
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def try_compile(code)
|
|
18
|
+
result = Prism.parse(code)
|
|
19
|
+
return unless result.success?
|
|
20
|
+
|
|
21
|
+
stmts = result.value.statements.body
|
|
22
|
+
return if stmts.size != 1
|
|
23
|
+
|
|
24
|
+
stack = [stmts.first]
|
|
25
|
+
compiled = []
|
|
26
|
+
|
|
27
|
+
until stack.empty?
|
|
28
|
+
case (node = stack.pop).type
|
|
29
|
+
when :interpolated_string_node
|
|
30
|
+
stack.concat(node.parts.reverse)
|
|
31
|
+
when :string_node
|
|
32
|
+
if !(unescaped = node.unescaped).empty?
|
|
33
|
+
compiled << [:static, unescaped]
|
|
34
|
+
end
|
|
35
|
+
when :embedded_statements_node
|
|
36
|
+
if (stmts = node.statements) && !(slice = stmts.slice).empty?
|
|
37
|
+
compiled << [:dynamic, slice]
|
|
38
|
+
end
|
|
39
|
+
when :embedded_variable_node
|
|
40
|
+
compiled << [:dynamic, node.variable.slice]
|
|
41
|
+
else
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
compiled
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def on_dynamic(code)
|
|
51
|
+
return [:dynamic, code] if code.include?("\n")
|
|
52
|
+
|
|
53
|
+
compiled = StringSplitter.try_compile(code)
|
|
54
|
+
return [:dynamic, code] unless compiled
|
|
55
|
+
|
|
56
|
+
temple = [:multi]
|
|
57
|
+
compiled.each do |type, content|
|
|
58
|
+
case type
|
|
59
|
+
when :static
|
|
60
|
+
temple << [:static, content]
|
|
61
|
+
when :dynamic
|
|
62
|
+
temple << on_dynamic(content)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
temple
|
|
66
|
+
end
|
|
67
|
+
elsif PARSER_ENGINE == :ripper
|
|
12
68
|
class << self
|
|
13
69
|
# `code` param must be valid string literal
|
|
14
70
|
def compile(code)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Temple
|
|
4
|
+
temple_parser_engine = ENV['TEMPLE_PARSER_ENGINE']
|
|
5
|
+
|
|
6
|
+
prism_available =
|
|
7
|
+
if !temple_parser_engine || temple_parser_engine == 'prism'
|
|
8
|
+
begin
|
|
9
|
+
require 'prism'
|
|
10
|
+
true
|
|
11
|
+
rescue LoadError
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ripper_available =
|
|
16
|
+
if !temple_parser_engine || temple_parser_engine == 'ripper'
|
|
17
|
+
begin
|
|
18
|
+
require 'ripper'
|
|
19
|
+
Ripper.respond_to?(:lex)
|
|
20
|
+
rescue LoadError
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
PARSER_ENGINE =
|
|
25
|
+
if prism_available
|
|
26
|
+
:prism
|
|
27
|
+
elsif ripper_available
|
|
28
|
+
:ripper
|
|
29
|
+
else
|
|
30
|
+
:none
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private_constant :PARSER_ENGINE
|
|
34
|
+
end
|
|
@@ -1,76 +1,122 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'ripper'
|
|
4
|
-
rescue LoadError
|
|
5
|
-
end
|
|
2
|
+
require 'temple/parser_engine'
|
|
6
3
|
|
|
7
4
|
module Temple
|
|
8
5
|
module StaticAnalyzer
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
:on_lparen, :on_rparen,
|
|
15
|
-
:on_lbrace, :on_rbrace, :on_label,
|
|
16
|
-
:on_int, :on_float, :on_imaginary,
|
|
17
|
-
:on_comma, :on_sp, :on_ignored_nl,
|
|
18
|
-
].freeze
|
|
6
|
+
if PARSER_ENGINE == :prism
|
|
7
|
+
class << self
|
|
8
|
+
def available?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
def static?(code)
|
|
13
|
+
return false if code.nil? || code.strip.empty?
|
|
14
|
+
(result = Prism.parse(code)).success? && static_node?(result.value)
|
|
15
|
+
end
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
def syntax_error?(code)
|
|
18
|
+
!Prism.parse_success?(code)
|
|
19
|
+
end
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
'=>',
|
|
30
|
-
].freeze
|
|
21
|
+
private
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
def static_node?(node)
|
|
24
|
+
case node.type
|
|
25
|
+
when :program_node
|
|
26
|
+
static_node?(node.statements)
|
|
27
|
+
when :parentheses_node
|
|
28
|
+
(stmts = node.body).is_a?(Prism::StatementsNode) &&
|
|
29
|
+
static_node?(stmts)
|
|
30
|
+
when :statements_node
|
|
31
|
+
node.body.size == 1 && static_node?(node.body.first)
|
|
32
|
+
when :interpolated_string_node
|
|
33
|
+
node.parts.all? { |part| static_node?(part) }
|
|
34
|
+
when :embedded_statements_node
|
|
35
|
+
(stmts = node.statements) && static_node?(stmts)
|
|
36
|
+
when :array_node
|
|
37
|
+
node.elements.all? { |elem| static_node?(elem) }
|
|
38
|
+
when :hash_node, :keyword_hash_node
|
|
39
|
+
node.elements.all? { |elem| static_node?(elem) }
|
|
40
|
+
when :assoc_node
|
|
41
|
+
static_node?(node.key) && static_node?(node.value)
|
|
42
|
+
when :string_node, :integer_node, :float_node, :imaginary_node,
|
|
43
|
+
:rational_node, :symbol_node, :true_node, :false_node, :nil_node
|
|
44
|
+
true
|
|
45
|
+
else
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
end
|
|
35
49
|
end
|
|
50
|
+
elsif PARSER_ENGINE == :ripper
|
|
51
|
+
STATIC_TOKENS = [
|
|
52
|
+
:on_tstring_beg, :on_tstring_end, :on_tstring_content,
|
|
53
|
+
:on_embexpr_beg, :on_embexpr_end,
|
|
54
|
+
:on_lbracket, :on_rbracket,
|
|
55
|
+
:on_qwords_beg, :on_words_sep, :on_qwords_sep,
|
|
56
|
+
:on_lparen, :on_rparen,
|
|
57
|
+
:on_lbrace, :on_rbrace, :on_label,
|
|
58
|
+
:on_int, :on_float, :on_imaginary,
|
|
59
|
+
:on_comma, :on_sp, :on_ignored_nl,
|
|
60
|
+
].freeze
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
62
|
+
DYNAMIC_TOKENS = [
|
|
63
|
+
:on_ident, :on_period,
|
|
64
|
+
].freeze
|
|
40
65
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
STATIC_KEYWORDS = [
|
|
67
|
+
'true', 'false', 'nil',
|
|
68
|
+
].freeze
|
|
69
|
+
|
|
70
|
+
STATIC_OPERATORS = [
|
|
71
|
+
'=>',
|
|
72
|
+
].freeze
|
|
73
|
+
|
|
74
|
+
class << self
|
|
75
|
+
def available?
|
|
76
|
+
true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def static?(code)
|
|
80
|
+
return false if code.nil? || code.strip.empty? || syntax_error?(code)
|
|
81
|
+
|
|
82
|
+
Ripper.lex(code).each do |_, token, str|
|
|
83
|
+
case token
|
|
84
|
+
when *STATIC_TOKENS
|
|
85
|
+
# noop
|
|
86
|
+
when :on_kw
|
|
87
|
+
return false unless STATIC_KEYWORDS.include?(str)
|
|
88
|
+
when :on_op
|
|
89
|
+
return false unless STATIC_OPERATORS.include?(str)
|
|
90
|
+
when *DYNAMIC_TOKENS
|
|
91
|
+
return false
|
|
92
|
+
else
|
|
93
|
+
return false
|
|
94
|
+
end
|
|
53
95
|
end
|
|
96
|
+
true
|
|
54
97
|
end
|
|
55
|
-
true
|
|
56
|
-
end
|
|
57
98
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
end
|
|
99
|
+
def syntax_error?(code)
|
|
100
|
+
SyntaxChecker.new(code).parse
|
|
101
|
+
false
|
|
102
|
+
rescue SyntaxChecker::ParseError
|
|
103
|
+
true
|
|
104
|
+
end
|
|
65
105
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class ParseError < StandardError; end
|
|
106
|
+
class SyntaxChecker < Ripper
|
|
107
|
+
class ParseError < StandardError; end
|
|
69
108
|
|
|
70
|
-
|
|
109
|
+
private
|
|
71
110
|
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
def on_parse_error(*)
|
|
112
|
+
raise ParseError
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
else
|
|
117
|
+
class << self
|
|
118
|
+
def available?
|
|
119
|
+
false
|
|
74
120
|
end
|
|
75
121
|
end
|
|
76
122
|
end
|
data/lib/temple/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: temple
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.10.
|
|
4
|
+
version: 0.10.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Magnus Holm
|
|
8
8
|
- Daniel Mendler
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: tilt
|
|
@@ -73,6 +73,7 @@ executables: []
|
|
|
73
73
|
extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
|
75
75
|
files:
|
|
76
|
+
- ".github/workflows/release.yml"
|
|
76
77
|
- ".github/workflows/test.yml"
|
|
77
78
|
- ".gitignore"
|
|
78
79
|
- ".yardopts"
|
|
@@ -126,6 +127,7 @@ files:
|
|
|
126
127
|
- lib/temple/mixins/options.rb
|
|
127
128
|
- lib/temple/mixins/template.rb
|
|
128
129
|
- lib/temple/parser.rb
|
|
130
|
+
- lib/temple/parser_engine.rb
|
|
129
131
|
- lib/temple/static_analyzer.rb
|
|
130
132
|
- lib/temple/templates.rb
|
|
131
133
|
- lib/temple/templates/rails.rb
|