html-native 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2ec4658fd7a7e2a040535c411ca661cc80291d705b59099c93b6eecd184fb35e
4
+ data.tar.gz: e96476a674a70ad4e28ba9ab223057bbc3bbdcab00ae91d1ff39c1788e383131
5
+ SHA512:
6
+ metadata.gz: 85deca5f040b4268a9b3cf533483f2fbe91de270e5b6dbaf35817cea6363e49e008863481d24eea85c9c18123715caf21cfbe08f2f2c872cd844866ab95c956f
7
+ data.tar.gz: 7eca06fcf63a3159af0e2de5c4502ad5d969707de4c3f0221df07ca73b9d0b49e224edae049d8c6a5f59c1f390664c56221c9b74bc499ee4e213817c100ac6e6
@@ -0,0 +1,60 @@
1
+ require "html-native/constants"
2
+ require "html-native/builder"
3
+
4
+ module HTMLComponent
5
+
6
+ # Excluded currently because it makes checking in `Builder` ugly
7
+ # Makes `include` and `extend` work exactly the same.
8
+ # It's a dirty hack based on laziness, and strict use of `extend` is preferred.
9
+ # def self.included(base)
10
+ # base.extend(self)
11
+ # end
12
+
13
+ # Generates generation methods for each HTML5-valid tag. These methods have the
14
+ # name of the tag. Note that this interferes with the builtin `p` method.
15
+ TAG_LIST.each do |tag|
16
+ HTMLComponent.define_method(tag) do |attrs = {}, &block|
17
+ attrs ||= {}
18
+ if block
19
+ body = block.call
20
+ Builder.new("<#{tag}#{attributes_list(tag, attrs)}>") + body + "</#{tag}>"
21
+ else
22
+ Builder.new("<#{tag}#{attributes_list(tag, attrs)}/>")
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.singleton(&block)
28
+ Module.new do
29
+ extend HTMLComponent
30
+ define_singleton_method :render, &block
31
+ end
32
+ end
33
+
34
+ # Checks if the attribute is valid for a given tag.
35
+ #
36
+ # For example, `class` and `hidden` are valid for everything, but `autoplay`
37
+ # is valid for only `video` and `audio` tags, and invalid for all other tags.
38
+ def valid_attribute?(tag, attribute)
39
+ if LIMITED_ATTRIBUTES.key?(attribute.to_sym)
40
+ return LIMITED_ATTRIBUTES[attribute.to_sym].include?(tag.to_sym)
41
+ end
42
+ return !FORBIDDEN_ATTRIBUTES.include?(attribute)
43
+ end
44
+
45
+ private
46
+
47
+ # Given a tag and a set of attributes as a hash, format the attributes to
48
+ # HTML-valid form. If an attribute doesn't have a value or the value is
49
+ # empty, it's treated as a boolean attribute and formatted as such.
50
+ def attributes_list(tag, attrs)
51
+ formatted = attrs.filter{|opt, value| valid_attribute?(tag, opt)}.map do |k,v|
52
+ if v&.to_s.empty?
53
+ k.to_s
54
+ else
55
+ "#{k}=\"#{v}\"" # render this appropriately for numeric fields (might already)
56
+ end
57
+ end.join(" ")
58
+ formatted.empty? ? "" : " " + formatted
59
+ end
60
+ end
@@ -0,0 +1,27 @@
1
+ require "html-native"
2
+ module HTMLComponent
3
+ class Builder
4
+ def initialize(strings = [])
5
+ if strings.kind_of? String
6
+ @strings = [strings]
7
+ else
8
+ @strings = strings.dup
9
+ end
10
+ end
11
+
12
+ def +(string)
13
+ if string.kind_of? Builder
14
+ @strings << string
15
+ elsif string.kind_of? HTMLComponent
16
+ @strings << string.render
17
+ else
18
+ @strings << string.to_s
19
+ end
20
+ self
21
+ end
22
+
23
+ def to_s
24
+ @strings.join
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,197 @@
1
+ require "html-native"
2
+ require "html-native/builder"
3
+
4
+ module Enumerable
5
+ def component_map
6
+ if block_given?
7
+ result = HTMLComponent::Builder.new
8
+ each do |e|
9
+ result += yield(e)
10
+ end
11
+ result
12
+ else
13
+ to_enum(:component_map)
14
+ end
15
+ end
16
+
17
+ def to_ol(attributes: {})
18
+ OrderedListComponent.new(self, attributes: attributes)
19
+ end
20
+
21
+ def to_ul(attributes: {})
22
+ UnorderedListComponent.new(self, attributes: attributes)
23
+ end
24
+ end
25
+
26
+ class OrderedListComponent
27
+ include HTMLComponent
28
+
29
+ def initialize(data, attributes: {})
30
+ @list_data = data
31
+ @list_attributes = attributes[:list] || {}
32
+ @item_attributes = attributes[:item] || {}
33
+ end
34
+
35
+ def render(&block)
36
+ ol(@list_attributes) do
37
+ @list_data.component_map do |l|
38
+ li(@item_attributes) do
39
+ block_given? ? yield(l) : l.to_s
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ class UnorderedListComponent
47
+ include HTMLComponent
48
+
49
+ def initialize(data, attributes: {})
50
+ @list_data = data
51
+ @list_attributes = attributes[:list] || {}
52
+ @item_attributes = attributes[:item] || {}
53
+ end
54
+
55
+ def render(&block)
56
+ ul(@list_attributes) do
57
+ @list_data.component_map do |l|
58
+ li(@item_attributes) do
59
+ block_given? ? yield(l) : l.to_s
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ class ListComponent
67
+ def initialize(data, attributes: {}, ordered: false)
68
+ @list = ordered ? OrderedListComponent.new(data, attributes) :
69
+ UnorderedListComponent.new(data, attributes)
70
+ end
71
+
72
+ def render(&block)
73
+ @list.render(&block)
74
+ end
75
+ end
76
+
77
+ class TableRowComponent
78
+ include HTMLComponent
79
+
80
+ def initialize(data, attributes: {})
81
+ @data = data
82
+ @row_attributes = attributes[:row] || {}
83
+ @cell_attributes = attributes[:cell] || {}
84
+ end
85
+
86
+ def render(&block)
87
+ tr(@row_attributes) do
88
+ @data.component_map do |c|
89
+ td(@cell_attributes) {block_given? ? yield(c) : c}
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # This needs some reworking, since it's not intuitive
96
+ class TableComponent
97
+ include HTMLComponent
98
+
99
+ def initialize(header, rows, attributes: {})
100
+ @header = header
101
+ @rows = rows
102
+ @table_attributes = attributes[:table] || {}
103
+ @header_attributes = attributes[:header] || {}
104
+ @header_cell_attributes = attributes[:header_cell] || {}
105
+ @row_attributes = attributes[:row] || {}
106
+ @cell_attributes = attributes[:cell] || {}
107
+ end
108
+
109
+ # header options:
110
+ # array - use as header
111
+ # symbol - if :from_data, then use first row, if :none, set @header to nil
112
+ def self.from_array(data, attributes: {}, header: :none)
113
+ head = rows = nil
114
+ if header == :from_data
115
+ head = data[0]
116
+ rows = data[1..]
117
+ else
118
+ head = header.kind_of?(Array) ? header : nil
119
+ rows = data
120
+ end
121
+ new(head, rows, attributes: attributes)
122
+ end
123
+
124
+ def self.from_hash(data, attributes: {}, vertical: true)
125
+ if vertical
126
+ rowcount = data.values.map(&:length).max
127
+ rows = [] * rowcount
128
+ data.each do |k,col|
129
+ rowcount.times do |i|
130
+ rows[i] << (i < col.size ? col[i] : nil)
131
+ end
132
+ end
133
+ new(data.keys, rows, attributes: attributes)
134
+ else
135
+ rows = data.map do |k, v|
136
+ [k] + v
137
+ end
138
+ new(nil, rows, attributes: attributes)
139
+ end
140
+ end
141
+
142
+ def render(&block)
143
+ table(@table_attributes) do
144
+ if @header
145
+ tr(@row_attributes.merge(@header_attributes)) do
146
+ @header.component_map do |h|
147
+ th(@cell_attributes.merge(@header_cell_attributes)) {h}
148
+ end
149
+ end
150
+ else
151
+ Builder.new
152
+ end +
153
+ @rows.component_map do |row|
154
+ TableRowComponent.new(row, attributes: {row: @row_attributes, cell: @cell_attributes}).render(&block)
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ class DropdownComponent
161
+ include HTMLComponent
162
+
163
+ def initialize(choices, name, attributes: {})
164
+ @choices = choices
165
+ @name = name
166
+ @menu_attributes = attributes[:menu]
167
+ @item_attributes = attributes[:item]
168
+ end
169
+
170
+ def render(&block)
171
+ select(@menu_attributes.merge({name: @name, id: "#{@name}-dropdown"})) do
172
+ @choices.component_map do |c|
173
+ option(@item_attributes.merge({value: c})) {block_given? ? yield(c) : c}
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ class RadioGroupComponent
180
+ include HTMLComponent
181
+
182
+ def initialize(choices, name, attributes: {}, labelled: true)
183
+ @choices = choices
184
+ @name = name
185
+ @button_attributes = attributes[:button]
186
+ @label_attributes = attributes[:label]
187
+ @labelled = labelled
188
+ end
189
+
190
+ def render(&block)
191
+ @choices.component_map do |c|
192
+ id = "#{@name}-#{c}"
193
+ input({type: "radio", id: id, name: @name, value: c}) +
194
+ (@labelled ? (label({for: id}) {block_given? ? yield(c) : c}) : nil)
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,140 @@
1
+ module HTMLComponent
2
+ TAG_LIST = [
3
+ :html,
4
+ :base, :head, :link, :meta, :style, :title,
5
+ :body,
6
+ :address, :article, :aside, :footer, :h1, :h2, :h3, :h4, :h5, :h6, :header,
7
+ :hgroup, :main, :nav, :section,
8
+ :blockquote, :dd, :div, :dl, :dt, :figcaption, :figure, :hr, :li, :ol, :p, :pre, :ul,
9
+ :a, :abbr, :b, :bdi, :bdo, :br, :cite, :code, :data, :dfn, :em, :i, :kbd, :mark, :q,
10
+ :rb, :rp, :rt, :rtc, :ruby, :s, :samp, :small, :span, :strong, :sub, :sup, :time,
11
+ :u, :var, :wbr,
12
+ :area, :audio, :img, :map, :track, :video,
13
+ :embed, :iframe, :object, :param, :picture, :portal, :source,
14
+ :svg, :math,
15
+ :canvas, :noscript, :script,
16
+ :del, :ins,
17
+ :caption, :col, :colgroup, :table, :tbody, :td, :tfoot, :th, :thead, :tr,
18
+ :button, :datalist, :fieldset, :form, :input, :label, :legend, :meter, :optgroup,
19
+ :option, :output, :progress, :select, :textarea,
20
+ :details, :dialog, :menu, :summary,
21
+ :slot, :template
22
+ ] + (1..6).map{|i| :"h#{i}"}
23
+
24
+ def self.tags
25
+ TAG_LIST.dup
26
+ end
27
+
28
+ LIMITED_ATTRIBUTES = {
29
+ accept: [:form, :input],
30
+ "accept-charset": [:form],
31
+ action: [:form],
32
+ align: [:caption, :col, :colgroup, :hr, :iframe, :img, :table,
33
+ :tbody, :td, :tfoot, :th, :thead, :tr],
34
+ allow: [:iframe],
35
+ alt: [:area, :img, :input],
36
+ async: [:script],
37
+ autocomplete: [:form, :input, :select, :textarea],
38
+ autofocus: [:button, :input, :select, :textarea],
39
+ autoplay: [:audio, :video],
40
+ buffered: [:audio, :video],
41
+ capture: [:input],
42
+ charset: [:meta, :script],
43
+ checked: [:input],
44
+ cite: [:blockquote, :del, :ins, :q],
45
+ cols: [:textarea],
46
+ colspan: [:td, :th],
47
+ content: [:meta],
48
+ controls: [:audio, :video],
49
+ coords: [:area],
50
+ crossorigin: [:audio, :img, :link, :script, :video],
51
+ csp: [:iframe],
52
+ data: [:object],
53
+ datatime: [:del, :ins, :time],
54
+ decoding: [:img],
55
+ default: [:track],
56
+ defer: [:script],
57
+ dirname: [:input, :textarea],
58
+ disabled: [:button, :fieldset, :input, :optgroup,
59
+ :option, :select, :textarea],
60
+ download: [:a, :area],
61
+ enctype: [:form],
62
+ enterkeyhint: [:textarea],
63
+ "for": [:label, :output],
64
+ form: [:button, :fieldset, :input, :label, :meter, :object,
65
+ :output, :progress, :select, :textarea],
66
+ formaction: [:input, :button],
67
+ formentype: [:button, :input],
68
+ formmethod: [:button, :input],
69
+ formnovalidate: [:button, :input],
70
+ formtarget: [:button, :input],
71
+ headers: [:td, :th],
72
+ height: [:canvas, :embed, :iframe, :img, :input, :object, :video],
73
+ high: [:meter],
74
+ href: [:a, :area, :base, :link],
75
+ hreflang: [:a, :area, :link],
76
+ "http-equiv": [:meta],
77
+ importance: [:iframe, :img, :link, :script],
78
+ integrity: [:link, :script],
79
+ inputmode: [:textarea],
80
+ ismap: [:img],
81
+ kind: [:track],
82
+ label: [:optgroup, :option, :track],
83
+ language: [:script],
84
+ loading: [:img, :iframe],
85
+ list: [:input],
86
+ loop: [:audio, :video],
87
+ low: [:meter],
88
+ max: [:input, :meter, :progress],
89
+ maxlength: [:input, :textarea],
90
+ minlength: [:input, :textarea],
91
+ media: [:a, :area, :link, :source, :style],
92
+ method: [:form],
93
+ min: [:input, :select],
94
+ multiple: [:input, :select],
95
+ muted: [:audio, :video],
96
+ name: [:button, :form, :fieldset, :iframe, :input, :object,
97
+ :output, :select, :textarea, :map, :meta, :param],
98
+ novalidate: [:form],
99
+ open: [:details],
100
+ optimum: [:meter],
101
+ pattern: [:input],
102
+ ping: [:a, :area],
103
+ placeholder: [:input, :textarea],
104
+ poster: [:video],
105
+ preload: [:audio, :video],
106
+ readonly: [:input, :textarea],
107
+ referrerpolicy: [:a, :area, :iframe, :img, :link, :script],
108
+ rel: [:a, :area, :link],
109
+ required: [:input, :select, :textarea],
110
+ reversed: [:ol],
111
+ rows: [:textarea],
112
+ rowspan: [:td, :th],
113
+ sandbox: [:iframe],
114
+ scope: [:th],
115
+ scoped: [:style],
116
+ selected: [:option],
117
+ shape: [:a, :area],
118
+ size: [:input, :select],
119
+ sizes: [:link, :img, :source],
120
+ span: [:col, :colgroup],
121
+ src: [:audio, :embed, :iframe, :img, :input, :script, :source, :track, :video],
122
+ srcdoc: [:iframe],
123
+ srclang: [:track],
124
+ srcset: [:img, :source],
125
+ start: [:ol],
126
+ step: [:input],
127
+ summary: [:table],
128
+ target: [:a, :area, :base, :form],
129
+ type: [:button, :input, :embed, :object, :script, :source, :style, :menu],
130
+ usemap: [:img, :input, :object],
131
+ value: [:button, :data, :input, :li, :meter, :option, :progress, :param],
132
+ width: [:canvas, :embed, :iframe, :img, :input, :object, :video],
133
+ wrap: [:textarea]
134
+ }
135
+
136
+ # These attributes are deprecated or outright forbidden. However, some people
137
+ # might still try to use them. These attributes are expressly disallowed
138
+ # during generation, and won't be included, even if provided.
139
+ FORBIDDEN_ATTRIBUTES = [:background, :bgcolor, :border, :color, :manifest]
140
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: html-native
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kellen Watt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-01-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: An html generation DSL designed for fluid code creation.
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/html-native.rb
20
+ - lib/html-native/builder.rb
21
+ - lib/html-native/collections.rb
22
+ - lib/html-native/constants.rb
23
+ homepage: https://github.com/KellenWatt/html-native
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubygems_version: 3.1.4
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: Ruby-native html generation
46
+ test_files: []