www_app 1.3.0 → 2.0.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/README.md +42 -24
  4. data/VERSION +1 -1
  5. data/bin/www_app +15 -4
  6. data/lib/public/vendor/hogan-3.0.2.min.js +5 -0
  7. data/lib/public/vendor/instruct_instruct_instruct.js +247 -0
  8. data/lib/public/vendor/jquery-2.1.3.min.js +4 -0
  9. data/lib/public/vendor/lodash.min.js +89 -0
  10. data/lib/public/www_app.js +885 -625
  11. data/lib/www_app/CSS.rb +310 -0
  12. data/lib/www_app/HTML.rb +219 -0
  13. data/lib/www_app/JavaScript.rb +51 -0
  14. data/lib/www_app/TO.rb +897 -0
  15. data/lib/www_app.rb +324 -945
  16. data/playground/config.ru +102 -0
  17. data/specs/client-side/index.html +1 -1
  18. data/specs/client-side/index.js +31 -379
  19. data/specs/lib/config.ru +60 -31
  20. data/specs/lib/helpers.rb +24 -2
  21. data/specs/server-side/0000-new.rb +1 -1
  22. data/specs/server-side/0001-underscore-double.rb +38 -0
  23. data/specs/server-side/0001-underscore.rb +73 -0
  24. data/specs/server-side/0010-attrs.rb +3 -4
  25. data/specs/server-side/0011-id.rb +5 -5
  26. data/specs/server-side/0020-tag.rb +2 -2
  27. data/specs/server-side/0020-tag_content.rb +1 -1
  28. data/specs/server-side/0021-body.rb +1 -1
  29. data/specs/server-side/0021-script.rb +66 -20
  30. data/specs/server-side/{0021-page_title.rb → 0021-title.rb} +5 -3
  31. data/specs/server-side/0030-mustache.rb +27 -20
  32. data/specs/server-side/0030-style.rb +64 -21
  33. data/specs/server-side/0040-css.rb +4 -4
  34. data/specs/server-side/0041-pseudo.rb +55 -0
  35. data/specs/server-side/0042-slash.rb +20 -0
  36. data/specs/server-side/0060-text.rb +26 -0
  37. metadata +18 -13
  38. data/lib/public/jquery-2.1.1.js +0 -4
  39. data/lib/public/underscore-1.7.0.js +0 -6
  40. data/lib/public/underscore-min.map +0 -1
  41. data/lib/public/underscore.string-2.3.0.js +0 -1
  42. data/lib/www_app/Clean.rb +0 -169
  43. data/lib/www_app/dsl.rb +0 -86
  44. data/lib/www_app/source.rb +0 -53
  45. data/specs/server-side/0050-on.rb +0 -64
  46. data/specs/server-side/0060-string.rb +0 -32
  47. /data/lib/public/{jquery.serialize-object.min.js → vendor/jquery.serialize-object.min.js} +0 -0
data/lib/www_app.rb CHANGED
@@ -1,99 +1,17 @@
1
1
 
2
2
 
3
- require 'mustache'
4
- require 'escape_escape_escape'
5
-
6
-
7
- # ===================================================================
8
- # === Mustache customizations: ======================================
9
- # ===================================================================
10
- Mustache.raise_on_context_miss = true
11
-
12
- class Mustache
13
-
14
- def render(data = template, ctx = {})
15
- ctx = data
16
- tpl = templateify(template)
17
-
18
- begin
19
- context.push(ctx)
20
- tpl.render(context)
21
- ensure
22
- context.pop
23
- end
24
- end # === def render
25
-
26
- class Context
27
-
28
- def find *args
29
- fail "No longer needed."
30
- end
31
-
32
- def fetch *args
33
- raise ContextMiss.new("Can't find: #{args.inspect}") if args.size != 2
34
-
35
- meth, key = args
36
-
37
- @stack.each { |frame|
38
- case
39
- when frame.is_a?(Hash) && meth == :coll && !frame.has_key?(key)
40
- return false
41
-
42
- when frame.is_a?(Hash) && meth == :coll && frame.has_key?(key)
43
- target = frame[key]
44
- if target == true || target == false || target == nil || target.is_a?(Array) || target.is_a?(Hash)
45
- return target
46
- end
47
- fail "Invalid value: #{key.inspect} (#{key.class})"
48
-
49
- when frame.is_a?(Hash) && frame.has_key?(key)
50
- return ::Escape_Escape_Escape.send(meth, frame[key])
51
-
52
- end
53
- }
54
-
55
- raise ContextMiss.new("Can't find .#{meth}(#{key.inspect})")
56
- end
57
-
58
- alias_method :[], :fetch
59
-
60
- end # === class Context
61
-
62
- class Generator
63
-
64
- alias_method :w_syms_on_fetch, :on_fetch
65
-
66
- def on_fetch(names)
67
- if names.length == 2
68
- "ctx[#{names.first.to_sym.inspect}, #{names.last.to_sym.inspect}]"
69
- else
70
- w_syms_on_fetch(names)
71
- end
72
- end
73
-
74
- end # === class Generator
75
-
76
- end # === class Mustache
77
- # ===================================================================
78
-
79
-
80
- # ===================================================================
81
- # === Symbol customizations: ========================================
82
- # ===================================================================
83
- class Symbol
84
-
85
- def to_mustache meth
86
- WWW_App::Sanitize.mustache meth, self
87
- end
88
-
89
- end # === class Symbol
90
- # ===================================================================
3
+ class WWW_App < BasicObject
4
+ end # === class WWW_App
91
5
 
6
+ require 'www_app/CSS'
7
+ require 'www_app/HTML'
8
+ require 'www_app/JavaScript'
9
+ require 'www_app/TO'
92
10
 
93
11
  # ===================================================================
94
12
  # === WWW_App ====================================================
95
13
  # ===================================================================
96
- class WWW_App < BasicObject
14
+ class WWW_App
97
15
  # ===================================================================
98
16
 
99
17
  include ::Kernel
@@ -106,982 +24,443 @@ class WWW_App < BasicObject
106
24
 
107
25
  ALWAYS_END_TAGS = [:script]
108
26
 
109
- SYM_CACHE = { attrs: {}, css_props: {}}
110
-
111
- Classes = []
112
27
  INVALID_ATTR_CHARS = /[^a-z0-9\_\-]/i
113
28
  IMAGE_AT_END = /image\z/i
114
29
 
115
- HASH = '#'
116
- DOT = '.'
117
- BANG = '!'
118
- NEW_LINE = "\n"
119
- SPACE = ' '
120
- BLANK = ''
121
- BODY = 'body'
122
- UNDERSCORE = '_'
123
-
124
- Document_Template = ::File.read(__FILE__).split("__END__").last.strip
125
-
126
- NO_END_TAGS = [:br, :input, :link, :meta, :hr, :img]
127
-
128
- Methods = {
129
- :elements => %w[
130
-
131
- title
132
- body div span
133
-
134
- img
135
- b em i strong u a
136
- abbr blockquote cite
137
- br cite code
138
- ul ol li p pre q
139
- sup sub
140
- form input button
141
-
142
- link
143
-
144
- script
145
-
146
- ].map(&:to_sym),
147
-
148
- :attributes => {
149
- :all => [:id, :class],
150
- :a => [:href, :rel],
151
- :form => [:action, :method, :accept_charset],
152
- :input => [:type, :name, :value],
153
- :style => [:type],
154
- :script => [:type, :src, :language],
155
- :link => [:rel, :type, :sizes, :href, :title],
156
- :meta => [:name, :http_equiv, :property, :content, :charset],
157
- :img => [:src, :width, :height]
158
- },
159
-
160
- :css => {
161
- :at_rules => [ 'font-face', 'media' ],
162
- :protocols => [ :relative ],
163
-
164
- :pseudo => %w[
165
- active checked default dir() disabled
166
- empty enabled
167
- first first-child first-of-type fullscreen focus
168
- hover
169
- indeterminate in-range invalid
170
- lang() last-child last-of-type left link
171
- not() nth-child() nth-last-child() nth-last-of-type() nth-of-type()
172
- only-child only-of-type optional out-of-range
173
- read-only read-write required right root
174
- scope
175
- target
176
- valid visited
177
- ].select { |name| name[/\A[a-z0-9\-]+\Z/] }.map { |name| name.gsub('-', '_').to_sym },
178
-
179
- # From: Sanitize::Config::RELAXED[:css][:properties]
180
- :properties => %w[
181
- background bottom font_variant_numeric position
182
- background_attachment box_decoration_break font_variant_position quotes
183
- background_clip box_shadow font_weight resize
184
- background_color box_sizing height right
185
- background_image clear hyphens tab_size
186
- background_origin clip icon table_layout
187
- background_position clip_path image_orientation text_align
188
- background_repeat color image_rendering text_align_last
189
- background_size column_count image_resolution text_combine_horizontal
190
- border column_fill ime_mode text_decoration
191
- border_bottom column_gap justify_content text_decoration_color
192
- border_bottom_color column_rule left text_decoration_line
193
- border_bottom_left_radius column_rule_color letter_spacing text_decoration_style
194
- border_bottom_right_radius column_rule_style line_height text_indent
195
- border_bottom_style column_rule_width list_style text_orientation
196
- border_bottom_width column_span list_style_image text_overflow
197
- border_collapse column_width list_style_position text_rendering
198
- border_color columns list_style_type text_shadow
199
- border_image content margin text_transform
200
- border_image_outset counter_increment margin_bottom text_underline_position
201
- border_image_repeat counter_reset margin_left top
202
- border_image_slice cursor margin_right touch_action
203
- border_image_source direction margin_top transform
204
- border_image_width display marks transform_origin
205
- border_left empty_cells mask transform_style
206
- border_left_color filter mask_type transition
207
- border_left_style float max_height transition_delay
208
- border_left_width font max_width transition_duration
209
- border_radius font_family min_height transition_property
210
- border_right font_feature_settings min_width transition_timing_function
211
- border_right_color font_kerning opacity unicode_bidi
212
- border_right_style font_language_override order unicode_range
213
- border_right_width font_size orphans vertical_align
214
- border_spacing font_size_adjust overflow visibility
215
- border_style font_stretch overflow_wrap white_space
216
- border_top font_style overflow_x widows
217
- border_top_color font_synthesis overflow_y width
218
- border_top_left_radius font_variant padding word_break
219
- border_top_right_radius font_variant_alternates padding_bottom word_spacing
220
- border_top_style font_variant_caps padding_left word_wrap
221
- border_top_width font_variant_east_asian padding_right z_index
222
- border_width font_variant_ligatures padding_top
223
- ].map(&:to_sym)
224
- }
225
-
226
- } # === end Methods
227
-
228
- ALLOWED_ATTRS = Methods[:attributes].inject({}) { |memo, (tag, attrs)|
229
- attrs.each { |a|
230
- memo[a] ||= []
231
- memo[a] << tag
30
+ NEW_LINE = "\n".freeze
31
+ HASH = '#'.freeze
32
+ DOT = '.'.freeze
33
+ BANG = '!'.freeze
34
+ SPACE = ' '.freeze
35
+ BLANK = ''.freeze
36
+ BODY = 'body'.freeze
37
+ UNDERSCORE = '_'.freeze
38
+
39
+ class << self
40
+ end # === class << self
41
+
42
+ include CSS
43
+ include HTML
44
+ include JavaScript
45
+ include TO
46
+
47
+ mods = included_modules.reject { |mod| mod == ::Kernel }
48
+ MULTI_DEFINED_METHS = mods.inject({}) { |memo, mod|
49
+ mod.instance_methods.each { |meth|
50
+ defs = mods.select { |o| o.instance_methods.include?(meth) }
51
+ if defs.size > 1
52
+ memo[meth] ||= defs
53
+ end
232
54
  }
233
55
  memo
234
56
  }
235
57
 
236
- class << self # ===================================================
237
- end # === class self ==============================================
238
-
239
- def initialize *files
240
- @js = []
241
- @style = {}
242
- @css_arr = []
243
- @css_id_override = nil
244
- @render_it = true
245
-
246
- @title = nil
247
- @scripts = []
248
- @body = []
249
- @compiled = nil
250
- @cache = {}
251
- @is_doc = false
252
- @page_title = nil
253
- @default_ids = {}
254
-
255
- @state = [:create]
256
- @ids = {}
257
-
258
- @tag_arr = []
259
- @current_tag_index = nil
260
- @mustache = nil
261
-
262
- @html_ids = {}
263
-
264
- tag(:head) {
58
+ if !MULTI_DEFINED_METHS.empty?
59
+ fail ::ArgumentError, "Methods already defined:\n#{MULTI_DEFINED_METHS.inspect}"
60
+ end
265
61
 
266
- @head = tag!
62
+ private # ===============================================
267
63
 
268
- tag(:style) {
269
- @style = tag!
270
- @style[:css] = {}
271
- }
272
-
273
- tag(:script) {
274
- tag![:content] = @js
275
- }
64
+ def initialize &blok
65
+ @html_ids = {}
66
+ @tags = []
67
+ @tag = nil
276
68
 
277
- } # === tag :head
278
-
279
- tag(:body) {
69
+ instance_eval &blok
70
+ end
280
71
 
281
- @body = tag!
72
+ def SPACES indent
73
+ SPACE * indent
74
+ end
282
75
 
283
- files.each { |file_name|
284
- eval ::File.read(file_name), nil, file_name
285
- }
76
+ def args_to_traversal_args *args
77
+ tags = nil
78
+ tag = nil
79
+ syms = []
286
80
 
287
- instance_eval(&(::Proc.new))
81
+ args.each { |a|
82
+ case a
83
+ when ::Symbol
84
+ syms << a
85
+ when ::Array
86
+ tags = a
87
+ when ::Hash
88
+ tag = a
89
+ tags = [a]
90
+ else
91
+ fail ::ArgumentError, "#{args.inspect}"
92
+ end
288
93
  }
289
94
 
290
- @mustache = ::Mustache.new
291
- @mustache.template = to_mustache
292
-
293
- freeze
294
- end # === def new_class
295
-
296
- def render_if name
297
- tag(:render_if) { tag![:attrs][:key] = name; yield }
298
- nil
95
+ tags ||= @tags.dup
96
+ tag ||= @tag
97
+ return [tags, tag, syms]
299
98
  end
300
99
 
301
- def render_unless name
302
- tag(:render_unless) { tag![:attrs][:key] = name; yield }
303
- nil
304
- end
305
-
306
- def render raw_data = {}
307
- @mustache.render raw_data
308
- end
100
+ def de_ref tag
101
+ t_name = tag[:tag_name]
102
+ return tag unless t_name == :_
309
103
 
310
- Allowed = {
311
- :attr => {}
312
- }
104
+ # === Remove unneeded values from tag:
105
+ dup = tag.dup
106
+ dup.delete :tag_name
107
+ dup.delete :parent
313
108
 
314
- Methods[:attributes].each { |tag, attrs|
315
- next if tag == :all
316
- attrs.each { |raw_attr|
317
- attr_name = raw_attr.to_s.gsub('-', '_').to_sym
318
- Allowed[:attr][attr_name] ||= {}
319
- Allowed[:attr][attr_name][tag.to_sym] = true
320
- }
321
- }
322
-
323
- Allowed[:attr].each { |name, tags|
324
- eval <<-EOF, nil, __FILE__, __LINE__ + 1
325
- def #{name} val
326
- allowed = Allowed[:attr][:#{name}]
327
- allowed = allowed && allowed[tag![:tag]]
328
- return super unless allowed
329
-
330
- tag![:attrs][:#{name}] = val
109
+ # === Find the true parent:
110
+ parent = tag
111
+ while parent && [:_, :style, :group].freeze.include?(parent[:tag_name])
112
+ parent = parent[:parent]
113
+ end
114
+ if !parent
115
+ parent = {:tag_name=>:body}
116
+ end
331
117
 
332
- if block_given?
333
- close_tag { yield }
334
- else
335
- self
336
- end
337
- end
338
- EOF
339
- }
118
+ # === Merge, Freeze, and Return:
119
+ t = {}
120
+ t.merge! parent
121
+ t.merge! dup
122
+ t.freeze
123
+ t
124
+ end # === def de_ref
340
125
 
126
+ # Ex:
341
127
  #
342
- # Example:
343
- # div.*('my_id') { }
128
+ # find_all :body, :style, :span
129
+ # find_all [], :body, :a
130
+ # find_all {tag}, :body, :a
344
131
  #
345
- def * raw_id
346
- id = ::Escape_Escape_Escape.html_id(raw_id)
132
+ def find_all *raw_args
133
+ tags, tag, syms = args_to_traversal_args(*raw_args)
347
134
 
348
- old_id = tag![:attrs][:id]
349
- fail("Id already set: #{old_id} new: #{id}") if old_id
135
+ fail ::ArgumentError, "tag names to find empty: #{syms.inspect}" if syms.empty?
350
136
 
351
- fail(HTML_ID_Duplicate, "Id already used: #{id.inspect}, tag index: #{@html_ids[id]}") if @html_ids[id]
352
- @html_ids[id] = tag![:tag_index]
353
-
354
- tag![:attrs][:id] = id
137
+ found = []
138
+ while !tags.empty?
139
+ t = tags.shift
140
+ if t[:children]
141
+ tags = t[:children].dup.concat tags
142
+ end
355
143
 
356
- if block_given?
357
- close_tag { yield }
358
- else
359
- self
144
+ (found << t) if syms.include?(t[:tag_name])
360
145
  end
146
+
147
+ found
361
148
  end
362
149
 
363
150
  #
364
- # Example:
365
- # div.^(:alert, :red_hot) { 'my content' }
151
+ # Ex:
366
152
  #
367
- def ^ *names
368
- tag![:attrs][:class].concat(names).uniq!
153
+ # detect :body, :style, :span
154
+ # detect [], :body, :a
155
+ # detect {tag}, :body, :a
156
+ #
157
+ def detect *raw_args
158
+ tags, tag, syms = args_to_traversal_args(*raw_args)
159
+
160
+ found = nil
161
+ while !found && !tags.empty?
162
+ found = tags.shift
163
+ if !syms.include?(found[:tag_name])
164
+ if found[:children]
165
+ tags = found[:children].dup.concat tags
166
+ end
167
+ found = nil
168
+ end
169
+ end
369
170
 
370
- if block_given?
371
- close_tag { yield }
171
+ found
172
+ end
173
+
174
+ def tag? *args
175
+ case args.size
176
+ when 1
177
+ tag = @tag
178
+ name = args.first
179
+ when 2
180
+ tag = args.first
181
+ name = args.last
372
182
  else
373
- self
183
+ fail "Unknown args: #{args.inspect}"
374
184
  end
185
+
186
+ tag && (tag[:tag_name] == name || !!tag[name])
375
187
  end
376
188
 
377
- private # =========================================================
189
+ def tag_or_ancestor? *args
190
+ !!find_nearest(*args)
191
+ end
378
192
 
379
- # =================================================================
380
- # Parent-related methods
381
- # =================================================================
193
+ def ancestor? *args
194
+ !!(find_ancestor *args)
195
+ end
382
196
 
383
- #
384
- # NOTE: Properties are defined first,
385
- # so :elements methods can over-write them,
386
- # just in case there are duplicates.
387
- Methods[:css][:properties].each { |name|
388
- str_name = name.to_s.gsub('_', '-')
389
- eval <<-EOF, nil, __FILE__, __LINE__ + 1
390
- def #{name} *args
391
- css_property(:#{name}, *args) {
392
- yield if block_given?
393
- }
394
- end
395
- EOF
396
- }
197
+ def find_nearest *raw_args
198
+ tags, tag, syms = args_to_traversal_args(*raw_args)
199
+ return tag if tag?(tag, syms.first)
200
+ find_ancestor *raw_args
201
+ end
397
202
 
398
- Methods[:elements].each { |name|
399
- eval <<-EOF, nil, __FILE__, __LINE__ + 1
400
- def #{name} *args
401
- if block_given?
402
- tag(:#{name}, *args) { yield }
403
- else
404
- tag(:#{name}, *args)
405
- end
406
- end
407
- EOF
408
- }
203
+ def find_ancestor *raw_args
204
+ tags, tag, syms = args_to_traversal_args(*raw_args)
205
+ name = syms.first
409
206
 
410
- # =================================================================
411
- # Future features...
412
- # =================================================================
207
+ return nil unless tag
208
+ ancestor = tag[:parent]
209
+ while ancestor && !tag?(ancestor, name)
210
+ ancestor = ancestor[:parent]
211
+ end
413
212
 
414
- def section
415
- fail
213
+ return ancestor if tag?(ancestor, name)
416
214
  end
417
215
 
418
- def on_top_of
419
- fail
420
- end
421
-
422
- def in_middle_of
423
- fail
216
+ def go_up_to_if_exists name
217
+ target = find_ancestor name
218
+ (@tag = target) if target
219
+ self
424
220
  end
425
221
 
426
- def at_bottom_of
427
- fail
222
+ def go_up_to name
223
+ go_up_to_if_exists name
224
+ fail "No parent found: #{name.inspect}" unless tag?(name)
225
+ self
428
226
  end
429
227
 
228
+ def stay_or_go_up_to_if_exists name
229
+ return self if tag?(name)
230
+ target = find_ancestor(name)
231
+ (@tag = target) if target
430
232
 
431
- # =================================================================
432
- # Miscellaneaous Helpers
433
- # =================================================================
233
+ self
234
+ end
434
235
 
435
- def is_doc?
436
- @is_doc || !@style[:css].empty? || !@js.empty?
236
+ def stay_or_go_up_to name
237
+ stay_or_go_up_to_if_exists name
238
+ fail "No parent found: #{name.inspect}" unless tag?(name)
239
+ self
437
240
  end
438
241
 
439
- def first_class
440
- tag![:attrs][:class].first
242
+ def go_up
243
+ @tag = @tag[:parent]
244
+ self
441
245
  end
442
246
 
443
- def html_element? e
444
- e.is_a?(Hash) && e[:type] == :html
247
+ def first_class
248
+ tag[:class] && tag[:class].first
445
249
  end
446
250
 
447
251
  def dom_id?
448
- tag![:attrs][:id]
252
+ tag && !!tag[:id]
449
253
  end
450
254
 
451
255
  #
452
- # Examples
453
- # dom_id -> the current dom id of the current element
454
- # dom_id :default -> if no dom it, set/get default of current element
455
- # dom_id {:element:} -> dom id of element: {:type=>:html, :tag=>...}
256
+ # Ex:
456
257
  #
457
- def dom_id *args
458
-
459
- use_default = false
460
-
258
+ # parent?
259
+ # parent? :body
260
+ # parent? tag, :div
261
+ #
262
+ def parent? *args
461
263
  case
462
- when args.empty?
463
- e = tag!
464
- # do nothing else
264
+ when args.length == 0
265
+ tag = @tag[:parent]
266
+ name = nil
465
267
 
466
- when args.size == 1 && args.first == :default
467
- e = tag!
468
- use_default = true
268
+ when args.length == 1 && args.first.is_a?(::Symbol)
269
+ tag = @tag
270
+ name = args.first
469
271
 
470
- when args.size == 1 && args.first.is_a?(::Hash) && args.first[:type]==:html
471
- e = args.first
272
+ when args.length == 2
273
+ tag = args.first
274
+ name = args.last
472
275
 
473
276
  else
474
- fail "Unknown args: #{args.inspect}"
475
- end
476
-
477
- id = e[:attrs][:id]
478
- return id if id
479
- return nil unless use_default
480
-
481
- e[:default_id] ||= begin
482
- key = e[:tag]
483
- @default_ids[key] ||= -1
484
- @default_ids[key] += 1
485
- end
486
- end # === def dom_id
277
+ fail ::ArgumentError, "Unknown args: #{args.inspect}"
278
+ end # === case
487
279
 
488
- #
489
- # Examples
490
- # selector_id -> a series of ids and tags to be used as a JS selector
491
- # Example:
492
- # #id tag tag
493
- # tag tag
494
- #
495
- #
496
- def selector_id
497
- i = tag![:tag_index]
498
- id_given = false
499
- classes = []
500
-
501
- while !id_given && i && i > -1
502
- e = @tag_arr[i]
503
- id = dom_id e
504
- (id_given = true) if id
505
-
506
- if e[:tag] == :body && !classes.empty?
507
- # do nothing because
508
- # we do not want 'body tag.class tag.class'
509
- else
510
- case
511
- when id
512
- classes << "##{id}"
513
- else
514
- classes << e[:tag]
515
- end # === case
516
- end # === if
517
-
518
- i = e[:parent_index]
519
- end
520
-
521
- return 'body' if classes.empty?
522
- classes.join SPACE
523
- end
280
+ p = parent(tag)
281
+ return true if p && !name
282
+ return true if p && p[:tag_name] == name
283
+ return true if !p && name == :body
284
+ false
285
+ end # === def parent?
524
286
 
525
287
  #
526
- # Examples
527
- # css_id -> current css id of element.
528
- # It uses the first class, if any, found.
529
- # #id.class -> if #id and first class found.
530
- # #id -> if class is missing and id given.
531
- # #id tag.class -> if class given and ancestor has id.
532
- # #id tag tag -> if no class given and ancestor has id.
533
- # tag tag tag -> if no ancestor has class.
534
- #
535
- # css_id :my_class -> same as 'css_id()' except
536
- # 'my_class' overrides :class attribute of current
537
- # element.
288
+ # Ex:
538
289
  #
290
+ # parent
291
+ # parent tag
539
292
  #
540
- def css_id *args
541
-
542
- str_class = nil
293
+ def parent *args
294
+ case
295
+ when args.length == 0
296
+ tag = @tag
543
297
 
544
- case args.size
545
- when 0
546
- fail "Not in a tag." unless tag!
547
- str_class = @css_id_override
548
- when 1
549
- str_class = args.first
298
+ when args.length == 1 && args.first.is_a?(::Hash)
299
+ tag = args.first
550
300
  else
551
- fail "Unknown args: #{args.inspect}"
552
- end
553
-
554
- i = tag![:tag_index]
555
- id_given = false
556
- classes = []
557
-
558
- while !id_given && i && i > -1
559
- e = @tag_arr[i]
560
- id = dom_id e
561
- first_class = e[:attrs][:class].first
562
-
563
- if id
564
- id_given = true
565
- if str_class
566
- classes.unshift(
567
- str_class.is_a?(::Symbol) ?
568
- "##{id}.#{str_class}" :
569
- "##{id}#{str_class}"
570
- )
571
- else
572
- classes.unshift "##{id}"
573
- end
301
+ fail ::ArgumentError, "Unknown args: #{args.inspect}"
302
+ end # === case
574
303
 
575
- else # no id given
576
- if str_class
577
- classes.unshift(
578
- str_class.is_a?(::Symbol) ?
579
- "#{e[:tag]}.#{str_class}" :
580
- "#{e[:tag]}#{str_class}"
581
- )
582
- elsif first_class
583
- classes.unshift "#{e[:tag]}.#{first_class}"
584
- else
585
- if e[:tag] != :body || (classes.empty?)
586
- classes.unshift "#{e[:tag]}"
587
- end
588
- end # if first_class
589
-
590
- end # if id
591
-
592
- i = e[:parent_index]
593
- break if i == @body[:tag_index] && !classes.empty?
594
- end
595
-
596
- classes.join SPACE
304
+ tag && tag[:parent]
597
305
  end
598
306
 
599
- # =================================================================
600
- # Parent-related methods
601
- # =================================================================
307
+ def in_tag t
308
+ if t.is_a?(::Symbol)
309
+ in_tag(detect(t)) {
310
+ yield
311
+ }
312
+ return self
602
313
 
603
- def css_parent?
604
- !@css_arr.empty?
605
- end
314
+ else # =============================
315
+ orig = @tag
316
+ @tag = t
317
+ yield
318
+ @tag = orig
319
+ nil
606
320
 
607
- def parents
608
- fail "not done"
321
+ end # === if t == :head
609
322
  end
610
323
 
611
- def parent? *args
612
- return(tag! && !tag![:parent_index].nil?) if args.empty?
613
- fail("Unknown args: #{args.first}") if args.size > 1
614
- return false unless parent
324
+ def create name, opts = nil
325
+ if @tag && @tag[:tag_name] == :_
615
326
 
616
- sym_tag = args.first
327
+ # Ex:
328
+ # _.id(:the_body).^(:loading)
329
+ # div {
330
+ # }
331
+ go_up if !@tag[:closed]
617
332
 
618
- case sym_tag
619
- when :html, :css, :script
620
- parent[:type] == sym_tag
621
- else
622
- parent[:tag] == sym_tag
333
+ # Ex:
334
+ # _.^(:happy) {
335
+ # a { }
336
+ # }
337
+ fail "New tags not allowed here." if (@tag && @tag[:closed]) && !ancestor?(:group)
623
338
  end
624
- end
625
-
626
- def parent
627
- fail "Not in a tag." unless tag!
628
- fail "No parent: #{tag![:tag].inspect}, #{tag![:tag_index]}" if !tag![:parent_index]
629
- @tag_arr[tag![:parent_index]]
630
- end
631
-
632
- # =================================================================
633
- # Tag (aka element)-related methods
634
- # =================================================================
635
-
636
- def tag!
637
- return nil unless @current_tag_index.is_a?(::Numeric)
638
- @tag_arr[@current_tag_index]
639
- end
640
-
641
- def tag? sym_tag
642
- tag![:tag] == sym_tag
643
- end
644
-
645
- def tag sym_name
646
- e = {
647
- :type => :html,
648
- :tag => sym_name,
649
- :attrs => {:class=>[]},
650
- :text => nil,
651
- :childs => [],
652
- :parent_index => @current_tag_index,
653
- :is_closed => false,
654
- :tag_index => @tag_arr.size,
655
- :render? => @render_it
656
- }
657
-
658
- @tag_arr << e
659
- @current_tag_index = e[:tag_index]
660
339
 
661
- if parent?
662
- parent[:childs] << e[:tag_index]
663
- else
664
- if !([:head, :body].include? e[:tag])
665
- fail "No parent found for: #{sym_name.inspect}"
340
+ # === If:
341
+ # we are creating an HTML element
342
+ # within a group, then we either start
343
+ # a new group or stay here.
344
+ if name != :group && tag_or_ancestor?(:groups)
345
+ if tag?(:groups)
346
+ create :group
347
+ else
348
+ stay_or_go_up_to_if_exists(:group)
666
349
  end
667
350
  end
668
351
 
669
- if block_given?
670
- close_tag { yield }
671
- else
672
- self
352
+ old = @tag
353
+ new = {:tag_name=>name}
354
+
355
+ # === Add to parent's children array:
356
+ if old
357
+ old[:children] ||= []
358
+ old[:children] << new
359
+ new[:parent] = old
360
+ else # === Example: :head, :body, etc.
361
+ @tags << new
673
362
  end
674
- end
675
363
 
676
- def in_tag t
677
- orig = @current_tag_index
678
- @current_tag_index = t[:tag_index]
679
- yield
680
- @current_tag_index = orig
681
- nil
682
- end
364
+ @tag = new
683
365
 
684
- public def /
685
- fail "No block allowed here: :/" if block_given?
686
- close_tag
687
- end
688
-
689
- def close_tag
690
- orig_tag = tag!
691
- is_script = tag?(:script)
366
+ @tag.merge!(opts) if opts
692
367
 
693
368
  if block_given?
694
-
695
- results = yield
696
-
697
- results = nil if is_script
698
-
699
- # The :yield may have left some opened tags, :input, :br/
700
- # So we make sure we are in the original tag/element
701
- # when we want to make some final changes.
702
- in_tag(orig_tag) {
703
- if tag?(:form)
704
- input(:hidden, :auth_token, :auth_token.to_mustache(:html))
705
- end
706
-
707
- if (results.is_a?(::Hash) && results[:type] && !results[:tag] && results[:type] != :string)
708
- fail Invalid_Type, results[:type].inspect
709
- end
710
-
711
- if (results.is_a?(::Hash) && results[:type] == :string) || results.is_a?(::String) || results.is_a?(::Symbol)
712
- tag![:text] = results
713
- end
714
- }
369
+ close { yield }
715
370
  end
716
371
 
717
- orig_tag[:is_closed] = true
718
- @current_tag_index = orig_tag[:parent_index]
719
-
720
- nil
721
- end
722
-
723
- # =================================================================
724
- # CSS-related methods
725
- # =================================================================
726
-
727
- def style
728
- orig = @render_it
729
- @render_it = false
730
- results = yield
731
- @render_it = orig
732
- results
733
- end
734
-
735
- def css_property name, val = nil
736
- prop = {:name=>name, :value=>val, :parent=>parent? ? parent : nil}
737
-
738
- id = css_id
739
- @style[:css][id] ||= {}
740
-
741
- @css_arr << prop
742
- @style[:css][id][@css_arr.map { |c| c[:name] }.join('_').to_sym] = val
743
- yield if block_given?
744
- @css_arr.pop
745
- end
746
-
747
- Methods[:css][:pseudo].each { |name|
748
- eval <<-EOF, nil, __FILE__, __LINE__ + 1
749
- def _#{name}
750
- orig = @css_id_override
751
- @css_id_override = ':#{name}'.freeze
752
- result = yield
753
- @css_id_override = orig
754
- result
755
- end
756
- EOF
757
- }
758
-
759
- # =================================================================
760
-
761
- def page_title
762
- @is_doc = true
763
- in_tag(@head) {
764
- tag(:title) { @page_title = yield }
765
- }
766
372
  self
767
- end
373
+ end # === def create
768
374
 
769
- def meta *args
375
+ def text str
770
376
  fail "No block allowed." if block_given?
771
- fail "Not allowed here." unless tag?(:body)
772
- c = nil
773
- in_tag(@tag) { c = tag(:meta, *args) }
774
- c
377
+ create(:text, :value=>str, :closed=>true)
378
+ go_up
775
379
  end
776
380
 
777
- def to_clean_text type, vals, tag = nil
778
- case
779
-
780
- when type == :javascript && vals.is_a?(::Array)
781
- clean_vals = vals.map { |raw_x|
782
- x = case raw_x
783
- when ::Symbol, ::String
784
- Sanitize.html(raw_x.to_s)
785
- when ::Array
786
- to_clean_text :javascript, raw_x
787
- when ::Numeric
788
- x
789
- else
790
- fail "Unknown type for json: #{raw_x.inspect}"
791
- end
792
- }
793
-
794
- when type == :to_json && vals.is_a?(::Array)
795
- ::Escape_Escape_Escape.json_encode(to_clean_text(:javascript, vals))
796
-
797
- when type == :style_classes && vals.is_a?(::Hash)
798
- h = vals
799
- h.map { |raw_k,styles|
800
- k = raw_k.to_s
801
-
802
- <<-EOF
803
- #{Sanitize.css_selector k} {
804
- #{to_clean_text :styles, styles}
805
- }
806
- EOF
807
- }.join.strip
808
-
809
- when type == :styles && vals.is_a?(::Hash)
810
- h = vals
811
- h.map { |k,raw_v|
812
- name = begin
813
- clean_k = ::WWW_App::Sanitize.css_attr(k.to_s.gsub('_','-'))
814
- fail("Invalid name for css property name: #{k.inspect}") if !clean_k || clean_k.empty?
815
- clean_k
816
- end
817
-
818
- raw_v = raw_v.to_s
819
-
820
- v = case
821
-
822
- when name[IMAGE_AT_END]
823
- case raw_v
824
- when 'inherit', 'none'
825
- raw_v
826
- else
827
- "url(#{Sanitize.href(raw_v)})"
828
- end
829
-
830
- when Methods[:css][:properties].include?(k)
831
- Sanitize.css_value raw_v
832
-
833
- else
834
- fail "Invalid css attr: #{name.inspect}"
835
-
836
- end # === case
837
-
838
- %^#{name}: #{v};^
839
- }.join("\n").strip
840
-
841
- when type == :attrs && vals.is_a?(::Hash)
842
- h = vals[:attrs]
843
- tag = vals
844
- final = h.map { |k,raw_v|
845
-
846
- fail "Unknown attr: #{k.inspect}" if !ALLOWED_ATTRS.include?(k)
847
-
848
- next if raw_v.is_a?(::Array) && raw_v.empty?
849
-
850
- v = raw_v
851
-
852
- attr_name = k.to_s.gsub(::WWW_App::INVALID_ATTR_CHARS, '_')
853
- fail("Invalid name for html attr: #{k.inspect}") if !attr_name || attr_name.empty?
854
-
855
- attr_val = case
856
- when k == :href && tag[:tag] == :a
857
- Sanitize.mustache :href, v
858
-
859
- when k == :action || k == :src || k == :href
860
- Sanitize.relative_href(v)
861
-
862
- when k == :class
863
- v.map { |n|
864
- Sanitize.css_class_name(n)
865
- }.join SPACE
866
-
867
- when k == :id
868
- Sanitize.html_id v.to_s
869
-
870
- when ALLOWED_ATTRS[k]
871
- Sanitize.html(v)
872
-
873
- else
874
- fail "Invalid attr: #{k.inspect}"
875
-
876
- end # === case
877
-
878
- %*#{attr_name}="#{attr_val}"*
879
-
880
- }.compact.join SPACE
881
-
882
- final.empty? ?
883
- '' :
884
- (" " << final)
885
-
886
- when type == :html && vals.is_a?(::Array)
887
- a = vals
888
- a.map { |tag_index|
889
- to_clean_text(:html, @tag_arr[tag_index])
890
- }.join NEW_LINE
891
-
892
- when type == :html && vals.is_a?(::Hash)
893
-
894
- h = vals
895
-
896
- fail("Unknown type: #{h.inspect}") if h[:type] != :html
897
-
898
- if h[:tag] == :style
899
- return <<-EOF
900
- <style type="text/css">
901
- #{to_clean_text :style_classes, h[:css]}
902
- </style>
903
- EOF
904
- end
905
-
906
- if h[:tag] == :script && h[:content] && !h[:content].empty?
907
- return <<-EOF
908
- <script type="text/css">
909
- WWW_App.compile(
910
- #{to_clean_text :to_json, h[:content]}
911
- );
912
- </script>
913
- EOF
914
- end
915
-
916
- html = h[:childs].map { |tag_index|
917
- to_clean_text(:html, @tag_arr[tag_index])
918
- }.join(NEW_LINE).strip
919
-
920
- return unless h[:render?]
921
-
922
- if html.empty? && h[:text]
923
- html = if h[:text].is_a?(::Symbol)
924
- h[:text].to_mustache(:html)
925
- else
926
- if h[:text].is_a?(::Hash)
927
- if h[:text][:escape] == false
928
- h[:text][:value]
929
- else
930
- Sanitize.html(h[:text][:value].strip)
931
- end
932
- else
933
- Sanitize.html(h[:text].strip)
934
- end
935
- end
936
- end # === if html.empty?
937
-
938
- (html = nil) if html.empty?
939
-
940
- case
941
- when h[:tag] == :render_if
942
- key = h[:attrs][:key]
943
- open = "{{# coll.#{key} }}"
944
- close = "{{/ coll.#{key} }}"
945
-
946
- when h[:tag] == :render_unless
947
- key = h[:attrs][:key]
948
- open = "{{^ coll.#{key} }}"
949
- close = "{{/ coll.#{key} }}"
950
-
951
- when Methods[:elements].include?(h[:tag])
952
- open = "<#{h[:tag]}#{to_clean_text(:attrs, h)}"
953
- if NO_END_TAGS.include?(h[:tag])
954
- open += ' />'
955
- close = nil
956
- else
957
- open += '>'
958
- close = "</#{h[:tag]}>"
959
- end
960
-
961
- else
962
- fail "Unknown html tag: #{h[:tag].inspect}"
963
-
964
- end # === case h[:tag]
965
-
966
- if h[:tag]
967
- [open, html, close].compact.join
968
- else
969
- html
970
- end
971
-
972
- else
973
- fail "Unknown vals: #{type.inspect}, #{vals.inspect}"
974
-
975
- end # case
976
- end # def to_clean_text
977
-
978
- def in_html?
979
- @state.last == :html
381
+ def raw_text str
382
+ fail "No block allowed." if block_given?
383
+ create(:text, :skip_escape=>true, :value=>str, :closed=>true)
384
+ go_up
980
385
  end
981
386
 
982
- def creating_html?
983
- @state.last == :create_html
984
- end
387
+ def close
985
388
 
986
- def js *args
987
- fail("No js event defined.") if @js.empty?
988
- if args.empty?
989
- @js.last
990
- else
991
- @js.last.concat args
992
- end
993
- end
389
+ group = find_nearest(:group)
994
390
 
995
- def on name, &blok
996
- fail "Block required." unless blok
391
+ if group
392
+ stay_or_go_up_to :group
393
+ final_parent = parent
997
394
 
998
- @js << 'create_event'
999
- @js << [selector_id, name]
395
+ # We set :groups=>true because
396
+ # we want tags created in the block to create their own
397
+ # group as a child element.
398
+ @tag[:groups] = true
1000
399
 
1001
- orig = @css_id_override
1002
- @css_id_override = name
1003
- results = yield
1004
- @css_id_override = orig
1005
-
1006
- if @js.last.size == 2
1007
- @js.pop
1008
- @js.pop
400
+ @tag[:closed] = true
401
+ yield
402
+ @tag = final_parent
403
+ return self
1009
404
  end
1010
405
 
1011
- results
1012
- end
406
+ @tag[:closed] = true
1013
407
 
1014
- def to_mustache
408
+ final_parent = parent
1015
409
 
1016
- return @compiled if @compiled
410
+ orig = @tag
1017
411
 
1018
- final = if is_doc?
1019
- # Remember: to use !BODY first, because
1020
- # :head content might include a '!HEAD'
1021
- # value.
1022
- (page_title { 'Unknown Page Title' }) unless @page_title
1023
-
1024
- Document_Template.
1025
- sub('!BODY', to_clean_text(:html, @body)).
1026
- sub('!HEAD', to_clean_text(:html, @head[:childs]))
1027
- else
1028
- to_clean_text(:html, @body[:childs])
1029
- end
412
+ if block_given?
413
+ results = yield
1030
414
 
1031
- utf_8 = Sanitize.clean_utf8(final)
415
+ # The :yield may have left some opened tags, :input, :br/
416
+ # So we make sure we are in the original tag/element
417
+ # when we want to make some final changes.
418
+ in_tag(orig) {
419
+ case
420
+ when tag?(:form)
421
+ input(:hidden, :auth_token, :auth_token.to_mustache(:html))
1032
422
 
1033
- @compiled = utf_8
1034
- end # === def to_mustache
423
+ when results.is_a?(::Symbol) && ancestor?(:script)
424
+ create :text, :skip_escape=>true, :value => results.to_mustache(:mustache, :html)
1035
425
 
426
+ when results.is_a?(::Symbol)
427
+ create :text, :skip_escape=>true, :value => results.to_mustache(:html)
1036
428
 
1037
- def input *args
1038
- case
1039
- when args.size === 3
1040
- tag(:input).type(args[0]).name(args[1]).value(args[2])
1041
- else
1042
- super
429
+ when results.is_a?(::String)
430
+ text results
431
+ end
432
+ }
1043
433
  end
1044
- end
1045
434
 
1046
- def add_class name
1047
- js("add_class", [name])
1048
- end
1049
-
1050
- class Sanitize
435
+ @tag = final_parent
1051
436
 
1052
- MUSTACHE_Regex = /\A\{\{\{? [a-z0-9\_\.]+ \}\}\}?\z/i
437
+ self
438
+ end # === close
1053
439
 
1054
- class << self
440
+ public # =======================================================
1055
441
 
1056
- def mustache *args
1057
- meth, val = args
1058
- if val.is_a?(Symbol)
1059
- m = "{{{ #{meth}.#{val} }}}"
1060
- fail "Unknown chars: #{args.inspect}" unless m[MUSTACHE_Regex]
1061
- else
1062
- m = ::Escape_Escape_Escape.send(meth, val)
1063
- end
1064
- m
1065
- end
442
+ #
443
+ # Ex:
444
+ # div.id(:main) {
445
+ # style {
446
+ # div.__._ { .. }
447
+ # }
448
+ # }
449
+ #
450
+ # div {
451
+ # _.^(:sad) {
452
+ # color '#000'
453
+ # }
454
+ # }
455
+ def _
456
+ create :_
457
+ if block_given?
458
+ return(close { yield })
459
+ end
1066
460
 
1067
- def method_missing name, *args
1068
- if args.last.is_a?(::Symbol)
1069
- args.push(args.pop.to_s)
1070
- end
1071
- ::Escape_Escape_Escape.send(name, *args)
1072
- end
461
+ self
462
+ end # === def _
1073
463
 
1074
- end # === class << self
1075
- end # === class Sanitize
1076
464
 
1077
465
  end # === class WWW_App ==========================================
1078
466
 
1079
- __END__
1080
- <!DOCTYPE html>
1081
- <html lang="en">
1082
- <head>
1083
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
1084
- !HEAD
1085
- </head>
1086
- !BODY
1087
- </html>