flurin-html_mockup 0.1.1

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