temple 0.10.4 → 0.10.6
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 +2 -1
- data/CHANGES +8 -0
- data/README.md +1 -1
- 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 +130 -60
- 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: 36199c665624018e6c8bfec5ff6d1e19bcd8015ee4aeedd7800b264136227b7b
|
|
4
|
+
data.tar.gz: f70ee914c911f6f7ad17c84f81fd88dc5cc6d974c37b2d773995bbcc03b17c5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3661349e66534eaed2be550577677c3a4457ed0fe4e2ec5c3b8a6dced46fa45d5aa6be774662cf84186ead6954ebcb51ff53357abf93c64093b559fb22f22ec
|
|
7
|
+
data.tar.gz: 4e81de0ec56c60964b9363bf87b270191a39d19a0fd0710dc9e07b0baaadf273a8a84d6c37a733edd55ab55f90bba7d4a1b676b50817f1ba1566efdb29e8a911
|
|
@@ -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
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Temple
|
|
2
2
|
======
|
|
3
3
|
|
|
4
|
-
[](https://github.com/judofyr/temple/actions/workflows/test.yml) [](https://github.com/judofyr/temple/actions/workflows/test.yml) [](https://qlty.sh/gh/judofyr/projects/temple) [](https://rubygems.org/gems/temple) [](http://rubydoc.info/gems/temple/frames)
|
|
5
5
|
|
|
6
6
|
Temple is an abstraction and a framework for compiling templates to pure Ruby.
|
|
7
7
|
It's all about making it easier to experiment, implement and optimize template
|
|
@@ -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,146 @@
|
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'true', 'false', 'nil',
|
|
26
|
-
].freeze
|
|
27
|
-
|
|
28
|
-
STATIC_OPERATORS = [
|
|
29
|
-
'=>',
|
|
30
|
-
].freeze
|
|
31
|
-
|
|
32
|
-
class << self
|
|
33
|
-
def available?
|
|
34
|
-
defined?(Ripper) && Ripper.respond_to?(:lex)
|
|
35
|
-
end
|
|
6
|
+
if PARSER_ENGINE == :prism
|
|
7
|
+
class << self
|
|
8
|
+
def available?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
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
|
|
16
|
+
|
|
17
|
+
def syntax_error?(code)
|
|
18
|
+
!Prism.parse_success?(code)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
36
22
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
51
45
|
else
|
|
52
|
-
|
|
46
|
+
false
|
|
53
47
|
end
|
|
54
48
|
end
|
|
55
|
-
true
|
|
56
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
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
rescue SyntaxChecker::ParseError
|
|
62
|
-
true
|
|
63
|
-
end
|
|
64
|
-
end
|
|
62
|
+
DYNAMIC_TOKENS = [
|
|
63
|
+
:on_ident, :on_period,
|
|
64
|
+
].freeze
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
STATIC_KEYWORDS = [
|
|
67
|
+
'true', 'false', 'nil',
|
|
68
|
+
].freeze
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
STATIC_OPERATORS = [
|
|
71
|
+
'=>',
|
|
72
|
+
].freeze
|
|
73
|
+
|
|
74
|
+
class << self
|
|
75
|
+
def available?
|
|
76
|
+
true
|
|
77
|
+
end
|
|
71
78
|
|
|
72
|
-
def
|
|
73
|
-
|
|
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
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
!ShorthandSyntaxChecker.shorthand?(code)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def syntax_error?(code)
|
|
101
|
+
SyntaxChecker.new(code).parse
|
|
102
|
+
false
|
|
103
|
+
rescue SyntaxChecker::ParseError
|
|
104
|
+
true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class SyntaxChecker < Ripper
|
|
108
|
+
class ParseError < StandardError; end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def on_parse_error(*)
|
|
113
|
+
raise ParseError
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class ShorthandSyntaxChecker < Ripper
|
|
118
|
+
class << self
|
|
119
|
+
def shorthand?(code)
|
|
120
|
+
instance = new(code)
|
|
121
|
+
instance.parse
|
|
122
|
+
instance.shorthand
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
attr_reader :shorthand
|
|
127
|
+
|
|
128
|
+
def initialize(*)
|
|
129
|
+
super
|
|
130
|
+
@shorthand = nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def on_assoc_new(key, value)
|
|
136
|
+
@shorthand = true if value.nil?
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
else
|
|
141
|
+
class << self
|
|
142
|
+
def available?
|
|
143
|
+
false
|
|
74
144
|
end
|
|
75
145
|
end
|
|
76
146
|
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.6
|
|
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
|