hexp 0.0.1.pre → 0.0.1.pre2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -16,3 +16,6 @@ tmp
16
16
  .yardoc
17
17
  _yardoc
18
18
  doc/
19
+
20
+ #yardstick report
21
+ measurements/report.txt
@@ -3,7 +3,6 @@ before_install: gem install bundler
3
3
  bundler_args: --without yard guard benchmarks
4
4
  script: "bundle exec rake ci:metrics"
5
5
  rvm:
6
- - ree
7
6
  - 1.9.2
8
7
  - 1.9.3
9
8
  - 2.0.0
data/Gemfile CHANGED
@@ -1,7 +1,11 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'nokogiri'
4
+ gem 'equalizer', github: 'dkubb/equalizer'
5
+
3
6
  group :development do
4
7
  gem 'devtools', :git => 'https://github.com/datamapper/devtools.git'
5
- gem 'json', '~> 1.7.7'
8
+ gem 'json', '~> 1.8.0'
6
9
  eval File.read('Gemfile.devtools')
7
10
  end
11
+
@@ -1,3 +1,11 @@
1
+ GIT
2
+ remote: git://github.com/dkubb/equalizer.git
3
+ revision: 7b0a30dee57ed0378df6b32e5defda64adaff0da
4
+ specs:
5
+ equalizer (0.0.5)
6
+ adamantium (~> 0.0.7)
7
+ backports (~> 3.2, >= 3.2.0)
8
+
1
9
  GIT
2
10
  remote: https://github.com/datamapper/devtools.git
3
11
  revision: f0e3a3a42673266c46b983bc79a4ab6e1623bccc
@@ -7,7 +15,7 @@ GIT
7
15
 
8
16
  GIT
9
17
  remote: https://github.com/troessner/reek.git
10
- revision: 79e1b9bb5400d5aba172cbab5e65531daab24b3e
18
+ revision: f302390f533dd6b59f837821d63249d0c729f931
11
19
  specs:
12
20
  reek (1.3.1)
13
21
  ruby2ruby (~> 2.0.2)
@@ -33,9 +41,6 @@ GEM
33
41
  thor
34
42
  descendants_tracker (0.0.1)
35
43
  diff-lcs (1.2.4)
36
- equalizer (0.0.5)
37
- adamantium (~> 0.0.6)
38
- backports (~> 3.0, >= 3.0.3)
39
44
  ffi (1.8.1)
40
45
  ffi-hunspell (0.3.0)
41
46
  ffi (~> 1.0)
@@ -60,7 +65,7 @@ GEM
60
65
  rspec (~> 2.11)
61
66
  ice_nine (0.7.0)
62
67
  inflecto (0.0.2)
63
- json (1.7.7)
68
+ json (1.8.0)
64
69
  kramdown (1.0.2)
65
70
  libnotify (0.8.0)
66
71
  ffi (>= 1.0.11)
@@ -71,7 +76,7 @@ GEM
71
76
  lumberjack (1.0.3)
72
77
  method_source (0.8.1)
73
78
  mime-types (1.23)
74
- multi_json (1.7.3)
79
+ multi_json (1.7.4)
75
80
  mutant (0.2.20)
76
81
  abstract_type (~> 0.0.3)
77
82
  adamantium (~> 0.0.7)
@@ -84,6 +89,7 @@ GEM
84
89
  rspec (~> 2.13.0)
85
90
  to_source (~> 0.2.19)
86
91
  mutant-melbourne (2.0.3)
92
+ nokogiri (1.5.9)
87
93
  pelusa (0.2.2)
88
94
  pry (0.9.12.2)
89
95
  coderay (~> 1.0.5)
@@ -142,17 +148,19 @@ DEPENDENCIES
142
148
  backports (~> 3.3, >= 3.3.0)
143
149
  coveralls (~> 0.6.6)
144
150
  devtools!
151
+ equalizer!
145
152
  flay (~> 2.2.0)
146
153
  flog (~> 4.0.0)
147
154
  guard (~> 1.8.0)
148
155
  guard-bundler (~> 1.0.0)
149
156
  guard-rspec (~> 2.5.4)
150
157
  jruby-openssl (~> 0.8.5)
151
- json (~> 1.7.7)
158
+ json (~> 1.8.0)
152
159
  kramdown (~> 1.0.1)
153
160
  libnotify (~> 0.8.0)
154
161
  listen (~> 1.0.2)
155
162
  mutant (~> 0.2.20)
163
+ nokogiri
156
164
  pelusa (~> 0.2.2)
157
165
  rake (~> 10.0.4)
158
166
  rb-fchange (~> 0.0.6)
data/README.md CHANGED
@@ -1,16 +1,115 @@
1
- hexp
2
- ====
3
-
4
1
  [![Gem Version](https://badge.fury.io/rb/hexp.png)][gem]
5
2
  [![Build Status](https://secure.travis-ci.org/plexus/hexp.png?branch=master)][travis]
6
3
  [![Dependency Status](https://gemnasium.com/plexus/hexp.png)][gemnasium]
7
4
  [![Code Climate](https://codeclimate.com/github/plexus/hexp.png)][codeclimate]
8
5
  [![Coverage Status](https://coveralls.io/repos/plexus/hexp/badge.png?branch=master)][coveralls]
9
6
 
10
- [gem]: https://rubygems.org/gems/dm-mapper
7
+ [gem]: https://rubygems.org/gems/hexp
11
8
  [travis]: https://travis-ci.org/plexus/hexp
12
9
  [gemnasium]: https://gemnasium.com/plexus/hexp
13
10
  [codeclimate]: https://codeclimate.com/github/plexus/hexp
14
11
  [coveralls]: https://coveralls.io/r/plexus/hexp
15
12
 
16
- HTML expressions
13
+ A bit about hexps
14
+ -----------------
15
+
16
+ **What on earth is a Hexp?**
17
+
18
+ Hexps are basically snippets of HTML written in nothing but Ruby, here's an example.
19
+
20
+ ````ruby
21
+ @message = "Hexps are fun for the whole family, from 9 to 99 years old."
22
+ @hexp = H[:div, {class: 'hexp-intro'}, [
23
+ [:p, @message]
24
+ ]
25
+ ]
26
+ ````
27
+
28
+ **Don't people use templates for this kind of thing?**
29
+
30
+ They do, this is an alternative approach. With templates you need to think about which parts need to be HTML-escaped, or you can make errors like forgetting a closing tag. With hexps you no longer need to think about escaping.
31
+
32
+ **Wait how is that?**
33
+
34
+ With traditional approaches you can insert plain text in your template, or snippets of HTML. The first must be escaped, the second should not. For your template they are all just strings, so you, the programmer, need to distinguish between the two in a way. For example by using `html_escape` on one (explicit escaping), or `html_safe` on the other (implicit escaping).
35
+
36
+ When using hexps you never deal with strings that actually contain HTML. Helper methods would return hexps instead, and you can combine those into bigger hexps. Strings inside a hexp are always just that, so they will always be escaped without you thinking about it.
37
+
38
+ **So that's it, easier escaping?**
39
+
40
+ Well that's not all, by having a simple lightweight representation of HTML that is _a real data structure_, you can really start programming your HTML. If you have an object that responds to `to_hexp`, you can use that object inside a hexp, so you can use Object Orientation for your user interface. Like so
41
+
42
+ ````ruby
43
+ class ProfileLink < Struct.new(:user)
44
+ def to_hexp
45
+ H[:a, {class: "profile-link", href: "/user/#{user.id}"}, user.name]
46
+ end
47
+ end
48
+
49
+ class Layout < Struct.new(:content)
50
+ def to_hexp
51
+ H[:html, [
52
+ [:body, [content]]
53
+ ]
54
+ end
55
+ end
56
+
57
+ render inline: Layout.new(ProfileLink.new(@user)).to_html
58
+ ````
59
+
60
+ **Does it get any better?**
61
+
62
+ It does! The really neat part is having filters that process this HTML tree before it gets serialized to text. This could be good for
63
+
64
+ - populating form fields
65
+ - adding extra admin buttons when logged in
66
+ - cleanly separate aspects of your app (e.g. discount codes) from 'core' implementation
67
+ - becoming filthy rich and/or ridiculously happy
68
+
69
+ **What's up with the funny name?**
70
+
71
+ Hexp stands for HTML expressions. It's a reference to s-expressions as they are known in LISP languages, a simple way to represent data as nested lists.
72
+
73
+ How to use it
74
+ -------------
75
+
76
+ Hexp objects come in two flavors : `Hexp::Node` and `Hexp::List`. A `Node` consists of three parts : its `tag`, `attributes` and `children`. A `List` is just that, a list (of nodes).
77
+
78
+ To construct a `Node` use `H[tag, attributes, children]`. Use a `Symbol` for the `tag`, a `Hash` for the `attributes`, and an `Array` for the `children`. Attributes or children can be omitted when they are empty.
79
+
80
+ The list of children will automatically be converted to `Hexp::List`, similarly for any nested nodes you can simply use `[tag, attributes, children]` without the `H`, all nodes in the tree will be converted to proper `Hexp::Node` objects.
81
+
82
+ The entire API is centered around these two classes, and one of them you can think of as essentially just an `Array`, in other words Hexp is super easy to learn. Try it out in `irb`, have a look at the examples, and *build cool stuff*!
83
+
84
+ A note on immutability
85
+ ----------------------
86
+
87
+ All Hexp objects are deep frozen on creation, you can never alter them afterwards. Operations always return a new `Hexp::Node` rather than working in place.
88
+
89
+ This might seem stringent when you are not used to this style of coding, but it's a pattern that generally promotes good code.
90
+
91
+ Can I already use it
92
+ --------------------
93
+
94
+ Hold your horses, this is *very* alpha level code! Feedback is very much appreciated, so try it out, and let me know what you think. Basic functionality is there, but it's far from finished, and the API will probably still change based on feedback. You have been warned.
95
+
96
+ Is it any good?
97
+ ---------------
98
+
99
+ Yes
100
+
101
+ How to install
102
+ --------------
103
+
104
+ At this point you're best off grabbing the Git repo, e.g. with bundler
105
+
106
+ ````sh
107
+ # Gemfile
108
+
109
+ gem 'hexp', github: 'plexus/hexp'
110
+ ````
111
+
112
+ Who is behind this
113
+ ------------------
114
+
115
+ Hexp is conceived and created by [Arne Brasseur](http://arnebrasseur.net)
@@ -0,0 +1,2 @@
1
+ ---
2
+ unit_test_timeout: 0.1
@@ -1,3 +1,3 @@
1
1
  ---
2
- threshold: 10
3
- total_score: 74
2
+ threshold: 7
3
+ total_score: 75.0
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 11.7
2
+ threshold: 17.4
@@ -1,2 +1,3 @@
1
- name: hexp
2
- namespace: Hexp
1
+ ---
2
+ name: your_lib
3
+ namespace: YourLib
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  Attribute:
3
- enabled: true
3
+ enabled: false
4
4
  exclude: []
5
5
  BooleanParameter:
6
6
  enabled: true
@@ -14,33 +14,41 @@ ControlParameter:
14
14
  DataClump:
15
15
  enabled: true
16
16
  exclude: []
17
- max_copies: 0
18
- min_clump_size: 1
17
+ max_copies: 2
18
+ min_clump_size: 2
19
19
  DuplicateMethodCall:
20
20
  enabled: true
21
21
  exclude: []
22
- max_calls: 1
22
+ max_calls: 2
23
23
  allow_calls: []
24
24
  FeatureEnvy:
25
25
  enabled: true
26
- exclude: []
26
+ exclude:
27
+ - Hexp::Node::Domize#set_attributes
28
+ - Hexp::Node::Normalize#children
29
+ - Hexp::Node::Normalize#normalized_children
30
+ - Hexp::Node::PP#pp_attributes
31
+ - Hexp::Node::PP#pp_children
27
32
  IrresponsibleModule:
28
33
  enabled: true
29
34
  exclude: []
30
35
  LongParameterList:
31
36
  enabled: true
32
37
  exclude: []
33
- max_params: 1
34
- overrides: {}
38
+ max_params: 2
39
+ overrides:
40
+ initialize:
41
+ max_params: 3
35
42
  LongYieldList:
36
43
  enabled: true
37
44
  exclude: []
38
- max_params: 0
45
+ max_params: 2
39
46
  NestedIterators:
40
47
  enabled: true
41
48
  exclude: []
42
49
  max_allowed_nesting: 1
43
- ignore_iterators: []
50
+ ignore_iterators:
51
+ - any?
44
52
  NilCheck:
45
53
  enabled: true
46
54
  exclude: []
@@ -51,15 +59,16 @@ RepeatedConditional:
51
59
  TooManyInstanceVariables:
52
60
  enabled: true
53
61
  exclude: []
54
- max_instance_variables: 12
62
+ max_instance_variables: 3
55
63
  TooManyMethods:
56
64
  enabled: true
57
65
  exclude: []
58
- max_methods: 15
66
+ max_methods: 10
59
67
  TooManyStatements:
60
68
  enabled: true
61
- exclude: []
62
- max_statements: 5
69
+ exclude:
70
+ - each
71
+ max_statements: 4
63
72
  UncommunicativeMethodName:
64
73
  enabled: true
65
74
  exclude: []
@@ -96,5 +105,8 @@ UnusedParameters:
96
105
  exclude: []
97
106
  UtilityFunction:
98
107
  enabled: true
99
- exclude: []
108
+ exclude:
109
+ - Hexp::List#initialize
110
+ - Hexp::Node::Domize#set_attributes
111
+ - Hexp::TextNode#attributes
100
112
  max_helper_calls: 0
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 100
2
+ threshold: 81
@@ -0,0 +1,54 @@
1
+ $:.unshift('/home/arne/github/hexp/lib')
2
+ require 'hexp/h'
3
+
4
+ class X
5
+ def to_hexp
6
+ [:p, {class: 'foo'}, [
7
+ [:br],
8
+ 'awesome',
9
+ [:br]]]
10
+ end
11
+ end
12
+
13
+ hexp = H[:p, [
14
+ [:div, {id: 'main'}, [
15
+ X.new,
16
+ X.new]],
17
+ [:hr]]]
18
+
19
+
20
+ hexp = hexp.filter do |node|
21
+ if node.attributes['class'] == 'foo'
22
+ [[:p, 'foo coming up!'], node]
23
+ else
24
+ [node]
25
+ end
26
+ end
27
+
28
+ puts hexp.pp
29
+
30
+ puts hexp.to_html
31
+
32
+
33
+ # >> H[:p, [
34
+ # >> H[:div, {"id"=>"main"}, [
35
+ # >> H[:p, [
36
+ # >> "foo coming up!"]],
37
+ # >> H[:p, {"class"=>"foo"}, [
38
+ # >> H[:br],
39
+ # >> "awesome",
40
+ # >> H[:br]]],
41
+ # >> H[:p, [
42
+ # >> "foo coming up!"]],
43
+ # >> H[:p, {"class"=>"foo"}, [
44
+ # >> H[:br],
45
+ # >> "awesome",
46
+ # >> H[:br]]]]],
47
+ # >> H[:hr]]]
48
+ # >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
49
+ # >> <p><div id="main">
50
+ # >> <p>foo coming up!</p>
51
+ # >> <p class="foo"><br>awesome<br></p>
52
+ # >> <p>foo coming up!</p>
53
+ # >> <p class="foo"><br>awesome<br></p>
54
+ # >> </div><hr></p>
@@ -1,5 +1,25 @@
1
+ require 'delegate'
2
+ require 'forwardable'
3
+
4
+ require 'nokogiri'
5
+ require 'ice_nine'
6
+ require 'equalizer'
7
+
1
8
  module Hexp
9
+ def self.deep_freeze(*args)
10
+ IceNine.deep_freeze(*args)
11
+ end
2
12
  end
3
13
 
4
14
  require 'hexp/version'
5
- require 'hexp/array'
15
+
16
+ require 'hexp/node'
17
+ require 'hexp/node/normalize'
18
+ require 'hexp/node/domize'
19
+ require 'hexp/node/pp'
20
+
21
+ require 'hexp/text_node'
22
+ require 'hexp/list'
23
+ require 'hexp/dom'
24
+
25
+ require 'hexp/nokogiri/equality'
@@ -0,0 +1,9 @@
1
+ module Hexp
2
+ module DOM
3
+ Document = Nokogiri::HTML::Document
4
+ Node = Nokogiri::XML::Node
5
+ Text = Nokogiri::XML::Text
6
+
7
+
8
+ end
9
+ end
@@ -0,0 +1,2 @@
1
+ require 'hexp'
2
+ H=Hexp::Node
@@ -0,0 +1,25 @@
1
+ module Hexp
2
+ # A list of nodes
3
+ #
4
+ # @example
5
+ # Hexp::List[
6
+ # Hexp::Node[:marquee, "Try Hexp for instanst satisfaction!"],
7
+ # Hexp::Node[:hr],
8
+ # ]
9
+ #
10
+ class List < SimpleDelegator
11
+ include Equalizer.new(:__getobj__)
12
+
13
+ def initialize(nodes)
14
+ super Hexp.deep_freeze nodes
15
+ end
16
+
17
+ def self.[](*args)
18
+ new(args)
19
+ end
20
+
21
+ def inspect
22
+ __getobj__.inspect
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,63 @@
1
+ module Hexp
2
+ # A Hexp Node
3
+ class Node
4
+ include Equalizer.new(:tag, :attributes, :children)
5
+ extend Forwardable
6
+
7
+ attr_reader :tag, :attributes, :children
8
+ def_delegators :@children, :empty?
9
+
10
+ # Normalize the arguments
11
+ #
12
+ # @param args [Array] args a Hexp node
13
+ # @return [Hexp::Node]
14
+ #
15
+ # @example
16
+ # Hexp::Node[:p, {'class' => 'foo'}, [[:b, "Hello, World!"]]]
17
+ #
18
+ # @api public
19
+ def initialize(*args)
20
+ @tag, @attributes, @children = Hexp.deep_freeze(
21
+ Normalize.new(args).call
22
+ )
23
+ end
24
+
25
+ def self.[](*args)
26
+ new(*args)
27
+ end
28
+
29
+ def to_hexp
30
+ self
31
+ end
32
+
33
+ def to_html
34
+ to_dom.to_html
35
+ end
36
+
37
+ def to_dom
38
+ Domize.new(self).call
39
+ end
40
+
41
+ def inspect
42
+ self.class.inspect_name + to_a.reject(&:empty?).inspect
43
+ end
44
+
45
+ def to_a
46
+ [tag, attributes, children]
47
+ end
48
+
49
+ def pp
50
+ self.class::PP.new(self).call
51
+ end
52
+
53
+ class << self
54
+ def inspect_name
55
+ if defined?(H) && H == self
56
+ 'H'
57
+ else
58
+ self.name
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,45 @@
1
+ module Hexp
2
+ class Node
3
+ # Turn nodes into DOM objects
4
+ class Domize
5
+ attr_reader :dom
6
+
7
+ def initialize(hexp, dom = Hexp::DOM)
8
+ @raw = hexp
9
+ @dom = dom
10
+ end
11
+
12
+ def call
13
+ dom::Document.new.tap do |doc|
14
+ @doc = doc
15
+ doc << domize(@raw)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def domize(hexp)
22
+ dom::Node.new(hexp.tag.to_s, @doc).tap do |node|
23
+ set_attributes(node, hexp.attributes)
24
+ set_children(node, hexp.children)
25
+ end
26
+ end
27
+
28
+ def set_attributes(node, attributes)
29
+ attributes.each do |key,value|
30
+ node[key] = value
31
+ end
32
+ end
33
+
34
+ def set_children(node, children)
35
+ children.each do |child|
36
+ if child.instance_of?(TextNode)
37
+ node << dom::Text.new(child, @doc)
38
+ else
39
+ node << domize(child)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,89 @@
1
+ module Hexp
2
+ class Node
3
+ # Normalize a node
4
+ #
5
+ class Normalize
6
+ # Set a node to be normalized
7
+ #
8
+ # @param [Array] node A non-strict hexp
9
+ #
10
+ # @example
11
+ # Hexp::Node::Normalize.new([:p, {class:'foo'}])
12
+ #
13
+ # @api public
14
+ #
15
+ def initialize(node)
16
+ @raw = node
17
+ end
18
+
19
+ # Normalize to strict hexp nodes, cfr SPEC.md for details
20
+ #
21
+ # @return [Array] strict hexp node
22
+ #
23
+ # @api private
24
+ #
25
+ def call
26
+ [@raw.first, normalized_attributes, normalized_children]
27
+ end
28
+
29
+ private
30
+
31
+ # Pulls the attributes hash out of a non-strict hexp
32
+ #
33
+ # @return [Hash] the attributes hash
34
+ #
35
+ # @api private
36
+ #
37
+ def attributes
38
+ attrs = @raw[1]
39
+ return attrs if attrs.instance_of?(Hash)
40
+ {}
41
+ end
42
+
43
+ def normalized_attributes
44
+ Hash[*
45
+ attributes.flat_map do |key, value|
46
+ [key, value].map(&:to_s)
47
+ end
48
+ ]
49
+ end
50
+
51
+ # Pulls the children list out of a non-strict hexp
52
+ #
53
+ # @return [Array] the list of child hexps, non-strict
54
+ #
55
+ # @api private
56
+ #
57
+ def children
58
+ @raw[1..2].each do |arg|
59
+ return Array(arg) unless [Symbol, Hash].any?{|klz| arg.instance_of?(klz)}
60
+ end
61
+ []
62
+ end
63
+
64
+ # Normalize the third element of a hexp node, the list of children
65
+ #
66
+ # @return [Array] list of normalized hexps
67
+ #
68
+ # @api private
69
+ #
70
+ def normalized_children
71
+ Hexp::List[*
72
+ children.map do |child|
73
+ case child
74
+ when String, TextNode
75
+ Hexp::TextNode.new(child)
76
+ when Array
77
+ Hexp::Node[*child]
78
+ else
79
+ if child.respond_to? :to_hexp
80
+ Hexp::Node[*child.to_hexp]
81
+ end
82
+ end
83
+ end
84
+ ]
85
+ end
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,38 @@
1
+ module Hexp
2
+ class Node
3
+ # Pretty-print a node and its contents
4
+ class PP
5
+ def initialize(node)
6
+ @node = node
7
+ end
8
+
9
+ def call
10
+ [
11
+ @node.class.inspect_name,
12
+ pp_tag,
13
+ PP.indent(pp_attributes + pp_children).strip
14
+ ].join
15
+ end
16
+
17
+ def pp_tag
18
+ "[#{@node.tag.inspect}"
19
+ end
20
+
21
+ def pp_attributes
22
+ attrs = @node.attributes
23
+ return '' if attrs.empty?
24
+ ', ' + attrs.inspect
25
+ end
26
+
27
+ def pp_children
28
+ children = @node.children
29
+ return ']' if children.empty?
30
+ ", [\n#{ children.map(&:pp).join(",\n") }]]"
31
+ end
32
+
33
+ def self.indent(string, indent = 2)
34
+ string.lines.map {|line| " "*indent + line}.join
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,65 @@
1
+ module Hexp
2
+ module Nokogiri
3
+ # Used in test to see if two Nokogiri objects have the same content,
4
+ # i.e. are equivalent as far as we are concerned
5
+ class Equality
6
+ CLASSES = [
7
+ ::Nokogiri::HTML::Document,
8
+ ::Nokogiri::HTML::DocumentFragment,
9
+ ::Nokogiri::XML::Document,
10
+ ::Nokogiri::XML::Node,
11
+ ::Nokogiri::XML::Text,
12
+ ::Nokogiri::XML::Element,
13
+ ::Nokogiri::XML::DocumentFragment,
14
+ ::Nokogiri::XML::DTD,
15
+ ]
16
+
17
+ def initialize(this, that)
18
+ @this, @that = this, that
19
+ [this, that].each do |input|
20
+ raise "#{input.class} is not a Nokogiri element." unless CLASSES.include?(input.class)
21
+ end
22
+ end
23
+
24
+ def call
25
+ [ equal_class?,
26
+ equal_name?,
27
+ equal_children?,
28
+ equal_attributes?,
29
+ equal_text? ].all?
30
+ end
31
+
32
+ def equal_class?
33
+ @this.class == @that.class
34
+ end
35
+
36
+ def equal_name?
37
+ @this.name == @that.name
38
+ end
39
+
40
+ def equal_children?
41
+ return true unless @this.respond_to? :children
42
+ @this.children.count == @that.children.count &&
43
+ compare_children.all?
44
+ end
45
+
46
+ def compare_children
47
+ @this.children.map.with_index do |child, idx|
48
+ self.class.new(child, @that.children[idx]).call
49
+ end
50
+ end
51
+
52
+ def equal_attributes?
53
+ return true unless @this.respond_to? :attributes
54
+ @this.attributes.keys.all? do |key|
55
+ @this[key] == @that[key]
56
+ end
57
+ end
58
+
59
+ def equal_text?
60
+ return true unless @this.instance_of?(::Nokogiri::XML::Text)
61
+ @this.text == @that.text
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,26 @@
1
+ module Hexp
2
+ # Represents text inside HTML, at the moment a wrapper
3
+ # around a plain String. Needs work
4
+ class TextNode < SimpleDelegator
5
+ def inspect
6
+ __getobj__.inspect
7
+ end
8
+
9
+ def tree_walk
10
+ yield self
11
+ end
12
+
13
+ def attributes
14
+ {}.freeze
15
+ end
16
+
17
+ def pp
18
+ inspect
19
+ end
20
+
21
+ def to_a
22
+ [:text, self, Hexp::List[]]
23
+ end
24
+
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Hexp
2
- VERSION = '0.0.1.pre'
2
+ VERSION = '0.0.1.pre2'
3
3
  end
@@ -1 +1,8 @@
1
1
  require 'hexp'
2
+ require 'devtools/spec_helper'
3
+
4
+ RSpec::Matchers.define :dom_eq do |other_dom|
5
+ match do |dom|
6
+ Hexp::Nokogiri::Equality.new(dom, other_dom).call
7
+ end
8
+ end
@@ -23,4 +23,4 @@ if ENV['COVERAGE'] == 'true'
23
23
 
24
24
  end
25
25
 
26
- require 'shared_helper' # requires hexp
26
+ require 'shared_helper'
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Node::Domize do
4
+ def build_doc(&blk)
5
+ Nokogiri::HTML::Builder.new(&blk).doc
6
+ end
7
+
8
+ subject { Hexp::Node::Domize.new(hexp).call }
9
+
10
+ context 'with the same single node' do
11
+ let(:dom) { build_doc { html } }
12
+ let(:hexp) { Hexp::Node[:html] }
13
+
14
+ it { should dom_eq(dom) }
15
+ end
16
+
17
+ context 'with a different single node' do
18
+ let(:dom) { build_doc { html } }
19
+ let(:hexp) { Hexp::Node[:body] }
20
+
21
+ it { should_not dom_eq(dom) }
22
+ end
23
+
24
+ context 'with nested nodes' do
25
+ let(:dom) { build_doc { html { div(class: 'big') } } }
26
+ let(:hexp) { Hexp::Node[:html, [ [:div, class: 'big'] ]] }
27
+
28
+ it { should dom_eq(dom) }
29
+ end
30
+
31
+ context 'with equal text nodes' do
32
+ let(:dom) { build_doc {
33
+ html do
34
+ div(class: 'big')
35
+ text "awesometown!"
36
+ end
37
+ } }
38
+ let(:hexp) {
39
+ Hexp::Node[:html, [
40
+ [:div, class: 'big'],
41
+ "awesometown!"
42
+ ]
43
+ ]
44
+ }
45
+
46
+ it { should dom_eq(dom) }
47
+ end
48
+
49
+ context 'with differing text nodes' do
50
+ let(:dom) { build_doc {
51
+ html do
52
+ div(class: 'big')
53
+ text "awesomevillage!"
54
+ end
55
+ } }
56
+ let(:hexp) {
57
+ Hexp::Node[:html, [
58
+ [:div, class: 'big'],
59
+ "awesometown!"
60
+ ]
61
+ ]
62
+ }
63
+
64
+ it { should_not dom_eq(dom) }
65
+ end
66
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Node::Normalize, '#call' do
4
+ subject { Hexp::Node[*node] }
5
+
6
+ describe 'with a single element' do
7
+ let (:node) { [:div] }
8
+
9
+ it 'should treat the first as the tag' do
10
+ subject.tag.should == :div
11
+ end
12
+ it 'should set an empty attribute list' do
13
+ subject.attributes.should == {}
14
+ end
15
+ it 'should set an empty children list' do
16
+ subject.children.should == Hexp::List[]
17
+ end
18
+ end
19
+
20
+ describe 'with two parameters' do
21
+ let (:node) { [:div, {class: 'foo'}] }
22
+
23
+ it 'should treat the first as the tag' do
24
+ subject.tag.should == :div
25
+ end
26
+ it 'should treat the second as the attribute list, if it is a Hash' do
27
+ subject.attributes.should == {'class' => 'foo'}
28
+ end
29
+ it 'should treat the second as a list of children, if it is an Array' do
30
+ subject.children.should == Hexp::List[]
31
+ end
32
+ end
33
+
34
+ describe 'with a single text child node' do
35
+ let(:node) { [:div, "this is text in the div"] }
36
+
37
+ it 'should set is as the single child' do
38
+ subject.children.should == Hexp::List["this is text in the div"]
39
+ end
40
+ end
41
+
42
+ describe 'with child nodes' do
43
+ let(:node) {
44
+ [:div, [
45
+ [:h1, "Big Title"],
46
+ [:p, {class: 'greeting'}, "hello world"],
47
+ "Some loose text"
48
+ ]
49
+ ]
50
+ }
51
+
52
+ it 'must normalize them recursively' do
53
+ subject.children.should == Hexp::List[
54
+ Hexp::Node[:h1, {}, Hexp::List["Big Title"] ],
55
+ Hexp::Node[:p, {class: 'greeting'}, Hexp::List["hello world"] ],
56
+ "Some loose text"
57
+ ]
58
+ end
59
+ end
60
+
61
+ describe 'with an object that responds to to_hexp' do
62
+ let(:hexpable) {
63
+ Class.new do
64
+ def to_hexp
65
+ Hexp::Node[:em, "I am in your hexpz"]
66
+ end
67
+ end
68
+ }
69
+ let(:node) {
70
+ [:div, [ hexpable.new ] ]
71
+ }
72
+
73
+ it 'must expand that object' do
74
+ subject.children.should == Hexp::List[
75
+ Hexp::Node[:em, {}, Hexp::List["I am in your hexpz"] ]
76
+ ]
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Node, 'pp' do
4
+ subject { object.pp }
5
+
6
+ context 'with no attributes or children' do
7
+ let(:object) { Hexp::Node[:p, {}] }
8
+ it { should == 'Hexp::Node[:p]'}
9
+ end
10
+
11
+ context 'with a single child' do
12
+ let(:object) { Hexp::Node[:p, [ [:abbr, {title: 'YAGNI'}, "You ain't gonna need it"] ]] }
13
+ it { should == %q^Hexp::Node[:p, [
14
+ Hexp::Node[:abbr, {"title"=>"YAGNI"}, [
15
+ "You ain't gonna need it"]]]]^.gsub(' '*22, '')
16
+ }
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Node, 'to_a' do
4
+ subject { object.to_a }
5
+ let(:object) { Hexp::Node.new(:p, class: 'foo') }
6
+
7
+ it { should == [:p, {'class' => 'foo'}, Hexp::List[]] }
8
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Node, 'to_dom' do
4
+ subject { Hexp::Node[:blink] }
5
+
6
+ it 'should delegate to Domize' do
7
+ Hexp::Node::Domize.should_receive(:new).with(subject).and_return( ->{ 'result' } )
8
+ subject.to_dom.should == 'result'
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Node, 'to_hexp' do
4
+ let(:object) {Hexp::Node.new(:p) }
5
+ subject { object.to_hexp }
6
+
7
+ it { should == subject }
8
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Node, 'to_html' do
4
+ subject { Hexp::Node[:tt] }
5
+
6
+ it 'should render HTML' do
7
+ subject.to_html.should =~ %r{<tt></tt>}
8
+ end
9
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Nokogiri::Equality do
4
+ let(:doc) { Nokogiri::HTML::Document.new }
5
+
6
+ context 'two empty documents' do
7
+ it 'should be equal' do
8
+ described_class.new(Nokogiri::HTML::Document.new, Nokogiri::HTML::Document.new).call.should be_true
9
+ end
10
+ end
11
+
12
+ context 'two nodes with the same attributes' do
13
+ it 'should be equal' do
14
+ node1 = Nokogiri::XML::Node.new('div', doc)
15
+ node1['class'] = 'hello'
16
+ node2 = Nokogiri::XML::Node.new('div', doc)
17
+ node2['class'] = 'hello'
18
+
19
+ described_class.new(node1, node2).call.should be_true
20
+ end
21
+ end
22
+
23
+ context 'one node has an attribute more' do
24
+ it 'should be equal' do
25
+ node1 = Nokogiri::XML::Node.new('div', doc)
26
+ node1['class'] = 'hello'
27
+ node2 = Nokogiri::XML::Node.new('div', doc)
28
+ node2['class'] = 'hello'
29
+ node2['id'] = 'zigzag'
30
+
31
+ described_class.new(node1, node2).call.should be_true
32
+ end
33
+ end
34
+
35
+ context 'two nodes with the same children' do
36
+ it 'should be equal' do
37
+ node1 = Nokogiri::XML::Node.new('div', doc)
38
+ node1 << Nokogiri::XML::Node.new('p', doc)
39
+ node2 = Nokogiri::XML::Node.new('div', doc)
40
+ node2 << Nokogiri::XML::Node.new('p', doc)
41
+
42
+ described_class.new(node1, node2).call.should be_true
43
+ end
44
+ end
45
+
46
+ context 'two nodes with a child of a different name' do
47
+ it 'should not be equal' do
48
+ node1 = Nokogiri::XML::Node.new('div', doc)
49
+ node1 << Nokogiri::XML::Node.new('p', doc)
50
+ node2 = Nokogiri::XML::Node.new('div', doc)
51
+ node2 << Nokogiri::XML::Node.new('em', doc)
52
+
53
+ described_class.new(node1, node2).call.should be_false
54
+ end
55
+ end
56
+
57
+ context 'one node has a child more than the other, otherwise identical' do
58
+ it 'should not be equal' do
59
+ node1 = Nokogiri::XML::Node.new('div', doc)
60
+ node1 << Nokogiri::XML::Node.new('p', doc)
61
+ node2 = Nokogiri::XML::Node.new('div', doc)
62
+ node2 << Nokogiri::XML::Node.new('p', doc)
63
+ node2 << Nokogiri::XML::Node.new('em', doc)
64
+
65
+ described_class.new(node1, node2).call.should be_false
66
+ end
67
+ end
68
+
69
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.pre
4
+ version: 0.0.1.pre2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-23 00:00:00.000000000 Z
12
+ date: 2013-06-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ice_nine
@@ -43,18 +43,35 @@ files:
43
43
  - README.md
44
44
  - Rakefile
45
45
  - SPEC.md
46
+ - config/devtools.yml
46
47
  - config/flay.yml
47
48
  - config/flog.yml
48
49
  - config/mutant.yml
49
50
  - config/reek.yml
50
51
  - config/yardstick.yml
52
+ - examples/filter.rb
51
53
  - hexp.gemspec
52
54
  - lib/hexp.rb
53
- - lib/hexp/array.rb
55
+ - lib/hexp/dom.rb
56
+ - lib/hexp/h.rb
57
+ - lib/hexp/list.rb
58
+ - lib/hexp/node.rb
59
+ - lib/hexp/node/domize.rb
60
+ - lib/hexp/node/normalize.rb
61
+ - lib/hexp/node/pp.rb
62
+ - lib/hexp/nokogiri/equality.rb
63
+ - lib/hexp/text_node.rb
54
64
  - lib/hexp/version.rb
55
65
  - spec/shared_helper.rb
56
66
  - spec/spec_helper.rb
57
- - spec/unit/hexp/array_spec.rb
67
+ - spec/unit/hexp/node/domize_spec.rb
68
+ - spec/unit/hexp/node/normalize_spec.rb
69
+ - spec/unit/hexp/node/pp_spec.rb
70
+ - spec/unit/hexp/node/to_a_spec.rb
71
+ - spec/unit/hexp/node/to_dom_spec.rb
72
+ - spec/unit/hexp/node/to_hexp_spec.rb
73
+ - spec/unit/hexp/node/to_html_spec.rb
74
+ - spec/unit/hexp/nokogiri/equality_spec.rb
58
75
  homepage: https://github.com/plexus/hexp
59
76
  licenses: []
60
77
  post_install_message:
@@ -69,7 +86,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
69
86
  version: '0'
70
87
  segments:
71
88
  - 0
72
- hash: -3692654222847691207
89
+ hash: 4219632061257453794
73
90
  required_rubygems_version: !ruby/object:Gem::Requirement
74
91
  none: false
75
92
  requirements:
@@ -85,4 +102,11 @@ summary: HTML expressions
85
102
  test_files:
86
103
  - spec/shared_helper.rb
87
104
  - spec/spec_helper.rb
88
- - spec/unit/hexp/array_spec.rb
105
+ - spec/unit/hexp/node/domize_spec.rb
106
+ - spec/unit/hexp/node/normalize_spec.rb
107
+ - spec/unit/hexp/node/pp_spec.rb
108
+ - spec/unit/hexp/node/to_a_spec.rb
109
+ - spec/unit/hexp/node/to_dom_spec.rb
110
+ - spec/unit/hexp/node/to_hexp_spec.rb
111
+ - spec/unit/hexp/node/to_html_spec.rb
112
+ - spec/unit/hexp/nokogiri/equality_spec.rb
@@ -1,43 +0,0 @@
1
- module Hexp
2
- class Array < ::Array
3
- def self.[](*args)
4
- super(*normalize(args))
5
- end
6
-
7
- def self.normalize(args)
8
- idx = 0
9
- case args[idx]
10
- when Symbol
11
- tag = args[idx] ; idx+=1
12
- attrs = case args[idx]
13
- when Hash
14
- idx += 1 ; args[idx-1]
15
- else
16
- {}
17
- end
18
- children = normalize_children args[idx]
19
- [tag, attrs, children]
20
- end
21
- end
22
-
23
- def self.normalize_children(children)
24
- case children
25
- when String
26
- [ children ]
27
- when ::Array
28
- children.map do |child|
29
- case child
30
- when String
31
- child
32
- when ::Array
33
- Hexp::Array[*child]
34
- else
35
- raise "bad input #{child}"
36
- end
37
- end
38
- else
39
- []
40
- end
41
- end
42
- end
43
- end
@@ -1,61 +0,0 @@
1
- require 'spec_helper'
2
-
3
- H=Hexp::Array
4
-
5
- describe Hexp::Array do
6
- describe 'normalization' do
7
- describe 'with a single parameter' do
8
- it 'should return a triplet' do
9
- H[:div].count.should == 3
10
- end
11
- it 'should treat the first as the tag' do
12
- H[:div][0].should == :div
13
- end
14
- it 'should set an empty attribute list' do
15
- H[:div][1].should == {}
16
- end
17
- it 'should set an empty children list' do
18
- H[:div][2].should == []
19
- end
20
- end
21
-
22
- describe 'with two parameters' do
23
- it 'should treat the first as the tag' do
24
- H[:div, {class: 'foo'}][0].should == :div
25
- end
26
- it 'should treat the second as the attribute list, if it is a Hash' do
27
- H[:div, {class: 'foo'}][1].should == {class: 'foo'}
28
- end
29
- it 'should treat the second as a list of children, if it is an Array' do
30
- H[:div, []][2].should == []
31
- end
32
- end
33
-
34
- describe 'with a single text child node' do
35
- it 'should set is as the single child' do
36
- H[:div, "this is text in the div"][2].should == ["this is text in the div"]
37
- end
38
- end
39
-
40
- describe 'with child nodes' do
41
- it 'must normalize them recursively' do
42
- H[:div, [
43
- [:h1, "Big Title"],
44
- [:p, {class: 'greeting'}, "hello world"],
45
- "Some loose text"
46
- ]
47
- ][2].should == [
48
- [:h1, {}, ["Big Title"] ],
49
- [:p, {class: 'greeting'}, ["hello world"] ],
50
- "Some loose text"
51
- ]
52
- end
53
- end
54
-
55
- describe 'with bad input' do
56
- it 'should raise exception' do
57
- expect { H[:p, {}, [123]] }.to raise_exception
58
- end
59
- end
60
- end
61
- end