html_mockup 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|