html_mockup 0.3.2
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/README.rdoc +81 -0
- data/bin/mockup +5 -0
- data/examples/config.ru +16 -0
- data/examples/html/green.gif +0 -0
- data/examples/html/index.html +18 -0
- data/examples/partials/test.part.rhtml +3 -0
- data/examples/script/server +20 -0
- data/lib/html_mockup/cli.rb +178 -0
- data/lib/html_mockup/rack/html_mockup.rb +56 -0
- data/lib/html_mockup/rack/html_validator.rb +26 -0
- data/lib/html_mockup/server.rb +80 -0
- data/lib/html_mockup/template.rb +129 -0
- data/lib/html_mockup/w3c_validator.rb +120 -0
- metadata +86 -0
data/README.rdoc
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
== HtmlMockup
|
2
|
+
|
3
|
+
HTML Mockup is a set of tools to create self-containing HTML mockups. HtmlMockup gives you the flexibility
|
4
|
+
of a templatinglanguage but at the same time keeps all HTML files viewable. HTML comments are
|
5
|
+
used to determine what partial (sub-template) to render.
|
6
|
+
|
7
|
+
HtmlMockup also provides tools for HTML validation.
|
8
|
+
|
9
|
+
=== Requirements
|
10
|
+
HtmlMockup requires the following dependencies
|
11
|
+
|
12
|
+
* Ruby 1.8.x (not tested in 1.9.x)
|
13
|
+
* Rubygems
|
14
|
+
* Thor (to use mockup binary)
|
15
|
+
* Rack > 0.3 (to use mockup serve)
|
16
|
+
|
17
|
+
=== Usage
|
18
|
+
|
19
|
+
Just write regular HTML files and include comment's like this:
|
20
|
+
|
21
|
+
<!-- [START:partial_name] -->Text<!-- [STOP:partial_name] -->
|
22
|
+
|
23
|
+
The data between the tags will be replaced by the partial contents. Partials are searched in
|
24
|
+
"../partials" relative to the directory the script you run resides in. This can be overridden with
|
25
|
+
commandline parameters. Partials always must have a .part.r?html ending and are evaluated as ERB during
|
26
|
+
insertion.
|
27
|
+
|
28
|
+
=== Syntax for HTML files
|
29
|
+
|
30
|
+
==== Standard partials
|
31
|
+
|
32
|
+
<!-- [START:partial_name] -->Text<!-- [STOP:partial_name] -->
|
33
|
+
|
34
|
+
==== Pass parameters to partials
|
35
|
+
|
36
|
+
You can pass in parameters to partials in the format of key=value&key2=value2 (it's just a regular CGI
|
37
|
+
query string and is parsed by CGI#parse). The partials wich are evaluated as ERB can access the variables
|
38
|
+
through standard instance methods. The example below would create the instance variable @key.
|
39
|
+
|
40
|
+
<!-- [START:partial_name?key=value] -->Text<!-- [STOP:partial_name] -->
|
41
|
+
|
42
|
+
|
43
|
+
=== Mockup commandline
|
44
|
+
|
45
|
+
==== mockup convert [directory/file]
|
46
|
+
|
47
|
+
Convert can be called with a single html file or a directory. If a directory is specified all .html files
|
48
|
+
will be converted.
|
49
|
+
|
50
|
+
*Warning:* Convert will overwrite the file itself!
|
51
|
+
|
52
|
+
Options:
|
53
|
+
--partial_path:: the path where the partial files can be found (*.part.html), defaults to director/../partials
|
54
|
+
--filter:: The filter to use when finding templates within directory, defaults to *.html
|
55
|
+
|
56
|
+
==== mockup serve [directory/file]
|
57
|
+
|
58
|
+
Serve can be used during development as a simple webserver (Webrick/Mongrel). It also supports
|
59
|
+
on-the-fly HTML validation.
|
60
|
+
|
61
|
+
You can also call ./script/server just above the HTML directory.
|
62
|
+
|
63
|
+
Options:
|
64
|
+
--port:: The port the server should listen on. Defaults to 9000
|
65
|
+
--partial_path:: the path where the partial files can be found (*.part.html), defaults to director/../partials
|
66
|
+
--validate:: Flag to set wether or not we should validate all html files (defaults to false)
|
67
|
+
|
68
|
+
==== mockup generate [directory]
|
69
|
+
|
70
|
+
Generate creates a directory structure in directory for use with new HTML mockups.
|
71
|
+
|
72
|
+
==== mockup validate [directory/file]
|
73
|
+
|
74
|
+
Validates all files within directory or just file with the W3C validator webservice.
|
75
|
+
|
76
|
+
Options:
|
77
|
+
--show_valid:: Flag to print a line for each valid file too (defaults to false)
|
78
|
+
--filter:: What files should be validated, defaults to [^_]*.html
|
79
|
+
|
80
|
+
=== Copyright & license
|
81
|
+
Copyright (c) 2009 Flurin Egger, DigitPaint, MIT Style License. (see MIT-LICENSE)
|
data/bin/mockup
ADDED
data/examples/config.ru
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require File.dirname(__FILE__) + "/../vendor/html_mockup/lib/html_mockup/server"
|
5
|
+
rescue LoadError => e
|
6
|
+
require 'rubygems'
|
7
|
+
require 'html_mockup/server'
|
8
|
+
end
|
9
|
+
|
10
|
+
root_path = Pathname.new(File.dirname(__FILE__)) + "html"
|
11
|
+
partial_path = (root_path + "../partials/").realpath
|
12
|
+
|
13
|
+
mockup = HtmlMockup::Server.new(root_path,partial_path)
|
14
|
+
|
15
|
+
run mockup.application
|
16
|
+
|
Binary file
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
7
|
+
|
8
|
+
<title>untitled</title>
|
9
|
+
|
10
|
+
</head>
|
11
|
+
|
12
|
+
<body>
|
13
|
+
<!-- [START:test?id=bla] -->
|
14
|
+
<!-- [STOP:test] -->
|
15
|
+
|
16
|
+
<img src="green.gif" alt="Green" />
|
17
|
+
</body>
|
18
|
+
</html>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require File.dirname(__FILE__) + "/../vendor/html_mockup/lib/html_mockup/server"
|
7
|
+
rescue LoadError => e
|
8
|
+
require 'rubygems'
|
9
|
+
require 'html_mockup/server'
|
10
|
+
end
|
11
|
+
|
12
|
+
root_path = Pathname.new(File.dirname(__FILE__)) + "../html"
|
13
|
+
partial_path = (root_path + "../partials/").realpath
|
14
|
+
|
15
|
+
mockup = HtmlMockup::Server.new(root_path,partial_path)
|
16
|
+
|
17
|
+
# Add some of your own middleware here.
|
18
|
+
# mockup.use Rack::CommonLogger
|
19
|
+
|
20
|
+
mockup.run
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'fileutils'
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + "/template"
|
9
|
+
require File.dirname(__FILE__) + "/w3c_validator"
|
10
|
+
|
11
|
+
module HtmlMockup
|
12
|
+
class Cli < Thor
|
13
|
+
desc "serve [directory]","Serve directory as HTML, defaults to current directory"
|
14
|
+
method_options :port => :string, # Defaults to 9000
|
15
|
+
:partial_path => :string, # Defaults to [directory]/../partials
|
16
|
+
:validate => :boolean, # Automatically validate all HTML responses @ the w3c
|
17
|
+
:handler => :string # The handler to use (defaults to mongrel)
|
18
|
+
def serve(path=".")
|
19
|
+
require File.dirname(__FILE__) + '/server'
|
20
|
+
|
21
|
+
@path,@partial_path = template_paths(path,options["partial_path"])
|
22
|
+
|
23
|
+
server_options = {}
|
24
|
+
server_options[:Port] = options["port"] || "9000"
|
25
|
+
|
26
|
+
server = Server.new(@path,@partial_path,options,server_options)
|
27
|
+
|
28
|
+
puts "Running HtmlMockup with #{server.handler.inspect} on port #{server_options[:Port]}"
|
29
|
+
puts " Taking partials from #{@partial_path} (#{HtmlMockup::Template.partial_files(@partial_path).size} found)"
|
30
|
+
|
31
|
+
server.run
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "validate [directory/file]", "Validates the file or all HTML in directory"
|
35
|
+
method_options :show_valid => :boolean, # Also print a line for each valid file
|
36
|
+
:filter => :string # What files should be found, defaults to [^_]*.html
|
37
|
+
def validate(path=".")
|
38
|
+
filter = options["filter"] || "[^_]*.html"
|
39
|
+
|
40
|
+
puts "Filtering on #{options["filter"]}" if options["filter"]
|
41
|
+
|
42
|
+
if File.directory?(path)
|
43
|
+
any_invalid = false
|
44
|
+
|
45
|
+
if (files = Dir.glob("#{path}/**/#{filter}")).any?
|
46
|
+
files.each do |file|
|
47
|
+
if !self.w3cvalidate(file)
|
48
|
+
any_invalid = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
if !any_invalid
|
52
|
+
puts "All files were considered valid"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
puts "No files matched \"#{filter}\""
|
56
|
+
end
|
57
|
+
elsif File.readable?(path)
|
58
|
+
self.w3cvalidate(path)
|
59
|
+
else
|
60
|
+
puts "No such file/directory #{path}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "generate [directory]","Create a new HTML mockup directory tree in directory"
|
65
|
+
def generate(path)
|
66
|
+
path = Pathname.new(path)
|
67
|
+
if path.directory?
|
68
|
+
puts "Directory #{path} already exists, please only use this to create new mockups"
|
69
|
+
else
|
70
|
+
example_path = Pathname.new(File.dirname(__FILE__) + "/../../examples")
|
71
|
+
path.mkpath
|
72
|
+
html_path = path + "html"
|
73
|
+
mkdir(html_path)
|
74
|
+
mkdir(html_path + "stylesheets")
|
75
|
+
mkdir(html_path + "images")
|
76
|
+
mkdir(html_path + "javascripts")
|
77
|
+
|
78
|
+
mkdir(path + "partials")
|
79
|
+
|
80
|
+
mkdir(path + "script")
|
81
|
+
cp(example_path + "script/server",path + "script/server")
|
82
|
+
cp(example_path + "config.ru",path + "config.ru")
|
83
|
+
(path + "script/server").chmod(0755)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "convert [directory]","Inject all partials, into all HTML files within directory"
|
88
|
+
method_options :partial_path => :string, # Defaults to [directory]/../partials
|
89
|
+
:filter => :string # What files should be converted defaults to **/*.html
|
90
|
+
def convert(path=".")
|
91
|
+
path,partial_path = template_paths(path,options["partial_path"])
|
92
|
+
filter = options["filter"] || "**/*.html"
|
93
|
+
puts "Converting #{filter} in #{path}"
|
94
|
+
puts " Taking partials from #{partial_path} (#{HtmlMockup::Template.partial_files(partial_path).size} found)"
|
95
|
+
|
96
|
+
if path.directory?
|
97
|
+
Dir.glob("#{path}/#{filter}").each do |file|
|
98
|
+
puts " Converting file: " + file
|
99
|
+
HtmlMockup::Template.open(file, :partial_path => partial_path).save
|
100
|
+
end
|
101
|
+
else
|
102
|
+
HtmlMockup::Template.open(path, :partial_path => partial_path).save
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
desc "extract [source_path] [target_path]", "Extract a fully relative html mockup into target_path. It will expand all absolute href's, src's and action's into relative links if they are absolute"
|
108
|
+
method_options :partial_path => :string, # Defaults to [directory]/../partials
|
109
|
+
:filter => :string # What files should be converted defaults to **/*.html
|
110
|
+
def extract(source_path=".",target_path="../out")
|
111
|
+
require 'hpricot'
|
112
|
+
source_path,target_path = Pathname.new(source_path),Pathname.new(target_path)
|
113
|
+
source_path,partial_path = template_paths(source_path,options["partial_path"])
|
114
|
+
filter = options["filter"] || "**/*.html"
|
115
|
+
raise "Target #{target_path} already exists, please choose a new directory to extract into" if target_path.exist?
|
116
|
+
|
117
|
+
mkdir_p(target_path)
|
118
|
+
target_path = target_path.realpath
|
119
|
+
|
120
|
+
# Copy source to target first, we'll overwrite the templates later on.
|
121
|
+
cp_r(source_path.children,target_path)
|
122
|
+
|
123
|
+
Dir.chdir(source_path) do
|
124
|
+
Dir.glob(filter).each do |file_name|
|
125
|
+
source = HtmlMockup::Template.open(file_name, :partial_path => partial_path).render
|
126
|
+
cur_dir = Pathname.new(file_name).dirname
|
127
|
+
up_to_root = File.join([".."] * (file_name.split("/").size - 1))
|
128
|
+
doc = Hpricot(source)
|
129
|
+
%w{src href action}.each do |attribute|
|
130
|
+
(doc/"*[@#{attribute}]").each do |tag|
|
131
|
+
next unless tag[attribute] =~ /\A\//
|
132
|
+
if true_file = resolve_path(cur_dir + up_to_root + tag[attribute].sub(/\A\//,""))
|
133
|
+
tag[attribute] = true_file.relative_path_from(cur_dir)
|
134
|
+
else
|
135
|
+
puts "Could not resolve link #{tag[attribute]} in #{file_name}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
File.open(target_path + file_name,"w"){|f| f.write(doc.to_original_html) }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
protected
|
146
|
+
|
147
|
+
def template_paths(path,partial_path=nil)
|
148
|
+
path = Pathname.new(path)
|
149
|
+
partial_path = partial_path && Pathname.new(partial_path) || (path + "../partials/").realpath
|
150
|
+
[path,partial_path]
|
151
|
+
end
|
152
|
+
|
153
|
+
def w3cvalidate(file)
|
154
|
+
validator = W3CValidator.new(File.read(file))
|
155
|
+
validator.validate!
|
156
|
+
if !options["show_valid"] && !validator.valid || options["show_valid"]
|
157
|
+
print "- #{file} "
|
158
|
+
print "(errors: #{validator.errors}, warnings: #{validator.warnings})\n"
|
159
|
+
end
|
160
|
+
validator.valid
|
161
|
+
end
|
162
|
+
|
163
|
+
def resolve_path(path)
|
164
|
+
path = Pathname.new(path) unless path.kind_of?(Pathname)
|
165
|
+
# Append index.html/index.htm/index.rhtml if it's a diretory
|
166
|
+
if path.directory?
|
167
|
+
search_files = %w{.html .htm}.map!{|p| path + "index#{p}" }
|
168
|
+
# If it ends with a slash or does not contain a . and it's not a directory
|
169
|
+
# try to add .html/.htm/.rhtml to see if that exists.
|
170
|
+
elsif (path =~ /\/$/) || (path =~ /^[^.]+$/)
|
171
|
+
search_files = [path.to_s + ".html", path.to_s + ".htm"].map!{|p| Pathname.new(p) }
|
172
|
+
else
|
173
|
+
search_files = [path]
|
174
|
+
end
|
175
|
+
search_files.find{|p| p.exist? }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'rack/response'
|
3
|
+
require 'rack/file'
|
4
|
+
|
5
|
+
module HtmlMockup
|
6
|
+
module Rack
|
7
|
+
class HtmlMockup
|
8
|
+
def initialize(root,partial_path)
|
9
|
+
@docroot = root
|
10
|
+
@partial_path = partial_path
|
11
|
+
@file_server = ::Rack::File.new(@docroot)
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
path = env["PATH_INFO"]
|
16
|
+
|
17
|
+
# Append index.html/index.htm/index.rhtml if it's a diretory
|
18
|
+
if File.directory?(File.join(@docroot,path))
|
19
|
+
search_files = %w{.html .htm .rhtml}.map!{|p| File.join(@docroot,path,"index#{p}")}
|
20
|
+
# If it's already a .html/.htm/.rhtml file, render that file
|
21
|
+
elsif (path =~ /\.r?html?$/)
|
22
|
+
search_files = [File.join(@docroot,path)]
|
23
|
+
# If it ends with a slash or does not contain a . and it's not a directory
|
24
|
+
# try to add .html/.htm/.rhtml to see if that exists.
|
25
|
+
elsif (path =~ /\/$/) || (path =~ /^[^.]+$/)
|
26
|
+
search_files = [path + ".html", path + ".htm", path + ".rhtml"].map!{|p| File.join(@docroot,p) }
|
27
|
+
# Otherwise don't render anything at all.
|
28
|
+
else
|
29
|
+
search_files = []
|
30
|
+
end
|
31
|
+
|
32
|
+
if template_path = search_files.find{|p| File.exist?(p)}
|
33
|
+
env["rack.errors"].puts "Rendering template #{template_path.inspect} (#{path.inspect})"
|
34
|
+
begin
|
35
|
+
templ = ::HtmlMockup::Template.open(template_path, :partial_path => @partial_path)
|
36
|
+
resp = ::Rack::Response.new do |res|
|
37
|
+
res.status = 200
|
38
|
+
res.write templ.render
|
39
|
+
end
|
40
|
+
resp.finish
|
41
|
+
rescue StandardError => e
|
42
|
+
env["rack.errors"].puts " #{e.message}"
|
43
|
+
resp = ::Rack::Response.new do |res|
|
44
|
+
res.status = 500
|
45
|
+
res.write "An error occurred"
|
46
|
+
end
|
47
|
+
resp.finish
|
48
|
+
end
|
49
|
+
else
|
50
|
+
env["rack.errors"].puts "Invoking file handler for #{path.inspect}"
|
51
|
+
@file_server.call(env)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'rack/response'
|
3
|
+
|
4
|
+
module HtmlMockup
|
5
|
+
module Rack
|
6
|
+
class HtmlValidator
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
resp = @app.call(env)
|
13
|
+
if resp[1]["Content-Type"].to_s.include?("html")
|
14
|
+
str = ""
|
15
|
+
resp[2].each{|c| str << c}
|
16
|
+
validator = W3CValidator.new(str)
|
17
|
+
validator.validate!
|
18
|
+
if !validator.valid
|
19
|
+
env["rack.errors"].puts "Validation failed on #{env["PATH_INFO"]}: (errors: #{validator.errors}, warnings: #{validator.warnings})"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
resp
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require File.dirname(__FILE__) + "/template"
|
3
|
+
require File.dirname(__FILE__) + "/w3c_validator"
|
4
|
+
require File.dirname(__FILE__) + "/rack/html_mockup"
|
5
|
+
require File.dirname(__FILE__) + "/rack/html_validator"
|
6
|
+
|
7
|
+
module HtmlMockup
|
8
|
+
class Server
|
9
|
+
attr_accessor :options,:server_options, :root, :partial_path
|
10
|
+
|
11
|
+
def initialize(root,partial_path,options={},server_options={})
|
12
|
+
@stack = ::Rack::Builder.new
|
13
|
+
|
14
|
+
@middleware = []
|
15
|
+
@root = root
|
16
|
+
@partial_path = partial_path
|
17
|
+
@options,@server_options = options,server_options
|
18
|
+
end
|
19
|
+
|
20
|
+
# Use the specified Rack middleware
|
21
|
+
def use(middleware, *args, &block)
|
22
|
+
@middleware << [middleware, args, block]
|
23
|
+
end
|
24
|
+
|
25
|
+
def handler
|
26
|
+
if self.options[:handler]
|
27
|
+
begin
|
28
|
+
@handler = ::Rack::Handler.get(self.options[:handler])
|
29
|
+
rescue LoadError
|
30
|
+
rescue NameError
|
31
|
+
end
|
32
|
+
if @handler.nil?
|
33
|
+
puts "Handler '#{self.options[:handler]}' not found, using fallback."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@handler ||= detect_rack_handler
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
self.handler.run self.application, @server_options do |server|
|
41
|
+
trap(:INT) do
|
42
|
+
## Use thins' hard #stop! if available, otherwise just #stop
|
43
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
44
|
+
puts "Bby HtmlMockup"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def application
|
50
|
+
return @app if @app
|
51
|
+
@stack.use ::Rack::ShowExceptions
|
52
|
+
@stack.use ::Rack::Lint
|
53
|
+
@stack.use ::Rack::ConditionalGet
|
54
|
+
|
55
|
+
@middleware.each { |c,a,b| @stack.use(c, *a, &b) }
|
56
|
+
|
57
|
+
@stack.use Rack::HtmlValidator if self.options["validate"]
|
58
|
+
@stack.run Rack::HtmlMockup.new(self.root, self.partial_path)
|
59
|
+
|
60
|
+
@app = @stack.to_app
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
# Sinatra's detect_rack_handler
|
67
|
+
def detect_rack_handler
|
68
|
+
servers = %w[mongrel thin webrick]
|
69
|
+
servers.each do |server_name|
|
70
|
+
begin
|
71
|
+
return ::Rack::Handler.get(server_name)
|
72
|
+
rescue LoadError
|
73
|
+
rescue NameError
|
74
|
+
end
|
75
|
+
end
|
76
|
+
raise "Server handler (#{servers.join(',')}) not found."
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'strscan'
|
3
|
+
require 'erb'
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
module HtmlMockup
|
7
|
+
|
8
|
+
class MissingPartial < StandardError; end
|
9
|
+
|
10
|
+
class Template
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def open(filename,options={})
|
14
|
+
raise "Unknown file #{filename}" unless File.exist?(filename)
|
15
|
+
self.new(File.read(filename),options.update(:target_file => filename))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns all available partials in path
|
19
|
+
def partials(path)
|
20
|
+
available_partials = {}
|
21
|
+
path = Pathname.new(path)
|
22
|
+
self.partial_files(path).inject({}) do |mem,f|
|
23
|
+
name = f.to_s.split(".",2)[0]
|
24
|
+
mem[name] = (path + f).read
|
25
|
+
mem
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def partial_files(path)
|
30
|
+
filter = "*.part.{?h,h}tml"
|
31
|
+
files = []
|
32
|
+
Dir.chdir(Pathname.new(path)) do
|
33
|
+
files = Dir.glob(filter)
|
34
|
+
end
|
35
|
+
files
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a new HtmlMockupTemplate
|
41
|
+
#
|
42
|
+
# ==== Parameters
|
43
|
+
# template<String>:: The template to parse
|
44
|
+
# options<Hash>:: See options
|
45
|
+
#
|
46
|
+
# ==== Options (optional)
|
47
|
+
# partial_path<String>:: Path where the partials reside (default: $0/../../partials)
|
48
|
+
#--
|
49
|
+
def initialize(template, options={})
|
50
|
+
defaults = {:partial_path => File.dirname(__FILE__) + "/../../partials/"}
|
51
|
+
@template = template
|
52
|
+
@options = defaults.update(options)
|
53
|
+
@scanner = StringScanner.new(@template)
|
54
|
+
raise "Partial path '#{self.options[:partial_path]}' not found" unless File.exist?(self.options[:partial_path])
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :template, :options, :scanner
|
58
|
+
|
59
|
+
# Renders the template and returns it as a string
|
60
|
+
#
|
61
|
+
# ==== Returns
|
62
|
+
# String:: The rendered template
|
63
|
+
#--
|
64
|
+
def render
|
65
|
+
out = ""
|
66
|
+
while (partial = self.parse_partial_tag!) do
|
67
|
+
tag,params,scanned = partial
|
68
|
+
# add new skipped content to output file
|
69
|
+
out << scanned
|
70
|
+
|
71
|
+
# scan until end of tag
|
72
|
+
current_content = self.scanner.scan_until(/<!-- \[STOP:#{tag}\] -->/)
|
73
|
+
out << (render_partial(tag,params) || current_content)
|
74
|
+
end
|
75
|
+
out << scanner.rest
|
76
|
+
end
|
77
|
+
|
78
|
+
def save(filename=self.options[:target_file])
|
79
|
+
File.open(filename,"w"){|f| f.write render}
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def available_partials(force=false)
|
85
|
+
return @_available_partials if @_available_partials && !force
|
86
|
+
@_available_partials = self.class.partials(self.options[:partial_path])
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_partial_tag!
|
90
|
+
params = {}
|
91
|
+
scanned = ""
|
92
|
+
begin_of_tag = self.scanner.scan_until(/<!-- \[START:/)
|
93
|
+
return nil unless begin_of_tag
|
94
|
+
scanned << begin_of_tag
|
95
|
+
scanned << tag = self.scanner.scan(/[a-z0-9_]+/)
|
96
|
+
if scanned_questionmark = self.scanner.scan(/\?/)
|
97
|
+
scanned << scanned_questionmark
|
98
|
+
scanned << raw_params = self.scanner.scan_until(/\] -->/)
|
99
|
+
raw_params.gsub!(/\] -->$/,"")
|
100
|
+
|
101
|
+
params = CGI.parse(raw_params)
|
102
|
+
params.keys.each{|k| params[k] = params[k].first }
|
103
|
+
else
|
104
|
+
scanned << self.scanner.scan_until(/\] -->/)
|
105
|
+
end
|
106
|
+
|
107
|
+
[tag,params,scanned]
|
108
|
+
end
|
109
|
+
|
110
|
+
def render_partial(tag,params)
|
111
|
+
unless self.available_partials[tag]
|
112
|
+
raise MissingPartial.new("Could not find partial '#{tag}' in partial path '#{@options[:partial_path]}'")
|
113
|
+
end
|
114
|
+
template = ERB.new(self.available_partials[tag])
|
115
|
+
context = TemplateContext.new(params)
|
116
|
+
"\n" + template.result(context.get_binding).rstrip + "\n<!-- [STOP:#{tag}] -->"
|
117
|
+
end
|
118
|
+
|
119
|
+
class TemplateContext
|
120
|
+
def initialize(params)
|
121
|
+
params.each do |k,v|
|
122
|
+
self.instance_variable_set("@#{k}",v)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
def get_binding; binding(); end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module HtmlMockup
|
7
|
+
class W3CValidator
|
8
|
+
|
9
|
+
ValidationUri = "http://validator.w3.org/check"
|
10
|
+
|
11
|
+
attr_reader :valid,:response,:errors,:warnings,:status
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def validation_uri
|
15
|
+
@uri ||= URI.parse(ValidationUri)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(html)
|
20
|
+
@html = html
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate!
|
24
|
+
@status = @warnings = @errors = @response = @valid = nil
|
25
|
+
options = {"output" => "json"}
|
26
|
+
query,headers = build_post_query(options)
|
27
|
+
response = self.request(:post,self.class.validation_uri.path,query,headers)
|
28
|
+
@status,@warnings,@errors = response["x-w3c-validator-status"],response["x-w3c-validator-warnings"].to_i,response["x-w3c-validator-errors"].to_i
|
29
|
+
|
30
|
+
if @status == "Valid" && @warnings == 0 && @errors == 0
|
31
|
+
return @valid = true
|
32
|
+
else
|
33
|
+
begin
|
34
|
+
@response = YAML.load(response.body)
|
35
|
+
rescue
|
36
|
+
end
|
37
|
+
return (@valid = (@errros == 0))
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def build_post_query(options)
|
45
|
+
boundary = "validate-this-content-please"
|
46
|
+
headers = {"Content-type" => "multipart/form-data, boundary=" + boundary + " "}
|
47
|
+
|
48
|
+
parts = []
|
49
|
+
options.each do |k,v|
|
50
|
+
parts << post_param(k,v)
|
51
|
+
end
|
52
|
+
parts << file_param("uploaded_file","index.html",@html,"text/html")
|
53
|
+
|
54
|
+
q = parts.map{|p| "--#{boundary}\r\n#{p}"}.join("") + "--#{boundary}--"
|
55
|
+
[q,headers]
|
56
|
+
end
|
57
|
+
|
58
|
+
def post_param(k,v)
|
59
|
+
"Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"\r\n\r\n#{v}\r\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
def file_param(k,filename,content,mime_type)
|
63
|
+
out = []
|
64
|
+
out << "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"; filename=\"#{filename}\""
|
65
|
+
out << "Content-Transfer-Encoding: binary"
|
66
|
+
out << "Content-Type: #{mime_type}"
|
67
|
+
out.join("\r\n") + "\r\n\r\n" + content + "\r\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
# Makes request to remote service.
|
74
|
+
def request(method, path, *arguments)
|
75
|
+
result = nil
|
76
|
+
result = http.send(method, path, *arguments)
|
77
|
+
handle_response(result)
|
78
|
+
rescue Timeout::Error => e
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
|
82
|
+
# Handles response and error codes from remote service.
|
83
|
+
def handle_response(response)
|
84
|
+
case response.code.to_i
|
85
|
+
when 301,302
|
86
|
+
raise "Redirect"
|
87
|
+
when 200...400
|
88
|
+
response
|
89
|
+
when 400
|
90
|
+
raise "Bad Request"
|
91
|
+
when 401
|
92
|
+
raise "Unauthorized Access"
|
93
|
+
when 403
|
94
|
+
raise "Forbidden Access"
|
95
|
+
when 404
|
96
|
+
raise "Rescoure not found"
|
97
|
+
when 405
|
98
|
+
raise "Method not allowed"
|
99
|
+
when 409
|
100
|
+
raise "Rescource conflict"
|
101
|
+
when 422
|
102
|
+
raise "Resource invalid"
|
103
|
+
when 401...500
|
104
|
+
raise "Client error"
|
105
|
+
when 500...600
|
106
|
+
raise "Server error"
|
107
|
+
else
|
108
|
+
raise "Unknown response: #{response.code.to_i}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def http
|
113
|
+
site = self.class.validation_uri
|
114
|
+
http = Net::HTTP.new(site.host, site.port)
|
115
|
+
# http.use_ssl = site.is_a?(URI::HTTPS)
|
116
|
+
# http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
|
117
|
+
http
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: html_mockup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Flurin Egger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-18 00:00:00 +01:00
|
13
|
+
default_executable: mockup
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thor
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.12.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rack
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.0
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: flurin@digitpaint.nl
|
37
|
+
executables:
|
38
|
+
- mockup
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README.rdoc
|
43
|
+
files:
|
44
|
+
- bin/mockup
|
45
|
+
- examples/config.ru
|
46
|
+
- examples/html/green.gif
|
47
|
+
- examples/html/index.html
|
48
|
+
- examples/partials/test.part.rhtml
|
49
|
+
- examples/script/server
|
50
|
+
- lib/html_mockup/cli.rb
|
51
|
+
- lib/html_mockup/rack/html_mockup.rb
|
52
|
+
- lib/html_mockup/rack/html_validator.rb
|
53
|
+
- lib/html_mockup/server.rb
|
54
|
+
- lib/html_mockup/template.rb
|
55
|
+
- lib/html_mockup/w3c_validator.rb
|
56
|
+
- README.rdoc
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://github.com/flurin/html_mockup
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --charset=UTF-8
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.5
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: HTML Mockup is a set of tools to create self-containing HTML mockups.
|
85
|
+
test_files: []
|
86
|
+
|