www_app 1.3.0 → 2.0.0

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