thot 1.0.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +18 -0
- data/Gemfile +0 -1
- data/README.md +86 -29
- data/VERSION +1 -1
- data/assets/images/logo_thot_full_large.png +0 -0
- data/assets/images/logo_thot_light_large.png +0 -0
- data/assets/images/logo_thot_normal_large.png +0 -0
- data/exe/thot +46 -19
- data/lib/dependencies.rb +11 -0
- data/lib/thot/cli.rb +27 -29
- data/lib/thot/processor.rb +24 -0
- data/lib/thot/rake/manage.rb +20 -0
- data/lib/thot/rake/tasks/template.task +13 -0
- data/lib/thot/template.rb +127 -0
- data/lib/thot/tokenizer.rb +39 -0
- data/lib/thot/varfiles.rb +33 -0
- data/lib/thot.rb +1 -124
- data/samples/.thot.env +5 -0
- data/samples/template.ttl +21 -0
- data/thot.gemspec +3 -0
- metadata +44 -4
- data/samples/template.txt +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8633ef1b1cb491681966a9cb0439d1e3d8bb69766291623757b0f10fb13ed27
|
4
|
+
data.tar.gz: b7f7297728de329b92fcb064aeeff5d92249b29d9aeb5c430a0a4cf767a4829c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cda6359c5666cf77c09bd903217a98120ed994a1bc3993409f4decc4781751b5fee290fcb8eb02afdc5cce2fe0de0cc7352be4364338640d59a920ac17ff4e32
|
7
|
+
data.tar.gz: 256758ca6758aedfdf49dbd70ac73e1b48933b6f037a89b974ec939fae91820c61676476d698633e1a34bc3ed500f3d5dea6ba864c3b2daa824f35723102d429
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.0.0
|
14
|
+
- name: Run the default task
|
15
|
+
run: |
|
16
|
+
gem install bundler -v 2.2.3
|
17
|
+
bundle install
|
18
|
+
bundle exec rake
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,6 +2,26 @@
|
|
2
2
|
|
3
3
|
Thot is THe Operative Templating : the simpliest solution for Ruby and command to templatize
|
4
4
|
|
5
|
+
![GitHub](https://img.shields.io/github/license/Ultragreen/thot)
|
6
|
+
|
7
|
+
[![Documentation](https://img.shields.io/badge/docs-rubydoc.info-brightgreen)](https://rubydoc.info/gems/thot)
|
8
|
+
![GitHub issues](https://img.shields.io/github/issues/Ultragreen/thot)
|
9
|
+
![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/Ultragreen/thot)
|
10
|
+
![GitHub top language](https://img.shields.io/github/languages/top/Ultragreen/thot)
|
11
|
+
![GitHub milestones](https://img.shields.io/github/milestones/open/Ultragreen/thot)
|
12
|
+
|
13
|
+
![Gem](https://img.shields.io/gem/dt/thot)
|
14
|
+
[![Gem Version](https://badge.fury.io/rb/thot.svg)](https://badge.fury.io/rb/thot)
|
15
|
+
![Twitter Follow](https://img.shields.io/twitter/follow/Ultragreen?style=social)
|
16
|
+
![GitHub Org's stars](https://img.shields.io/github/stars/Ultragreen?style=social)
|
17
|
+
![GitHub watchers](https://img.shields.io/github/watchers/Ultragreen/thot?style=social)
|
18
|
+
|
19
|
+
<noscript><a href="https://liberapay.com/ruydiaz/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
|
20
|
+
|
21
|
+
|
22
|
+
![Thot logo](assets/images/logo_thot_full_large.png)
|
23
|
+
_The Operative Templating_
|
24
|
+
|
5
25
|
## Installation
|
6
26
|
|
7
27
|
Add this line to your application's Gemfile:
|
@@ -22,14 +42,16 @@ Or install it yourself as:
|
|
22
42
|
## Principe
|
23
43
|
|
24
44
|
Thot is a simple templating tool, with :
|
25
|
-
- a template including token, like :
|
45
|
+
- a template including token, like : **%%TOKEN_NAME%%** => Token MUST be in uppercase
|
26
46
|
- a hash of data (symbols as keys) corresponding, like : <pre>{token_name: 'value'}</pre>
|
27
47
|
It could generate an output.
|
28
48
|
|
29
|
-
|
49
|
+
From versions upper than 1.2.0, Thot support token syntax like **{{TOKEN_NAME}}**
|
50
|
+
|
51
|
+
### Simple usecase
|
30
52
|
|
31
53
|
- with data : <pre>{name: 'Romain'}</pre>
|
32
|
-
- and template content : "Hello
|
54
|
+
- and template content : "Hello **%%NAME%%** !"
|
33
55
|
|
34
56
|
Thot simply generate :
|
35
57
|
'Hello Romain !'
|
@@ -37,23 +59,50 @@ Thot simply generate :
|
|
37
59
|
### Advanced usecase
|
38
60
|
|
39
61
|
- with data : <pre>{firstname: 'romain', name: 'georges', nickname: 'zaidyur'}</pre>
|
40
|
-
- and template content : "Hello
|
62
|
+
- and template content : "Hello **%%FIRSTNAME.capitalize%%** **%%NAME.upcase%%** your nickname is : **%%NICKNAME.reverse.capitalize%%** !"
|
41
63
|
|
42
64
|
Thot generate :
|
43
65
|
"Hello Romain GEORGES your nickname is : Ruydiaz !"
|
44
66
|
|
67
|
+
This usecase use filters, see it in the following chapter.
|
68
|
+
**Note** : Your could monkey patch String or use Refinment for implementing our own filters.
|
69
|
+
|
70
|
+
|
71
|
+
### Thot templating Language (TTL) reference
|
72
|
+
|
73
|
+
- Token cloud be construct with {{TOKEN}} or %%TOKEN%%.
|
74
|
+
- Token must include filtering methods : Thot actually supports String to String piped filters
|
75
|
+
- Filters don't support parameters
|
76
|
+
- Filters must be stacked seperated by '.'
|
77
|
+
- Filters must be in lowercase
|
78
|
+
- Filters must be String instance methods returning a String (Modifier)
|
79
|
+
- Token should have default value
|
80
|
+
- Default values don't support multiline correctly.
|
81
|
+
|
82
|
+
This is some examples of correct TTL syntaxes :
|
83
|
+
|
84
|
+
With %%TOKEN%% :
|
85
|
+
- filters alone : %%NAME.capitalize%%
|
86
|
+
- stacked filters alone : %%SURNAME.upcase.reverse%%
|
87
|
+
- token only with default value : %%TOTO(default value)%%
|
88
|
+
- token only : %%NAME%%
|
89
|
+
- with filters with default value : %%TOTO.downcase(default value static)%%
|
90
|
+
- stacked filters with default value : %%SURNAME.upcase.reverse(default)%%
|
91
|
+
|
45
92
|
|
46
|
-
|
47
|
-
- filters
|
48
|
-
- filters
|
49
|
-
-
|
93
|
+
with {{TOKEN}} :
|
94
|
+
- filters alone: {{NAME.capitalize}}
|
95
|
+
- stacked filters alone : {{SURNAME.upcase.reverse}}
|
96
|
+
- token only with default value : {{TOTO(default value)}}
|
97
|
+
- token only : {{NAME}}
|
98
|
+
- with filters with default value : {{TOTO.downcase(default value static)}}
|
99
|
+
- stacked filters with default value : {{SURNAME.upcase.reverse(default)}}
|
50
100
|
|
51
|
-
Note : Your could monkey patch String or use Refinment for implementing our own filters.
|
52
101
|
|
53
102
|
|
54
103
|
## Usage
|
55
104
|
|
56
|
-
Thot is
|
105
|
+
Thot is a library for you usage AND a CLI tool.
|
57
106
|
|
58
107
|
### Ruby Library usage
|
59
108
|
|
@@ -61,7 +110,7 @@ you could use Thot in your Ruby code :
|
|
61
110
|
|
62
111
|
#### Strict mode and accessor input
|
63
112
|
|
64
|
-
Note : Considering 'template.txt' with : 'Hello
|
113
|
+
Note : Considering 'template.txt' with : 'Hello **%%NAME%%** !!'
|
65
114
|
Note : in strict mode if the Tokens in template file don't match exactly the given token list, Thot raise an exception.
|
66
115
|
|
67
116
|
```ruby
|
@@ -70,7 +119,7 @@ Note : in strict mode if the Tokens in template file don't match exactly the giv
|
|
70
119
|
template = Template::new list_token: [:name] , template_file: './template.txt'
|
71
120
|
template.name = 'Romain'
|
72
121
|
puts template.output
|
73
|
-
|
122
|
+
```
|
74
123
|
|
75
124
|
return
|
76
125
|
|
@@ -82,10 +131,10 @@ return
|
|
82
131
|
```ruby
|
83
132
|
require 'thot'
|
84
133
|
include Thot
|
85
|
-
template = Template::new list_token: [:name, :surname] , template_content: 'Hello %%
|
134
|
+
template = Template::new list_token: [:name, :surname] , template_content: 'Hello %%NAM%% !!'
|
86
135
|
template.name = 'Romain'
|
87
136
|
puts template.output
|
88
|
-
|
137
|
+
```
|
89
138
|
|
90
139
|
return
|
91
140
|
|
@@ -96,10 +145,10 @@ return
|
|
96
145
|
```ruby
|
97
146
|
require 'thot'
|
98
147
|
include Thot
|
99
|
-
template = Template::new list_token: [:name, :surname] , template_content: 'Hello %%NAME !!'
|
148
|
+
template = Template::new list_token: [:name, :surname] , template_content: 'Hello %%NAME%% !!'
|
100
149
|
template.map {name: 'Romain', surname: 'Georges' }
|
101
150
|
puts template.output
|
102
|
-
|
151
|
+
```
|
103
152
|
|
104
153
|
return
|
105
154
|
|
@@ -109,17 +158,31 @@ return
|
|
109
158
|
|
110
159
|
### CLI usage
|
111
160
|
|
112
|
-
Thot come with a CLI for templating :
|
161
|
+
Thot come with a CLI for templating, you could :
|
113
162
|
- reading from STDIN or list files arguments
|
114
|
-
- getting values from variables file by argument
|
163
|
+
- getting values from variables file by argument --env-var-file, -f FILENAME or from Thot Varfile, see after
|
115
164
|
- display output on STDOUT
|
116
165
|
- verbose mode on STDERR if -v options.
|
166
|
+
- defining environment with --environment, -e ENV (efault environement is :development)
|
167
|
+
- debug mode on STDERR if -d options (cumulative with verbose).
|
168
|
+
|
169
|
+
Note : the Thot CLI look for ~/.thot.env file or ./.thot.env file
|
170
|
+
|
171
|
+
This file support INI format or flat format, keys in INI format are used for environment override
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
Note : CLI work only strict mode false, you could have unused keys in datas and undefined value for tokens (substitute by '').
|
176
|
+
|
177
|
+
Order for variable priorities :
|
178
|
+
- ~/.thot.env
|
179
|
+
- ./.thot.env
|
180
|
+
- file passed by --env-var-file
|
117
181
|
|
118
|
-
Note : CLI work only strict mode false, you could have unused keys in datas.
|
119
182
|
|
120
183
|
#### Pre-requisites
|
121
184
|
|
122
|
-
* a file 'template.
|
185
|
+
* a file 'template.ttl' with : "Hello **%%NAME%%** !!"
|
123
186
|
* a variables file with lines, like :
|
124
187
|
```
|
125
188
|
key=value
|
@@ -139,25 +202,19 @@ In the same path
|
|
139
202
|
#### STDIN from echo
|
140
203
|
|
141
204
|
```
|
142
|
-
$ echo "Hello %%NAME%% !!" |thot -
|
205
|
+
$ echo "Hello %%NAME%% !!" |thot -f env.test
|
143
206
|
```
|
144
207
|
|
145
208
|
#### STDIN from input
|
146
209
|
|
147
210
|
```
|
148
|
-
$ thot -
|
149
|
-
```
|
150
|
-
|
151
|
-
#### Files list
|
152
|
-
|
153
|
-
```
|
154
|
-
$ thot -e env.test template1.txt template2.txt
|
211
|
+
$ thot -f env.test < template.ttl
|
155
212
|
```
|
156
213
|
|
157
214
|
#### Typical usage
|
158
215
|
|
159
216
|
```
|
160
|
-
$ thot -
|
217
|
+
$ thot -f env.test < template.ttl > output.txt
|
161
218
|
```
|
162
219
|
|
163
220
|
###
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.2.0
|
Binary file
|
Binary file
|
Binary file
|
data/exe/thot
CHANGED
@@ -1,30 +1,57 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
|
4
|
+
require 'carioca'
|
5
|
+
|
6
|
+
Carioca::Registry.configure do |spec|
|
7
|
+
spec.debug = false
|
8
|
+
spec.init_from_file = false
|
9
|
+
spec.output_mode = :mono
|
10
|
+
spec.output_emoji = true
|
11
|
+
spec.output_colors = true
|
12
|
+
spec.output_target = STDERR
|
13
|
+
end
|
14
|
+
|
4
15
|
require 'optparse'
|
5
16
|
require 'thot'
|
6
17
|
require 'thot/cli'
|
7
18
|
|
8
|
-
|
9
|
-
OptionParser.new do |opts|
|
10
|
-
opts.banner = "Usage: example.rb [options]"
|
11
|
-
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
12
|
-
options[:verbose] = v
|
13
|
-
end
|
14
|
-
opts.on("-eFILENAME", "--env-var-file=FILENAME", "[MANDATORY] Environment variables file (key=value pairs by line)") do |file|
|
15
|
-
options[:env_var_file] = file
|
16
|
-
end
|
17
|
-
end.parse!
|
18
|
-
|
19
|
-
list_input = ARGV.dup
|
20
|
-
ARGV.clear
|
21
|
-
|
22
|
-
|
23
|
-
include Thot
|
24
|
-
|
25
|
-
cli = CLI::new options: options, list_templates_file: list_input
|
26
|
-
cli.generate
|
19
|
+
output = Carioca::Registry::init.get_service name: :output
|
27
20
|
|
21
|
+
options = {}
|
22
|
+
options[:environment] = :development
|
23
|
+
begin
|
24
|
+
OptionParser.new do |opts|
|
25
|
+
opts.banner = "Usage: thot [options]"
|
26
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
27
|
+
options[:verbose] = v
|
28
|
+
end
|
29
|
+
opts.on("-d", "--[no-]debug", "Run on debug") do |d|
|
30
|
+
options[:debug] = d
|
31
|
+
end
|
32
|
+
opts.on("-f FILENAME", "--env-var-file=FILENAME", "Environment variables file (key=value pairs by line)") do |file|
|
33
|
+
options[:env_var_file] = file
|
34
|
+
end
|
35
|
+
opts.on("-e ENVIRONMENT", "--environment=ENVIRONMENT", "Environment name") do |evt|
|
36
|
+
options[:environment] = evt
|
37
|
+
end
|
38
|
+
end.parse!
|
39
|
+
|
40
|
+
list_input = ARGV.dup
|
41
|
+
ARGV.clear
|
42
|
+
|
43
|
+
include Thot
|
44
|
+
cli = CLI::new options: options, template_file: list_input.first
|
45
|
+
cli.generate
|
46
|
+
rescue Interrupt => e
|
47
|
+
output.error "Execution interrupted"
|
48
|
+
rescue SignalException => e
|
49
|
+
output.error "Execution interrupted"
|
50
|
+
rescue Exception => e
|
51
|
+
output.error "Execution error : #{e.message}"
|
52
|
+
rescue OptionParser::MissingArgument => e
|
53
|
+
output.error "incorrect usage : #{e.message}"
|
54
|
+
end
|
28
55
|
|
29
56
|
|
30
57
|
|
data/lib/dependencies.rb
ADDED
data/lib/thot/cli.rb
CHANGED
@@ -1,9 +1,18 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
1
4
|
module Thot
|
2
|
-
class CLI
|
5
|
+
class CLI < Carioca::Container
|
6
|
+
|
7
|
+
inject service: :output
|
8
|
+
|
9
|
+
def initialize(options: , template_file: nil )
|
3
10
|
|
4
|
-
def initialize(options: , list_templates_file: nil )
|
5
|
-
@list_templates_file = list_templates_file
|
6
11
|
@options = options
|
12
|
+
output.level = (@options[:debug])? :debug : :info
|
13
|
+
output.debug "Debugging mode activated" if @options[:debug]
|
14
|
+
@template_file = template_file
|
15
|
+
output.info "Assuming Environment : #{@options[:environment]}" if @options[:verbose]
|
7
16
|
getting_data
|
8
17
|
getting_content
|
9
18
|
end
|
@@ -12,7 +21,7 @@ module Thot
|
|
12
21
|
def generate
|
13
22
|
template = Template::new(list_token: @data.keys, template_content: @content, strict: false)
|
14
23
|
template.map @data
|
15
|
-
|
24
|
+
output.info "Generating output" if @options[:verbose]
|
16
25
|
puts template.output
|
17
26
|
end
|
18
27
|
|
@@ -20,42 +29,31 @@ module Thot
|
|
20
29
|
|
21
30
|
def getting_data
|
22
31
|
if @options[:env_var_file] then
|
23
|
-
|
24
|
-
@data = read_evt_file(@options[:env_var_file])
|
32
|
+
output.info "Environment file given : #{@options[:env_var_file]}" if @options[:verbose]
|
25
33
|
else
|
26
|
-
|
34
|
+
output.info "Environment variables file argument missing, (--env-var-file) " if @options[:verbose]
|
27
35
|
end
|
36
|
+
@data = Varfiles::new(environment: @options[:environment], varfile: @options[:env_var_file]).data
|
28
37
|
end
|
29
38
|
|
30
39
|
def getting_content
|
31
|
-
|
32
|
-
|
40
|
+
@content = ""
|
41
|
+
if @template_file.nil?
|
42
|
+
output.info "Reading content from STDIN (CTRL+D to commit)" if STDIN.tty?
|
43
|
+
output.info "Getting content from STDIN" if @options[:verbose] and not STDIN.tty?
|
33
44
|
@content = ARGF.readlines.join
|
34
45
|
else
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
end
|
46
|
+
output.info "Reading content from file : #{@template_file}" if @options[:verbose]
|
47
|
+
if File::exist? @template_file
|
48
|
+
@content = File::readlines(@template_file).join
|
49
|
+
else
|
50
|
+
raise "file not found #{@template_file}"
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
45
54
|
|
46
55
|
def read_evt_file(file)
|
47
|
-
|
48
|
-
if File::exist? file
|
49
|
-
content = File::readlines(file)
|
50
|
-
else
|
51
|
-
raise "Environment variables file not found #{file}"
|
52
|
-
end
|
53
|
-
content.each do |line|
|
54
|
-
next if line =~ /#/
|
55
|
-
key,value = line.split('=')
|
56
|
-
res[key.strip.to_sym] = value.strip if value
|
57
|
-
end
|
58
|
-
return res
|
56
|
+
return
|
59
57
|
end
|
60
58
|
|
61
59
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Thot
|
2
|
+
class Processor
|
3
|
+
attr_reader :value
|
4
|
+
attr_reader :rule
|
5
|
+
|
6
|
+
def initialize(value: , rule:)
|
7
|
+
@value = value
|
8
|
+
@rule = rule
|
9
|
+
end
|
10
|
+
|
11
|
+
def result
|
12
|
+
if @value.nil? then
|
13
|
+
result = (@rule[:default])? @rule[:default] : ''
|
14
|
+
else
|
15
|
+
result = @value
|
16
|
+
@rule[:filters].each do |filter|
|
17
|
+
result = result.send filter.to_sym if result.respond_to? filter.to_sym
|
18
|
+
end
|
19
|
+
end
|
20
|
+
return result
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'thot'
|
4
|
+
|
5
|
+
$VERBOSE = nil
|
6
|
+
if Gem::Specification.respond_to?(:find_by_name)
|
7
|
+
begin
|
8
|
+
spec = Gem::Specification.find_by_name('thot')
|
9
|
+
res = spec.lib_dirs_glob.split('/')
|
10
|
+
rescue LoadError
|
11
|
+
res = []
|
12
|
+
end
|
13
|
+
else
|
14
|
+
spec = Gem.searcher.find('carioca')
|
15
|
+
res = Gem.searcher.lib_dirs_for(spec).split('/')
|
16
|
+
end
|
17
|
+
|
18
|
+
res.pop
|
19
|
+
tasks_path = res.join('/').concat('/lib/thot/rake/tasks/')
|
20
|
+
Dir["#{tasks_path}/*.task*"].each { |ext| load ext }
|
@@ -0,0 +1,13 @@
|
|
1
|
+
namespace :thot do
|
2
|
+
|
3
|
+
desc "templatize file from ENV vars : THOT_VARFILE, THOT_TEMPLATE "
|
4
|
+
task :templatize do
|
5
|
+
|
6
|
+
if ENV.include? 'THOT_VARFILE' and ENV.include? 'THOT_VARFILE'
|
7
|
+
system("thot -f #{ENV['THOT_VARFILE']} #{ENV['THOT_TEMPLATE']}")
|
8
|
+
else
|
9
|
+
puts 'Please precise THOT_VARFILE and THOT_TEMPLATE environment variables'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Thot base module
|
2
|
+
module Thot
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
|
6
|
+
# KISS template Engine
|
7
|
+
class Template
|
8
|
+
|
9
|
+
# getter of the list of token
|
10
|
+
attr_reader :list_token
|
11
|
+
# getter of the template file
|
12
|
+
attr_reader :template_file
|
13
|
+
# getter of the flat content of the template
|
14
|
+
attr_reader :content
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
# constructor : generate the pseudo accessor for template Class from token list
|
20
|
+
def initialize(template_file: nil, list_token: , strict: true, template_content: nil)
|
21
|
+
@myput = Carioca::Registry::init.get_service name: :output if defined?(Carioca::Injector)
|
22
|
+
@result = ""
|
23
|
+
if template_file
|
24
|
+
@template_file = template_file
|
25
|
+
raise NoTemplateFile::new('No template file found') unless File::exist?(@template_file)
|
26
|
+
begin
|
27
|
+
@content = IO::readlines(@template_file).join.chomp
|
28
|
+
rescue
|
29
|
+
raise NoTemplateFile::new('Template file read error')
|
30
|
+
end
|
31
|
+
elsif template_content
|
32
|
+
@content = template_content
|
33
|
+
else
|
34
|
+
raise NoTemplateFile::new('No template file found or template content')
|
35
|
+
end
|
36
|
+
|
37
|
+
@tokenizer = Tokenizer::new string: @content ; @tokenizer.detect
|
38
|
+
token_from_template = @tokenizer.tokens
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
begin
|
43
|
+
@list_token = list_token
|
44
|
+
@hash_token = Hash::new; @list_token.each{|_item| @hash_token[_item.to_s] = String::new('')}
|
45
|
+
rescue
|
46
|
+
raise InvalidTokenList::new("Token list malformation")
|
47
|
+
end
|
48
|
+
if strict
|
49
|
+
raise InvalidTokenList::new("Token list doesn't match the template") unless token_from_template.sort == @list_token.sort
|
50
|
+
end
|
51
|
+
if @myput then
|
52
|
+
if @myput.level == :debug then
|
53
|
+
@myput.debug "Template :"
|
54
|
+
@content.split("\n").each do |line|
|
55
|
+
@myput.debug " #{line}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@list_token.each do |_token|
|
60
|
+
self.instance_eval do
|
61
|
+
define_singleton_method(:"#{_token}=") {|_value| raise ArgumentError::new('Not a String') unless _value.class == String; @hash_token[__callee__.to_s.chomp('=')] = _value }
|
62
|
+
define_singleton_method(_token.to_sym) { return @hash_token[__callee__.to_s] }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# generic accessor
|
69
|
+
# @param [Symbol] _token in the token list
|
70
|
+
# @param [String] _value a text value
|
71
|
+
# @raise [ArgumentError] if _valu is not a String
|
72
|
+
def token(_token,_value)
|
73
|
+
raise ArgumentError::new('Not a String') unless _value.class == String
|
74
|
+
@hash_token[_token.to_s] = _value
|
75
|
+
end
|
76
|
+
|
77
|
+
# map a hash against templates token_list
|
78
|
+
# @param [Hash] _hash a hash data to map
|
79
|
+
def map(_hash)
|
80
|
+
_data = {}
|
81
|
+
_hash.each { |item,val|
|
82
|
+
raise ArgumentError::new("#{item} : Not a String") unless val.class == String
|
83
|
+
_data[item.to_s.downcase] = val
|
84
|
+
}
|
85
|
+
@hash_token = _data
|
86
|
+
end
|
87
|
+
|
88
|
+
# collector for pseudo accessor to prevent bad mapping
|
89
|
+
# @raise [NotAToken] if caling an accessor not mapped in token list
|
90
|
+
def method_missing(_name,*_args)
|
91
|
+
raise NotAToken
|
92
|
+
end
|
93
|
+
|
94
|
+
# the templater himself : proceed to templating
|
95
|
+
# @return [String] the template output
|
96
|
+
def output
|
97
|
+
@result = @content
|
98
|
+
|
99
|
+
@tokenizer.definitions.each do |item, rule|
|
100
|
+
@result.gsub!(item, Thot::Processor::new(value: @hash_token[rule[:key].downcase], rule: rule).result)
|
101
|
+
end
|
102
|
+
|
103
|
+
if @myput then
|
104
|
+
if @myput.level == :debug then
|
105
|
+
@myput.debug "Output :"
|
106
|
+
@result.split("\n").each do |line|
|
107
|
+
@myput.debug " #{line}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
return @result
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
# Exception for an invalid Token list
|
117
|
+
class InvalidTokenList < Exception; end
|
118
|
+
# Exception for an malformed token
|
119
|
+
class NotAToken < Exception; end
|
120
|
+
# Exception for an invalid template file
|
121
|
+
class NoTemplateFile < Exception; end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Thot
|
2
|
+
class Tokenizer
|
3
|
+
|
4
|
+
attr_reader :string
|
5
|
+
attr_reader :tokens
|
6
|
+
attr_reader :definitions
|
7
|
+
|
8
|
+
def initialize(string: )
|
9
|
+
@string = string
|
10
|
+
@definitions = {}
|
11
|
+
@tokens = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def detect
|
15
|
+
detected = @string.scan(/%%[^\%]*%%/).concat @string.scan(/\{\{[^\}]*\}\}/)
|
16
|
+
detected.each do |token|
|
17
|
+
key,*rest = token[2,(token.length - 4)].split('.')
|
18
|
+
filters = []; default = nil
|
19
|
+
if key =~ /(.*)\((.*)\)/ then
|
20
|
+
key = $1
|
21
|
+
default = $2
|
22
|
+
end
|
23
|
+
rest.each {|item|
|
24
|
+
if item =~ /(.*)\((.*)\)/ then
|
25
|
+
filters.push $1
|
26
|
+
default = $2
|
27
|
+
|
28
|
+
else
|
29
|
+
filters.push item
|
30
|
+
end
|
31
|
+
}
|
32
|
+
@tokens.push key unless @tokens.include? key
|
33
|
+
@definitions[token] = {key: key, filters: filters , default: default }
|
34
|
+
end
|
35
|
+
@tokens.map!(&:downcase)
|
36
|
+
@tokens.map!(&:to_sym)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Thot
|
2
|
+
class Varfiles
|
3
|
+
|
4
|
+
extend Carioca::Injector if defined?(Carioca::Injector)
|
5
|
+
attr_reader :data
|
6
|
+
inject service: :output if self.respond_to? :inject
|
7
|
+
|
8
|
+
def initialize(varfile: nil, environment: nil, dotfiles: ["./.thot.env","~/.thot.env"])
|
9
|
+
@name = self.class
|
10
|
+
@data = {}
|
11
|
+
scanned_files = dotfiles
|
12
|
+
scanned_files.push varfile unless varfile.nil?
|
13
|
+
scanned_files.each do |file|
|
14
|
+
real_file = File.expand_path(file)
|
15
|
+
if File::exists? real_file then
|
16
|
+
output.debug "Negociated files : #{real_file}, merging..." if self.respond_to?(:output)
|
17
|
+
datafile = IniFile.load(real_file)
|
18
|
+
@data.merge! datafile["global"]
|
19
|
+
@data.merge! datafile[environment] if datafile.sections.include? environment.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
@data.transform_keys!(&:to_sym)
|
23
|
+
if self.respond_to?(:output) then
|
24
|
+
if output.level == :debug then
|
25
|
+
output.debug "merged data:"
|
26
|
+
@data.each do |key,val|
|
27
|
+
output.debug "* #{key} = #{val}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/thot.rb
CHANGED
@@ -1,126 +1,3 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
2
|
+
require_relative "dependencies"
|
3
3
|
|
4
|
-
# Thot base module
|
5
|
-
module Thot
|
6
|
-
class Error < StandardError; end
|
7
|
-
|
8
|
-
|
9
|
-
# KISS template Engine
|
10
|
-
class Template
|
11
|
-
|
12
|
-
# getter of the list of token
|
13
|
-
attr_reader :list_token
|
14
|
-
# getter of the template file
|
15
|
-
attr_reader :template_file
|
16
|
-
# getter of the flat content of the template
|
17
|
-
attr_reader :content
|
18
|
-
|
19
|
-
# constructor : generate the pseudo accessor for template Class from token list
|
20
|
-
def initialize(template_file: nil, list_token: , strict: true, template_content: nil)
|
21
|
-
|
22
|
-
@result = ""
|
23
|
-
if template_file
|
24
|
-
@template_file = template_file
|
25
|
-
raise NoTemplateFile::new('No template file found') unless File::exist?(@template_file)
|
26
|
-
begin
|
27
|
-
@content = IO::readlines(@template_file).join.chomp
|
28
|
-
rescue
|
29
|
-
raise NoTemplateFile::new('Template file read error')
|
30
|
-
end
|
31
|
-
elsif template_content
|
32
|
-
@content = template_content
|
33
|
-
else
|
34
|
-
raise NoTemplateFile::new('No template file found or template content')
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
token_from_template = @content.scan(/%%(\w+)%%/).flatten.uniq.map{ |item| item.downcase.to_sym}
|
39
|
-
begin
|
40
|
-
@list_token = list_token
|
41
|
-
@hash_token = Hash::new; @list_token.each{|_item| @hash_token[_item.to_s] = String::new('')}
|
42
|
-
rescue
|
43
|
-
raise InvalidTokenList::new("Token list malformation")
|
44
|
-
end
|
45
|
-
if strict
|
46
|
-
raise InvalidTokenList::new("Token list doesn't match the template") unless token_from_template.sort == @list_token.sort
|
47
|
-
else
|
48
|
-
raise InvalidTokenList::new("Token list doesn't match the template") unless (token_from_template.sort & @list_token.sort) == token_from_template.sort
|
49
|
-
end
|
50
|
-
@list_token.each do |_token|
|
51
|
-
self.instance_eval do
|
52
|
-
define_singleton_method(:"#{_token}=") {|_value| raise ArgumentError::new('Not a String') unless _value.class == String; @hash_token[__callee__.to_s.chomp('=')] = _value }
|
53
|
-
define_singleton_method(_token.to_sym) { return @hash_token[__callee__.to_s] }
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
# generic accessor
|
60
|
-
# @param [Symbol] _token in the token list
|
61
|
-
# @param [String] _value a text value
|
62
|
-
# @raise [ArgumentError] if _valu is not a String
|
63
|
-
def token(_token,_value)
|
64
|
-
raise ArgumentError::new('Not a String') unless _value.class == String
|
65
|
-
@hash_token[_token.to_s] = _value
|
66
|
-
end
|
67
|
-
|
68
|
-
# map a hash against templates token_list
|
69
|
-
# @param [Hash] _hash a hash data to map
|
70
|
-
def map(_hash)
|
71
|
-
_data = {}
|
72
|
-
_hash.each { |item,val|
|
73
|
-
raise ArgumentError::new("#{item} : Not a String") unless val.class == String
|
74
|
-
_data[item.to_s.downcase] = val
|
75
|
-
}
|
76
|
-
raise InvalidTokenList::new("Token list malformation") unless _data.keys.sort == @list_token.map{|_token| _token.to_s }.sort
|
77
|
-
@hash_token = _data
|
78
|
-
end
|
79
|
-
|
80
|
-
# collector for pseudo accessor to prevent bad mapping
|
81
|
-
# @raise [NotAToken] if caling an accessor not mapped in token list
|
82
|
-
def method_missing(_name,*_args)
|
83
|
-
raise NotAToken
|
84
|
-
end
|
85
|
-
|
86
|
-
# the templater;proceed to templating
|
87
|
-
# @return [String] the template output
|
88
|
-
def output
|
89
|
-
@result = @content
|
90
|
-
@list_token.each{|_token|
|
91
|
-
self.filtering @content.scan(/%%(#{_token.to_s.upcase}[\.\w+]+)%%/).flatten
|
92
|
-
@result.gsub!(/%%#{_token.to_s.upcase}%%/,@hash_token[_token.to_s])
|
93
|
-
}
|
94
|
-
return @result
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
def filtering(list)
|
100
|
-
@filtered_tokens = {}
|
101
|
-
list.each do |pipe|
|
102
|
-
token, *filters = pipe.split('.')
|
103
|
-
@filtered_tokens[pipe] = @hash_token[token.downcase]
|
104
|
-
filters.each do |filter|
|
105
|
-
@filtered_tokens[pipe] = @filtered_tokens[pipe].send filter.to_sym if @filtered_tokens[pipe].respond_to? filter.to_sym
|
106
|
-
end
|
107
|
-
end
|
108
|
-
@filtered_tokens.each do |item,value|
|
109
|
-
@result.gsub!(/%%#{item}%%/,value)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
|
114
|
-
end
|
115
|
-
|
116
|
-
# Exception for an invalid Token list
|
117
|
-
class InvalidTokenList < Exception; end
|
118
|
-
# Exception for an malformed token
|
119
|
-
class NotAToken < Exception; end
|
120
|
-
# Exception for an invalid template file
|
121
|
-
class NoTemplateFile < Exception; end
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
|
126
|
-
|
data/samples/.thot.env
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
With %
|
2
|
+
filters alone : %%NAME.capitalize%% %%SURNAME.upcase%%
|
3
|
+
stacked filters alone : %%SURNAME.upcase.reverse%%
|
4
|
+
no tokens
|
5
|
+
empty with default value : %%TOTO(default value)%%
|
6
|
+
only defined token : %%NAME%%
|
7
|
+
empty with filters with default value : %%TOTO.downcase(default value static)%%
|
8
|
+
empty : %%TOTO%%
|
9
|
+
defined stacked filters with default value : %%SURNAME.upcase.reverse(default)%%
|
10
|
+
empty stacked filters with default value : %%TOTO.upcase.reverse(default)%%
|
11
|
+
|
12
|
+
with {
|
13
|
+
filters alone: {{NAME.capitalize}} {{SURNAME.upcase}}
|
14
|
+
stacked filters alone : {{SURNAME.upcase.reverse}}
|
15
|
+
no tokens
|
16
|
+
empty with default value : {{TOTO(default value)}}
|
17
|
+
only defined token : {{NAME}}
|
18
|
+
empty with filters with default value : {{TOTO.downcase(default value static)}}
|
19
|
+
empty : {{TOTO}}
|
20
|
+
defined stacked filters with default value : {{SURNAME.upcase.reverse(default)}}
|
21
|
+
empty stacked filters with default value : {{TOTO.upcase.reverse(default)}}
|
data/thot.gemspec
CHANGED
@@ -22,6 +22,9 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
+
spec.add_dependency "carioca", "~> 2.0"
|
26
|
+
spec.add_dependency "inifile", "~> 3.0"
|
27
|
+
|
25
28
|
spec.add_development_dependency 'rake', '~> 12.0'
|
26
29
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
30
|
spec.add_development_dependency 'rubocop', '~> 1.32'
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Romain GEORGES
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: carioca
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: inifile
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: rake
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -130,6 +158,7 @@ executables:
|
|
130
158
|
extensions: []
|
131
159
|
extra_rdoc_files: []
|
132
160
|
files:
|
161
|
+
- ".github/workflows/ruby.yml"
|
133
162
|
- ".gitignore"
|
134
163
|
- ".rspec"
|
135
164
|
- ".rubocop.yml"
|
@@ -138,14 +167,25 @@ files:
|
|
138
167
|
- README.md
|
139
168
|
- Rakefile
|
140
169
|
- VERSION
|
170
|
+
- assets/images/logo_thot_full_large.png
|
171
|
+
- assets/images/logo_thot_light_large.png
|
172
|
+
- assets/images/logo_thot_normal_large.png
|
141
173
|
- bin/console
|
142
174
|
- bin/setup
|
143
175
|
- exe/thot
|
176
|
+
- lib/dependencies.rb
|
144
177
|
- lib/thot.rb
|
145
178
|
- lib/thot/cli.rb
|
179
|
+
- lib/thot/processor.rb
|
180
|
+
- lib/thot/rake/manage.rb
|
181
|
+
- lib/thot/rake/tasks/template.task
|
182
|
+
- lib/thot/template.rb
|
183
|
+
- lib/thot/tokenizer.rb
|
184
|
+
- lib/thot/varfiles.rb
|
146
185
|
- lib/thot/version.rb
|
147
186
|
- samples/.env.prod
|
148
|
-
- samples
|
187
|
+
- samples/.thot.env
|
188
|
+
- samples/template.ttl
|
149
189
|
- thot.gemspec
|
150
190
|
- ultragreen_roodi_coding_convention.yml
|
151
191
|
homepage: https://github.com/Ultragreen/thot
|
@@ -168,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
208
|
- !ruby/object:Gem::Version
|
169
209
|
version: '0'
|
170
210
|
requirements: []
|
171
|
-
rubygems_version: 3.
|
211
|
+
rubygems_version: 3.3.5
|
172
212
|
signing_key:
|
173
213
|
specification_version: 4
|
174
214
|
summary: THe Operative Templating
|
data/samples/template.txt
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
Hello %%NAME.capitalize%% %%SURNAME.upcase%% !!
|