hoshi 0.0.186

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rake/gempackagetask'
2
+ require 'rake/rdoctask'
3
+
4
+
5
+ spec = Gem::Specification.new { |s|
6
+ s.platform = Gem::Platform::RUBY
7
+
8
+ s.author = "Pete Elmore"
9
+ s.email = "pete.elmore@gmail.com"
10
+ s.files = Dir["{lib,doc,bin,ext}/**/*"].delete_if {|f|
11
+ /\/rdoc(\/|$)/i.match f
12
+ } + %w(Rakefile)
13
+ s.require_path = 'lib'
14
+ s.has_rdoc = true
15
+ s.extra_rdoc_files = Dir['doc/*'].select(&File.method(:file?))
16
+ s.extensions << 'ext/extconf.rb' if File.exist? 'ext/extconf.rb'
17
+ Dir['bin/*'].map(&File.method(:basename)).map(&s.executables.method(:<<))
18
+
19
+ s.name = 'hoshi'
20
+ s.rubyforge_project = 'hoshi-view'
21
+ s.summary = "Nice, object-oriented, first-class views."
22
+ s.homepage = "http://debu.gs/#{s.name}"
23
+ %w(metaid hpricot).each &s.method(:add_dependency)
24
+ s.version = '0.0.186'
25
+ }
26
+
27
+ Rake::GemPackageTask.new(spec) { |pkg|
28
+ pkg.need_tar_bz2 = true
29
+ }
30
+
31
+ task(:install => :package) {
32
+ g = "pkg/#{spec.name}-#{spec.version}.gem"
33
+ system "gem install -l #{g}"
34
+ }
data/bin/html2hoshi ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # A script that turns HTML into a Hoshi method. Useful to turn some HTML
4
+ # (generated or not) into Ruby so that you can add some code to it or just for
5
+ # readability.
6
+ #
7
+
8
+ require 'rubygems'
9
+
10
+ %w(
11
+ html2hoshi
12
+ ).each &method(:require)
13
+
14
+ if ARGV.empty?
15
+ $stderr.puts "Usage: #{$0} html_file [output_file]\nFor either input " \
16
+ "or output, using - as the filename does the usual thing."
17
+ exit 1
18
+ end
19
+
20
+ html_filename = ARGV.shift
21
+ html_file =
22
+ if html_filename == '-'
23
+ html_file = $stdin
24
+ elsif File.exist?(html_filename)
25
+ File.open html_filename
26
+ else
27
+ $stderr.puts "Apparently, #{html_filename} does not exist."
28
+ exit 1
29
+ end
30
+
31
+ x = ARGV.shift
32
+ if x.nil? || x == '-'
33
+ output = File.basename(html_filename, '.html')
34
+ output = 'unnamed_view' if output == '-'
35
+ outfile = $stdout
36
+ else
37
+ output = File.basename(x, '.rb')
38
+ outfile = File.open(x, 'w')
39
+ end
40
+
41
+ class_name = output.capitalize.gsub(/_(\w)/) { $1.upcase }
42
+ indent = "\t" # TODO: Make it a command-line option.
43
+
44
+ hoshi = Hoshi.from_html html_file.read, indent
45
+
46
+ outfile.print(<<EOHOSHI)
47
+ #!/usr/bin/env ruby
48
+
49
+ require 'rubygems'
50
+ require 'hoshi'
51
+
52
+ # Generated with html2hoshi. You will likely want to change the class name,
53
+ # method name, and class that it inherits from.
54
+ class #{class_name} < Hoshi::View :html
55
+ #{indent}permissive!
56
+ #{hoshi}
57
+ end
58
+
59
+ # This file can also be run stand-alone:
60
+ if __FILE__ == $0
61
+ #{indent}require 'cgi'
62
+ #{indent}puts CGI.pretty(#{class_name}.new.page)
63
+ end
64
+ EOHOSHI
65
+ outfile.close
data/doc/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008 Peter Elmore (pete.elmore at gmail.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ DEALINGS IN THE SOFTWARE.
data/doc/README ADDED
@@ -0,0 +1,180 @@
1
+ = Hoshi
2
+
3
+ == Summary
4
+
5
+ Hoshi is a library for creating real first-class HTML/XML views. So,
6
+ unlike template libraries, you can take advantage of mixins,
7
+ inheritance, and all the other wonderful features of Ruby's object
8
+ system. There is also support for easy RSS feeds and CGI.
9
+
10
+ Hoshi is designed to:
11
+ * Generate clean HTML/XHTML/XML with minimal effort
12
+ * Be easy for a coder to use and understand
13
+ * Take full advantage of Ruby's object sytem
14
+ * Be more readable and easier to write than bare HTML
15
+
16
+ It is semi-modeled after Markaby, but with a much more straightforward
17
+ implementation and different semantics (e.g., no instance_eval, so scope
18
+ inside a tag is as expected). Hoshi also allows a tag to follow another
19
+ tag without requiring any <<, +, or +=.
20
+
21
+ == Installation
22
+
23
+ You can install via rubygems,
24
+
25
+ gem install hoshi
26
+
27
+ or by downloading from github (http://github.com/pete/hoshi).
28
+
29
+ == Usage
30
+
31
+ These examples and more featured in the fabulous doc/examples directory.
32
+ Also, there is a program included called html2hoshi (and associated
33
+ lib/html2hoshi.rb; see Hoshi.from_html) that takes HTML as input and
34
+ converts it to Ruby code using Hoshi.
35
+
36
+ === Class-based
37
+
38
+ These should be fairly straightforward:
39
+
40
+ require 'hoshi'
41
+
42
+ class Trivial < Hoshi::View :html4
43
+ def show
44
+ doctype
45
+ html {
46
+ head {
47
+ title "Hello, world!"
48
+ link :rel => 'stylesheet', :href => '/css/hoshi.css'
49
+ }
50
+
51
+ body {
52
+ h1 "Hello, world!"
53
+ p "This is a greeting to the world."
54
+ }
55
+ }
56
+ render
57
+ end
58
+ end
59
+
60
+ puts Trivial.new.show
61
+
62
+ You can get a little more complicated:
63
+
64
+ require 'hoshi'
65
+ require 'cgi'
66
+
67
+ module Layout
68
+ def main_page(t)
69
+ doctype
70
+ html {
71
+ head {
72
+ title t
73
+ script(:type => 'text/javascript') {
74
+ raw "alert(\"Hi, I'm some javascript, I suppose.\");"
75
+ }
76
+ }
77
+
78
+ body {
79
+ h1 t, :class => 'page_title'
80
+
81
+ yield
82
+ }
83
+ }
84
+ end
85
+
86
+ def list_page(t)
87
+ main_page(t) {
88
+ ul {
89
+ yield
90
+ }
91
+ }
92
+ end
93
+ end
94
+
95
+
96
+ class Fibonacci < Hoshi::View :xhtml1
97
+ include Layout
98
+
99
+ def list_page(n)
100
+ super("Fibonacci: f(0)..f(#{n})") {
101
+ fib_upto(n).map { |i| li i.to_s }
102
+ }
103
+ CGI.pretty(render)
104
+ end
105
+
106
+ private
107
+
108
+ def fib_upto n
109
+ a = Array.new(n)
110
+
111
+ 0.upto(n) { |i|
112
+ a[i] =
113
+ if i < 2
114
+ 1
115
+ else
116
+ a[i - 1] + a[i - 2]
117
+ end
118
+ }
119
+
120
+ a
121
+ end
122
+ end
123
+
124
+ puts Fibonacci.new.list_page(n)
125
+
126
+ === Block-based
127
+
128
+ For simpler cases where you only intend to produce markup, perhaps for use as a templating engine.
129
+
130
+ require 'hoshi'
131
+
132
+ str = Hoshi::View::HTML4.build {
133
+ doctype
134
+ html {
135
+ head {
136
+ title "Hello, world!"
137
+ link :rel => 'stylesheet', :href => '/css/hoshi.css'
138
+ }
139
+
140
+ body {
141
+ h1 "Hello, world!"
142
+ p "This is a greeting to the world."
143
+ }
144
+ }
145
+ }
146
+
147
+ puts str
148
+
149
+ == Bugs
150
+
151
+ There needs to be some work done on correcting the tags.
152
+
153
+ == Credits
154
+
155
+ Author:
156
+ Pete Elmore -- (pete.elmore(a)gmail.com)
157
+
158
+ Initial Design:
159
+ Dan Yoder -- (danyoder(a)mac.com)
160
+
161
+ Simple block version:
162
+ Nolan Darilek -- (nolan(a)thewordnerd.info)
163
+
164
+ Homie that be lookin' out for my broken deps:
165
+ Lars Lethonen
166
+
167
+ The guys that paid me to do this:
168
+ AT&T Interactive. (By the way, email me if you want to come work here.)
169
+
170
+ Also, I guess I should credit Attractive Eighties Women
171
+ (http://attractiveeightieswomen.com/), since I was blasting them the
172
+ whole time I was developing this. Like, over and over. I couldn't stop
173
+ listening. My friends and family are becoming concerned. I don't feel
174
+ that I am yet ready to take the first step by admitting there is a
175
+ problem. Intervention may be required. This paragraph should probably
176
+ be considered a cry for help.
177
+
178
+ == Home page
179
+
180
+ http://debu.gs/hoshi
data/doc/TODO ADDED
@@ -0,0 +1,10 @@
1
+ TODO for 1.0:
2
+ * Separate the tags by close type in the various View sub-classes. (The most
3
+ tedious thing on the list, but required for compliance.)
4
+ * Still not too happy about the way RSS feeds are done, going to rework.
5
+
6
+ TODO for later:
7
+ * Have html2hoshi do a smarter job of deciding doctypes, not using permissive!
8
+ so much.
9
+ * View logic helpers.
10
+ * Ooh, ooh, SVG!
@@ -0,0 +1,18 @@
1
+ require 'hoshi'
2
+
3
+ str = Hoshi::View::HTML4.build {
4
+ doctype
5
+ html {
6
+ head {
7
+ title "Hello, world!"
8
+ link :rel => 'stylesheet', :href => '/css/hoshi.css'
9
+ }
10
+
11
+ body {
12
+ h1 "Hello, world!"
13
+ p "This is a greeting to the world."
14
+ }
15
+ }
16
+ }
17
+
18
+ puts str
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Perl, eat your heart out.
4
+
5
+ require 'rubygems'
6
+ require 'hoshi'
7
+
8
+ qstring = ENV.delete 'QUERY_STRING'
9
+ query = CGI.parse qstring if qstring
10
+
11
+ Hoshi::View(:html4) {
12
+ doc {
13
+ head { title "env.cgi, just like Mom used to make" }
14
+ body {
15
+ h1 "CGI Environment:"
16
+ ul {
17
+ ENV.to_a.sort.map { |k,v|
18
+ li CGI.escapeHTML("#{k} => #{v}")
19
+ }
20
+ }
21
+
22
+ if query
23
+ h1 "Arguments:"
24
+ ul {
25
+ query.sort.map { |k,v|
26
+ li {
27
+ safe "#{k} = "
28
+ if v.size == 1
29
+ safe v
30
+ else
31
+ ul { v.map { |sv| li { safe sv } } }
32
+ end
33
+ }
34
+ }
35
+ }
36
+ else
37
+ h1 "No Arguments"
38
+ end
39
+ }
40
+ }
41
+ render_cgi
42
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'hoshi'
5
+
6
+ class Feed < Hoshi::View :html
7
+ permissive!
8
+ def show
9
+ raw "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
10
+ raw "<?xml-stylesheet href=\"http://feeds.feedburner.com/~d/styles/rss2full.xsl\" type=\"text/xsl\" media=\"screen\"?>"
11
+ raw "<?xml-stylesheet href=\"http://feeds.feedburner.com/~d/styles/itemcontent.css\" type=\"text/css\" media=\"screen\"?>"
12
+ rss("xmlns:wfw"=>"http://wellformedweb.org/CommentAPI/", "xmlns:atom"=>"http://www.w3.org/2005/Atom", "version"=>"2.0", "xmlns:content"=>"http://purl.org/rss/1.0/modules/content/", "xmlns:dc"=>"http://purl.org/dc/elements/1.1/") {
13
+ channel {
14
+ title "Debugs"
15
+ link "http://debu.gs/blog/debugs.rss"
16
+ description
17
+ language "en"
18
+ tag "atom10:link", nil, "href"=>"http://feeds.feedburner.com/debugs", "rel"=>"self", "xmlns:atom10"=>"http://www.w3.org/2005/Atom", "type"=>"application/rss+xml"
19
+ item {
20
+ title "Hoshi 0.1.0 Released...Soon"
21
+ link "http://debu.gs/hoshi-010-released"
22
+ pubDate "Thu, 06 Nov 2008 00:00:00 +0000"
23
+ description "Bringing first-class views to Ruby."
24
+ }
25
+ item {
26
+ title "LiveConsole 0.2.0 Released"
27
+ link "http://debu.gs/liveconsole-020-released"
28
+ pubDate "Thu, 16 Oct 2008 00:00:00 +0000"
29
+ description "LiveConsole 0.2.0 released with support for Unix Domain Sockets and arbitrary bindings."
30
+ }
31
+ }
32
+ }
33
+ render
34
+ end
35
+ end
36
+
37
+ if __FILE__ == $0
38
+ require 'cgi'
39
+ puts CGI.pretty(Feed.new.show)
40
+ end
@@ -0,0 +1,60 @@
1
+ require 'hoshi'
2
+ require 'cgi'
3
+
4
+ module Layout
5
+ def main_page(t)
6
+ doctype
7
+ html {
8
+ head {
9
+ title t
10
+ script(:type => 'text/javascript') {
11
+ raw "alert(\"Hi, I'm some javascript, I suppose.\");"
12
+ }
13
+ }
14
+
15
+ body {
16
+ h1 t, :class => 'page_title'
17
+ yield
18
+ }
19
+ }
20
+ end
21
+
22
+ def list_page(t)
23
+ main_page(t) {
24
+ ul {
25
+ yield
26
+ }
27
+ }
28
+ end
29
+ end
30
+
31
+
32
+ class Fibonacci < Hoshi::View :xhtml1
33
+ include Layout
34
+
35
+ def list_page(n)
36
+ super("Fibonacci: f(0)..f(#{n})") {
37
+ fib_upto(n).map { |i| li i.to_s }
38
+ }
39
+ CGI.pretty(render)
40
+ end
41
+
42
+ private
43
+
44
+ def fib_upto n
45
+ a = Array.new(n)
46
+
47
+ 0.upto(n) { |i|
48
+ a[i] =
49
+ if i < 2
50
+ 1
51
+ else
52
+ a[i - 1] + a[i - 2]
53
+ end
54
+ }
55
+
56
+ a
57
+ end
58
+ end
59
+
60
+ puts Fibonacci.new.list_page(10)
@@ -0,0 +1,52 @@
1
+ require 'hoshi'
2
+ require 'ostruct'
3
+
4
+ class BlogFeed < Hoshi::View :rss2
5
+ def initialize(blog)
6
+ super()
7
+
8
+ # This will generate the inside of the channel tag.
9
+ def_channel {
10
+ title blog.title
11
+ link blog.url
12
+ description blog.description
13
+ }
14
+
15
+ # This block gets called once per item.
16
+ def_item { |i|
17
+ title i.title
18
+ link i.url
19
+ description i.summary
20
+ # pub_date is a helper for pubDate:
21
+ pub_date i.time
22
+ author i.author.email if i.author
23
+ }
24
+
25
+ # You need items to have a feed.
26
+ self.items = blog.entries
27
+ end
28
+ end
29
+
30
+ # We're going to stub out a fake blog object full of fake entries before we get
31
+ # to the good part.
32
+ blog = OpenStruct.new(:title => 'Hello, world of syndication!',
33
+ :url => 'http://www.example.com/blog',
34
+ :description => 'Nope, no, not at all.')
35
+
36
+ author = Struct.new(:email)
37
+ blog.entries = [
38
+ { :title => "first ps0t!!!1!",
39
+ :url => 'http://www.example.com/blog/first_psot',
40
+ :description => 'This is the first post.',
41
+ :time => (Time.now - 60 * 60 * 24),
42
+ :author => author.new('biff@example.com'),
43
+ },
44
+ { :title => "What is with Biff?",
45
+ :url => 'http://www.example.com/blog/biff_sux',
46
+ :description => 'Jerk stole the first post from me.',
47
+ :time => Time.now,
48
+ :author => author.new('emily_postnews@example.com'),
49
+ },
50
+ ].map &OpenStruct.method(:new)
51
+
52
+ puts BlogFeed.new(blog).render
@@ -0,0 +1,21 @@
1
+ require 'hoshi'
2
+
3
+ class Trivial < Hoshi::View :html4
4
+ def show
5
+ doctype
6
+ html {
7
+ head {
8
+ title "Hello, world!"
9
+ link :rel => 'stylesheet', :href => '/css/hoshi.css'
10
+ }
11
+
12
+ body {
13
+ h1 "Hello, world!"
14
+ p "This is a greeting to the world."
15
+ }
16
+ }
17
+ render
18
+ end
19
+ end
20
+
21
+ puts Trivial.new.show
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ # Makes this hash fit to be put into a tag.
3
+ # { :a => 1, :b => "two" }.to_html_options # => 'a="one" b="two"'
4
+ def to_html_options double_quotes = true
5
+ qchar = double_quotes ? '"' : "'"
6
+ map { |k,v| "#{k}=#{qchar}#{v}#{qchar}" }.join(' ')
7
+ end
8
+ end
data/lib/hoshi/tag.rb ADDED
@@ -0,0 +1,42 @@
1
+ module Hoshi
2
+ # Represents an HTML tag. You usually won't be using this class directly.
3
+ class Tag
4
+ attr_accessor :name, :close_type
5
+
6
+ # A tag currently has only two attributes: a name and a method for
7
+ # closing it, which both decide how it is rendered as a string.
8
+ # A self-closing tag:
9
+ # Tag.new('test', :self).render # => "<test />"
10
+ # A tag that does not need to close:
11
+ # Tag.new('test', :none).render('test this') # => "<test>test this\n"
12
+ # And a regular tag:
13
+ # Tag.new('test').render # => "<test></test>"
14
+ def initialize(name, close_type = nil)
15
+ @name, @close_type = name, close_type
16
+ end
17
+
18
+ # Generates a string from this tag. inside should be the contents to
19
+ # put between the opening and closing tags (if any), and opts are the
20
+ # HTML options to put in the tag. For example,
21
+ # Tag.new('div').render('Click for an alert.',
22
+ # :onclick => "alert('Hi.');")
23
+ # gets you this:
24
+ # <div onclick="alert('Hi.');">Click for an alert.</div>
25
+ def render(inside = nil, opts = {})
26
+ inside = inside.to_s
27
+
28
+ s = "<#{name} #{opts.to_html_options}".strip
29
+ if inside.empty? && close_type == :self
30
+ return s << " />"
31
+ end
32
+
33
+ s << ">" << inside
34
+
35
+ if close_type == :none
36
+ s << "\n"
37
+ else
38
+ s << "</#{name}>"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ class Hoshi::View
2
+ class HTML < self
3
+ # So, 'p' may look a little out of place here, but if you sub-class
4
+ # View(:html) directly and use permissive!, you'll end up with Kernel#p
5
+ # rather than a <p> tag. Almost never what you want.
6
+ tags *%w(html head body p)
7
+
8
+ def self.content_type
9
+ 'text/html'
10
+ end
11
+
12
+ def cdata str
13
+ append! "<![CDATA[\n" + str + "\n]]>"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ require 'hoshi/view/html'
2
+
3
+ class Hoshi::View
4
+ class HTML3 < HTML
5
+ dtd! "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">"
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ require 'hoshi/view/html'
2
+
3
+ class Hoshi::View
4
+ class HTML4 < HTML
5
+ dtd! "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " \
6
+ "\"http://www.w3.org/TR/html4/strict.dtd\">"
7
+
8
+
9
+ tags *%w(a address applet area base basefont bdo blockquote body br
10
+ button caption center col colgroup dd div dl dt fieldset font
11
+ form frame frameset h1 h2 h3 h4 h5 h6 head hr html iframe img
12
+ input isindex label legend li link map meta noframes noscript
13
+ object ol optgroup option p param pre q script select span
14
+ style table tbody textarea tfoot thead title tr ul)
15
+ end
16
+ end
17
+
@@ -0,0 +1,8 @@
1
+ require 'hoshi/view/html4'
2
+
3
+ class Hoshi::View
4
+ class HTML4Frameset < HTML4
5
+ dtd! "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 " \
6
+ "Frameset//EN\" \"http://www.w3.org/TR/html4/frameset.dtd\">"
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'hoshi/view/html4'
2
+
3
+ class Hoshi::View
4
+ class HTML4Transitional < HTML
5
+ dtd! "<DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 " \
6
+ "Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
7
+ end
8
+ end
@@ -0,0 +1,56 @@
1
+ require 'time' # Doesn't everything?
2
+
3
+ class Hoshi::View
4
+ class RSS2 < self
5
+ class InvalidItemError < ValidationError; end
6
+ class InvalidChannelError < ValidationError; end
7
+
8
+ tags *%w(author category channel cloud comments copyright description
9
+ docs enclosure generator guid item language lastBuildDate link
10
+ managingEditor pubDate rating skipDays skipHours source title
11
+ ttl webMaster rss image url )
12
+
13
+ attr_accessor :channel_block, :item_block, :items
14
+
15
+ dtd! "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
16
+
17
+ def self.content_type
18
+ 'application/rss+xml'
19
+ end
20
+
21
+ def def_channel &b
22
+ self.channel_block = b
23
+ end
24
+
25
+ def def_item &b
26
+ self.item_block = b
27
+ end
28
+
29
+ # A small helper so you can just pass a date, time, or string rather
30
+ # than worrying about format.
31
+ def pub_date dt
32
+ dt =
33
+ case dt
34
+ when Time, Date, DateTime
35
+ dt.rfc822
36
+ else
37
+ dt
38
+ end
39
+ pubDate dt
40
+ end
41
+
42
+ def render
43
+ clear!
44
+ doctype
45
+ rss(:version => '2.0') {
46
+ channel {
47
+ channel_block.call
48
+ items.each { |i|
49
+ item { item_block.call i }
50
+ }
51
+ }
52
+ }
53
+ super()
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ require 'hoshi/view/html'
2
+
3
+ class Hoshi::View
4
+ class XHTML < HTML
5
+ def self.content_type
6
+ 'application/xhtml+xml'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ require 'hoshi/view/xhtml'
2
+
3
+ class Hoshi::View
4
+ class XHTML1 < XHTML
5
+ dtd! "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 " \
6
+ "Strict//EN\" " \
7
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
8
+ tags *%w(a abbr acronym address applet area b base basefont bdo big
9
+ blockquote body br button caption center cite code col
10
+ colgroup dd del dfn dir div dl dt em fieldset font form h1 h2
11
+ h3 h4 h5 h6 head hr html i iframe img input ins isindex kbd
12
+ label legend li link map menu meta noframes noscript object ol
13
+ optgroup option p param pre q s samp script select small span
14
+ strike strong style sub sup table tbody td textarea tfoot th
15
+ thead title tr tt u ul var)
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ require 'hoshi/view/xhtml1'
2
+
3
+ class Hoshi::View
4
+ class XHTML1Frameset < XHTML1
5
+ dtd! "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 " \
6
+ "Frameset//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">"
7
+ tags *%w(a abbr acronym address applet area b base basefont bdo big
8
+ blockquote body br button caption center cite code col
9
+ colgroup dd del dfn dir div dl dt em fieldset font form frame
10
+ frameset h1 h2 h3 h4 h5 h6 head hr html i iframe img input ins
11
+ isindex kbd label legend li link map menu meta noframes
12
+ noscript object ol optgroup option p param pre q s samp script
13
+ select small span strike strong style sub sup table tbody td
14
+ textarea tfoot th thead title tr tt u ul var)
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ require 'hoshi/view/xhtml'
2
+
3
+ class Hoshi::View
4
+ class XHTML1 < XHTML
5
+ dtd! "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " \
6
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ require 'hoshi/view/xhtml'
2
+
3
+ class Hoshi::View
4
+ class XHTML1Transitional < XHTML
5
+ dtd! "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 " \
6
+ "Transitional//EN\" " \
7
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'hoshi/view/xhtml'
2
+
3
+ class Hoshi::View
4
+ class XHTML2 < XHTML
5
+ dtd! "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
6
+ end
7
+ end
data/lib/hoshi/view.rb ADDED
@@ -0,0 +1,205 @@
1
+ require 'metaid'
2
+ require 'cgi'
3
+
4
+ module Hoshi
5
+ # The View class is the super-class for views you create with Hoshi. More
6
+ # likely, though, you'll be using one of View's many sub-classes as the
7
+ # super-class for your view, like this:
8
+ # class MyView < Hoshi::View :html4
9
+ # or
10
+ # class MyView < Hoshi::View::XHTML1Frameset
11
+ # Of course, using View[] is the preferred method for the sake of brevity.
12
+ # When you create a view class, you'll want to define one or more methods
13
+ # that eventually call View#render, which turns your view into HTML.
14
+ # (Private methods and methods that build up state do not need to do so.)
15
+ class View
16
+ class ValidationError < StandardError; end
17
+
18
+ # This creates an instance method for this view which appends a tag.
19
+ # Most of these are handled for you. The arguments to this method
20
+ # match those to Tag.new. See also View#permissive!.
21
+ # tag('h1')
22
+ # def show_an_h1
23
+ # h1 "I have been shown"
24
+ # end
25
+ def self.tag(name, close_type = nil)
26
+ class_eval <<-EOHACK
27
+ def #{name}(*opts, &b)
28
+ if b
29
+ tag #{name.inspect}, #{close_type.inspect}, *opts, &b
30
+ else
31
+ tag #{name.inspect}, #{close_type.inspect}, *opts, &b
32
+ end
33
+ end
34
+ EOHACK
35
+ end
36
+
37
+ # A short-hand for creating multiple tags via View.tag. For tags that
38
+ # do not require closing, see View.open_tags.
39
+ def self.tags *names
40
+ names.map &method(:tag)
41
+ end
42
+
43
+ # A short-hand for creating multiple tags that are left open.
44
+ def self.open_tags *names
45
+ names.map { |n| tag n, :none }
46
+ end
47
+
48
+ # This method choses, based on the provided doctype, the proper
49
+ # sub-class of View. Generally, you'll be using this rather than
50
+ # sub-classing View directly. The doctype argument is case- and
51
+ # underscore-insensitive, and valid arguments are names of View
52
+ # subclasses that are inside the View namespace.
53
+ def self.[] doctype
54
+ doctype = doctype.to_s.downcase.gsub('_', '')
55
+ const_get(constants.find { |c|
56
+ cl = const_get c
57
+ (cl.ancestors.include?(self) &&
58
+ c.to_s.downcase == doctype) rescue false
59
+ }) rescue nil
60
+ end
61
+
62
+ # Sets the doctype declaration for this class.
63
+ def self.dtd! dtd
64
+ dtd += "\n"
65
+ define_method(:doctype) { append! dtd }
66
+ end
67
+ def doctype
68
+ comment "No doctype defined; are you sub-classing View directly " \
69
+ "and not calling dtd!()?"
70
+ end
71
+
72
+ # Free-form tags. Basically, dynamic tag creation by method_missing.
73
+ def self.permissive!
74
+ @permissive = true
75
+ end
76
+
77
+ # Returns true if we add tags to this class on the fly.
78
+ def self.permissive?
79
+ @permissive
80
+ end
81
+
82
+ # Only the tags already specified are allowed. No dynamic tag
83
+ # creation. This is the default.
84
+ def self.strict!
85
+ @permissive = false
86
+ end
87
+
88
+ # Returns true if we do not add tags to the class on the fly.
89
+ def self.strict?
90
+ !permissive?
91
+ end
92
+
93
+ # Create and render a view via a block.
94
+ def self.build(&block)
95
+ c = new
96
+ c.instance_eval(&block)
97
+ c.render
98
+ end
99
+
100
+ # This is overridden in HTML/XHTML, and you'll definitely want to
101
+ # override it if you subclass View directly.
102
+ def self.content_type
103
+ 'application/octet-stream'
104
+ end
105
+
106
+ # Most of these files depend on the above method definitions.
107
+ Dir["#{File.dirname(__FILE__)}/view/*.rb"].each &method(:require)
108
+
109
+ def initialize
110
+ clear!
111
+ end
112
+
113
+ # Clears the current state of this view.
114
+ def clear!
115
+ self.tree = []
116
+ self.current = tree
117
+ end
118
+
119
+ # Adds a comment.
120
+ def comment(*a)
121
+ if a.include?('--')
122
+ raise ValidationError, "Comments can't include '--'."
123
+ else
124
+ append! "<!-- #{a} -->"
125
+ end
126
+ end
127
+
128
+ # Appends a tag to the current document, for when a tag is only needed
129
+ # once or has a name that is not a valid method name.
130
+ def tag(tname, close_type = nil, *opts, &b)
131
+ t = Tag.new(tname, close_type)
132
+
133
+ if b
134
+ old, self.current = current, []
135
+ b.call
136
+ inside, self.current = current.map { |i| i.to_s }.join, old
137
+ else
138
+ inside = opts.shift if opts.first.kind_of?(String)
139
+ end
140
+
141
+ append! t.render(inside, opts.first || {})
142
+ end
143
+
144
+ # Appends something to the document. The comment, decl, and various
145
+ # tag methods call this.
146
+ def append! x
147
+ current << x
148
+ x
149
+ end
150
+
151
+ # If you're tired of typing "doctype\nhtml" every single time.
152
+ def doc &b
153
+ doctype
154
+ html &b
155
+ end
156
+
157
+ # Turns things in to strings, properly escapes them, and appends them
158
+ # to the document.
159
+ def safe *things
160
+ append! CGI.escapeHTML(things.map { |i| i.to_s }.join("\n"))
161
+ end
162
+
163
+ # Appends one or more non-escaped strings to the document.
164
+ def raw *things
165
+ append! things.join
166
+ end
167
+
168
+ # Returns the string representation of the document. This is what you
169
+ # want to eventually call.
170
+ def render
171
+ tree.flatten.map { |i| i.to_s }.join
172
+ end
173
+
174
+ # Prints the string representation of the docutment, with HTTP headers.
175
+ # Useful for one-off CGI scripts. Takes an optional hash argument for
176
+ # headers (Content-Type and Status are set by default). See CGI#header
177
+ # for information on how the header hash should look.
178
+ def render_cgi(extra_headers = {})
179
+ h = {
180
+ 'type' => self.class.content_type,
181
+ 'status' => 'OK',
182
+ }.merge(extra_headers)
183
+
184
+ CGI.new.out(h) { render }
185
+ end
186
+
187
+ # Dynamically add tags if the view class for this object is permissive.
188
+ def method_missing(mname, *args, &b)
189
+ if self.class.permissive?
190
+ self.class.tag mname
191
+ if b
192
+ send mname, *args, &b
193
+ else
194
+ send mname, *args
195
+ end
196
+ else
197
+ super
198
+ end
199
+ end
200
+
201
+ private
202
+
203
+ attr_accessor :tree, :current
204
+ end
205
+ end
data/lib/hoshi.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'hoshi/monkey_patches'
2
+
3
+ require 'hoshi/tag'
4
+ require 'hoshi/view'
5
+
6
+ # This is the namespace for all of Hoshi, which currently only includes
7
+ # Hoshi::View and Hoshi::Tag . For an overview, see doc/README . For
8
+ # specifics, check out Hoshi::View .
9
+ module Hoshi
10
+ # This is a cosmetic method; you may do Hoshi::View[:type],
11
+ # Hoshi::View(:type), or Hoshi::View :type
12
+ def self.View(*a, &b)
13
+ klass = View[*a]
14
+ if b
15
+ klass.build &b
16
+ else
17
+ klass
18
+ end
19
+ end
20
+ end
data/lib/html2hoshi.rb ADDED
@@ -0,0 +1,85 @@
1
+ require 'hpricot'
2
+
3
+ module Hoshi
4
+ # A semi-hacky method for converting HTML text to a Hoshi method. It takes
5
+ # an HTML document as a string, and optionally the string to use for each
6
+ # level of indentation.
7
+ def self.from_html(html, indent = "\t")
8
+ "#{indent}def page" << tree_to_hoshi(parse(html), indent, 2) <<
9
+ "\n#{indent * 2}render\n#{indent}end"
10
+ end
11
+
12
+ private
13
+
14
+ def self.parse(doc)
15
+ Hpricot.method(
16
+ if doc.include?('<?xml') # Seems like a safe assumption.
17
+ :XML
18
+ else
19
+ :parse
20
+ end).call(doc)
21
+ end
22
+
23
+ def self.tree_to_hoshi(tree, indent = "\t", indentlevel = 0)
24
+ idt = indent * indentlevel
25
+ str = ''
26
+ tree.each_child { |i|
27
+ str << ("\n#{idt}" <<
28
+ case i
29
+ when Hpricot::Elem
30
+ # Something simple that resembles rules for ruby method naming.
31
+ # I know that both XML and Ruby allow most valid unicode
32
+ # characters in tags/method names, but this is Good Enough.
33
+ using_tag = !/^[a-z][a-zA-Z0-9_]*[!?]?$/.match(i.name)
34
+
35
+ args, block = [], nil
36
+ s = if using_tag
37
+ args << i.name.inspect << 'nil'
38
+ 'tag'
39
+ else
40
+ i.name
41
+ end
42
+
43
+ if i.children.size == 1 &&
44
+ i.children.first.kind_of?(Hpricot::Text)
45
+ fc = i.children.first.to_s.strip
46
+ args << fc.inspect unless fc.empty?
47
+ elsif !i.children.empty?
48
+ block = ' {' <<
49
+ tree_to_hoshi(i, indent, indentlevel + 1) <<
50
+ "\n#{idt}}"
51
+ end
52
+
53
+ unless i.attributes.empty?
54
+ args << i.attributes.inspect.sub(/^\{/, '').sub(/\}$/, '')
55
+ end
56
+
57
+ unless args.empty?
58
+ arg_s = args.join(', ')
59
+ if block
60
+ s << "(#{arg_s})"
61
+ else
62
+ s << " #{arg_s}"
63
+ end
64
+ end
65
+
66
+ s << block if block
67
+
68
+ s
69
+ when Hpricot::Comment
70
+ "comment #{i.content.inspect}"
71
+ when Hpricot::BogusETag
72
+ "raw #{i.to_s.inspect} # Dangling end tag."
73
+ when Hpricot::DocType, Hpricot::ProcIns, Hpricot::XMLDecl,
74
+ Hpricot::Text, Object
75
+ x = i.to_s
76
+ if /\A\s*\Z/.match(x)
77
+ next
78
+ else
79
+ "raw #{i.to_s.strip.inspect}"
80
+ end
81
+ end)
82
+ }
83
+ str
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hoshi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.186
5
+ platform: ruby
6
+ authors:
7
+ - Pete Elmore
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-10 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: metaid
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hpricot
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: pete.elmore@gmail.com
37
+ executables:
38
+ - html2hoshi
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - doc/README
43
+ - doc/TODO
44
+ - doc/LICENSE
45
+ files:
46
+ - lib/html2hoshi.rb
47
+ - lib/hoshi.rb
48
+ - lib/hoshi
49
+ - lib/hoshi/monkey_patches.rb
50
+ - lib/hoshi/view
51
+ - lib/hoshi/view/html.rb
52
+ - lib/hoshi/view/xhtml1_strict.rb
53
+ - lib/hoshi/view/xhtml1_frameset.rb
54
+ - lib/hoshi/view/html4_transitional.rb
55
+ - lib/hoshi/view/xhtml.rb
56
+ - lib/hoshi/view/html3.rb
57
+ - lib/hoshi/view/html4_frameset.rb
58
+ - lib/hoshi/view/xhtml1.rb
59
+ - lib/hoshi/view/html4.rb
60
+ - lib/hoshi/view/xhtml2.rb
61
+ - lib/hoshi/view/xhtml1_transitional.rb
62
+ - lib/hoshi/view/rss2.rb
63
+ - lib/hoshi/tag.rb
64
+ - lib/hoshi/view.rb
65
+ - doc/README
66
+ - doc/examples
67
+ - doc/examples/layouts.rb
68
+ - doc/examples/trivial.rb
69
+ - doc/examples/blocks.rb
70
+ - doc/examples/env.cgi
71
+ - doc/examples/feed.rb
72
+ - doc/examples/rss2.rb
73
+ - doc/TODO
74
+ - doc/LICENSE
75
+ - bin/html2hoshi
76
+ - Rakefile
77
+ has_rdoc: true
78
+ homepage: http://debu.gs/hoshi
79
+ post_install_message:
80
+ rdoc_options: []
81
+
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ requirements: []
97
+
98
+ rubyforge_project: hoshi-view
99
+ rubygems_version: 1.3.1
100
+ signing_key:
101
+ specification_version: 2
102
+ summary: Nice, object-oriented, first-class views.
103
+ test_files: []
104
+