hoshi 0.0.186

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.
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
+