templet 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5f53a3264d04846496a5fab85e4870ca1f1028b5
4
+ data.tar.gz: 35f92da74584cca4bf699f0aff237b96f4772a69
5
+ SHA512:
6
+ metadata.gz: c5c00ae7fc9c270c20986ec45c9fcfa601dbd64f3cf30ae711ef06192eb86ad4a83e85cf16c3402609b6677ece80db87efe3a68df02284ad0335263f2aacdc84
7
+ data.tar.gz: 8290792806560547f80b385b7a95ac22003b262e4b23c033a1b130b27a714c8984846bba9546fd65fe8dfb5cc773dadd8f9aef02b77b929f1381841bb2659ca3
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ **.swp
10
+ *~
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ before_install: gem install bundler -v 1.16.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in templet.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ templet (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.10.2)
10
+ rake (10.5.0)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ bundler (~> 1.16)
17
+ minitest (~> 5.0)
18
+ rake (~> 10.0)
19
+ templet!
20
+
21
+ BUNDLED WITH
22
+ 1.16.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,533 @@
1
+
2
+ # Templet
3
+
4
+ ## Introduction
5
+
6
+ This gem is a stand-alone feather-weight DSL in pure Ruby
7
+ that renders HTML via method calls.
8
+
9
+ It can run from anywhere (within reason), and with next to no set up.
10
+ Also, it won't cause installation conflicts,
11
+ as it has no external run-time dependencies.
12
+
13
+ For example, it may be used with a framework (like Rails or Sinatra),
14
+ or as part of a static site generator, or inside a command line script.
15
+
16
+ It has three main sections:
17
+
18
+ 1. A basic (in-line) renderer.
19
+
20
+ 2. Base classes for splitting up a view into separate components.
21
+
22
+ 3. Helper methods for rendering HTML tables and lists.
23
+
24
+ _Incidentally, and if such a need arises,
25
+ there is another renderer class, *Templet::Renderers::ERb*,
26
+ that you may use to insert markup inside of an ERb layout,
27
+ either on file or as a string._
28
+
29
+ ## The basic DSL (Renderer)
30
+
31
+ The DSL processing (i.e. HTML rendering) is handled by
32
+ the class, *Templet::Renderer*.
33
+ But don't expect anything earth-shattering,
34
+ as this class breaks no new ground.
35
+ It's as simple a DSL as you're likely to come across.
36
+ Also, there are a good few others that are similar,
37
+ e.g. XML Builder, Markaby, Arbre, and Fortitude.
38
+
39
+ The DSL allows you to pass in local variables,
40
+ as well as multiple contexts for method look-ups,
41
+ which means that the DSL is able to expose a wide range of methods
42
+ that can be called from the markup code directly.
43
+
44
+ However, this basic Renderer, when used by itself,
45
+ has no in-built functionality for modularity,
46
+ and so is not really suitable for long HTML pages,
47
+ or for handling variation, or for code sharing.
48
+
49
+ ## Components (Layouts and Partials)
50
+
51
+ To avoid these limitations, there are, in addition,
52
+ two base classes supplied that can be used as building blocks
53
+ for composing view segments.
54
+
55
+ They ought to be used in preference to the (above mentioned)
56
+ *Renderer* class, which they use internally.
57
+ The *Renderer* provides the context in which these Components are run.
58
+
59
+ The two classes, whose API's have just a single *call* method, are:
60
+
61
+ 1. *Templet::Component::Layout*
62
+
63
+ 2. *Templet::Component::Partial*
64
+
65
+ In most cases they are to be used as superclasses,
66
+ from which your own custom components (as subclasses) inherit.
67
+
68
+ You begin with a single *Layout*, which, typically,
69
+ contains calls to a number of *Partials*,
70
+ maybe interspersed with some markup code.
71
+
72
+ Using these as base classes,
73
+ your own subclasses can receive (local) input variables,
74
+ encapsulate helper methods,
75
+ and act as a container for constituent elements.
76
+
77
+ More specifically,
78
+ these classes allow you to develop general-purpose components,
79
+ such as, HTML layouts, Rails forms, Bootstrap menus,
80
+ and even a full-blown Rails Scaffolding alternative.
81
+
82
+ But there's no need for you to embark on such a venture yourself,
83
+ as these things are implemented in the related gem:
84
+ [templet\_rails](https://github.com/srycyk/templet_rails).
85
+
86
+ ## HTML Helpers
87
+
88
+ Some further classes are included (in the *templet/html/* sub-directory),
89
+ that provide a shortened way to render HTML tables and lists.
90
+ Their content is determined by a Ruby Hash or Array,
91
+ which is given as input.
92
+
93
+ Examples of using these are given towards the end.
94
+
95
+ ## Renderer Usage
96
+
97
+ Except for very small jobs,
98
+ it's best not to construct an instance of a *Renderer* explicitly,
99
+ even though this is illustrated in the examples which follow.
100
+
101
+ As noted, it's neater to use the *Renderer* implicitly,
102
+ that is, by means of a *Layout* containing a number of *Partials*.
103
+
104
+ Still, it is useful to have a grounding in the basic rules and techniques,
105
+ as they apply to the Components as well.
106
+
107
+ ### Rudimentary Renderer Usage
108
+
109
+ No need to begin by reading an API,
110
+ _(anyway, there isn't one - but most classes just have a **call** method),_
111
+ as this example shows elementary usage:
112
+
113
+ ```ruby
114
+ require 'templet/renderer'
115
+
116
+ message = 'Hello there'
117
+
118
+ html = Templet::Renderer.new.call do
119
+ header = head { title 'Role' }
120
+
121
+ # +message+ is the closure variable above
122
+ content = p { message }
123
+
124
+ # The Renderer only shows what a block returns
125
+ # which should be a string, or a callable that returns a string
126
+ # To return multiple values, use an array of such
127
+ html { [ header, body(content) ] }
128
+ end
129
+
130
+ puts html
131
+ ```
132
+
133
+ This produces the following HTML:
134
+
135
+ ```html
136
+ <html><head><title>Role</title>
137
+ </head>
138
+
139
+ <body><p>Hello there</p>
140
+ </body>
141
+ </html>
142
+ ```
143
+
144
+ > Note that the code blocks (passed to the *Renderer*)
145
+ > are not run in the lexical context that blocks normally are.
146
+ > The blocks are actually executed inside of an instance of *Renderer*
147
+ > which inherits from *BasicObject* -
148
+ > which is a threadbare class of very few methods.
149
+ > It's up to you to provide the lookup context(s),
150
+ > which are passed into the *Renderer's* constructor.
151
+
152
+ ### More Advanced Renderer Usage
153
+
154
+ The following example explains more
155
+ and covers a lot to do with visibility and scoping:
156
+
157
+ ```ruby
158
+ require 'templet'
159
+
160
+ class Lister
161
+ def items
162
+ %w(One Two Three)
163
+ end
164
+ end
165
+
166
+ def content
167
+ 'Some content'
168
+ end
169
+
170
+ col_tag = Templet::Renderers::Tag.new(:div, class: :col_md_4)
171
+
172
+ # The first two arguments are contexts for method lookups
173
+ #
174
+ # The final argument list local variables which take precendence
175
+ #
176
+ # The block renders the markup
177
+ # Note the shortcut call, this call is stated in full in the above example
178
+ html = Templet.(self, Lister.new, title: 'A Title') do
179
+ more_content = 'More content'
180
+
181
+ # +title+ is (in effect) a local variable passed into the constru
182
+ ctor
183
+ [ -> { h1(title, :strong) }, # you can include anything callable
184
+
185
+ # Calls +items+ from Lister instance, given as a contructor arg
186
+ ument
187
+ # The +_p+ call renders a <p> tag
188
+ # without the underscore the Kernel#p method would override
189
+ _p(ul(:list_unstyled) { items.map {|item| li item } }),
190
+
191
+ div(:row) do
192
+ # Calls +content+ because +self+ is passed into the contructo
193
+ r
194
+ # +col_tag+ is a closure variable defined above
195
+ [ col_tag.(content), col_tag.(more_content), col_tag.('...')
196
+ ]
197
+ end
198
+ ]
199
+ end
200
+
201
+ puts html
202
+ ```
203
+
204
+ This produces the following HTML:
205
+
206
+ ```html
207
+ <h1 class='strong'>A Title</h1>
208
+
209
+ <p><ul class='list-unstyled'><li>One</li>
210
+
211
+ <li>Two</li>
212
+
213
+ <li>Three</li>
214
+ </ul>
215
+ </p>
216
+
217
+ <div class='row'><div class='col-md-4'>Some content</div>
218
+
219
+ <div class='col-md-4'>More content</div>
220
+
221
+ <div class='col-md-4'>...</div>
222
+ </div>
223
+ ```
224
+
225
+ ## Notes on Usage
226
+
227
+ The main quirk is that the *Renderer* only outputs
228
+ the actual return value of a given block.
229
+ That is, statements preceding the very last one won't
230
+ show up in the resultant markup.
231
+ *This behaviour differs from that of most other markup API's.*
232
+
233
+ A block's return type must be either an array of strings or of *callable*
234
+ entities (which themselves return one or more strings).
235
+ This array can be nested at any depth.
236
+
237
+ Importantly, the block, passed to *Renderer#call*, is **not**
238
+ executed in its natural local (i.e. lexical) scope.
239
+ This means that, although local variables will be accessible,
240
+ the methods (within the current context) won't - unless the
241
+ current value of *self* is passed into the *Renderer's* constructor,
242
+ as one of the initial arguments.
243
+
244
+ *To put this in more practical terms: be aware that if an error does occur,
245
+ its origin may be flagrantly misreported in the stack trace.*
246
+
247
+ The tests have more examples of usage,
248
+ also, the source code is easy to follow and is commented.
249
+
250
+ But don't dig too deep.
251
+ There's not a lot you need to learn to get started,
252
+ and you should be able to pick up the rest as you go along.
253
+
254
+ ## Components
255
+
256
+ As said, Components facilitate a modular (object oriented) approach
257
+ to rendering markup.
258
+
259
+ You begin with a Layout, which, typically, contains a number of Partials.
260
+
261
+ There's no need to have more than a single Layout,
262
+ since Partials can be nested inside one another.
263
+
264
+ Together, they perform much the same function as their namesakes in Rails.
265
+
266
+ ### Examples of a Layout with Partials
267
+
268
+ #### A Very Basic Layout Example
269
+
270
+ ```ruby
271
+ require 'templet/component'
272
+
273
+ html = Templet::Component::Layout.new(heading: 'A Title').call do
274
+ # The method calls become HTML tags
275
+ html do
276
+ # +heading+ is passed by name above, it overrides
277
+ [ head(title heading), body { div 'Hello' } ]
278
+ end
279
+ end
280
+
281
+ puts html
282
+ ```
283
+
284
+ This produces the following HTML:
285
+
286
+ ```html
287
+ <html><head><title>A Title</title>
288
+ </head>
289
+
290
+ <body><div>Hello</div>
291
+ </body>
292
+ </html>
293
+ ```
294
+
295
+ #### A Slightly More Realistic Example
296
+
297
+ ```ruby
298
+ require 'templet/component'
299
+
300
+ class HtmlLayout < Templet::Component::Layout
301
+ def call
302
+ super do
303
+ html do
304
+ [ head { title heading },
305
+ # The renderer is passed to calling block
306
+ body(yield renderer) ]
307
+ end
308
+ end
309
+ end
310
+ end
311
+
312
+ class BodyPart < Templet::Component::Partial
313
+ def call
314
+ super do
315
+ span hello
316
+ end
317
+ end
318
+
319
+ private
320
+
321
+ def hello
322
+ 'Hello'
323
+ end
324
+ end
325
+
326
+ class BodyBuild < Templet::Component::Partial
327
+ def call
328
+ # This is an alternative way to render markup, i.e. without a super call
329
+ renderer.call { div BodyPart.new(renderer), :row }
330
+ end
331
+ end
332
+
333
+ html = HtmlLayout.new(heading: 'Down').call do |renderer|
334
+ # No need to explicitly call(), this'll be done later on
335
+ BodyBuild.new(renderer)
336
+ end
337
+
338
+ puts html
339
+ ```
340
+
341
+ This produces the following HTML:
342
+
343
+ ```html
344
+ <html><head><title>Down</title>
345
+ </head>
346
+
347
+ <body><div class='row'><span>Hello</span>
348
+ </div>
349
+ </body>
350
+ </html>
351
+ ```
352
+
353
+ > Although this DSL relies heavily on *method_missing*,
354
+ > this has not brought poor performance - quite the reverse.
355
+ > After all, this DSL is tiny, and thus quick to load and run,
356
+ > making no calls to external libraries.
357
+ > In ERb, or HAML, the source code is parsed (byte by byte)
358
+ > so as to generate some new Ruby code, that is finally executed.
359
+
360
+ ## Examples of Rendering HTML Composites
361
+
362
+ In these examples, a *nil* value is passed to the constructor,
363
+ but in application code, this will be, in nearly all cases,
364
+ replaced by an instance of a *Renderer*.
365
+
366
+ > To load these claases you must add: `require 'templet/html'`.
367
+
368
+ ### An Unordered List
369
+
370
+ ```ruby
371
+ Templet::Html::List.new(nil).(["One", "Two", "Three"])
372
+ ```
373
+
374
+ This produces the following HTML:
375
+
376
+ ```html
377
+ <ul><li>One</li>
378
+
379
+ <li>Two</li>
380
+
381
+ <li>Three</li>
382
+ </ul>
383
+ ```
384
+
385
+ ### A Definition List
386
+
387
+ In basic cases, you pass in a Hash,
388
+ where the key is the title (the *dt* tag),
389
+ and the value is the data (the *dd* tag).
390
+ This is done as follows:
391
+
392
+ ```ruby
393
+ Templet::Html::DefinitionList.new(nil).({"One"=>"First", "Two"=>"Second", "Three"=>"Third"})
394
+ ```
395
+
396
+ This produces the following HTML:
397
+
398
+ ```html
399
+ <dl><dt>One</dt>
400
+ <dd>First</dd>
401
+
402
+ <dt>Two</dt>
403
+ <dd>Second</dd>
404
+
405
+ <dt>Three</dt>
406
+ <dd>Third</dd>
407
+ </dl>
408
+ ```
409
+
410
+ In addition, the value of an entry in this *control* Hash
411
+ can also be a Symbol or Proc.
412
+ In these cases you also supply a record, as a second parameter to *call*.
413
+ If a Symbol is given then it's used as a (Hash) key,
414
+ as in `record[key]`.
415
+ If a Proc is given then it's called with the
416
+ record passed as the first parameter, as in `func.call(record)`.
417
+
418
+ ```ruby
419
+ record = OpenStruct.new(field_1: 'Value 1', field_2: 'Value 2')
420
+
421
+ controls = { first: :field_1, second: -> (record) { record.send(:field_2) } }
422
+
423
+ Templet::Html::DefinitionList.new(nil).(controls, record, html_class: 'low')
424
+ ```
425
+
426
+ This produces the following HTML:
427
+
428
+ ```html
429
+ <dl class='low'><dt>First</dt>
430
+ <dd>Value 1</dd>
431
+
432
+ <dt>Second</dt>
433
+ <dd>Value 2</dd>
434
+ </dl>
435
+ ```
436
+
437
+ ### A Table
438
+
439
+ To render an HTML table you pass to the *call* method,
440
+ a control Hash (as set out just above),
441
+ and a list of records, obviously, with a table row for each record.
442
+
443
+ ```ruby
444
+ controls = { 'Title 1' => nil, # shows the whole of the 'numbers' hash
445
+ # if an array was given, it'd be indexed
446
+ 'Title 2' => 'Two', # shows the 'numbers' hash entry 'Two'
447
+ # calls this proc - the first param is a Renderer instance
448
+ 'Title 3' => proc {|_, numbers| numbers['Three'] }
449
+ }
450
+
451
+ Templet::Html::Table.new(nil).(controls, [{"One"=>"First", "Two"=>"Second", "Three"=>"Third"}])
452
+ ```
453
+
454
+ This produces the following HTML:
455
+
456
+ ```html
457
+ <table><thead><tr><th>Title 1</th>
458
+
459
+ <th>Title 2</th>
460
+
461
+ <th>Title 3</th>
462
+ </tr>
463
+ </thead>
464
+
465
+ <tbody><tr>{"One"=>"First", "Two"=>"Second", "Three"=>"Third"}
466
+ <td>Second</td>
467
+
468
+ <td>Third</td>
469
+ </tr>
470
+ </tbody>
471
+
472
+ <tfoot><tr><td colspan='3'></td>
473
+ </tr>
474
+ </tfoot>
475
+ </table>
476
+ ```
477
+
478
+ ## Examples of Rendering Tags
479
+
480
+ As mentioned, the *Renderer* class,
481
+ (via a block passed to its *call* method),
482
+ lets you render markup by direct Ruby method calls.
483
+
484
+ Here are a few examples of some of the ways of using the API to render tags:
485
+
486
+ * `span('Hello', :small, id: '999') => <span id='999' class='small'>Hello</span>`
487
+
488
+ * `__p(:pull_right) { 'Hello' } => <p class='pull-right'>Hello</p>`
489
+
490
+ * `div('', :pull_right, class: 'clearfix') => <div class='clearfix pull-right'></div>`
491
+
492
+ As you can see, an HTML class can be given as an argument.
493
+ Any underscores, in the class name, will be replaced
494
+ by dashes in the (generated) markup.
495
+
496
+ To avoid method name clashes, you can prefix a method call with a number of
497
+ underscores, and these won't appear in the corresponding tag name.
498
+ For example, this is particularly important when rendering the HTML tag *p*,
499
+ as there's a Ruby *Kernel* method called *p*, that may be called first.
500
+
501
+ If you prefer, you can call a *tag* helper method instead of
502
+ inferring a tag name from a method name. For example:
503
+
504
+ * `tag(:p, 'Hello', :pull_right) => <p class='pull-right'>Hello</p>`
505
+
506
+ Note that this method will mask any other method,
507
+ of the same name (i.e. *tag*), higher up in the inheritance hierarchy.
508
+
509
+ ## Installation
510
+
511
+ Add this line to your application's Gemfile:
512
+
513
+ ```ruby
514
+ gem 'templet'
515
+ ```
516
+
517
+ And then execute:
518
+
519
+ ```
520
+ $ bundle
521
+ ```
522
+
523
+ Or install it yourself as:
524
+
525
+ ```
526
+ $ gem install templet
527
+ ```
528
+
529
+ ## Licence
530
+
531
+ The gem is available as open source under the terms
532
+ of the [MIT License](https://opensource.org/licenses/MIT).
533
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "templet"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,20 @@
1
+
2
+ module Templet
3
+ module Component
4
+ # For composing views - the first step
5
+ class Layout < Partial
6
+ # +contexts+:: A list containing objects whose methods will be looked up
7
+ # +locals+:: Objects you can reference by the given name
8
+ def initialize(*contexts, **locals)
9
+ contexts = [ self, *contexts ]
10
+
11
+ self.renderer = Renderer.new(*contexts, **locals)
12
+ end
13
+
14
+ def self.call(*contexts, **locals, &block)
15
+ new(*contexts, **locals).(&block)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,32 @@
1
+
2
+ module Templet
3
+ module Component
4
+ # Used for composing views, either within a Layout or another Parial
5
+ class Partial
6
+ attr_accessor :renderer
7
+
8
+ # +contexts+:: A list containing objects whose methods will be looked up
9
+ # +locals+:: Objects you can reference by the name given as the key
10
+ def initialize(renderer, *contexts, **locals)
11
+ self.renderer = renderer&.new_instance(self, *contexts, **locals) ||
12
+ Renderer.new(self, *contexts, **locals)
13
+ end
14
+
15
+ # Entry point - the block returns markup
16
+ def call(&block)
17
+ renderer.(&block)
18
+ end
19
+
20
+ # Shortcut
21
+ def self.call(renderer, *contexts, **locals, &block)
22
+ new(renderer, *contexts, **locals).(&block)
23
+ end
24
+
25
+ private
26
+
27
+ # If +call+ gets overriden then use +compose+ instead
28
+ alias compose call
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,10 @@
1
+
2
+ require 'templet'
3
+
4
+ require 'templet/component/partial'
5
+ require 'templet/component/layout'
6
+
7
+ module Templet
8
+ module Component
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+
2
+ require 'templet/component/partial'
3
+
4
+ module Templet
5
+ module Html
6
+ # Renders an HTML dl from a Hash
7
+ class DefinitionList < Component::Partial
8
+ # +controls+ [Hash]
9
+ # The key is the field's title
10
+ # The value is the field value || a Proc which calls the record's method
11
+ def call(controls, record=nil, html_class: nil)
12
+ super() do
13
+ dl(html_class || default_html_class) do
14
+ controls.to_h.map do |title, data|
15
+ title = title.to_s.capitalize.tr('_', ' ')
16
+
17
+ if data.respond_to?(:call)
18
+ data = data.(record)
19
+ elsif Symbol === data
20
+ data = if record and record.respond_to?(:[])
21
+ record[data]
22
+ else
23
+ data.to_s.capitalize.tr('_', ' ')
24
+ end
25
+ end
26
+
27
+ dt(title) + dd(data)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def default_html_class
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,50 @@
1
+
2
+ require 'templet/component/partial'
3
+
4
+ module Templet
5
+ module Html
6
+ class List < Component::Partial
7
+ def call(*items, html_class: nil,
8
+ item_class: nil,
9
+ selection: nil,
10
+ selected_class: 'active')
11
+ super() do
12
+ ul html_class do
13
+ items.flatten.map.with_index do |item, index|
14
+ li item, li_class(selection, item, index,
15
+ item_class, selected_class)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def selected?(selection, item, index)
24
+ case selection
25
+ when nil, false
26
+ false
27
+ when Integer
28
+ index == selection
29
+ when Regexp
30
+ selection === item
31
+ else
32
+ selection.to_s == item.to_s
33
+ end
34
+ end
35
+
36
+ def li_class(selection, item, index, item_class, selected_class)
37
+ if selected?(selection, item, index)
38
+ if item_class and not item_class.empty?
39
+ "#{item_class} #{selected_class}"
40
+ else
41
+ selected_class
42
+ end
43
+ else
44
+ item_class
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,64 @@
1
+
2
+ require 'templet/component/partial'
3
+
4
+ module Templet
5
+ module Html
6
+ # Renders an HTML table from a Hash
7
+ class Table < Component::Partial
8
+ # +controls+ [Hash]
9
+ # The key is a Proc || a field name (if it begins with _ it's not shown)
10
+ # The value is a Proc || an index || nil (for a single column table)
11
+ def call(controls, records, opaque_heading: nil, opaque_row: nil,
12
+ html_class: nil, footer: '')
13
+ controls = controls.to_h
14
+
15
+ super() do
16
+ _table(html_class || default_html_class) do
17
+ [ thead do
18
+ tr do
19
+ controls.keys.map do |title|
20
+ if title.respond_to?(:call)
21
+ th title.(self, opaque_heading, opaque_row)
22
+ else
23
+ th heading(title)
24
+ end
25
+ end
26
+ end
27
+ end,
28
+
29
+ tbody do
30
+ records.map do |record|
31
+ tr do
32
+ controls.values.map.with_index do |value, index|
33
+ if value.respond_to?(:call)
34
+ td value.(self, record, opaque_row)
35
+ elsif value
36
+ td record[value]
37
+ else
38
+ Array === record ? record[index] : record
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end,
44
+
45
+ tfoot do
46
+ tr do
47
+ td(colspan: controls.size) { footer }
48
+ end
49
+ end
50
+ ]
51
+ end
52
+ end
53
+ end
54
+
55
+ def default_html_class
56
+ end
57
+
58
+ def heading(title)
59
+ title[0] == '_' ? '' : title.capitalize
60
+ end
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,11 @@
1
+
2
+ require 'templet'
3
+
4
+ require 'templet/html/table'
5
+ require 'templet/html/list'
6
+ require 'templet/html/definition_list'
7
+
8
+ module Templet
9
+ module Html
10
+ end
11
+ end
@@ -0,0 +1,60 @@
1
+
2
+ require "ostruct"
3
+
4
+ require "templet/renderers/helpers"
5
+ require "templet/renderers/list_presenter"
6
+ require "templet/renderers/tag"
7
+
8
+ module Templet
9
+ # Performs the rendition
10
+ class Renderer < BasicObject
11
+ include Renderers::Helpers
12
+
13
+ # +contexts+ a list of object refererences for method lookups
14
+ #
15
+ # +locals+ named variables passed into the renderer
16
+ def initialize(*contexts, **locals)
17
+ @contexts = contexts.flatten
18
+
19
+ # Local variables take precedence
20
+ @contexts.unshift ::OpenStruct.new(**locals) if locals.any?
21
+ end
22
+
23
+ # Used for augmenting and overriding method lookups in children
24
+ def new_instance(*contexts, **locals)
25
+ Renderer.new(*(contexts | @contexts), **locals)
26
+ end
27
+
28
+ # The block contains the markup
29
+ def call(&block)
30
+ Renderers::ListPresenter.new.(instance_eval &block)
31
+ end
32
+
33
+ def method_missing(name, *args, &block)
34
+ @contexts.each do |context|
35
+ if context.respond_to?(name, true)
36
+ return context.send(name, *args, &block)
37
+ end
38
+ end
39
+ fallback(name, *args, &block)
40
+ end
41
+
42
+ def respond_to?(method_name, *)
43
+ @contexts.each do |context|
44
+ return true if context.respond_to?(method_name, true)
45
+ end
46
+ false
47
+ end
48
+
49
+ private
50
+
51
+ def tag(name, *args, &block)
52
+ Renderers::Tag.new(name).(*args, &block)
53
+ end
54
+
55
+ # Allows you to reimplement +fallback+ in a subclass
56
+ # For example, you might use the Rails helper method +content_tag+ instead
57
+ alias fallback tag
58
+ end
59
+ end
60
+
@@ -0,0 +1,44 @@
1
+
2
+ require 'erb'
3
+ require 'ostruct'
4
+
5
+ module Templet
6
+ module Renderers
7
+ # For rendering a supplied block within an ERb template
8
+ class ERb
9
+ attr_accessor :erb, :context
10
+
11
+ # template_path can be a local ERb file, or template text
12
+ #
13
+ # locals are local variables for the ERb template alone
14
+ def initialize(template, **locals)
15
+ self.erb = ERB.new get_template(template)
16
+
17
+ self.context = OpenStruct.new(**locals)
18
+ end
19
+
20
+ # The return value from the block in substituted in <%= yield %>
21
+ def call(&block)
22
+ locals_binding = context.instance_eval { binding }
23
+
24
+ erb.result locals_binding, &block
25
+ end
26
+
27
+ # Shortcut to instance method
28
+ def self.call(template_path, **locals, &block)
29
+ new(template_path, **locals).(&block)
30
+ end
31
+
32
+ private
33
+
34
+ def get_template(template)
35
+ if template =~ /<%/
36
+ template
37
+ else
38
+ IO.read template
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,16 @@
1
+
2
+ module Templet
3
+ # For general purpose methods, it's included in the Renderer class
4
+ module Renderers
5
+ module Helpers
6
+ private
7
+
8
+ def echo(*elements)
9
+ elements
10
+ end
11
+
12
+ alias returns echo
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,22 @@
1
+
2
+ module Templet
3
+ module Renderers
4
+ # Converts lists of strings and/or callable objects into a multiline string
5
+ class ListPresenter
6
+ def call(*elements)
7
+ elements.flatten.compact.map do |element|
8
+ element = recall(element)
9
+
10
+ Array === element ? call(*element) : element.to_s
11
+ end * EOL
12
+ end
13
+
14
+ private
15
+
16
+ def recall(element)
17
+ element.respond_to?(:call) ? recall(element.call) : element
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,86 @@
1
+
2
+ require "templet/renderers/list_presenter"
3
+
4
+ module Templet
5
+ module Renderers
6
+ # Renders an XML tag
7
+ class Tag < Struct.new(:tag_name, :default_atts)
8
+ @@level = 0
9
+
10
+ # A shortcut for calling directly.
11
+ def self.call(*args, &block)
12
+ new(args.shift).(*args, &block)
13
+ end
14
+
15
+ # +args+:: The first element becomes the tag's name.
16
+ #
17
+ # +args+:: The remaining elements are the tag's body and/or HTML class.
18
+ #
19
+ # +atts+:: An optional final (Hash) argument are rendered as the tag's attributes.
20
+ #
21
+ # If a block is given:
22
+ # The tag's body is what the block returns
23
+ # If there's an argument (String Symbol) then it's added to the tag's HTML class.
24
+ #
25
+ # If NO block is given:
26
+ # The tag's body is the first argument.
27
+ # If there's a second (String Symbol) argument it's added to the tag's HTML class.
28
+ #
29
+ # Note that in attribute (and tag) names underscores are replaced
30
+ # with dashes.
31
+ def call(*args, **atts)
32
+ content, html_class = block_given? ? [ yield, args.first ] : args
33
+
34
+ attributes = set_html_class all_atts(atts), html_class
35
+
36
+ render tag_name, content, attributes
37
+ end
38
+
39
+ alias to_s call
40
+
41
+ private
42
+
43
+ def render(name, content, **atts)
44
+ name = dashit name.to_s.sub(/^_+/, '')
45
+
46
+ content = ListPresenter.new.(content)
47
+
48
+ "<#{name}#{atts_to_s atts}>#{content}</#{name}>#{EOL}"
49
+ end
50
+
51
+ # Change underscores to dashes when specifying
52
+ # XML tag names, attribute names and html classes.
53
+ # For a single underscore, put in two.
54
+ def dashit(name)
55
+ (name || '').to_s.tr('_', '-').gsub(/--/, '_') # could be better!
56
+ end
57
+
58
+ def dash_symbol(value)
59
+ Symbol === value ? dashit(value) : value
60
+ end
61
+
62
+ def atts_to_s(atts)
63
+ atts.reduce '' do |acc, (key, value)|
64
+ acc << " #{dashit key}='#{dash_symbol value}'"
65
+ end
66
+ end
67
+
68
+ def all_atts(atts)
69
+ (default_atts || {}).merge atts
70
+ end
71
+
72
+ # If there's already an HTML class then append to it
73
+ def set_html_class(atts, html_class)
74
+ unless (new_html_class = dashit(html_class)).empty?
75
+ atts[:class] = existing_html_class(atts) << new_html_class
76
+ end
77
+ atts
78
+ end
79
+
80
+ def existing_html_class(atts)
81
+ atts.key?(:class) ? "#{dash_symbol atts[:class]} " : ''
82
+ end
83
+ end
84
+ end
85
+ end
86
+
@@ -0,0 +1,3 @@
1
+ module Templet
2
+ VERSION = "0.1.0"
3
+ end
data/lib/templet.rb ADDED
@@ -0,0 +1,21 @@
1
+
2
+ require "templet/version"
3
+ require "templet/renderer"
4
+ require "templet/renderers/erb"
5
+
6
+ require "templet/component"
7
+ require "templet/html"
8
+
9
+ module Templet
10
+ EOL = "\n"
11
+
12
+ def self.call(*contexts, &block)
13
+ renderer = Renderer.new(*contexts)
14
+
15
+ block ? renderer.(&block) : renderer
16
+ end
17
+
18
+ def self.erb(template_path, **locals, &block)
19
+ Renderers::Erb.(template_path, **locals, &block)
20
+ end
21
+ end
data/templet.gemspec ADDED
@@ -0,0 +1,38 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "templet/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "templet"
8
+ spec.version = Templet::VERSION
9
+ spec.authors = ["stephen"]
10
+ spec.email = ["stephen@googlemail.com"]
11
+
12
+ spec.summary = %q{DSL to render XML/HTML}
13
+ spec.description = %q{Generates HTML tags from pure Ruby, using a method name as a tag's name, via method_missing. Also renders HTML components.}
14
+ spec.homepage = "https://github.com/srycyk/templet"
15
+ spec.license = "MIT"
16
+
17
+ =begin
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
22
+ else
23
+ raise "RubyGems 2.0 or newer is required to protect against " \
24
+ "public gem pushes."
25
+ end
26
+ =end
27
+
28
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
29
+ f.match(%r{^(test|spec|features)/})
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.add_development_dependency "bundler", "~> 1.16"
36
+ spec.add_development_dependency "rake", "~> 10.0"
37
+ spec.add_development_dependency "minitest", "~> 5.0"
38
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: templet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - stephen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
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.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ description: Generates HTML tags from pure Ruby, using a method name as a tag's name,
56
+ via method_missing. Also renders HTML components.
57
+ email:
58
+ - stephen@googlemail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - bin/console
71
+ - bin/setup
72
+ - lib/templet.rb
73
+ - lib/templet/component.rb
74
+ - lib/templet/component/layout.rb
75
+ - lib/templet/component/partial.rb
76
+ - lib/templet/html.rb
77
+ - lib/templet/html/definition_list.rb
78
+ - lib/templet/html/list.rb
79
+ - lib/templet/html/table.rb
80
+ - lib/templet/renderer.rb
81
+ - lib/templet/renderers/erb.rb
82
+ - lib/templet/renderers/helpers.rb
83
+ - lib/templet/renderers/list_presenter.rb
84
+ - lib/templet/renderers/tag.rb
85
+ - lib/templet/version.rb
86
+ - templet.gemspec
87
+ homepage: https://github.com/srycyk/templet
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.6.12
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: DSL to render XML/HTML
111
+ test_files: []