jig 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.
data/lib/jig/css.rb ADDED
@@ -0,0 +1,307 @@
1
+
2
+ require 'jig'
3
+
4
+ class Jig
5
+ # Jig::CSS is a subclass of Jig designed to facilitate the
6
+ # construction of CSS rule sets. This class should be considered
7
+ # experimental. The documentation uses C instead Jig::CSS to
8
+ # simplify the examples.
9
+ #
10
+ # An instance of Jig::CSS represents a CSS rule consisting of
11
+ # a selector list and an associated declaration block. The
12
+ # selector list or the delcaration block or both may be empty.
13
+ # Declarations are specified via a hash.
14
+ #
15
+ # C.new # => {}
16
+ # C.new('h1') # => h1 {}
17
+ # C.new(nil, 'color' => 'red') # => {color: red; }
18
+ #
19
+ # A simple CSS type selector with an empty declaration is the
20
+ # default construction:
21
+ #
22
+ # C.div # => div {}
23
+ # C.li # => li {}
24
+ #
25
+ # Rules can be combined with each other or with a hash via the
26
+ # 'pipe' operator.
27
+ #
28
+ # big = { 'font-size' => '24pt' }
29
+ # bold = { 'font-weight' => 'bold' }
30
+ # big_div = C.div | big # => div {font-size: 24pt; }
31
+ # big_bold_div = bigger | bold # => div {font-size: 24pt; font-weight: bold; }
32
+ # C.h1 | C.h2 # => h1, h2 { }
33
+ # C.h1 | big | C.h2 | bold # => h1, h2 { font-size: 24pt; font-weight: bold; }
34
+ class CSS < Jig
35
+ module IntegerHelper
36
+ def in; "#{self}in"; end
37
+ def cm; "#{self}cm"; end
38
+ def mm; "#{self}mm"; end
39
+ def pt; "#{self}pt"; end
40
+ def pc; "#{self}pc"; end
41
+ def em; "#{self}em"; end
42
+ def ex; "#{self}ex"; end
43
+ def px; "#{self}px"; end
44
+ def pct; "#{self}%"; end
45
+ end
46
+
47
+ module FloatHelper
48
+ def pct; "%.2f%%" % (self*100); end
49
+ end
50
+
51
+ Integer.class_eval { include IntegerHelper }
52
+ Float.class_eval { include FloatHelper }
53
+
54
+ Newlines = [:html, :head, :body, :title, :div, :p, :table, :script, :form]
55
+ Encode = Hash[*%w{& amp " quot > gt < lt}]
56
+
57
+ class <<self
58
+ # Construct a simple type selector based on the method name.
59
+ # C.new('div') # => div {}
60
+ # C.div # => div {}
61
+ def method_missing(sym, *args)
62
+ new(sym.to_s, *args)
63
+ end
64
+
65
+ def media(*types)
66
+ indent = Gap.new(:___) { |text| Jig.new { text.to_s.split("\n").map { |line| " #{line}" }.join("\n")} }
67
+ Jig.new("@media #{types.join(', ')} {\n", indent, "\n}\n")
68
+ end
69
+
70
+ def import(url, *types)
71
+ Jig.new("@import url(\"#{url}\") #{types.join(', ')}")
72
+ end
73
+ end
74
+
75
+ # Construct a CSS rule.
76
+ def initialize(selector='*', declarations=nil)
77
+ super(selector, :__s, " {", :__ds, to_declarations(declarations), :__de, "}").freeze
78
+ end
79
+
80
+ def to_declarations(hash)
81
+ return nil unless hash
82
+ hash.inject(Jig.null) do |djig, (property, value)|
83
+ djig.push(
84
+ case value
85
+ when Gap
86
+ value
87
+ when Symbol
88
+ Gap.new(value) { |fill| declaration(property, fill) }
89
+ else
90
+ declaration(property, value)
91
+ end
92
+ )
93
+ end
94
+ end
95
+
96
+ # Convert property/value pair for use in a CSS rule jig.
97
+ # Any underscores in the property are converted to hyphens.
98
+ # If the property ends in '!', the '!' is stripped and the
99
+ # declaration is marked with the CSS keyword '!important'.
100
+ # * If +value+ is nil or false, the empty string is returned.
101
+ # * If +value+ is a symbol, a declaration gap is returned.
102
+ # * If +value+ is a gap, the gap is returned.
103
+ # * If +value+ is a proc, method, or jig, a deferred
104
+ # declaration gap is returned by wrapping the construction
105
+ # in a lambda in a jig.
106
+ # * If the +value+ is an array, it is converted to a string
107
+ # via #join. If the property is 'font-family', the values
108
+ # are joined with a comma, otherwise a space is used.
109
+ # * Otherwise property and value are converted to strings and
110
+ # a CSS declaration string is returned.
111
+ def declaration(property, value)
112
+ case value
113
+ when nil, false
114
+ ""
115
+ when Symbol
116
+ Gap.new(value) { |fill| declaration(property, fill) }
117
+ when Gap
118
+ value
119
+ when Jig
120
+ Jig.new { declaration(property, value.to_s) }
121
+ when Proc, Method
122
+ Jig.new { declaration(property, value.call) }
123
+ when Array
124
+ seperator = (property == 'font[-_]family' ? ", " : " ")
125
+ declaration(property, value.join(seperator))
126
+ else
127
+ property.to_s =~ /\A(.*[^!])(!?)\z/
128
+ property = $1.to_s.tr('_','-')
129
+ "#{property}: #{value}#{" !important" unless $2.empty?}; "
130
+ end
131
+ end
132
+
133
+ # Construct a child selector. The parent is the lhs selector and
134
+ # the child is the rhs selector.
135
+ # div > p # => "div > p {}"
136
+ def >(other)
137
+ before(:__s, " > ", other.selector).before(:__de, other.declarations)
138
+ end
139
+
140
+ # Construct an adjacent sibling selector. The first sibling is
141
+ # the lhs selector and the other sibling is rhs selector.
142
+ # h1 + p # => "h1 + p {}"
143
+ def +(other)
144
+ before(:__s, " + ", other.selector).before(:__de, other.declarations)
145
+ end
146
+
147
+ # Construct a general sibling selector. The first sibling is
148
+ # the lhs selector and the other sibling is rhs selector.
149
+ # div ~ p # => "div ~ p {}"
150
+ def ~(other)
151
+ before(:__s, "~", other.selector).before(:__de, other.declarations)
152
+ end
153
+
154
+ # Construct an id selector. The id is the rhs value.
155
+ # h1 * 'chapter-one' # => "h1#chapter-one {}"
156
+ def *(id)
157
+ before(:__s, "#", id.to_s)
158
+ end
159
+
160
+ # Construct a pseudo-selector.
161
+ # h1/:first_letter # => "h1:first-letter {}"
162
+ # a/:active # => "a:active {}"
163
+ def /(pseudo)
164
+ before(:__s, ":", pseudo.to_s)
165
+ end
166
+
167
+ # Construct a descendent selector. The parent is the lhs selector and
168
+ # the descendent is the rhs selector.
169
+ # div >> p # => "div p {}"
170
+ def >>(other)
171
+ before(:__s, " ", other.selector).before(:__de, other.declarations)
172
+ end
173
+
174
+ # Construct a negation pseudo class. The delcarations associated
175
+ # with the other selector are discarded.
176
+ # div - p # => "div:not(p) {}"
177
+ def -(other)
178
+ before(:__s, ":not(", other.selector, ")")
179
+ end
180
+
181
+ # Construct a nth_child pseudo class.
182
+ def nth_child(a=0,b=0)
183
+ before(:__s, ":nth-child(#{a}n+#{b})")
184
+ end
185
+
186
+ # Construct a nth_last_child pseudo class.
187
+ def nth_last_child(a=0,b=0)
188
+ before(:__s, ":nth-last-child(#{a}n+#{b})")
189
+ end
190
+
191
+ # Construct a nth-of-type pseudo class.
192
+ def nth_of_type(a=0,b=0)
193
+ before(:__s, ":nth-of-type(#{a}n+#{b})")
194
+ end
195
+
196
+ # Construct a nth-last-of-type pseudo class.
197
+ def nth_last_of_type(a=0,b=0)
198
+ before(:__s, ":nth-last-of-type(#{a}n+#{b})")
199
+ end
200
+
201
+ # Construct a lang pseudo class.
202
+ def lang(lang)
203
+ before(:__s, ":lang(#{lang})")
204
+ end
205
+
206
+ # Merge this rule with another object.
207
+ # * If the other object is a hash, the hash is converted
208
+ # to a CSS declaration list and merged with the current list.
209
+ # * If the other object is a rule, the other selectors and
210
+ # declarations are merged with the current selectors and
211
+ # declarations.
212
+ # * Any other object is assumed to be a selector string
213
+ # and is merged with the current selectors.
214
+ #
215
+ # C.div.merge(:color => 'red') # => div { color: red; }
216
+ # C.div.merge(C.h1) # => div, h1 {}
217
+ # C.div(:color => 'blue').merge(C.h1(:font_size => '10pt'))
218
+ # # => div, h1 { color: blue; font-size: 10pt }
219
+ # C.div.merge('h1, h2, h3') # => div, h1, h2, h3 {}
220
+ def merge(other)
221
+ return self unless other
222
+ case other
223
+ when Hash
224
+ before(:__de, to_declarations(other))
225
+ when self.class
226
+ before(:__s, ", ", other.selector).before(:__de, other.declarations)
227
+ else
228
+ before(:__s, ", ", other)
229
+ end
230
+ end
231
+
232
+ # Returns a standard jig representation of the CSS jig. Gaps that are
233
+ # used internally by the CSS class are closed. #to_jig should be used before
234
+ # combining one or more CSS jigs.
235
+ def to_jig
236
+ Jig.new(plug( :__s => nil, :__de => nil, :__ds => nil ))
237
+ end
238
+
239
+ alias | :merge
240
+
241
+ # Extract the selector list from the rule as a jig.
242
+ # (div | h1).selector # => Jig["div, h1"]
243
+ def selector
244
+ slice(0...index(:__s))
245
+ end
246
+
247
+ # Extract the declaration list from the rule. The list is returned as
248
+ # a jig and not as a hash.
249
+ # div(:color => 'red').declarations # => Jig["color: red; ", :__de]
250
+ def declarations
251
+ slice(index(:__ds)+1..index(:__de)-1)
252
+ end
253
+
254
+ # Missing methods are rewritten as calls to #class_, which
255
+ # constructs class selectors.
256
+ # C.div.class_('urgent') # => div.urgent {}
257
+ # C.div.urgent # => div.urgent {}
258
+ # C.div.note.caution # => div.note.caution {}
259
+ def method_missing(sym, declarations=nil)
260
+ class_(sym, declarations)
261
+ end
262
+
263
+ # Add a class selector to pending selector. Usually
264
+ # this method is called indirectly via method_missing.
265
+ # C.div.class_('urgent') # => div.urgent {}
266
+ # C.div.urgent # => div.urgent {}
267
+ def class_(klass, declarations=nil)
268
+ before(:__s, ".#{klass}") | declarations
269
+ end
270
+
271
+ # Construct an attribute selector. If the argument is a
272
+ # simple string a simple attribute selector is constructed. If
273
+ # the argument is a hash with a string as the value, an exact
274
+ # attribute selector is constructed. If the value is a regular
275
+ # expression, a partial attribute selector is constructed. If
276
+ # the key is the literal string 'lang', a language attribute
277
+ # selector is constructed.
278
+ #
279
+ # input[:type] # => input[type] {}
280
+ # input[:type => 'password'] # => input[type="password"] {}
281
+ # input[:lang => 'en'] # => input[lang|="en"] {}
282
+ # input[:class => /heading/] # => input[class=~"heading"] {}
283
+ def [](*args)
284
+ if args.size == 1 && args.first.respond_to?(:to_hash) && args.first.size == 1
285
+ k,v = *args.first.to_a.first
286
+ case v
287
+ when String
288
+ before(:__s, %Q{[#{k}="#{v}"]})
289
+ when Regexp
290
+ v = v.to_s.split(':').last.chop # strip out the processing flags
291
+ if k.to_s == 'lang'
292
+ before(:__s, %Q{[lang|="#{v}"]})
293
+ else
294
+ before(:__s, %Q{[#{k}~="#{v}"]})
295
+ end
296
+ else
297
+ self
298
+ end
299
+ elsif args.size == 1 && args.first.respond_to?(:to_s)
300
+ before(:__s, "[#{args.first}]")
301
+ else
302
+ self
303
+ end
304
+ end
305
+
306
+ end
307
+ end
data/lib/jig/xhtml.rb ADDED
@@ -0,0 +1,283 @@
1
+ require 'jig'
2
+ require 'jig/xml'
3
+
4
+ class Jig
5
+ # Jig::XHTML is a subclass of Jig::XML and is designed to assist in the
6
+ # construction of XHTML documents.
7
+ class XHTML < XML
8
+ attr_accessor :extra
9
+ protected :extra
10
+
11
+ def initialize(*args)
12
+ super
13
+ @extra = {}
14
+ end
15
+
16
+ def initialize_copy(other)
17
+ super
18
+ @extra = other.extra.dup
19
+ end
20
+
21
+ def freeze
22
+ @extra.freeze
23
+ super
24
+ end
25
+
26
+ def eid
27
+ extra[:eid]
28
+ end
29
+
30
+ def eid=(eid)
31
+ raise RuntimeError, "no eid reassignment permitted" if extra[:eid]
32
+ extra[:eid] = eid
33
+ end
34
+
35
+ class <<self
36
+ # Construct a jig for an XHTML element with _tag as the tag and include
37
+ # an ID attribute with a guaranteed unique value.
38
+ # Example:
39
+ # puts Jig::XHTML.element_with_id('div') # <div id="x2354322">\n</div>
40
+ def element_with_id(tag, *args)
41
+ attrs = { 'id' => :id }
42
+ attrs = attrs.merge!(args.pop) if args.last.respond_to?(:fetch)
43
+ args.push(Proc.new) if block_given?
44
+ args.push attrs
45
+ newjig = element(tag, *args)
46
+ newjig.eid = "x#{newjig.object_id}"
47
+ newjig.plug!(:id, newjig.eid )
48
+ end
49
+
50
+ # Construct a jig for an emtpy XHTML element with _tag as the tag and include
51
+ # an ID attribute with a guaranteed unique value. The selected id is
52
+ # accessible via the eid attribute.
53
+ # Example:
54
+ # j = Jig::XHTML.element_with_id('input', :name=>'login')
55
+ # puts j # <input name="login" id="x2354328"/>
56
+ # puts j.eid # x2354328
57
+ def element_with_id!(tag, *args)
58
+ attrs = { 'id' => :id }
59
+ attrs = attrs.merge!(args.pop) if args.last.respond_to?(:fetch)
60
+ args.push(Proc.new) if block_given?
61
+ args.push attrs
62
+ jig = element!(tag, *args)
63
+ jig.eid = "x#{newjig.object_id}"
64
+ jig.plug!(:id, jig.eid)
65
+ end
66
+
67
+ # Construct an element based on the method name. If the method name
68
+ # ends in '_with_id' or '_with_id!', the element is constructed with
69
+ # a unique XML id attribute otherwise the Jig::XML element construction
70
+ # rules apply.
71
+ def method_missing(sym, *args, &block)
72
+ text = sym.to_s
73
+ if text.to_s =~ /_with_id!*$/
74
+ element_with_id(text.sub(/_with_id!*$/,'').to_sym, *args, &block)
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ DOCTYPES = {
81
+ :strict, %{"-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"},
82
+ :transitional, %{"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"},
83
+ :frameset, %{"-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"}
84
+ }
85
+
86
+ # Construct an XHTML DOCTYPE declaration. The first argument, _dtype_, specifies
87
+ # the type of document: :strict, :transitional, or :frameset. Any additional
88
+ # arguments are rendered after the DOCTYPE declaration. A default gap is *not*
89
+ # inserted if their are no arguments. Examples:
90
+ #
91
+ # puts X.doctype(:strict)
92
+ # # => <!DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd>
93
+ # puts X.doctype(:strict).gaps.size # 0
94
+ def doctype(dtype, *args, &block)
95
+ new(%{<!DOCTYPE html PUBLIC #{DOCTYPES.fetch(dtype)}>\n}, *args, &block)
96
+ end
97
+
98
+ # call-seq:
99
+ # xhtml([attributes]) -> a_jig
100
+ # xhtml(doctype, [attributes]) -> a_jig
101
+ # xhtml(doctype, *items, [attributes]) -> a_jig
102
+ # xhtml(*items, [attributes]) -> a_jig
103
+ # Construct a generic XHTML document. With no arguments, a transitional
104
+ # document with three gaps, <tt>:title</tt>, <tt>:head</tt>, and <tt>:___</tt>
105
+ # is generated. The default gap represents the contents of the body element.
106
+ #
107
+ # With a single symbol argument, <i>doctype</i>, an XHTMl document is generated
108
+ # with a corresponding DOCTYPE declaration (see #doctype).
109
+ #
110
+ # If any arguments are provided other than a doctype, the additional arguments
111
+ # are used as the contents of the HTML element.
112
+ #
113
+ # A trailing hash argument is assumed to represent additional XML attributes for
114
+ # the html element.
115
+ # X.xhtml # transitional document with :title, :head, and :___
116
+ # X.xhtml :strict # strict document with :title, :head, and :___
117
+ # X.xhtml(:strict, head, body) # strict document with explicit head & body
118
+ # X.xhtml(head, body) # transitional document with explicit head & body
119
+ # X.xhtml :lang => 'jp' # transition document with HTML attributes merged
120
+ def xhtml(dtype=:transitional, *args, &block)
121
+ unless Symbol === dtype
122
+ dtype,args = :transitional,args.unshift(dtype)
123
+ end
124
+ attrs = {:lang=>'en', "xml:lang"=>'en', :xmlns=>'http://www.w3.org/1999/xhtml'}
125
+ attrs.merge!(args.pop) if args.last.respond_to?(:fetch)
126
+ args.push(Proc.new) if block_given?
127
+ args.push(head(title(:title),:head),body) if args.empty?
128
+ args.push(attrs)
129
+ doctype(dtype,html(*args))
130
+ end
131
+
132
+ # A convenience method for constructing an XHTML element with a named
133
+ # CSS class and an unique XHTML element ID.
134
+ #
135
+ # X.container('span', 'urgent', 'This is urgent!')
136
+ # => <span class="urgent" id="x2337852"></span>
137
+ def container(tag, css, *args)
138
+ args.push(Proc.new) if block_given?
139
+ args.push(:class => css)
140
+ jig = element_with_id(tag, *args)
141
+ jig.send(:extra)[:css] = css
142
+ jig
143
+ end
144
+
145
+ # An even shorter way to construct a div container
146
+ #
147
+ # X.divc('urgent', 'This is urgent!')
148
+ # => <div class="urgent" id="x2337852"></div>
149
+ def divc(css_class, *args, &block)
150
+ container(:div, css_class, *args, &block)
151
+ end
152
+
153
+ # Generate a link element for a favicon. Extra attributes
154
+ # may be specified via the optional argument, _extra_.
155
+ #
156
+ # X.link_favicon
157
+ # => <link src="/favicon.ico" type="image/x-icon" rel="icon"/>
158
+ def link_favicon(extra={})
159
+ attrs = {:type=>"image/x-icon", :rel=>"icon", :src=>'/favicon.ico'}
160
+ attrs.merge! extra
161
+ link!(attrs)
162
+ end
163
+
164
+ # XXX: is this no longer needed?
165
+ def normalize_args(args, attrs={}) # :nodoc"
166
+ attrs.merge!(args.pop) if args.last.respond_to?(:fetch)
167
+ args.push(Proc.new) if block_given?
168
+ args.push INNER if args.empty?
169
+ return args, attrs
170
+ end
171
+
172
+ # Generate a CSS style sheet element. If a 'src' attribute
173
+ # is provided the contents is empty. Without a 'src' attribute
174
+ # a CDATA block wraps the contents of the element.
175
+ #
176
+ # j = Jig::XHTML.style
177
+ # puts j.plug('/* CSS style sheet */')
178
+ #
179
+ # <style media="all" type="text/css">
180
+ # <![CDATA[
181
+ # /* CSS style sheet */
182
+ # ]]>
183
+ # </style>
184
+ def style(*args, &block)
185
+ attrs = {:type=>"text/css", :media=>"all"}
186
+ attrs.merge!(args.pop) if args.last.respond_to?(:fetch)
187
+ args.push(Proc.new) if block_given?
188
+ if attrs.has_key?(:src)
189
+ args = [attrs]
190
+ else
191
+ args = ["\n", cdata(*args.push("\n")), attrs]
192
+ end
193
+ element(:style, *args)
194
+ end
195
+
196
+ def style_link(src)
197
+ link!('rel' => 'stylesheet', 'type' => 'text/css', 'href' => src)
198
+ end
199
+
200
+ # Generate a script element. XXX
201
+ #
202
+ # j = Jig::XHTML.script
203
+ # puts j.plug(cdata("# the script"))
204
+ #
205
+ # <script type=">
206
+ # <![CDATA[
207
+ # # the script
208
+ # ]]>
209
+ # </script>
210
+ def script(*args, &block)
211
+ attrs = args.pop if args.last.respond_to?(:fetch)
212
+ args.push(Proc.new) if block_given?
213
+ if attrs.has_key?(:fetch)
214
+ args = [attrs]
215
+ else
216
+ args.push(attrs)
217
+ end
218
+ element(:script, *args)
219
+ end
220
+
221
+ def input(*args, &block)
222
+ element_with_id!(:input, *args, &block)
223
+ end
224
+ def textarea(*args, &block)
225
+ element_with_id(:textarea, *args, &block)
226
+ end
227
+ def select(*args, &block)
228
+ element_with_id(:select, *args, &block)
229
+ end
230
+
231
+ def more(ajig, bjig)
232
+ body = div_with_id({:style => 'display: none'}, bjig)
233
+ new(a({:href=>"#", :onclick => "toggle(#{body.eid})"}, '(details)'), body)
234
+ end
235
+
236
+ # Generate a Javascript comment.
237
+ #
238
+ # j = Jig::XHTML.js_comment
239
+ # puts j.plug("comment")
240
+ #
241
+ # /* comment */
242
+ def js_comment(*args, &block)
243
+ gap = Jig::Gap.new(:comment) { |*filling|
244
+ filling.map {|item|
245
+ item.to_s.split("\n").map {|line| "// #{line}" }
246
+ }.join("\n")
247
+ }
248
+ new(gap, "\n").plug(:comment, *args)
249
+ end
250
+
251
+ # Generate a multiline Javascript comment.
252
+ #
253
+ # j = Jig::XHTML.js_comments
254
+ # puts j.plug("line 1\nline 2")
255
+ #
256
+ # /*
257
+ # line 1
258
+ # line 2
259
+ # */
260
+ def js_mlcomment(*args, &block)
261
+ new("/*\n", new(*args, &block), "\n */\n")
262
+ end
263
+
264
+ # Generate an inline script element for javascript.
265
+ # The body of the script is wrapped in a CDATA block.
266
+ #
267
+ # j = Jig::XHTML.javascript
268
+ # puts j.plug("// the script")
269
+ #
270
+ # <script type="text/javascript" language="JavaScript">
271
+ # <![CDATA[
272
+ # // the script
273
+ # ]]>
274
+ # </script>
275
+ def javascript(*args, &block)
276
+ attrs = {:type=>"text/javascript", :language=>"JavaScript"}
277
+ attrs.merge!(args.pop) if args.last.respond_to?(:fetch)
278
+ args.push(Proc.new) if block_given?
279
+ script("//<![CDATA[\n", new(*args), "\n//]]>\n", attrs)
280
+ end
281
+ end
282
+ end
283
+ end