super_template 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +142 -0
- data/Rakefile +3 -0
- data/lib/generators/super_template/abstract_generator.rb +41 -0
- data/lib/generators/super_template/template/template_generator.rb +58 -0
- data/lib/generators/super_template/template/templates/template.rb.erb +13 -0
- data/lib/super_template/base.rb +122 -0
- data/lib/super_template/compiler.rb +54 -0
- data/lib/super_template/errors.rb +4 -0
- data/lib/super_template/railtie.rb +8 -0
- data/lib/super_template/version.rb +3 -0
- data/lib/super_template.rb +8 -0
- data/lib/tasks/super_template_tasks.rake +4 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7208b9c0dea29c3eec75b4a4259c93d4c690d3c2ddcc3364e5b06dbda420552c
|
4
|
+
data.tar.gz: b1aa10783c8e29874a280c9f637ad50278b24951fdb5285598ff4cd366d53bf9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 026126cc765646c81692b5af1f6b7089d1aa86c160527d67064d5795de015a47f084a284cc152671b036abc4dc32805c74caa13c7ea0495852e3a4150a5d31fb
|
7
|
+
data.tar.gz: fe490f46b1d804f73950728c6310a7722226b73a2c02541eeae6ec01904cf0ec8504f980af931fb1588d6b7ea98210e9bf3d4abe2df5a531841bab519e59d3c3
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2023 Chongchen Chen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# [WIP]Super Template
|
2
|
+
|
3
|
+
This library not only can be used to generate sql, but also designed as a general template library.
|
4
|
+
|
5
|
+
Maintain your raw SQL logic in rails with plain old ruby object.
|
6
|
+
|
7
|
+
Every sql template is reusable, you don't need to write similar SQL logic again and again!
|
8
|
+
|
9
|
+
Inspired by [ViewComponent](https://viewcomponent.org/)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem "super_template"
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ bundle
|
23
|
+
```
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
```bash
|
28
|
+
$ gem install super_template
|
29
|
+
```
|
30
|
+
|
31
|
+
## Quick start
|
32
|
+
|
33
|
+
A SuperTemplate is a Ruby object.
|
34
|
+
|
35
|
+
Templates are subclasses of `SuperTemplate::Base` and live in `app/sqls`.
|
36
|
+
|
37
|
+
Use the template generator to create a new Sql Template.
|
38
|
+
|
39
|
+
The generator accepts a folder name, template name and a list of arguments:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
$ bin/rails generate component MySqlTemplate limit
|
43
|
+
|
44
|
+
invoke test_unit
|
45
|
+
create test/sqls/my_sql_template_test.rb
|
46
|
+
create app/sqls/my_sql_template.rb
|
47
|
+
create app/sqls/my_sql_template.sql.erb
|
48
|
+
```
|
49
|
+
|
50
|
+
If you want to generate files in another folder. you can execute:
|
51
|
+
|
52
|
+
```bash
|
53
|
+
$ bin/rails generate component MySqlTemplate limit --dir another_folder
|
54
|
+
|
55
|
+
invoke test_unit
|
56
|
+
create test/another_folder/my_sql_template_test.rb
|
57
|
+
create app/another_folder/my_sql_template.rb
|
58
|
+
create app/another_folder/my_sql_template.sql.erb
|
59
|
+
```
|
60
|
+
|
61
|
+
Template can be instantiated and passed to Rails' connection execute method:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
ActiveRecord::Base.connection.execute(MySqlTemplate.new(limit: 10).call, {name: "dummy"})
|
65
|
+
```
|
66
|
+
|
67
|
+
## Implementation
|
68
|
+
|
69
|
+
### ActiveReocrd or Arel mode
|
70
|
+
|
71
|
+
Edit file `app/sqls/my_sql_template.rb`
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class MySqlTemplate < SuperTemplate::Base
|
75
|
+
|
76
|
+
def initialize(limit:)
|
77
|
+
@limit = limit
|
78
|
+
end
|
79
|
+
|
80
|
+
def render_template
|
81
|
+
MyTable.limit(@limit).all.to_sql
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
|
87
|
+
### Inline style
|
88
|
+
|
89
|
+
Edit file `app/sqls/my_sql_template.rb`
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class MySqlTemplate < SuperTemplate::Base
|
93
|
+
erb_template <<-ERB
|
94
|
+
SELECT * FROM my_table WHERE col = :name limit <%= @limit %>
|
95
|
+
ERB
|
96
|
+
|
97
|
+
def initialize(limit:)
|
98
|
+
@limit = limit
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
### Template File
|
104
|
+
|
105
|
+
Edit file `app/sqls/my_sql_template.html.erb`
|
106
|
+
|
107
|
+
```erb
|
108
|
+
SELECT * FROM my_table WHERE col = :name limit <%= @limit %>
|
109
|
+
```
|
110
|
+
|
111
|
+
Edit file `app/sqls/my_sql_template.rb`
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class MySqlTemplate < SuperTemplate::Base
|
115
|
+
def initialize(limit:)
|
116
|
+
@limit = limit
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
## Call Super
|
122
|
+
Edit file `app/sqls/sub_sql_template.rb`
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
class SubSqlTemplate < SuperTemplate::Base
|
126
|
+
def initialize(limit:, offset:)
|
127
|
+
super(limit: limit)
|
128
|
+
@offset = offset
|
129
|
+
end
|
130
|
+
template :erb, <<~ERB
|
131
|
+
<%= super %> limit <%= @offset %>
|
132
|
+
ERB
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
## Contributing
|
137
|
+
|
138
|
+
Contribution directions go here.
|
139
|
+
|
140
|
+
## License
|
141
|
+
|
142
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperTemplate
|
4
|
+
module Generators
|
5
|
+
module AbstractGenerator
|
6
|
+
def copy_view_file
|
7
|
+
template "component.#{options["format"]}.#{engine_name}", destination unless options["inline"]
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def destination
|
13
|
+
File.join(destination_directory, "#{destination_file_name}.#{options["format"]}.#{engine_name}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def destination_directory
|
17
|
+
if sidecar?
|
18
|
+
File.join(template_path, class_path, destination_file_name)
|
19
|
+
else
|
20
|
+
File.join(template_path, class_path)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def destination_file_name
|
25
|
+
"#{file_name}_template"
|
26
|
+
end
|
27
|
+
|
28
|
+
def file_name
|
29
|
+
@_file_name ||= super.sub(/_template\z/i, "")
|
30
|
+
end
|
31
|
+
|
32
|
+
def template_path
|
33
|
+
"app/templates"
|
34
|
+
end
|
35
|
+
|
36
|
+
def sidecar?
|
37
|
+
options["sidecar"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative "../abstract_generator"
|
2
|
+
module SuperTemplate
|
3
|
+
module Generators
|
4
|
+
class TemplateGenerator < Rails::Generators::NamedBase
|
5
|
+
|
6
|
+
include SuperTemplate::Generators::AbstractGenerator
|
7
|
+
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
10
|
+
argument :attributes, type: :array, default: [], banner: "attribute"
|
11
|
+
check_class_collision suffix: "Template"
|
12
|
+
|
13
|
+
class_option :inline, type: :boolean, default: false
|
14
|
+
class_option :parent, type: :string, desc: "The parent class for the generated template"
|
15
|
+
class_option :format, type: :string, default: "sql"
|
16
|
+
class_option :template_engine, type: :string, default: "erb"
|
17
|
+
|
18
|
+
def create_sidecar_file
|
19
|
+
unless options[:inline]
|
20
|
+
create_file File.join(template_path, class_path, "#{file_name}_template.#{options[:format]}.#{options[:template_engine]}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_template_file
|
25
|
+
template "template.rb.erb", File.join(template_path, class_path, "#{file_name}_template.rb")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def template_engine
|
31
|
+
options[:template_engine]
|
32
|
+
end
|
33
|
+
|
34
|
+
def parent_class
|
35
|
+
return options[:parent] if options[:parent]
|
36
|
+
default_parent_class
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize_signature
|
40
|
+
return if attributes.blank?
|
41
|
+
|
42
|
+
attributes.map { |attr| "#{attr.name}:" }.join(", ")
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize_body
|
46
|
+
attributes.map { |attr| "@#{attr.name} = #{attr.name}" }.join("\n ")
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize_call_method_for_inline?
|
50
|
+
options["inline"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_parent_class
|
54
|
+
defined?(ApplicationTemplate) ? ApplicationTemplate : SuperTemplate::Base
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "super_template/base"
|
2
|
+
|
3
|
+
class <%= class_name %>Template < <%= parent_class %>
|
4
|
+
<%- if initialize_signature -%>
|
5
|
+
def initialize(<%= initialize_signature %>)
|
6
|
+
<%= initialize_body %>
|
7
|
+
end
|
8
|
+
<%- end -%>
|
9
|
+
<%- if initialize_call_method_for_inline? -%>
|
10
|
+
template <%= template_engine.to_sym.inspect -%>, <<-<%= template_engine.to_s.upcase %>
|
11
|
+
<%= template_engine.to_s.upcase %>
|
12
|
+
<%- end -%>
|
13
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require_relative 'compiler'
|
5
|
+
|
6
|
+
module SuperTemplate
|
7
|
+
class Base
|
8
|
+
def call
|
9
|
+
self.class.compile unless self.class.compiled?
|
10
|
+
render_template
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
# Strips trailing whitespace from templates before compiling them.
|
16
|
+
#
|
17
|
+
# ```ruby
|
18
|
+
# class MyTemplate < SuperTemplate::Base
|
19
|
+
# strip_trailing_whitespace
|
20
|
+
# end
|
21
|
+
# ```
|
22
|
+
#
|
23
|
+
# @param value [Boolean] Whether to strip newlines.
|
24
|
+
def strip_trailing_whitespace(value = true)
|
25
|
+
@__st_strip_trailing_whitespace = value
|
26
|
+
end
|
27
|
+
|
28
|
+
# Whether trailing whitespace will be stripped before compilation.
|
29
|
+
#
|
30
|
+
# @return [Boolean]
|
31
|
+
def strip_trailing_whitespace?
|
32
|
+
@__st_strip_trailing_whitespace
|
33
|
+
end
|
34
|
+
|
35
|
+
def source_location
|
36
|
+
@source_location ||= Object.const_source_location(self.name)[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
def compiled?
|
40
|
+
@__st_compiled ||= false
|
41
|
+
end
|
42
|
+
|
43
|
+
def compile(raise_errors: false, force: false)
|
44
|
+
@__st_compiled ||= compiler.compile(raise_errors: raise_errors, force: force)
|
45
|
+
end
|
46
|
+
|
47
|
+
def compiler
|
48
|
+
@__st_compiler ||= SuperTemplate::Compiler.new(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param extension [Symbol] extension
|
52
|
+
# @param source [String] template source
|
53
|
+
def template(extension, source)
|
54
|
+
caller = caller_locations(1..1)[0]
|
55
|
+
@__st_inline_template = OpenStruct.new(
|
56
|
+
source: source,
|
57
|
+
type: extension,
|
58
|
+
path: caller.absolute_path || caller.path,
|
59
|
+
lineno: caller.lineno
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def inline_template
|
64
|
+
@__st_inline_template
|
65
|
+
end
|
66
|
+
|
67
|
+
# Find sidecar files for the given extensions.
|
68
|
+
#
|
69
|
+
# The provided array of extensions is expected to contain
|
70
|
+
# strings starting without the dot, example: `["erb", "haml"]`.
|
71
|
+
#
|
72
|
+
# For example, one might collect sidecar CSS files that need to be compiled.
|
73
|
+
# @param extensions [Array<String>] Extensions of which to return matching sidecar files.
|
74
|
+
def sidecar_files(extensions)
|
75
|
+
return [] unless source_location
|
76
|
+
|
77
|
+
extensions = extensions.join(",")
|
78
|
+
|
79
|
+
# view files in a directory named like the component
|
80
|
+
directory = File.dirname(source_location)
|
81
|
+
filename = File.basename(source_location, ".rb")
|
82
|
+
component_name = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
|
83
|
+
|
84
|
+
# Add support for nested components defined in the same file.
|
85
|
+
#
|
86
|
+
# for example
|
87
|
+
#
|
88
|
+
# class MyTemplate < SuperTemplate::Base
|
89
|
+
# class MyTemplate < SuperTemplate::Base
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
94
|
+
nested_component_files =
|
95
|
+
if name.include?("::") && component_name != filename
|
96
|
+
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
97
|
+
else
|
98
|
+
[]
|
99
|
+
end
|
100
|
+
|
101
|
+
# view files in the same directory as the component
|
102
|
+
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
103
|
+
|
104
|
+
sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
|
105
|
+
|
106
|
+
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
extend ClassMethods
|
111
|
+
|
112
|
+
def self.inherited(child)
|
113
|
+
child.extend ClassMethods
|
114
|
+
super
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def get_binding
|
119
|
+
binding
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "concurrent-ruby"
|
3
|
+
require_relative "errors"
|
4
|
+
|
5
|
+
module SuperTemplate
|
6
|
+
class Compiler
|
7
|
+
def initialize(template_class)
|
8
|
+
@template_class = template_class
|
9
|
+
@redefinition_lock = Mutex.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def compile(raise_errors: false, force: false)
|
13
|
+
return if template_class.compiled? && !force
|
14
|
+
return if template_class == SuperTemplate::Base
|
15
|
+
template_class.superclass.compile(raise_errors: raise_errors)
|
16
|
+
|
17
|
+
redefinition_lock.synchronize do
|
18
|
+
templates = []
|
19
|
+
templates = find_sidecar_templates
|
20
|
+
templates << template_class.inline_template if template_class.inline_template
|
21
|
+
raise TemplateError, "There are #{templates.size} templates defined for #{self}" if templates.size >= 2
|
22
|
+
unless templates.empty?
|
23
|
+
compile_template(templates[0])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
return true
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def find_sidecar_templates
|
32
|
+
template_class.sidecar_files([:erb]).map do |path|
|
33
|
+
pieces = File.basename(path).split(".")
|
34
|
+
OpenStruct.new(
|
35
|
+
source: File.read(path),
|
36
|
+
type: pieces.last,
|
37
|
+
path: path,
|
38
|
+
lineno: 0
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def compile_template(template)
|
44
|
+
source = template.source
|
45
|
+
source = source.rstrip! if template_class.strip_trailing_whitespace?
|
46
|
+
erb = ERB.new(source)
|
47
|
+
erb.filename = template.path
|
48
|
+
erb.lineno = template.lineno
|
49
|
+
erb.def_method(template_class, 'render_template()')
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :template_class, :redefinition_lock
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: super_template
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chongchen Chen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-01-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.0'
|
27
|
+
description: SQL Template for Ruby
|
28
|
+
email:
|
29
|
+
- chenkovsky@qq.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/generators/super_template/abstract_generator.rb
|
38
|
+
- lib/generators/super_template/template/template_generator.rb
|
39
|
+
- lib/generators/super_template/template/templates/template.rb.erb
|
40
|
+
- lib/super_template.rb
|
41
|
+
- lib/super_template/base.rb
|
42
|
+
- lib/super_template/compiler.rb
|
43
|
+
- lib/super_template/errors.rb
|
44
|
+
- lib/super_template/railtie.rb
|
45
|
+
- lib/super_template/version.rb
|
46
|
+
- lib/tasks/super_template_tasks.rake
|
47
|
+
homepage: https://github.com/chenkovsky/super_template
|
48
|
+
licenses:
|
49
|
+
- MIT
|
50
|
+
metadata:
|
51
|
+
homepage_uri: https://github.com/chenkovsky/super_template
|
52
|
+
source_code_uri: https://github.com/chenkovsky/super_template
|
53
|
+
changelog_uri: https://github.com/chenkovsky/super_template/CHANGELOG.md
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubygems_version: 3.5.3
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: SQL Template for Ruby
|
73
|
+
test_files: []
|