serbea 0.6.3 β†’ 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73996980f3fc5dad7372d79b970912fafc75ae4da2663838f6af696c57be29aa
4
- data.tar.gz: d5eabe6bc2a70b461870e085ebfb9d628941e862e09b8e567b50e5dd4798f334
3
+ metadata.gz: 72bb8c66c7a574ed69bbec8995cd8a5e28fa72caf82768264b95b602ce1db815
4
+ data.tar.gz: 3cece4d891ef8100bf0a5eb51570f89bdc58566914fe6f0b514d21a3a76e2f7e
5
5
  SHA512:
6
- metadata.gz: d281941782dbf85eae47f60d8918bd293ffd4e9f760e8725d0e3249faf638ddd42bc5652cfe9d1d3ccc7be0619a54fe9184a5f04a8ecfef186a17c43760ce6f4
7
- data.tar.gz: 8c652f07cafdd6026b81508442f6cee1d1bc8b9f87c946b53f90370b15f9da39017aeaeed27114d0c704e456010cce0a8ace18d3fd173ee6ed6b4755d1ead8d6
6
+ metadata.gz: 3b8d9d2677748d88ec34dbea4abbf65fb05253c0ccd8d1f4bcde2f9781ef9056194a6c25bfe8027dc20f906055fd7db023587aedbb944025109cc443ad9e720a
7
+ data.tar.gz: a9622d5f4a502291fd8472eb24f2f6e479f434897eec51095eb2ba668b95fd1dd438eb400274d9811eaddfbd485e523d76ce7230245f9e8d11a284170afe3725
data/Gemfile CHANGED
@@ -4,3 +4,5 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in serbea.gemspec
6
6
  gemspec
7
+
8
+ gem "erubi", github: "jaredcwhite/erubi", branch: "config-literal-prefix"
data/README.md CHANGED
@@ -1,3 +1,109 @@
1
- πŸ‘‘ Serbea: Similar to ERB, Except Awesomer
1
+ # πŸ‘‘ Serbea: Similar to ERB, Except Awesomer
2
2
 
3
- The Ruby template language you always dreamed of is here. Le roi est mort, vive le roi!
3
+ The Ruby template language you've always dreamed of is finally here. _Le roi est mort, vive le roi!_
4
+
5
+ Serbea combines the best ideas from "brace-style" template languages such as Liquid, Nunjucks, Twig, Jinja, Mustache, etc.β€”and applies them to the world of ERB. You can use Serbea in Rails applications, Bridgetown static sites, or pretty much any Ruby scenario you could imagine.
6
+
7
+ ## Features
8
+
9
+ * Real Ruby. Like, for real.
10
+ * Filters!!! Pipeline operators!!!
11
+ * Autoescaped variables by default within the pipeline (`{{ }}`) tags. Use the `safe`/`raw` or `escape`/`h` filters to control escaping on output.
12
+ * Render directive `{%@ %}` as shortcut for rendering either string-named partials (`render "tmpl"`) or object instances (`render MyComponent.new`).
13
+ * Supports every convention of ERB and builds upon it with new features (which is why it's "awesomer!").
14
+ * Builtin frontmatter support so you can access the variables written into the top YAML within your templates. In any Rails view, including layouts, you'll have access to the `@frontmatter` ivar which is a merged `HashWithDotAccess::Hash` with data from any part of the view tree (partials, pages, layout).
15
+ * The filters accessible within Serbea templates are either helpers (where the variable gets passed as the first argument) or instance methods of the variable itself, so you can build extremely expressive pipelines that take advantage of the code you already know and love. (For example, in Rails you could write `{{ "My Link" | link_to: route_path }}`).
16
+ * The `Serbea::Pipeline.exec` method lets you pass a pipeline template in, along with an optional input value or included helpers module, and you'll get the output as a object of any type (not converted to a string like in traditional templates). For example: `Serbea::Pipeline.exec("[1,2,3] |> map: ->(i) { i * 10 }")` will return `[10, 20, 30]`.
17
+
18
+ ## What It Looks Like
19
+
20
+ ```hbs
21
+ # example.serb
22
+
23
+ {% wow = capture do %}
24
+ This is {{ "amazing" + "!" | upcase }}
25
+ {% end.each_char.reduce("") do |newstr, c|
26
+ newstr += " #{c}"
27
+ end.strip %}
28
+
29
+ {{ wow | prepend: "OMG! " }}
30
+ ```
31
+
32
+ ```hbs
33
+ <p>
34
+ {%
35
+ helper :multiply_array do |input, multiply_by = 2|
36
+ input.map do |i|
37
+ i.to_i * multiply_by
38
+ end
39
+ end
40
+ %}
41
+
42
+ Multiply! {{ [1,3,6, "9"] | multiply_array: 10 }}
43
+ </p>
44
+ ```
45
+
46
+ ```hbs
47
+ {%= form classname: "checkout" do |f| %}
48
+ {{ f.input :first_name, required: true | errors: error_messages }}
49
+ {% end %}
50
+ ```
51
+
52
+ ```hbs
53
+ {%= render "box" do %}
54
+ This is **dope!**
55
+ {%= render "card", title: "Nifty!" do %}
56
+ So great.
57
+ {% end %}
58
+ {% end %}
59
+
60
+ # Let's simplify that using the new render directive!
61
+
62
+ {%@ "box" do %}
63
+ This is **dope!**
64
+ {%@ "card", title: "Nifty!" do %}
65
+ So great.
66
+ {% end %}
67
+ {% end %}
68
+ ```
69
+
70
+ ```hbs
71
+ # Works with ViewComponent!
72
+
73
+ {%= render(Theme::DropdownComponent.new(name: "banner", label: "Banners")) do |dropdown| %}
74
+ {% RegistryTheme::BANNERS.each do |banner| %}
75
+ {% dropdown.slot(:item, value: banner) do %}
76
+ <img src="{{ banner | parameterize: separator: "_" | prepend: "/themes/" | append: ".jpg" }}">
77
+ <strong>{{ banner }}</strong>
78
+ {% end %}
79
+ {% end %}
80
+ {% end %}
81
+
82
+ # Even better, use the new render directive!
83
+
84
+ {%@ Theme::DropdownComponent name: "banner", label: "Banners" do |dropdown| %}
85
+ {% RegistryTheme::BANNERS.each do |banner| %}
86
+ {% dropdown.slot(:item, value: banner) do %}
87
+ <img src="{{ banner | parameterize: separator: "_" | prepend: "/themes/" | append: ".jpg" }}">
88
+ <strong>{{ banner }}</strong>
89
+ {% end %}
90
+ {% end %}
91
+ {% end %}
92
+ ```
93
+
94
+ ```hbs
95
+ # The | and |> pipeline operators are equivalent, so you can write like this if you want!
96
+
97
+ {{
98
+ [1,2,3] |>
99
+ map: -> i {
100
+ i * 10
101
+ } |>
102
+ filter: -> i do
103
+ i > 15
104
+ end |>
105
+ assign_to: :array_length
106
+ }}
107
+
108
+ Array length: {{ @array_length.length }}
109
+ ```
data/Rakefile CHANGED
@@ -1,10 +1,12 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
+ require "bundler"
4
+
5
+ Bundler.setup
3
6
 
4
7
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/test_*.rb"]
8
+ t.test_files = FileList["test/test.rb"]
9
+ t.warning = false
8
10
  end
9
11
 
10
12
  task :default => :test
@@ -4,12 +4,17 @@ require "tilt/erubi"
4
4
  require "serbea/helpers"
5
5
  require "serbea/pipeline"
6
6
  require "serbea/template_engine"
7
- require "serbea/component_renderer"
8
7
 
9
8
  module Tilt
10
9
  class SerbeaTemplate < ErubiTemplate
11
10
  def prepare
12
- @options.merge!(outvar: "@_erbout", bufval: "Serbea::Buffer.new", engine_class: Serbea::TemplateEngine)
11
+ @options.merge!(
12
+ outvar: "@_erbout",
13
+ bufval: "Serbea::Buffer.new",
14
+ literal_prefix: "{%",
15
+ literal_postfix: "%}",
16
+ engine_class: Serbea::TemplateEngine
17
+ )
13
18
  super
14
19
  end
15
20
 
@@ -26,6 +31,10 @@ if defined?(Rails::Railtie)
26
31
  initializer :serbea do |app|
27
32
  ActiveSupport.on_load(:action_view) do
28
33
  require "serbea/rails_support"
34
+
35
+ app.config.after_initialize do
36
+ Serbea::Plugin.initialize_frontmatter
37
+ end
29
38
  end
30
39
  end
31
40
  end
@@ -40,6 +40,8 @@ module Bridgetown
40
40
  #
41
41
  # @return [String] The converted content.
42
42
  def convert(content, convertible)
43
+ return content if convertible.data[:template_engine] != "serbea"
44
+
43
45
  serb_view = Bridgetown::SerbeaView.new(convertible)
44
46
 
45
47
  serb_renderer = Tilt::SerbeaTemplate.new(convertible.relative_path) { content }
@@ -53,16 +55,17 @@ module Bridgetown
53
55
  end
54
56
  end
55
57
 
56
- def matches(ext, convertible = nil)
57
- if convertible
58
- if convertible.data[:template_engine] == "serbea" ||
59
- (convertible.data[:template_engine].nil? &&
60
- @config[:template_engine] == "serbea")
61
- return true
62
- end
58
+ def matches(ext, convertible)
59
+ if convertible.data[:template_engine] == "serbea" ||
60
+ (convertible.data[:template_engine].nil? &&
61
+ @config[:template_engine] == "serbea")
62
+ convertible.data[:template_engine] = "serbea"
63
+ return true
63
64
  end
64
65
 
65
- super(ext)
66
+ super(ext).tap do |ext_matches|
67
+ convertible.data[:template_engine] = "serbea" if ext_matches
68
+ end
66
69
  end
67
70
 
68
71
  def output_ext(ext)
@@ -71,13 +74,3 @@ module Bridgetown
71
74
  end
72
75
  end
73
76
  end
74
-
75
- Bridgetown::Hooks.register :site, :pre_render, reloadable: false do |site|
76
- # make sure Liquid doesn't find {% %} and decide to process Serbea code!
77
- site.contents.each do |convertible|
78
- convertible.data.render_with_liquid = false if convertible.extname == ".serb"
79
- end
80
- site.layouts.values.each do |convertible|
81
- convertible.data.render_with_liquid = false if convertible.ext == ".serb"
82
- end
83
- end
@@ -22,16 +22,34 @@ module Serbea
22
22
  @_erbout = previous_buffer_state
23
23
  @output_buffer = previous_ob_state
24
24
 
25
- result.html_safe
25
+ result&.html_safe
26
26
  end
27
27
 
28
28
  def pipeline(context, value)
29
29
  Pipeline.new(context, value)
30
30
  end
31
31
 
32
- def helper(name, &block)
33
- self.class.send(:define_method, name, &block)
32
+ def helper(name, &helper_block)
33
+ self.class.define_method(name) do |*args, &block|
34
+ previous_buffer_state = @_erbout
35
+ @_erbout = Serbea::Buffer.new
36
+
37
+ # For compatibility with ActionView, not used by Bridgetown normally
38
+ previous_ob_state = @output_buffer
39
+ @output_buffer = Serbea::Buffer.new
40
+
41
+ result = helper_block.call(*args, &block)
42
+ if @output_buffer != ""
43
+ # use Rails' ActionView buffer if present
44
+ result = @output_buffer
45
+ end
46
+ @_erbout = previous_buffer_state
47
+ @output_buffer = previous_ob_state
48
+
49
+ result.is_a?(String) ? result.html_safe : result
50
+ end
34
51
  end
52
+ alias_method :macro, :helper
35
53
 
36
54
  def h(input)
37
55
  ERB::Util.h(input.to_s)
@@ -2,7 +2,6 @@
2
2
 
3
3
  # inspired by https://github.com/haml/haml/blob/main/lib/haml/plugin.rb
4
4
  module Serbea
5
-
6
5
  # This module makes Serbea work with Rails using the template handler API.
7
6
  class Plugin
8
7
  def handles_encoding?; true; end
@@ -16,6 +15,19 @@ module Serbea
16
15
 
17
16
  new.compile(template, source)
18
17
  end
18
+
19
+ def self.initialize_frontmatter
20
+ if defined?(ApplicationHelper)
21
+ Serbea::TemplateEngine.front_matter_preamble = "self.set_page_frontmatter = local_frontmatter = YAML.load"
22
+
23
+ ApplicationHelper.define_method(:set_page_frontmatter=) do |data|
24
+ @frontmatter ||= HashWithDotAccess::Hash.new
25
+ @frontmatter.update(data)
26
+ end
27
+
28
+ ApplicationController.before_action { @frontmatter ||= HashWithDotAccess::Hash.new }
29
+ end
30
+ end
19
31
  end
20
32
  end
21
33
 
@@ -1,3 +1,5 @@
1
+ require "strscan"
2
+
1
3
  module Serbea
2
4
  class Buffer < String
3
5
  def concat_to_s(input)
@@ -64,13 +66,11 @@ module Serbea
64
66
 
65
67
  # Ensure the raw "tag" will strip out all ERB-style processing
66
68
  until string.empty?
67
- text, code, string = string.partition(/{% raw %}.*?{% endraw %}/m)
69
+ text, code, string = string.partition(/{% raw %}(.*?){% endraw %}/m)
68
70
 
69
71
  buff << text
70
72
  if code.length > 0
71
- buff << code.
72
- sub("{% raw %}", "").
73
- sub("{% endraw %}", "").
73
+ buff << $1.
74
74
  gsub("{{", "__RAW_START_PRINT__").
75
75
  gsub("}}", "__RAW_END_PRINT__").
76
76
  gsub("{%", "__RAW_START_EVAL__").
@@ -82,27 +82,61 @@ module Serbea
82
82
  string = buff
83
83
  buff = ""
84
84
  until string.empty?
85
- text, code, string = string.partition(/{{.*?}}/m)
85
+ text, code, string = string.partition(/{{(.*?)}}/m)
86
86
 
87
87
  buff << text
88
88
  if code.length > 0
89
89
  original_line_length = code.lines.size
90
- processed_filters = false
91
-
92
- code = code.gsub('\|', "__PIPE_C__")
93
-
94
- subs = code.gsub(/\s*\|>?\s+(.*?)\s([^|}]*)/) do
95
- args = $2
96
- args = nil if args.strip == ""
97
- prefix = processed_filters ? ")" : "))"
98
- processed_filters = true
99
- "#{prefix}.filter(:#{$1.chomp(":")}" + (args ? ", #{args}" : "")
90
+
91
+ s = StringScanner.new($1)
92
+ escaped_segment = ""
93
+ segments = []
94
+ until s.eos?
95
+ portion = s.scan_until(/\|>?/)
96
+ if portion
97
+ if portion.end_with?('\|')
98
+ # the pipe is escaped, so save that for later
99
+ escaped_segment += portion.sub(/\\\|$/, "|")
100
+ elsif escaped_segment.length > 0
101
+ # we already have escaped content, so finish that up
102
+ segments << escaped_segment + portion.sub(/\|>?$/, "")
103
+ escaped_segment = ""
104
+ else
105
+ # let's find out if this is actionable now
106
+ if s.check(/\|/)
107
+ # nope, the next character is another pipe, so let's escape
108
+ s.pos += 1
109
+ escaped_segment += portion + "|"
110
+ else
111
+ # finally, we have liftoff!
112
+ segments << portion.sub(/\|>?$/, "")
113
+ end
114
+ end
115
+ else
116
+ # we've reached the last bit of the code
117
+ if escaped_segment.length > 0
118
+ # escape and get the rest
119
+ segments << escaped_segment + s.rest
120
+ else
121
+ # or just the rest will do
122
+ segments << s.rest
123
+ end
124
+ s.terminate
125
+ end
126
+ end
127
+
128
+ segments[0] = "pipeline(self, (#{segments[0].strip}))"
129
+ segments[1..-1].each_with_index do |segment, index|
130
+ filter, args = segment.strip.match(/([^ :]*)(.*)/m).captures
131
+ segments[index + 1] = ".filter(:" + filter
132
+ if args == ""
133
+ segments[index + 1] += ")"
134
+ else
135
+ segments[index + 1] += "," + args.sub(/^:/, "") + ")"
136
+ end
100
137
  end
101
-
102
- pipeline_suffix = processed_filters ? ") %}" : ")) %}"
103
-
104
- subs = subs.sub("{{", "{%= pipeline(self, (").sub("}}", pipeline_suffix).gsub("__PIPE_C__", '|')
105
138
 
139
+ subs = "{%= #{segments.join} %}"
106
140
  buff << subs
107
141
 
108
142
  (original_line_length - subs.lines.size).times do
@@ -111,24 +145,15 @@ module Serbea
111
145
  end
112
146
  end
113
147
 
114
- # Process any directives
115
- #
116
- # TODO: allow custom directives! aka
117
- # {%@something whatever %}
118
- # {%@script
119
- # const foo = "really? #{really}!"
120
- # alert(foo.toLowerCase())
121
- # %}
122
- # {%@preact AwesomeChart data={#{ruby_data.to_json}} %}
148
+ # Process any render directives
123
149
  string = buff
124
150
  buff = ""
125
151
  until string.empty?
126
- text, code, string = string.partition(/{%@.*?%}/m)
152
+ text, code, string = string.partition(/{%@(.*?)%}/m)
127
153
 
128
154
  buff << text
129
155
  if code.length > 0
130
- code.sub! /^\{%@/, ""
131
- code.sub! /%}$/, ""
156
+ code = $1
132
157
  unless ["end", ""].include? code.strip
133
158
  original_line_length = code.lines.size
134
159
 
@@ -1,3 +1,3 @@
1
1
  module Serbea
2
- VERSION = "0.6.3"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -16,7 +16,10 @@ Gem::Specification.new do |spec|
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r!^(test|script|spec|features)/!) }
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.add_runtime_dependency("erubi", "~> 1.9")
20
19
  spec.add_runtime_dependency("activesupport", "~> 6.0")
20
+ spec.add_runtime_dependency("erubi", "~> 1.9")
21
+ spec.add_runtime_dependency("hash_with_dot_access", "~> 1.1")
21
22
  spec.add_runtime_dependency("tilt", "~> 2.0")
23
+
24
+ spec.add_development_dependency("rake", "~> 13.0")
22
25
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serbea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bridgetown Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-29 00:00:00.000000000 Z
11
+ date: 2020-11-14 00:00:00.000000000 Z
12
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: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: erubi
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -25,19 +39,19 @@ dependencies:
25
39
  - !ruby/object:Gem::Version
26
40
  version: '1.9'
27
41
  - !ruby/object:Gem::Dependency
28
- name: activesupport
42
+ name: hash_with_dot_access
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '6.0'
47
+ version: '1.1'
34
48
  type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '6.0'
54
+ version: '1.1'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: tilt
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +66,20 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
55
83
  description:
56
84
  email: maintainers@bridgetownrb.com
57
85
  executables: []
@@ -64,7 +92,6 @@ files:
64
92
  - Rakefile
65
93
  - lib/serbea.rb
66
94
  - lib/serbea/bridgetown_support.rb
67
- - lib/serbea/component_renderer.rb
68
95
  - lib/serbea/helpers.rb
69
96
  - lib/serbea/pipeline.rb
70
97
  - lib/serbea/rails_support.rb
@@ -1,19 +0,0 @@
1
- module Serbea
2
- class ComponentRenderer
3
- include Helpers
4
-
5
- def initialize(variables = {})
6
- @variables = variables
7
- end
8
-
9
- def respond_to_missing?(key, include_private = false)
10
- @variables.key?(key)
11
- end
12
-
13
- def method_missing(key)
14
- return @variables[key] if respond_to_missing?(key)
15
-
16
- super
17
- end
18
- end
19
- end