rubyoshka 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dcac0e7abbbb0bad4f11b3c3b500ed7c14f506d06c6aa8ecbcec4cfc3503ce16
4
+ data.tar.gz: 8fd105613444edd272644aff2cb054b4c0a540789f5e63f41a4d9c033fc36577
5
+ SHA512:
6
+ metadata.gz: 4f693a62532ae3235383068ed789cc144fd38ec7723c5ff728ace5b0a5b0ae267e61fc3e8140ac8972420a8c35fa0731b543dbd84016bda049843038ae5bafa4
7
+ data.tar.gz: 3ca48bcd081c077635cffa99d36823e2f19c1ac832a26426ae510e233d1054880a0e74b3047aeef389dcb88b4905a95339e90145cd131efcd2a8edba23599dea
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ 0.1 2019-01-06
2
+ --------------
3
+
4
+ * First working version
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Rubyoshka - Composable HTML templating for Ruby
2
+
3
+ [INSTALL](#installing-rubyoshka) |
4
+ [TUTORIAL](#getting-started) |
5
+ [EXAMPLES](examples)
6
+
7
+ ## What is Rubyoshka
8
+
9
+ Rubyoshka is an HTML templating engine for Ruby that offers the following
10
+ features:
11
+
12
+ - HTML templating using plain Ruby syntax
13
+ - Minimal boilerplate
14
+ - Automatic HTML escaping
15
+ - Composable nested components
16
+ - Access to a global context from anywhere in the component hierarchy
17
+ - High performance
18
+
19
+ With Rubyoshka you can structure your templates like a Russian doll, each
20
+ component containing any number of nested components, in a similar fashion to
21
+ React. The name *Rubyoshka* is a nod to Matryoshka, the Russian nesting doll.
22
+
23
+ ## Installing Rubyoshka
24
+
25
+ ```bash
26
+ $ gem install polyphony
27
+ ```
28
+
29
+ ## Getting started
30
+
31
+ To use Rubyoshka in your code just require it:
32
+
33
+ ```ruby
34
+ require 'rubyoshka'
35
+ ```
36
+
37
+ Alternatively, you can import it using [Modulation](https://github.com/digital-fabric/modulation):
38
+
39
+ ```ruby
40
+ Rubyoshka = import('rubyoshka')
41
+ ```
42
+
43
+ To create a template use `Rubyoshka.new` or the global method `Kernel#H`:
44
+
45
+ ```ruby
46
+ # can also use Rubyoshka.new
47
+ html = H {
48
+ div { p 'hello' }
49
+ }
50
+ ```
51
+
52
+ To render the template use `render`:
53
+
54
+ ```ruby
55
+ H { span 'best span' }.render #=> "<span>best span</span>"
56
+ ```
57
+
58
+ The render method accepts an arbitrary context variable:
59
+
60
+ ```ruby
61
+ html = H {
62
+ h1 context[:title]
63
+ }
64
+
65
+ html.render(title: 'My title') #=> "<h1>My title</h1>"
66
+ ```
67
+
68
+ ## All about tags
69
+
70
+ Tags are added using unqualified method calls, and are nested using blocks:
71
+
72
+ ```ruby
73
+ H {
74
+ html {
75
+ head {
76
+ title 'page title'
77
+ }
78
+ body {
79
+ article {
80
+ h1 'article title'
81
+ }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ Tag methods accept a string argument, a block, or no argument at all:
88
+
89
+ ```ruby
90
+ H { p 'hello' }.render #=> "<p>hello</p>"
91
+
92
+ H { p { span '1'; span '2' } }.render #=> "<p><span>1</span><span>2</span></p>"
93
+
94
+ H { hr() }.render #=> "<hr/>"
95
+ ```
96
+
97
+ Tag methods also accept tag attributes, given as a hash:
98
+
99
+ ```ruby
100
+ H { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
101
+
102
+ H { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
103
+ ```
104
+
105
+ ## Templates as components
106
+
107
+ Rubyoshka makes it easy to compose multiple templates into a whole HTML
108
+ document. Each template can be defined as a self-contained component that can
109
+ be reused inside other components. Components should be defined as constants,
110
+ either in the global namespace, or on the Rubyoshka namespace:
111
+
112
+ ```ruby
113
+ # Item is actually a Proc that returns a template
114
+ Item = ->(id:, text:, checked:) {
115
+ H {
116
+ li {
117
+ input name: id, type: 'checkbox', checked: checked
118
+ label text, for: id
119
+ }
120
+ }
121
+ }
122
+
123
+ def render_items(items)
124
+ html = H {
125
+ ul {
126
+ items.each { |id, attributes|
127
+ Item id: id, text: attributes[:text], checked: attributes[:active]
128
+ }
129
+ }
130
+ }.render
131
+ end
132
+ ```
133
+
134
+ ## Wrapping arbitrary HTML
135
+
136
+ Components can be used to wrap arbitrary HTML content by defining them as procs
137
+ that accept blocks:
138
+
139
+ ```ruby
140
+ Header = ->(&inner_html) {
141
+ header {
142
+ h1 'title'
143
+ emit inner_html
144
+ }
145
+ }
146
+
147
+ H { Header { button 'OK'} }.render #=> "<header><h1>title</h1><button>OK</button></header>"
148
+ ```
149
+
150
+ ## Some interesting use cases
151
+
152
+ Rubyoshka opens up all kinds of new possibilities when it comes to putting
153
+ together pieces of HTML. Feel free to explore the possibilities!
154
+
155
+ ### Routing in the view
156
+
157
+ The following example demonstrates a router component implemented as a pure
158
+ function. The router simply returns the correct component for the given path:
159
+
160
+ ```ruby
161
+ Router = ->(path) {
162
+ case path
163
+ when '/'
164
+ PostIndex()
165
+ when /^posts\/(.+)$/
166
+ post = get_post($1)
167
+ if post
168
+ Post(post)
169
+ else
170
+ ErrorPage(404)
171
+ end
172
+ end
173
+ }
174
+
175
+ Blog = H {
176
+ html {
177
+ head {
178
+ title: 'My blog'
179
+ }
180
+ body {
181
+ Topbar()
182
+ Sidebar()
183
+ div id: 'content' { Router(context[:path]) }
184
+ }
185
+ }
186
+ }
187
+ ```
data/lib/rubyoshka.rb ADDED
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation/gem'
4
+ require 'escape_utils'
5
+
6
+ export_default :Rubyoshka
7
+
8
+ # A Rubyoshka is a template representing a piece of HTML
9
+ class Rubyoshka
10
+ # A Rendering is a rendering of a Rubyoshka
11
+ class Rendering
12
+ attr_reader :context
13
+
14
+ # Initializes attributes and renders the given block
15
+ # @param context [Hash] rendering context
16
+ # @param block [Proc] template block
17
+ # @return [void]
18
+ def initialize(context, &block)
19
+ @context = context
20
+ @buffer = +''
21
+ instance_eval(&block)
22
+ end
23
+
24
+ # Returns the result of the rendering
25
+ # @return [String]
26
+ def to_s
27
+ @buffer
28
+ end
29
+
30
+ S_TAG_METHOD = <<~EOF
31
+ def %1$s(*args, &block)
32
+ tag(:%1$s, *args, &block)
33
+ end
34
+ EOF
35
+
36
+ R_CONST_SYM = /^[A-Z]/
37
+
38
+ # Catches undefined tag method call and handles them by defining the method
39
+ # @param sym [Symbol] HTML tag or component identifier
40
+ # @param args [Array] method call arguments
41
+ # @param block [Proc] block passed to method call
42
+ # @return [void]
43
+ def method_missing(sym, *args, &block)
44
+ if sym =~ R_CONST_SYM
45
+ o = instance_eval(sym.to_s) rescue Rubyoshka.const_get(sym) \
46
+ rescue Object.const_get(sym)
47
+ case o
48
+ when ::Proc
49
+ self.class.define_method(sym) { |*a, &b| emit o.(*a, &b) }
50
+ emit o.(*args, &block)
51
+ when Rubyoshka
52
+ self.class.define_method(sym) { emit o }
53
+ emit(o)
54
+ when ::String
55
+ @buffer << o
56
+ else
57
+ e = StandardError.new "Cannot render #{o.inspect}"
58
+ e.set_backtrace(caller)
59
+ raise e
60
+ end
61
+ else
62
+ self.class.class_eval(S_TAG_METHOD % sym)
63
+ tag(sym, *args, &block)
64
+ end
65
+ end
66
+
67
+ # Emits the given object into the rendering buffer
68
+ # @param o [Proc, Rubyoshka, String] emitted object
69
+ # @return [void]
70
+ def emit(o)
71
+ case o
72
+ when ::Proc
73
+ instance_eval(&o)
74
+ when Rubyoshka
75
+ instance_eval(&o.block)
76
+ else
77
+ @buffer << o.to_s
78
+ end
79
+ end
80
+ alias_method :e, :emit
81
+
82
+ S_LT = '<'
83
+ S_GT = '>'
84
+ S_LT_SLASH = '</'
85
+ S_SPACE_LT_SLASH = ' </'
86
+ S_SLASH_GT = '/>'
87
+ S_SPACE = ' '
88
+ S_EQUAL_QUOTE = '="'
89
+ S_QUOTE = '"'
90
+
91
+ E = EscapeUtils
92
+
93
+ # Emits an HTML tag
94
+ # @param sym [Symbol] HTML tag
95
+ # @param text [String] text content of tag
96
+ # @param props [Hash] tag attributes
97
+ # @param block [Proc] nested HTML block
98
+ # @return [void]
99
+ def tag(sym, text = nil, **props, &block)
100
+ sym = sym.to_s
101
+
102
+ @buffer << S_LT << sym
103
+ emit_props(props) unless props.empty?
104
+
105
+ if block
106
+ @buffer << S_GT
107
+ instance_eval(&block)
108
+ @buffer << S_LT_SLASH << sym << S_GT
109
+ elsif Rubyoshka === text
110
+ @buffer << S_GT
111
+ emit(text)
112
+ @buffer << S_LT_SLASH << sym << S_GT
113
+ elsif text
114
+ @buffer << S_GT << E.escape_html(text.to_s) <<
115
+ S_LT_SLASH << sym << S_GT
116
+ else
117
+ @buffer << S_SLASH_GT
118
+ end
119
+ end
120
+
121
+ # Emits tag attributes into the rendering buffer
122
+ # @param props [Hash] tag attributes
123
+ # @return [void]
124
+ def emit_props(props)
125
+ props.each { |k, v|
126
+ case k
127
+ when :text
128
+ when :src, :href
129
+ @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
130
+ E.escape_uri(v) << S_QUOTE
131
+ else
132
+ if v == true
133
+ @buffer << S_SPACE << k.to_s
134
+ else
135
+ @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << v << S_QUOTE
136
+ end
137
+ end
138
+ }
139
+ end
140
+
141
+ # Emits the p tag
142
+ # @param text [String] text content of tag
143
+ # @param props [Hash] tag attributes
144
+ # @para block [Proc] nested HTML block
145
+ # @return [void]
146
+ def p(text = nil, **props, &block)
147
+ tag(:p, text, **props, &block)
148
+ end
149
+
150
+ S_HTML5_DOCTYPE = '<!DOCTYPE html>'
151
+
152
+ # Emits an HTML5 doctype tag and an html tag with the given block
153
+ # @param block [Proc] nested HTML block
154
+ # @return [void]
155
+ def html5(&block)
156
+ @buffer << S_HTML5_DOCTYPE
157
+ self.html(&block)
158
+ end
159
+
160
+ # Emits text into the rendering buffer
161
+ # @param data [String] text
162
+ def text(data)
163
+ @buffer << E.escape_html(text)
164
+ end
165
+ end
166
+
167
+ attr_reader :block
168
+
169
+ # Initializes a Rubyoshka with the given block
170
+ # @param block [Proc] nested HTML block
171
+ # @param [void]
172
+ def initialize(&block)
173
+ @block = block
174
+ end
175
+
176
+ # Renders the associated block and returns the string result
177
+ # @param context [Hash] context
178
+ # @return [String]
179
+ def render(context = {})
180
+ Rendering.new(context, &block).to_s
181
+ end
182
+ end
183
+
184
+ module ::Kernel
185
+ # Convenience method for creating a new Rubyoshka
186
+ def H(&block)
187
+ Rubyoshka.new(&block)
188
+ end
189
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rubyoshka
4
+ VERSION = '0.1'
5
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubyoshka
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Sharon Rosner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: modulation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: '0.18'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: '0.18'
27
+ - !ruby/object:Gem::Dependency
28
+ name: escape_utils
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.2.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 5.11.3
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 5.11.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: benchmark-ips
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.7.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 2.7.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: erubis
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 2.7.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 2.7.0
83
+ description:
84
+ email: ciconia@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files:
88
+ - README.md
89
+ files:
90
+ - CHANGELOG.md
91
+ - README.md
92
+ - lib/rubyoshka.rb
93
+ - lib/rubyoshka/version.rb
94
+ homepage: http://github.com/digital-fabric/rubyoshka
95
+ licenses:
96
+ - MIT
97
+ metadata:
98
+ source_code_uri: https://github.com/digital-fabric/rubyoshka
99
+ post_install_message:
100
+ rdoc_options:
101
+ - "--title"
102
+ - rubyoshka
103
+ - "--main"
104
+ - README.md
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.7.3
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: 'Rubyoshka: composable HTML templating for Ruby'
123
+ test_files: []