express_templates 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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