express_templates 0.2.0 → 0.2.2

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -6
  3. data/lib/core_extensions/proc.rb +42 -0
  4. data/lib/express_templates.rb +7 -2
  5. data/lib/express_templates/compiler.rb +27 -0
  6. data/lib/express_templates/components.rb +7 -4
  7. data/lib/express_templates/components/base.rb +54 -0
  8. data/lib/express_templates/components/capabilities/conditionality.rb +52 -0
  9. data/lib/express_templates/components/capabilities/configurable.rb +91 -0
  10. data/lib/express_templates/components/capabilities/iterating.rb +80 -0
  11. data/lib/express_templates/components/capabilities/parenting.rb +74 -0
  12. data/lib/express_templates/components/capabilities/rendering.rb +93 -0
  13. data/lib/express_templates/components/capabilities/templating.rb +196 -0
  14. data/lib/express_templates/components/capabilities/wrapping.rb +100 -0
  15. data/lib/express_templates/components/column.rb +13 -0
  16. data/lib/express_templates/components/container.rb +7 -0
  17. data/lib/express_templates/components/form_rails_support.rb +19 -0
  18. data/lib/express_templates/components/row.rb +28 -0
  19. data/lib/express_templates/expander.rb +51 -35
  20. data/lib/express_templates/indenter.rb +44 -0
  21. data/lib/express_templates/macro.rb +44 -0
  22. data/lib/express_templates/markup.rb +9 -0
  23. data/lib/express_templates/{components → markup}/html_tag.rb +9 -3
  24. data/lib/express_templates/markup/tag.rb +118 -0
  25. data/lib/express_templates/markup/wrapper.rb +88 -0
  26. data/lib/express_templates/{components → markup}/yielder.rb +2 -2
  27. data/lib/express_templates/renderer.rb +3 -8
  28. data/lib/express_templates/template/handler.rb +1 -1
  29. data/lib/express_templates/version.rb +1 -1
  30. data/lib/tasks/{gara_tasks.rake → express_templates.rake} +0 -0
  31. data/test/compiler_test.rb +9 -0
  32. data/test/components/base_test.rb +77 -0
  33. data/test/components/column_test.rb +11 -0
  34. data/test/components/conditionality_test.rb +37 -0
  35. data/test/components/configurable_test.rb +43 -0
  36. data/test/components/container_test.rb +49 -0
  37. data/test/components/iterating_test.rb +85 -0
  38. data/test/components/row_test.rb +16 -0
  39. data/test/core_extensions/proc_test.rb +41 -0
  40. data/test/dummy/log/test.log +72489 -0
  41. data/test/expander_test.rb +55 -13
  42. data/test/{gara_test.rb → express_templates_test.rb} +0 -0
  43. data/test/handler_test.rb +4 -4
  44. data/test/indenter_test.rb +25 -0
  45. data/test/markup/tag_test.rb +127 -0
  46. data/test/markup/wrapper_test.rb +42 -0
  47. data/test/markup/yielder_test.rb +9 -0
  48. data/test/performance_test.rb +2 -2
  49. data/test/test_helper.rb +6 -0
  50. metadata +78 -24
  51. data/lib/express_templates/component.rb +0 -92
  52. data/lib/express_templates/components/wrapper.rb +0 -51
  53. data/lib/express_templates/html5_emitter.rb +0 -45
  54. data/test/component_test.rb +0 -77
  55. data/test/wrapper_test.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f322ad6cb1fb8bf620577f100a9a70274c6172d1
4
- data.tar.gz: ea903155b79430c8e5ec5f557ea80ee7461d9fdd
3
+ metadata.gz: 00a0446a67ac0022ae2769db3a19f9c0399fa4c1
4
+ data.tar.gz: 5feea8d4b6a5732aec2f49edfc0ebd4024129f85
5
5
  SHA512:
6
- metadata.gz: 9b71ed00ff0f5a2424a94b84da6c4104b988942b9fa37d7e36ec7c892118ccc33be46893c862be0e5ffec0b2ded2c5ed844d713e7650b3f5d0c82f0f78d0e726
7
- data.tar.gz: d026690d15f79913cd90f112f437990449af06c795e718cf512a2d1a34d037ffa43e8e8161b20f9a7db4ebe9cf4aee9ea34421f628449d5f51b2584f98460446
6
+ metadata.gz: bd40eaa5f1d713c36084fd91ae72c844cf129b83683aa4a15dd730cc4e5e05a50e2e360ca31eef7fc81b25596075caf9fb54d79af98ab67bbc5ef7f896de36f4
7
+ data.tar.gz: 873c79cbf53186da5931979d05f62183432ec14426f0f37a2984d9fac64f38ea1210a4156ce40e8fbcf454113bbb21a76141d75f64208a5716f0612083b6fc25
data/README.md CHANGED
@@ -36,18 +36,22 @@ Set your editor syntax for .et files to Ruby.
36
36
 
37
37
  ExpressTemplates works via a good-enough stand in for a true macro system in Ruby which would make this type of thing considerably easier.
38
38
 
39
- Basically, we use these "macros" and Ruby's block structure to build up a tree of components corresponding to the HTML structure of a document fragment. Each HTML5 tag is a component available in the form of a macro. Unrecognized identifiers are wrapped for later evaluation, presumably in a ViewContext.
39
+ Basically, we use these "macros" and Ruby's block structure to build up a tree of components corresponding to the HTML structure of a document fragment. Each HTML5 tag is a Component available in the form of a macro. Unrecognized identifiers are wrapped for later evaluation, presumably in a ViewContext.
40
40
 
41
- yield and local variables which we may expect to be available in a view context are also wrapped for evaluation later.
41
+ yield and local variables which we may expect to be available in a ViewContext are also wrapped for evaluation later.
42
+
43
+ Templates are first "expanded", then "compiled" and then finally "rendered."
44
+
45
+ Expanding results in a tree of Component like objects which have children and respond to #compile(). The result of #compile on a component is a Ruby code fragment which may be evaluated in a ViewContext to produce markup. Compiling is similar to what HAML or Erb does.
42
46
 
43
47
  ## Background
44
48
 
45
- Sufficent motivation for this gem can be explained thusly: The bondage of HAML is unnecessary. The clutter of Erb is unsightly.
49
+ The bondage of HAML is unnecessary. The clutter of Erb is unsightly.
46
50
 
47
- I generall prefer "one syntax per file" for reasons of cognative load and maintainability.
51
+ I generally prefer "one syntax per file" for reasons of cognative load and maintainability.
48
52
 
49
- Ultimately my objective with ExpressTemplates is to get away from writing HTML directly and to use this as a substrate for building pages out of higher-level, reusable components which include not only DOM elements but also behaviors.
53
+ The introduction of an macro-like pre-processing step yielding a tree of Components as described above allows us to implement higher-level components which inherit behavior via normal OO Ruby. This points the way to a UX framework and component library that will play nice with Rails caching and conventions.
50
54
 
51
- ExpressTemplates are part of the AppExpress platform at [appexpress.io](http://appexpress.io).
55
+ ExpressTemplates form part of the AppExpress platform at [appexpress.io](http://appexpress.io).
52
56
 
53
57
  This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,42 @@
1
+ require 'ripper'
2
+ require 'pp'
3
+ class Proc
4
+
5
+ TOKEN_PAIRS = {[:on_lbrace, '{'] => [:on_rbrace, '}'],
6
+ [:on_kw, 'do'] => [:on_kw, 'end'],
7
+ [:on_tlambeg, '{'] => [:on_rbrace, '}']}
8
+
9
+ # Make a best effort to provide the original source for a block
10
+ # based on extracting a string from the file identified in
11
+ # Proc#source_location using Ruby's tokenizer.
12
+ #
13
+ # This works for first block declared on a line in a source
14
+ # file. If additional blocks are specified inside the first block
15
+ # on the same line as the start of the block, only the outer-most
16
+ # block declaration will be identified as a the block we want.
17
+ #
18
+ # If you require only the source of blocks-within-other-blocks, start them
19
+ # on a new line as would be best practice for clarity and readability.
20
+ def source
21
+ file, line_no = source_location
22
+ raise "no line number provided for source_location: #{self}" if line_no.nil?
23
+ tokens = Ripper.lex File.read(file)
24
+ tokens_on_line = tokens.select {|pos, lbl, str| pos[0].eql?(line_no) }
25
+ starting_token = tokens_on_line.detect do |pos, lbl, str|
26
+ TOKEN_PAIRS.keys.include? [lbl, str]
27
+ end
28
+ starting_token_type = [starting_token[1], starting_token[2]]
29
+ ending_token_type = TOKEN_PAIRS[starting_token_type]
30
+ source_str = ""
31
+ remaining_tokens = tokens.slice(tokens.index(starting_token)..-1)
32
+ nesting = -1
33
+ while token = remaining_tokens.shift
34
+ source_str << token[2]
35
+ nesting += 1 if [token[1], token[2]] == starting_token_type
36
+ is_ending_token = [token[1], token[2]].eql?(ending_token_type)
37
+ break if is_ending_token && nesting.eql?(0)
38
+ nesting -= 1 if is_ending_token
39
+ end
40
+ source_str
41
+ end
42
+ end
@@ -1,10 +1,15 @@
1
1
  module ExpressTemplates
2
+ require 'core_extensions/proc'
3
+ require 'express_templates/indenter'
4
+ require 'express_templates/macro'
5
+ require 'express_templates/markup'
6
+ require 'express_templates/components'
2
7
  require 'express_templates/template/handler'
3
- require 'express_templates/html5_emitter'
4
8
  require 'express_templates/renderer'
5
9
  require 'express_templates/expander'
6
- require 'express_templates/components'
10
+ require 'express_templates/compiler'
7
11
  extend Renderer
12
+ extend Compiler
8
13
  if defined?(Rails)
9
14
  ::ActionView::Template.register_template_handler :et, ExpressTemplates::Template::Handler
10
15
  end
@@ -0,0 +1,27 @@
1
+ module ExpressTemplates
2
+ module Compiler
3
+ def compile(template_or_src=nil, &block)
4
+ template, src = _normalize(template_or_src)
5
+
6
+ expander = Expander.new(template)
7
+
8
+ compiled = expander.expand(src, &block).map(&:compile)
9
+
10
+ return compiled.join("+").gsub('"+"', '').tap do |s|
11
+ puts("\n"+template.inspect+"\n"+s) if ENV['DEBUG'].eql?('true')
12
+ end
13
+ end
14
+
15
+ private
16
+ def _normalize(template_or_src)
17
+ template, src = nil, nil
18
+ if template_or_src.respond_to?(:source)
19
+ template = template_or_src
20
+ src = template_or_src.source
21
+ else
22
+ src = template_or_src
23
+ end
24
+ return template, src
25
+ end
26
+ end
27
+ end
@@ -2,7 +2,10 @@ module ExpressTemplates
2
2
  module Components
3
3
  end
4
4
  end
5
- require 'express_templates/component'
6
- require 'express_templates/components/html_tag'
7
- require 'express_templates/components/wrapper'
8
- require 'express_templates/components/yielder'
5
+
6
+ require 'express_templates/expander'
7
+ require 'express_templates/components/base'
8
+ require 'express_templates/components/container'
9
+ require 'express_templates/components/row'
10
+ require 'express_templates/components/column'
11
+ require 'express_templates/components/form_rails_support'
@@ -0,0 +1,54 @@
1
+ capabilities = Dir.glob(File.join(File.dirname(__FILE__), 'capabilities', '*.rb'))
2
+ capabilities.each {|capability| require capability}
3
+
4
+ module ExpressTemplates
5
+ # Components provide self-contained reusable view code meant to be shared
6
+ # within a project or across many projects through a library of components
7
+ #
8
+ # Components gain their functionality through inclusion of Capabilities.
9
+ #
10
+ # Most Components are descendents of Components::Base.
11
+ #
12
+ module Components
13
+
14
+ # Components::Base is the base class for ExpressTemplates view components.
15
+ #
16
+ # View components are available as macros in ExpressTemplates and may be
17
+ # used to encapsulate common view patterns, behavior and functionality in
18
+ # reusable classes that can be shared within and across projects.
19
+ #
20
+ # Components intended to provide a base framework for a library of reusable
21
+ # components to cut development time across a multitude of projects.
22
+ #
23
+ # Components gain their functionality through including Capabilities.
24
+ #
25
+ # Example capabilities include:
26
+ #
27
+ # * Managing related ExpressTemplate fragments
28
+ # * Compiling template fragments for evaluation in a View Context
29
+ # * Specifying rendering logic to be executed in the View Context
30
+ # * Potentially referencing external assets that may be required
31
+ # for the component to work.
32
+ #
33
+ # Components::Base includes the following capabilities:
34
+ #
35
+ # * Capabilities::Templating
36
+ # * Capabilities::Rendering
37
+ # * Capabilities::Wrapping
38
+ # * Capabilities::Iterating
39
+ #
40
+ class Base
41
+ include ExpressTemplates::Macro
42
+ include Capabilities::Templating
43
+ include Capabilities::Rendering
44
+ include Capabilities::Wrapping
45
+ include Capabilities::Iterating
46
+
47
+ def self.inherited(klass)
48
+ ExpressTemplates::Expander.register_macros_for klass
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,52 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ module Capabilities
4
+ # Adds the capability for a component to only render
5
+ # its markup when a condition to be evaluated in the
6
+ # view is true.
7
+ #
8
+ # Example:
9
+ #
10
+ # class PageHeader < ExpressTemplates::Components::Base
11
+ # include ExpressTemplates::Components::Capabilities::Conditionality
12
+ #
13
+ # emits {
14
+ # h1 { content_for(:page_header) }
15
+ # }
16
+ #
17
+ # only_if -> { content_for?(:page_header) }
18
+ #
19
+ # end
20
+ # end
21
+ #
22
+ # The condition supplied to only if in the form of a proc
23
+ # is evaluated in the view context.
24
+ #
25
+ # The component will render an empty string if the proc returns false.
26
+ module Conditionality
27
+ def self.included(base)
28
+ base.class_eval do
29
+ extend ClassMethods
30
+ end
31
+ end
32
+
33
+ module ClassMethods
34
+
35
+ def condition_proc
36
+ @condition_proc
37
+ end
38
+
39
+ def only_if condition_proc
40
+ @condition_proc = condition_proc
41
+
42
+ using_logic do |component, options|
43
+ condition = instance_exec(&component.condition_proc)
44
+ eval(component[:markup]) if condition
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,91 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ module Capabilities
4
+
5
+ # Configurable components accept options which they can use to alter
6
+ # their markup each time they are invoked within a template.
7
+ #
8
+ # They do not compile their markup fragments at load time as simpler
9
+ # components do for efficiency. Rather they compile their fragments
10
+ # when they are themselves undergoing compilation. This facilitates
11
+ # access to arguments which were passed to the component at initialization.
12
+ #
13
+ # For example, if we have a Row component that is Configurable:
14
+ #
15
+ # row(:main)
16
+ #
17
+ # might process to:
18
+ #
19
+ # <div id="main" class="row" />
20
+ #
21
+
22
+ module Configurable
23
+ def self.included(base)
24
+ base.class_eval do
25
+ extend ClassMethods
26
+ include InstanceMethods
27
+
28
+ # Stores arguments for later processing, eg., compile time
29
+ def initialize(*args)
30
+ @args = args.dup
31
+ @config = {}
32
+ _process_args!(args)
33
+ super(*args)
34
+ end
35
+ end
36
+ end
37
+
38
+ module ClassMethods
39
+
40
+ protected
41
+
42
+ # Override Templating._compile_fragment to delay compilation
43
+ def _compile_fragment(block, options = {})
44
+ if options.delete(:force_compile)
45
+ super(block, options)
46
+ else
47
+ block
48
+ end
49
+ end
50
+
51
+ def _lookup(name, options = {})
52
+ super(name, options.merge(force_compile: true))
53
+ end
54
+
55
+ end
56
+
57
+ module InstanceMethods
58
+
59
+ def config
60
+ @config
61
+ end
62
+
63
+ alias :my :config
64
+
65
+ def expand_locals
66
+ {my: config}
67
+ end
68
+
69
+ # Override Templating#lookup to pass locals
70
+ def lookup(fragment_name)
71
+ self.class.send(:_lookup, fragment_name, expand_locals)
72
+ end
73
+
74
+
75
+ private
76
+
77
+ def _process_args!(args)
78
+ if args.first.kind_of?(Symbol)
79
+ config.merge!(id: args.shift)
80
+ end
81
+ while arg = args.shift
82
+ if arg.kind_of?(Hash)
83
+ config.merge!(arg)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,80 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ module Capabilities
4
+ #
5
+ # Adds the capability to iterate over a collection repeating a markup
6
+ # fragment for each member.
7
+ #
8
+ # Example:
9
+ #
10
+ # class ParagraphsComponent < ExpressTemplates::Components::Base
11
+ #
12
+ # emits -> { p { item } } # item is the default local variable name
13
+ #
14
+ # for_each -> { paragraphs } # evaluated in view context
15
+ #
16
+ # end
17
+ #
18
+ # Must specify an <tt>iterator</tt> either as a proc to be evaluated in the
19
+ # view context or else as a variable name in the form of a symbol which is
20
+ # assumed to be available in the view context.
21
+ #
22
+ # Provides:
23
+ #
24
+ # * Iterating::ClassMethods (for_each)
25
+ #
26
+ module Iterating
27
+ def self.included(base)
28
+ base.class_eval do
29
+ extend ClassMethods
30
+ end
31
+ end
32
+
33
+ module ClassMethods
34
+ # Sets the component up to use iterating logic to reproduce a fragment
35
+ # for a collection.
36
+ #
37
+ # Parameters include an iterator that may be :@variable assumed to be
38
+ # available in context or a proc that when evaluated in the context
39
+ # should return a collection.
40
+ #
41
+ # An <tt>:emit</tt> option may specify a fragment to emit.
42
+ # Defaults to <tt>:markup</tt>
43
+ #
44
+ # An <tt>:as</tt> option specifies the local variable name for each
45
+ # item in the collection for use in the fragment. Defaults to: <tt>item</tt>
46
+ #
47
+ # An <tt>:empty</tt> option specifies a fragment to use for the
48
+ # empty state when the iterator returns an empty collection.
49
+ def for_each(iterator, as: :item, emit: :markup, empty: nil)
50
+ if iterator.kind_of?(Symbol)
51
+ var_name = iterator.to_s.gsub(/^@/,'').singularize.to_sym
52
+ else
53
+ var_name = as
54
+ end
55
+ using_logic do |component, options|
56
+ collection = if iterator.kind_of?(Proc)
57
+ if iterator.arity.eql?(1)
58
+ instance_exec(options, &iterator)
59
+ else
60
+ instance_exec &iterator
61
+ end
62
+ else
63
+ eval(iterator.to_s)
64
+ end
65
+ if collection.empty?
66
+ empty ? component[empty] : ''
67
+ else
68
+ collection.map do |item|
69
+ b = binding
70
+ b.local_variable_set(var_name, item)
71
+ b.eval(component[emit], __FILE__)
72
+ end.join
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,74 @@
1
+ module ExpressTemplates
2
+ module Components
3
+ module Capabilities
4
+ module Parenting
5
+
6
+ # Parenting adds the capability for a component to have and render
7
+ # children that may be specified in the template fragment which
8
+ # includes the component.
9
+ #
10
+ # Example parenting component:
11
+ #
12
+ # class Line < ExpressTemplates::Components::Base
13
+ # include Capabilities::Parenting
14
+ #
15
+ # emits { p.line { _yield } }
16
+ # end
17
+ #
18
+ # You might then use this component like so:
19
+ #
20
+ # line "In", "Xanadu", "did", "Kubla", "Khan"
21
+ # line { "A stately pleasure-dome decree :" }
22
+ # line { "Where Alph, the sacred river, ran" }
23
+ # line %q(Through caverns measureless to man)
24
+ # line %q|Down to a sunless sea.|
25
+ #
26
+ # Provides
27
+ #
28
+ # * ClassMethods for rendering
29
+ # * InstanceMethods for managing children
30
+ #
31
+ def self.included(base)
32
+ base.class_eval do
33
+ extend ClassMethods
34
+ include InstanceMethods
35
+ end
36
+ end
37
+
38
+ module ClassMethods
39
+ def render_with_children(context, locals = {}, child_markup_src = nil)
40
+ _wrap_it(context, locals) do |component|
41
+ child_markup_src
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ module InstanceMethods
48
+ def children
49
+ @children ||= []
50
+ end
51
+
52
+ def children=(children)
53
+ @children =children
54
+ end
55
+
56
+ def compile
57
+ locals = (expand_locals rescue nil).inspect
58
+ compiled_children = nil
59
+ args = %w(self)
60
+ args << locals
61
+ Indenter.for(:compile) do |indent, indent_with_newline|
62
+ compiled_children = children.map { |child| indent_with_newline + child.compile }.join("+")
63
+ compiled_children.gsub!('"+"', '') # avoid unnecessary string concatenation
64
+ args << compiled_children unless compiled_children.empty?
65
+ end
66
+ closing_paren = compiled_children.empty? ? ')' : "\n#{Indenter.for(:compile)})"
67
+ "#{self.class.to_s}.render_with_children(#{args.join(', ')}#{closing_paren}"
68
+ end
69
+
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end