paggio 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
+ SHA1:
3
+ metadata.gz: a5cd310cbe256ffe70545ac23588ee479889fe91
4
+ data.tar.gz: 188cb2e8cc974b8f3742cf0c48b4f91f207e5009
5
+ SHA512:
6
+ metadata.gz: ac2680077e63109eef45e47dde0c8b0ca8c15c16328e3c66a064868e2e8af45f72a5b52b4bc4c6eb5475a00f1d736ed51b7fd5387b5ea5f796664100def2c24f
7
+ data.tar.gz: 569b5c556e145aa41323f09fe470dcd5a6a4daa67c8b30e1d38c9bc7f582ed506b84ac1fb5926033eb6815ee95b09083737572b726bc136a74a856b3645eaec6
@@ -0,0 +1,127 @@
1
+ paggio
2
+ ======
3
+ A Ruby DSL to generate HTML and CSS.
4
+
5
+ HTML
6
+ ----
7
+ Inspired by markaby with some specialized additions for certain kinds of
8
+ elements.
9
+
10
+ ```ruby
11
+ # html! avoids outputting the doctype and <html> tags
12
+ html! do
13
+ div.content! do
14
+ p <<-EOS
15
+ I like trains
16
+ EOS
17
+ end
18
+ end
19
+ ```
20
+
21
+ This will output:
22
+
23
+ ```html
24
+ <div id="content">
25
+ I like trains.
26
+ </div>
27
+ ```
28
+
29
+ CSS
30
+ ---
31
+ Inspired by SCSS with some nice CSS unit handling monkey-patching.
32
+
33
+ ```ruby
34
+ css do
35
+ rule '.content' do
36
+ background :black
37
+ color :white
38
+
39
+ rule '&:hover' do
40
+ background :white
41
+ color :black
42
+ end
43
+
44
+ rule '.stuff' do
45
+ font size: 50.px
46
+ end
47
+ end
48
+ end
49
+ ```
50
+
51
+ This will output:
52
+ ```css
53
+ .content {
54
+ background: black;
55
+ color: white;
56
+ }
57
+
58
+ .content .stuff {
59
+ font-size: 50px;
60
+ }
61
+
62
+ .content:hover {
63
+ background: white;
64
+ color: black;
65
+ }
66
+ ```
67
+
68
+ With Sinatra
69
+ ------------
70
+ Because why not.
71
+
72
+ ```ruby
73
+ require 'sinatra'
74
+ require 'paggio'
75
+
76
+ get '/' do
77
+ Paggio.html do
78
+ head do
79
+ title "Yo, I'm on Sinatra"
80
+
81
+ style do
82
+ rule 'html', 'body' do
83
+ width 100.%
84
+ height 100.%
85
+
86
+ # reset some stuff
87
+ margin 0
88
+ padding 0
89
+ position :absolute
90
+ top 0
91
+
92
+ background :black
93
+ color :white
94
+ end
95
+
96
+ rule '#content' do
97
+ width 50.%
98
+ height 100.%
99
+
100
+ margin 0, :auto
101
+
102
+ border left: [3.px, :solid, :white],
103
+ right: [3.px, :solid, :white]
104
+
105
+ text align: :center
106
+ font size: 23.px
107
+
108
+ rule '& > div' do
109
+ padding 20.px
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ body do
116
+ div.content! do
117
+ div 'Hello world!'
118
+ end
119
+ end
120
+ end
121
+ end
122
+ ```
123
+
124
+ Why?
125
+ ----
126
+ Because HAML and SCSS are too mainstream. On a serious note, why have
127
+ templating systems when you can just write Ruby?
@@ -0,0 +1,29 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'stringio'
12
+
13
+ require 'paggio/css'
14
+ require 'paggio/html'
15
+ require 'paggio/format'
16
+
17
+ class Paggio
18
+ def self.css(*args, &block)
19
+ CSS.new(*args, &block).format.string
20
+ end
21
+
22
+ def self.html(*args, &block)
23
+ HTML.new(*args, &block).format.string
24
+ end
25
+
26
+ def self.html!(&block)
27
+ HTML.new(&block).root!.format.string
28
+ end
29
+ end
@@ -0,0 +1,78 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'paggio/css/unit'
12
+ require 'paggio/css/definition'
13
+
14
+ class Paggio
15
+
16
+ class CSS < BasicObject
17
+ Rule = ::Struct.new(:selector, :definition)
18
+
19
+ def self.selector(list)
20
+ result = ''
21
+
22
+ list.each {|part|
23
+ if part.start_with?('&')
24
+ result += part[1 .. -1]
25
+ else
26
+ result += " " + part
27
+ end
28
+ }
29
+
30
+ if result[0] == " "
31
+ result[1 .. -1]
32
+ else
33
+ result
34
+ end
35
+ end
36
+
37
+ attr_reader :rules
38
+
39
+ def initialize(&block)
40
+ ::Kernel.raise ::ArgumentError, 'no block given' unless block
41
+
42
+ @selector = []
43
+ @current = []
44
+ @rules = []
45
+
46
+ if block.arity == 0
47
+ instance_exec(&block)
48
+ else
49
+ block.call(self)
50
+ end
51
+ end
52
+
53
+ def rule(*names, &block)
54
+ return unless block
55
+
56
+ if names.any? { |n| n.include? ',' }
57
+ ::Kernel.raise ::ArgumentError, 'selectors cannot contain commas'
58
+ end
59
+
60
+ names.each {|name|
61
+ @selector << name
62
+ @current << Rule.new(CSS.selector(@selector), Definition.new)
63
+
64
+ block.call(self)
65
+
66
+ @selector.pop
67
+ @rules << @current.pop
68
+ }
69
+ end
70
+
71
+ # this is needed because the methods inside the rule blocks are actually
72
+ # called on the CSS object
73
+ def method_missing(name, *args, &block)
74
+ @current.last.definition.__send__(name, *args, &block)
75
+ end
76
+ end
77
+
78
+ end
@@ -0,0 +1,241 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ class Paggio; class CSS < BasicObject
12
+
13
+ class Definition < BasicObject
14
+ Style = ::Struct.new(:name, :value, :important?)
15
+
16
+ def initialize(&block)
17
+ @style = []
18
+
19
+ if block.arity == 0
20
+ instance_exec(&block)
21
+ else
22
+ block.call(self)
23
+ end if block
24
+ end
25
+
26
+ def empty?
27
+ @style.empty?
28
+ end
29
+
30
+ def each(&block)
31
+ return enum_for :each unless block
32
+
33
+ @style.each(&block)
34
+
35
+ self
36
+ end
37
+
38
+ def gradient(*args)
39
+ Gradient.new(*args)
40
+ end
41
+
42
+ def background(*args)
43
+ if Gradient === args.first
44
+ if args.length > 1
45
+ raise NotImplementedError, "multiple gradients not implemented yet"
46
+ end
47
+
48
+ args.first.each {|s|
49
+ style s.name || 'background-image', s.value
50
+ }
51
+ else
52
+ if ::Hash === args.first
53
+ args.first.each {|sub, value|
54
+ style "background-#{sub}", value
55
+ }
56
+ else
57
+ style :background, args
58
+ end
59
+ end
60
+ end
61
+
62
+ def border(*args)
63
+ if ::Hash === args.first
64
+ if args.length == 1
65
+ options = args.first
66
+ end
67
+
68
+ options.each {|name, value|
69
+ case name
70
+ when :radius
71
+ if ::Hash === value
72
+ value.each {|horizontal, value|
73
+ value.each {|vertical, value|
74
+ style "-moz-border-radius-#{horizontal}#{vertical}", value
75
+ style "-webkit-border-#{horizontal}-#{vertical}-radius", value
76
+ style "border-#{horizontal}-#{vertical}-radius", value
77
+ }
78
+ }
79
+ else
80
+ style '-moz-border-radius', value
81
+ style '-webkit-border-radius', value
82
+ style 'border-radius', value
83
+ end
84
+
85
+ when :color
86
+ if ::Hash === value
87
+ value.each {|name, value|
88
+ style "border-#{name}-color", value
89
+ }
90
+ else
91
+ style 'border-color', value
92
+ end
93
+
94
+ else
95
+ style "border-#{name}", value
96
+ end
97
+ }
98
+ else
99
+ style :border, args
100
+ end
101
+ end
102
+
103
+ def box(options)
104
+ if ::Hash === options
105
+ options.each {|name, value|
106
+ case name
107
+ when :shadow
108
+ if ::Array === value
109
+ value = value.join ', '
110
+ end
111
+
112
+ style '-moz-box-shadow', value
113
+ style '-webkit-box-shadow', value
114
+ style 'box-shadow', value
115
+
116
+ else
117
+ style "box-#{name}", value
118
+ end
119
+ }
120
+ else
121
+ style :box, options
122
+ end
123
+ end
124
+
125
+ def opacity(value)
126
+ style 'opacity', value
127
+ style '-moz-opacity', value
128
+
129
+ style '-ms-filter', %Q{"progid:DXImageTransform.Microsoft.Alpha(Opacity=#{(value * 100).to_i})"}
130
+ style 'filter', "alpha(opacity=#{(value * 100).to_i})"
131
+ end
132
+
133
+ def method_missing(name, *args, &block)
134
+ name = name.to_s
135
+ important = name.end_with? ?!
136
+ name = name[0 .. -2] if important
137
+
138
+ @important = true if important
139
+
140
+ if important && respond_to?(name)
141
+ __send__ name, *args, &block
142
+ @important = false
143
+
144
+ return
145
+ end
146
+
147
+ if args.length == 1
148
+ argument = args.first
149
+
150
+ if ::Hash === argument
151
+ argument.each {|sub, value|
152
+ style "#{name}-#{sub}", value
153
+ }
154
+ else
155
+ style name, argument
156
+ end
157
+ else
158
+ style name, args.join(' ')
159
+ end
160
+
161
+ @important = false
162
+
163
+ self
164
+ end
165
+
166
+ private
167
+ def style(name, value = nil, important = @important)
168
+ if ::Array === value
169
+ value = value.join ' '
170
+ end
171
+
172
+ if Style === name
173
+ @style << name
174
+ else
175
+ @style << Style.new(name, value, important)
176
+ end
177
+ end
178
+
179
+ class Gradient
180
+ # TODO: all of it, seriously
181
+ def initialize(*args)
182
+ options = ::Hash === args.last ? args.pop : {}
183
+
184
+ @to = options[:to]
185
+ @from = options[:from]
186
+
187
+ if @to && !@from
188
+ @from = other(@to)
189
+ elsif @from && !@to
190
+ @to = other(@from)
191
+ end
192
+
193
+ @start = args.shift
194
+ @end = args.shift
195
+ end
196
+
197
+ def each(&block)
198
+ block.call style("-moz-linear-gradient(#@to, #@start 0%, #@end 100%)")
199
+
200
+ if horizontal?
201
+ block.call style("-webkit-gradient(linear, #@from top, #@to top, color-stop(0%, #@start), color-stop(100%, #@end))")
202
+ else
203
+ block.call style("-webkit-gradient(linear, left #@from, left #@to, color-stop(0%, #@start), color-stop(100%, #@end))")
204
+ end
205
+
206
+ block.call style("-webkit-linear-gradient(#@to, #@start 0%, #@end 100%)")
207
+ block.call style("-o-linear-gradient(#@to, #@start 0%, #@end 100%)")
208
+ block.call style("-ms-linear-gradient(#@to, #@start 0%, #@end 100%)")
209
+ block.call style("linear-gradient(to #@to, #@start 0%, #@end 100%)")
210
+ end
211
+
212
+ def horizontal?
213
+ @to == :left || @to == :right
214
+ end
215
+
216
+ def vertical?
217
+ @to == :top || @to == :bottom
218
+ end
219
+
220
+ private
221
+ def other(side)
222
+ case side
223
+ when :left then :right
224
+ when :right then :left
225
+ when :top then :bottom
226
+ when :bottom then :top
227
+ end
228
+ end
229
+
230
+ # FIXME: use default args
231
+ def style(*args)
232
+ if args.length == 1
233
+ Style.new(nil, args.first)
234
+ else
235
+ Style.new(*args)
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ end; end
@@ -0,0 +1,200 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ class Paggio; class CSS < BasicObject
12
+
13
+ class Unit
14
+ COMPATIBLE = %i[in pt mm cm px pc]
15
+
16
+ attr_reader :type, :number
17
+
18
+ def initialize(number, type)
19
+ @number = number
20
+ @type = type
21
+ end
22
+
23
+ def coerce(other)
24
+ return self, other
25
+ end
26
+
27
+ def ==(other)
28
+ @number == convert(other, @type)
29
+ end
30
+
31
+ def ===(other)
32
+ @type == other.type && @number == other.number
33
+ end
34
+
35
+ alias eql? ==
36
+
37
+ def hash
38
+ [@number, @type].hash
39
+ end
40
+
41
+ %i[em ex ch rem vh vw vmin vmax px mm cm in pt pc].each {|name|
42
+ define_method name do
43
+ Unit.new(convert(self, name), name)
44
+ end
45
+ }
46
+
47
+ def +(other)
48
+ return Unit.new(@number + other, @type) unless Unit === other
49
+
50
+ if @type == other.type
51
+ Unit.new(@number + other.number, @type)
52
+ elsif compatible?(self) and compatible?(other)
53
+ Unit.new(@number + convert(other, @type), @type)
54
+ else
55
+ raise ArgumentError, "#{other.type} isn't compatible with #{@type}"
56
+ end
57
+ end
58
+
59
+ def -(other)
60
+ return Unit.new(@number - other, @type) unless Unit === other
61
+
62
+ if @type == other.type
63
+ Unit.new(@number - other.number, @type)
64
+ elsif compatible?(self) and compatible?(other)
65
+ Unit.new(@number - convert(other, @type), @type)
66
+ else
67
+ raise ArgumentError, "#{other.type} isn't compatible with #{@type}"
68
+ end
69
+ end
70
+
71
+ def *(other)
72
+ return Unit.new(@number * other, @type) unless Unit === other
73
+
74
+ if @type == other.type
75
+ Unit.new(@number * other.number, @type)
76
+ elsif compatible?(self) and compatible?(other)
77
+ Unit.new(@number * convert(other, @type), @type)
78
+ else
79
+ raise ArgumentError, "#{other.type} isn't compatible with #{@type}"
80
+ end
81
+ end
82
+
83
+ def /(other)
84
+ return Unit.new(@number / other, @type) unless Unit === other
85
+
86
+ if @type == other.type
87
+ Unit.new(@number / other.number, @type)
88
+ elsif compatible?(self) and compatible?(other)
89
+ Unit.new(@number / convert(other, @type), @type)
90
+ else
91
+ raise ArgumentError, "#{other.type} isn't compatible with #{@type}"
92
+ end
93
+ end
94
+
95
+ def -@
96
+ Unit.new(@number * -1, @type)
97
+ end
98
+
99
+ def +@
100
+ Unit.new(@number, @type)
101
+ end
102
+
103
+ def to_i
104
+ @number.to_i
105
+ end
106
+
107
+ def to_f
108
+ @number.to_f
109
+ end
110
+
111
+ def to_u
112
+ self
113
+ end
114
+
115
+ def to_s
116
+ "#{@number}#{@type}"
117
+ end
118
+
119
+ alias to_str to_s
120
+ alias inspect to_s
121
+
122
+ private
123
+ def compatible?(unit)
124
+ COMPATIBLE.include?(unit.type)
125
+ end
126
+
127
+ def convert(unit, type)
128
+ value = unit.number
129
+
130
+ return value if unit.type == type
131
+
132
+ px = case unit.type
133
+ when :in then value * 96
134
+ when :pt then value * 4.0 / 3.0
135
+ when :pc then value / 12 * 4.0 / 3.0
136
+ when :mm then value * 3.77953
137
+ when :cm then value * 10 * 3.77953
138
+ when :px then value
139
+ end
140
+
141
+ case type
142
+ when :in then px / 96.0
143
+ when :pt then px / 4.0 / 3.0
144
+ when :pc then px * 12 / 4.0 / 3.0
145
+ when :mm then px / 3.77953
146
+ when :cm then px / 10 / 3.77953
147
+ when :px then px
148
+ end
149
+ end
150
+ end
151
+
152
+ end; end
153
+
154
+ class Numeric
155
+ %i[em ex ch rem vh vw vmin vmax px mm cm in pt pc].each {|name|
156
+ define_method name do
157
+ Paggio::CSS::Unit.new(self, name)
158
+ end
159
+ }
160
+
161
+ def to_u
162
+ self
163
+ end
164
+ end
165
+
166
+ [Fixnum, Float].each {|klass|
167
+ klass.class_eval {
168
+ alias old_percent %
169
+
170
+ def %(other = nil)
171
+ if other
172
+ old_percent(other)
173
+ else
174
+ Paggio::CSS::Unit.new(self, :%)
175
+ end
176
+ end
177
+ }
178
+ }
179
+
180
+ class String
181
+ def to_u
182
+ if matches = match(/^([\d+.]+)(.+)?$/)
183
+ value = matches[1].to_f
184
+
185
+ if unit = matches[2]
186
+ value.__send__(unit.downcase)
187
+ else
188
+ value
189
+ end
190
+ else
191
+ 0
192
+ end
193
+ end
194
+ end
195
+
196
+ class NilClass
197
+ def to_u
198
+ 0
199
+ end
200
+ end
@@ -0,0 +1,117 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ class Paggio
12
+
13
+ module Format
14
+ def self.puts(io, text, level = 0)
15
+ io.puts "#{"\t" * level}#{text}"
16
+ end
17
+
18
+ def self.print(io, text, level = 0)
19
+ io.print "#{"\t" * level}#{text}"
20
+ end
21
+
22
+ def self.escape(string)
23
+ string.to_s
24
+ end
25
+ end
26
+
27
+ class HTML < BasicObject
28
+ Format = ::Paggio::Format
29
+
30
+ def format(io = ::StringIO.new, options = { indent: 0 })
31
+ case @version
32
+ when 5
33
+ Format.puts io, "<!DOCTYPE html>", options[:indent]
34
+ end
35
+
36
+ Format.puts io, "<html>", options[:indent]
37
+
38
+ each {|root|
39
+ root.format(io, indent: options[:indent] + 1)
40
+ }
41
+
42
+ Format.puts io, "</html>", options[:indent]
43
+
44
+ io
45
+ end
46
+
47
+ class Element < BasicObject
48
+ Format = ::Paggio::Format
49
+
50
+ def format(io = ::StringIO.new, options = { indent: 0 })
51
+ if @attributes.empty? && @class.empty?
52
+ Format.puts io, "<#{name}>", options[:indent]
53
+ else
54
+ attrs = @attributes.map {|name, value|
55
+ "#{Format.escape(name)}=\"#{Format.escape(value)}\""
56
+ }
57
+
58
+ unless @class.empty?
59
+ attrs << "class=\"#{Format.escape(@class.join(' '))}\""
60
+ end
61
+
62
+ Format.puts io, "<#{name} #{attrs.join(' ')}>", options[:indent]
63
+ end
64
+
65
+ each {|child|
66
+ case child
67
+ when ::String
68
+ child.lines.each {|line|
69
+ Format.puts io, line.strip, options[:indent] + 1
70
+ }
71
+
72
+ when CSS
73
+ Format.puts io, "<style>", options[:indent] + 1
74
+ child.format(io, indent: options[:indent] + 2)
75
+ Format.puts io, "</style>", options[:indent] + 1
76
+
77
+ else
78
+ child.format(io, indent: options[:indent] + 1)
79
+ end
80
+
81
+ io.puts
82
+ }
83
+
84
+ io.seek(-1, ::IO::SEEK_CUR)
85
+
86
+ Format.puts io, "</#{name}>", @children.empty? ? 0 : options[:indent]
87
+
88
+ io
89
+ end
90
+ end
91
+ end
92
+
93
+ class CSS < BasicObject
94
+ Format = ::Paggio::Format
95
+
96
+ def format(io = ::StringIO.new, options = { indent: 0 })
97
+ rules.reverse.each {|rule|
98
+ next if rule.definition.empty?
99
+
100
+ Format.puts io, "#{rule.selector} {", options[:indent]
101
+
102
+ rule.definition.each {|style|
103
+ Format.puts io, "#{style.name}: #{style.value}#{' !important' if style.important?};",
104
+ options[:indent] + 1
105
+ }
106
+
107
+ Format.puts io, "}", options[:indent]
108
+ io.puts
109
+ }
110
+
111
+ io.seek(-1, ::IO::SEEK_CUR)
112
+
113
+ io
114
+ end
115
+ end
116
+
117
+ end
@@ -0,0 +1,111 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'paggio/html/element'
12
+
13
+ class Paggio
14
+
15
+ class HTML < BasicObject
16
+ attr_reader :version
17
+
18
+ def initialize(version = 5, &block)
19
+ ::Kernel.raise ::ArgumentError, 'no block given' unless block
20
+
21
+ @version = version
22
+ @roots = []
23
+ @current = nil
24
+
25
+ if block.arity == 0
26
+ instance_exec(&block)
27
+ else
28
+ block.call(self)
29
+ end
30
+ end
31
+
32
+ def <<(what)
33
+ @current << what
34
+ end
35
+
36
+ def root!
37
+ @roots.first
38
+ end
39
+
40
+ def roots!
41
+ @roots
42
+ end
43
+
44
+ def element!
45
+ @current
46
+ end
47
+
48
+ def extend!(element = nil, &block)
49
+ old, @current = @current, element
50
+
51
+ result = block.call(self)
52
+
53
+ if ::String === result
54
+ @current.inner_html = result
55
+ end
56
+
57
+ @current = old
58
+
59
+ self
60
+ end
61
+
62
+ def each(&block)
63
+ return enum_for :each unless block
64
+
65
+ @roots.each(&block)
66
+
67
+ self
68
+ end
69
+
70
+ def style(&block)
71
+ (@current || @roots) << CSS.new(&block)
72
+ end
73
+
74
+ def method_missing(name, *args, &block)
75
+ if name.to_s.end_with? ?!
76
+ return super
77
+ end
78
+
79
+ if ::String === args.first
80
+ content = args.shift
81
+ end
82
+
83
+ element = Element.new(self, name, *args)
84
+ element << content if content
85
+
86
+ if block
87
+ parent = @current
88
+ @current = element
89
+ result = block.call(self)
90
+ @current = parent
91
+
92
+ if ::String === result
93
+ element.inner_html = result
94
+ end
95
+ end
96
+
97
+ (@current || @roots) << element
98
+
99
+ element
100
+ end
101
+
102
+ def inspect
103
+ if @roots.empty?
104
+ "#<HTML(#@version)>"
105
+ else
106
+ "#<HTML(#@version): #{@roots.inspect[1 .. -2]}>"
107
+ end
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,98 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ class Paggio; class HTML < BasicObject
12
+
13
+ class Element < BasicObject
14
+ def self.new(owner, name, attributes = {})
15
+ return super unless self == Element
16
+
17
+ const = name.capitalize
18
+
19
+ if const_defined?(const)
20
+ const_get(const).new(owner, name, attributes)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ attr_reader :name, :attributes
27
+ attr_accessor :inner_html
28
+
29
+ def initialize(owner, name, attributes = {})
30
+ @owner = owner
31
+ @name = name
32
+ @attributes = attributes
33
+ @children = []
34
+ @class = []
35
+ end
36
+
37
+ def each(&block)
38
+ return enum_for :each unless block
39
+
40
+ @children.each(&block)
41
+
42
+ self
43
+ end
44
+
45
+ def <<(what)
46
+ @children << what
47
+
48
+ self
49
+ end
50
+
51
+ def text(text)
52
+ @children << text.to_s
53
+
54
+ self
55
+ end
56
+
57
+ def method_missing(name, content = nil, &block)
58
+ if content
59
+ self << content
60
+ end
61
+
62
+ if name.to_s.end_with? ?!
63
+ @attributes[:id] = name[0 .. -2]
64
+ else
65
+ @last = name
66
+ @class.push(name)
67
+ end
68
+
69
+ @owner.extend!(self, &block) if block
70
+
71
+ self
72
+ end
73
+
74
+ def [](*names)
75
+ return unless @last
76
+
77
+ @class.delete(@last)
78
+ @class.push([@last, *names].join('-'))
79
+
80
+ self
81
+ end
82
+
83
+ def do(&block)
84
+ @owner.extend!(self, &block)
85
+
86
+ self
87
+ end
88
+
89
+ def inspect
90
+ if @children.empty?
91
+ "#<HTML::Element(#{@name.upcase})>"
92
+ else
93
+ "#<HTML::Element(#{@name.upcase}): #{@children.inspect[1 .. -2]}>"
94
+ end
95
+ end
96
+ end
97
+
98
+ end; end
@@ -0,0 +1,23 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'paggio'
12
+
13
+ def html(*args, &block)
14
+ puts Paggio.html(*args, &block)
15
+ end
16
+
17
+ def html!(&block)
18
+ puts Paggio.html!(&block)
19
+ end
20
+
21
+ def css(*args, &block)
22
+ puts Paggio.css(*args, &block)
23
+ end
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new {|s|
2
+ s.name = 'paggio'
3
+ s.version = '0.1.0'
4
+ s.author = 'meh.'
5
+ s.email = 'meh@schizofreni.co'
6
+ s.homepage = 'http://github.com/meh/paggio'
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = 'Ruby, HTML and CSS at war.'
9
+
10
+ s.files = `git ls-files`.split("\n")
11
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
12
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
+ s.require_paths = ['lib']
14
+ }
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: paggio
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - meh.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-12 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: meh@schizofreni.co
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - README.md
20
+ - lib/paggio.rb
21
+ - lib/paggio/css.rb
22
+ - lib/paggio/css/definition.rb
23
+ - lib/paggio/css/unit.rb
24
+ - lib/paggio/format.rb
25
+ - lib/paggio/html.rb
26
+ - lib/paggio/html/element.rb
27
+ - lib/paggio/now.rb
28
+ - paggio.gemspec
29
+ homepage: http://github.com/meh/paggio
30
+ licenses: []
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.1.5
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Ruby, HTML and CSS at war.
52
+ test_files: []