flurin-html_mockup 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + "/../lib/html_mockup/cli"
4
+
5
+ HtmlMockup::Cli.start
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,3 @@
1
+ <div id="<%= @id || "fault" %>">
2
+ Think?
3
+ </div>
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require File.dirname(__FILE__) + "/../vendor/html_mockup/lib/html_mockup/cli"
5
+ rescue LoadError => e
6
+ require 'rubygems'
7
+ require 'html_mockup/cli'
8
+ end
9
+
10
+
11
+ a = ARGV.dup
12
+ a.unshift(File.dirname(__FILE__) + "/../html")
13
+ a.unshift("serve")
14
+ HtmlMockup::Cli.start(a)
@@ -0,0 +1,133 @@
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 => :optional, # Defaults to 9000
15
+ :partial_path => :optional, # Defaults to [directory]/../partials
16
+ :validate => :boolean # Automatically validate all HTML responses @ the w3c
17
+ def serve(path=".")
18
+ @path,@partial_path = template_paths(path,options["partial_path"])
19
+ require 'rack'
20
+ require File.dirname(__FILE__) + "/rack/html_mockup"
21
+ require File.dirname(__FILE__) + "/rack/html_validator"
22
+ chain = ::Rack::Builder.new do
23
+ use ::Rack::ShowExceptions
24
+ use ::Rack::Lint
25
+ end
26
+ chain.use Rack::HtmlValidator if options["validate"]
27
+ chain.run Rack::HtmlMockup.new(@path, @partial_path)
28
+
29
+ begin
30
+ server = ::Rack::Handler::Mongrel
31
+ rescue LoadError => e
32
+ server = ::Rack::Handler::WEBrick
33
+ end
34
+
35
+ server_options = {}
36
+ server_options[:Port] = options["port"] || "9000"
37
+
38
+ puts "Running #{server.inspect} on port #{server_options[:Port]}"
39
+ server.run chain.to_app, server_options
40
+ end
41
+
42
+ desc "validate [directory/file]", "Validates the file or all HTML in directory"
43
+ method_options :show_valid => :boolean, # Also print a line for each valid file
44
+ :filter => :optional # What files should be found, defaults to [^_]*.html
45
+ def validate(path=".")
46
+ filter = options["filter"] || "[^_]*.html"
47
+
48
+ puts "Filtering on #{options["filter"]}" if options["filter"]
49
+
50
+ if File.directory?(path)
51
+ any_invalid = false
52
+
53
+ if (files = Dir.glob("#{path}/**/#{filter}")).any?
54
+ files.each do |file|
55
+ if !self.w3cvalidate(file)
56
+ any_invalid = true
57
+ end
58
+ end
59
+ if !any_invalid
60
+ puts "All files were considered valid"
61
+ end
62
+ else
63
+ puts "No files matched \"#{filter}\""
64
+ end
65
+ elsif File.readable?(path)
66
+ self.w3cvalidate(path)
67
+ else
68
+ puts "No such file/directory #{path}"
69
+ end
70
+ end
71
+
72
+ desc "generate [directory]","Create a new HTML mockup directory tree in directory"
73
+ def generate(path)
74
+ path = Pathname.new(path)
75
+ if path.directory?
76
+ puts "Directory #{path} already exists, please only use this to create new mockups"
77
+ else
78
+ example_path = Pathname.new(File.dirname(__FILE__) + "/../../examples")
79
+ path.mkpath
80
+ html_path = path + "html"
81
+ mkdir(html_path)
82
+ mkdir(html_path + "stylesheets")
83
+ mkdir(html_path + "images")
84
+ mkdir(html_path + "javascripts")
85
+
86
+ mkdir(path + "partials")
87
+
88
+ mkdir(path + "script")
89
+ cp(example_path + "script/server",path + "script/server")
90
+ (path + "script/server").chmod(744)
91
+ end
92
+ end
93
+
94
+ desc "convert [directory]","Inject all partials, into all HTML files within directory"
95
+ method_options :partial_path => :optional, # Defaults to [directory]/../partials
96
+ :filter => :optional # What files should be converted defaults to *.html
97
+ def convert(path=".")
98
+ path,partial_path = template_paths(path,options["partial_path"])
99
+ filter = options["filter"] || "*.html"
100
+ puts "Converting #{filter} in #{path}"
101
+ puts " Taking partials from #{partial_path} (#{HtmlMockup::Template.partial_files(partial_path).size} found)"
102
+
103
+ if path.directory?
104
+ Dir.glob("#{path}/#{filter}").each do |file|
105
+ puts " Converting file: " + file
106
+ HtmlMockup::Template.open(file, :partial_path => partial_path).save
107
+ end
108
+ else
109
+ HtmlMockup::Template.open(path, :partial_path => partial_path).save
110
+ end
111
+
112
+ end
113
+
114
+
115
+ protected
116
+
117
+ def template_paths(path,partial_path=nil)
118
+ path = Pathname.new(path)
119
+ partial_path = partial_path && Pathname.new(partial_path) || (path + "../partials/").cleanpath
120
+ [path,partial_path]
121
+ end
122
+
123
+ def w3cvalidate(file)
124
+ validator = W3CValidator.new(File.read(file))
125
+ validator.validate!
126
+ if !options["show_valid"] && !validator.valid || options["show_valid"]
127
+ print "- #{file} "
128
+ print "(errors: #{validator.errors}, warnings: #{validator.warnings})\n"
129
+ end
130
+ validator.valid
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,47 @@
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
+ templ = ::HtmlMockup::Template.open(template_path, :partial_path => @partial_path)
35
+ resp = ::Rack::Response.new do |res|
36
+ res.status = 200
37
+ res.write templ.render
38
+ end
39
+ resp.finish
40
+ else
41
+ env["rack.errors"].puts "Invoking file handler for #{path.inspect}"
42
+ @file_server.call(env)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ 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,124 @@
1
+ require 'pathname'
2
+ require 'strscan'
3
+ require 'erb'
4
+ require 'cgi'
5
+
6
+ module HtmlMockup
7
+ class Template
8
+
9
+ class << self
10
+ def open(filename,options={})
11
+ raise "Unknown file #{filename}" unless File.exist?(filename)
12
+ self.new(File.read(filename),options.update(:target_file => filename))
13
+ end
14
+
15
+ # Returns all available partials in path
16
+ def partials(path)
17
+ available_partials = {}
18
+ path = Pathname.new(path)
19
+ self.partial_files(path).inject({}) do |mem,f|
20
+ name = f.to_s.split(".",2)[0]
21
+ mem[name] = (path + f).read
22
+ mem
23
+ end
24
+ end
25
+
26
+ def partial_files(path)
27
+ filter = "*.part.?html"
28
+ files = []
29
+ Dir.chdir(Pathname.new(path)) do
30
+ files = Dir.glob(filter)
31
+ end
32
+ files
33
+ end
34
+
35
+ end
36
+
37
+ # Create a new HtmlMockupTemplate
38
+ #
39
+ # ==== Parameters
40
+ # template<String>:: The template to parse
41
+ # options<Hash>:: See options
42
+ #
43
+ # ==== Options (optional)
44
+ # partial_path<String>:: Path where the partials reside (default: $0/../../partials)
45
+ #--
46
+ def initialize(template, options={})
47
+ defaults = {:partial_path => File.dirname(__FILE__) + "/../../partials/"}
48
+ @template = template
49
+ @options = defaults.update(options)
50
+ @scanner = StringScanner.new(@template)
51
+ raise "Partial path '#{self.options[:partial_path]}' not found" unless File.exist?(self.options[:partial_path])
52
+ end
53
+
54
+ attr_reader :template, :options, :scanner
55
+
56
+ # Renders the template and returns it as a string
57
+ #
58
+ # ==== Returns
59
+ # String:: The rendered template
60
+ #--
61
+ def render
62
+ out = ""
63
+ while (partial = self.parse_partial_tag!) do
64
+ tag,params,scanned = partial
65
+ # add new skipped content to output file
66
+ out << scanned
67
+
68
+ # scan until end of tag
69
+ current_content = self.scanner.scan_until(/<!-- \[STOP:#{tag}\] -->/)
70
+ out << (render_partial(tag,params) || current_content)
71
+ end
72
+ out << scanner.rest
73
+ end
74
+
75
+ def save(filename=self.options[:target_file])
76
+ File.open(filename,"w"){|f| f.write render}
77
+ end
78
+
79
+ protected
80
+
81
+ def available_partials(force=false)
82
+ return @_available_partials if @_available_partials && !force
83
+ @_available_partials = self.class.partials(self.options[:partial_path])
84
+ end
85
+
86
+ def parse_partial_tag!
87
+ params = {}
88
+ scanned = ""
89
+ begin_of_tag = self.scanner.scan_until(/<!-- \[START:/)
90
+ return nil unless begin_of_tag
91
+ scanned << begin_of_tag
92
+ scanned << tag = self.scanner.scan(/[a-z0-9_]+/)
93
+ if scanned_questionmark = self.scanner.scan(/\?/)
94
+ scanned << scanned_questionmark
95
+ scanned << raw_params = self.scanner.scan_until(/\] -->/)
96
+ raw_params.gsub!(/\] -->$/,"")
97
+
98
+ params = CGI.parse(raw_params)
99
+ params.keys.each{|k| params[k] = params[k].first }
100
+ else
101
+ scanned << self.scanner.scan_until(/\] -->/)
102
+ end
103
+
104
+ [tag,params,scanned]
105
+ end
106
+
107
+ def render_partial(tag,params)
108
+ return nil unless self.available_partials[tag]
109
+ template = ERB.new(self.available_partials[tag])
110
+ context = TemplateContext.new(params)
111
+ "\n" + template.result(context.get_binding).rstrip + "\n<!-- [STOP:#{tag}] -->"
112
+ end
113
+
114
+ class TemplateContext
115
+ def initialize(params)
116
+ params.each do |k,v|
117
+ self.instance_variable_set("@#{k}",v)
118
+ end
119
+ end
120
+ def get_binding; binding(); end
121
+ end
122
+
123
+ end
124
+ 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,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flurin-html_mockup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Flurin Egger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-14 00:00:00 -07: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.9.9
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: 0.3.0
34
+ version:
35
+ description:
36
+ email: f.p.egger@gmail.com
37
+ executables:
38
+ - mockup
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - bin/mockup
45
+ - examples/html/green.gif
46
+ - examples/html/index.html
47
+ - examples/partials/test.part.rhtml
48
+ - examples/script/server
49
+ - lib/html_mockup/cli.rb
50
+ - lib/html_mockup/rack/html_mockup.rb
51
+ - lib/html_mockup/rack/html_validator.rb
52
+ - lib/html_mockup/template.rb
53
+ - lib/html_mockup/w3c_validator.rb
54
+ - README.rdoc
55
+ has_rdoc: false
56
+ homepage: http://github.com/flurin/html_mockup
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.2.0
78
+ signing_key:
79
+ specification_version: 2
80
+ summary: HTML Mockup is a set of tools to create self-containing HTML mockups.
81
+ test_files: []
82
+