paggio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []