html_mockup 0.8.4 → 0.9.0

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.
Files changed (50) hide show
  1. data/.gitignore +2 -2
  2. data/.travis.yml +12 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +5 -0
  5. data/README.md +95 -0
  6. data/Rakefile +9 -4
  7. data/bin/mockup +1 -1
  8. data/doc/cli.md +46 -0
  9. data/doc/mockupfile.md +3 -0
  10. data/doc/templating.md +88 -0
  11. data/html_mockup.gemspec +8 -4
  12. data/lib/html_mockup/cli.rb +57 -65
  13. data/lib/html_mockup/cli/command.rb +23 -0
  14. data/lib/html_mockup/cli/generate.rb +5 -0
  15. data/lib/html_mockup/cli/release.rb +10 -0
  16. data/lib/html_mockup/cli/serve.rb +29 -0
  17. data/lib/html_mockup/generators.rb +18 -1
  18. data/lib/html_mockup/generators/generator.rb +23 -0
  19. data/lib/html_mockup/generators/new.rb +4 -3
  20. data/lib/html_mockup/generators/templates/generator.tt +13 -0
  21. data/lib/html_mockup/project.rb +11 -11
  22. data/lib/html_mockup/release.rb +3 -3
  23. data/lib/html_mockup/resolver.rb +51 -28
  24. data/lib/html_mockup/template.rb +95 -11
  25. data/roger.gemspec +29 -0
  26. data/test/project/Gemfile +2 -1
  27. data/test/project/Gemfile.lock +17 -12
  28. data/test/project/Mockupfile +3 -0
  29. data/test/project/html/formats/index.html +1 -0
  30. data/test/project/html/formats/json.json.erb +0 -0
  31. data/test/project/html/layouts/content-for.html.erb +17 -0
  32. data/test/project/html/mockup/encoding.html +3 -0
  33. data/test/project/html/partials/load_path.html.erb +3 -0
  34. data/test/project/layouts/test.html.erb +8 -1
  35. data/test/project/layouts/yield.html.erb +1 -0
  36. data/test/project/lib/generators/test.rb +9 -0
  37. data/test/project/partials/formats/erb.html.erb +1 -0
  38. data/test/project/partials/partials-test.html.erb +1 -0
  39. data/test/project/partials/test/front_matter.html.erb +1 -0
  40. data/test/project/partials/test/json.json.erb +1 -0
  41. data/test/project/partials/test/simple.html.erb +1 -0
  42. data/test/project/partials2/partials2-test.html.erb +1 -0
  43. data/test/unit/cli_test.rb +12 -0
  44. data/test/unit/generators_test.rb +75 -0
  45. data/test/unit/release/cleaner_test.rb +13 -8
  46. data/test/unit/resolver_test.rb +92 -0
  47. data/test/unit/template_test.rb +127 -0
  48. metadata +100 -8
  49. data/README.rdoc +0 -89
  50. data/test/generator-subcommand.rb +0 -54
@@ -0,0 +1,23 @@
1
+ module HtmlMockup
2
+
3
+ class Cli::Command < Thor::Group
4
+
5
+ class_option :verbose,
6
+ :desc => "Set's verbose output",
7
+ :aliases => ["-v"],
8
+ :default => false,
9
+ :type => :boolean
10
+
11
+ def initialize_project
12
+ @project = Cli::Base.project
13
+ end
14
+
15
+ protected
16
+
17
+ def project_banner(project)
18
+ puts " Html: \"#{project.html_path}\""
19
+ puts " Partials: \"#{project.partial_path}\""
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,5 @@
1
+ module HtmlMockup
2
+ class Cli::Generate < Thor
3
+
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ module HtmlMockup
2
+ class Cli::Release < Cli::Command
3
+
4
+ desc "Release the current project"
5
+
6
+ def release
7
+ @project.release.run!
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,29 @@
1
+ module HtmlMockup
2
+ class Cli::Serve < Cli::Command
3
+
4
+
5
+ desc "Serve the current project"
6
+
7
+ class_options :port => :string, # Defaults to 9000
8
+ :handler => :string, # The handler to use (defaults to mongrel)
9
+ :validate => :boolean # Run validation?
10
+
11
+ def serve
12
+
13
+ server_options = {}
14
+ options.each{|k,v| server_options[k.to_sym] = v }
15
+ server_options[:server] = {}
16
+ [:port, :handler, :validate].each do |k|
17
+ server_options[:server][k] = server_options.delete(k) if server_options.has_key?(k)
18
+ end
19
+
20
+ server = @project.server
21
+ server.set_options(server_options[:server])
22
+
23
+ puts "Running HtmlMockup with #{server.handler.inspect} on port #{server.port}"
24
+ puts project_banner(@project)
25
+
26
+ server.run!
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,23 @@
1
+ require 'thor'
2
+ require 'thor/group'
3
+
1
4
  module HtmlMockup
2
5
  module Generators
6
+
7
+ class Base < Cli::Command
8
+ def self.register(sub)
9
+ name = sub.to_s.sub(/Generator$/, "").sub(/^.*Generators::/,"").downcase
10
+ usage = "#{name} #{sub.arguments.map{ |arg| arg.banner }.join(" ")}"
11
+ long_desc = sub.desc || "Run #{name} generator"
12
+
13
+ Cli::Generate.register sub, name, usage, long_desc
14
+ Cli::Generate.tasks[name].options = sub.class_options if sub.class_options
15
+ end
16
+ end
17
+
3
18
  end
4
19
  end
5
20
 
6
- require File.dirname(__FILE__) + "/generators/new"
21
+ # Default generators
22
+ require File.dirname(__FILE__) + "/generators/new"
23
+ require File.dirname(__FILE__) + "/generators/generator"
@@ -0,0 +1,23 @@
1
+ class HtmlMockup::Generators::GeneratorGenerator < HtmlMockup::Generators::Base
2
+
3
+ include Thor::Actions
4
+
5
+ desc "Create your own generator for html_mockup"
6
+ argument :name, :type => :string, :required => true, :desc => "Name of the new generator"
7
+ argument :path, :type => :string, :required => true, :desc => "Path to generate the new generator"
8
+ # class_option :template, :type => :string, :aliases => ["-t"], :desc => "Template to use, can be a path or a git repository remote, uses built in minimal as default"
9
+
10
+ def self.source_root
11
+ File.dirname(__FILE__)
12
+ end
13
+
14
+ def create_lib_file
15
+ destination = "#{path}/#{name}_generator.rb"
16
+ template('templates/generator.tt', destination)
17
+ say "Add `require #{destination}` to your mockup file and run mockup generate #{name}."
18
+ end
19
+
20
+
21
+ end
22
+
23
+ HtmlMockup::Generators::Base.register HtmlMockup::Generators::GeneratorGenerator
@@ -1,6 +1,6 @@
1
1
  require 'shellwords'
2
2
 
3
- class HtmlMockup::Generators::New < Thor::Group
3
+ class HtmlMockup::Generators::NewGenerator < Thor::Group
4
4
 
5
5
  include Thor::Actions
6
6
 
@@ -62,5 +62,6 @@ class HtmlMockup::Generators::New < Thor::Group
62
62
  directory(".", ".")
63
63
  end
64
64
 
65
-
66
- end
65
+ end
66
+
67
+ HtmlMockup::Generators::Base.register HtmlMockup::Generators::NewGenerator
@@ -0,0 +1,13 @@
1
+ class <%= name.capitalize %>Generator < HtmlMockup::Generators::Base
2
+
3
+ include Thor::Actions
4
+
5
+ desc "<%= name.capitalize %> generator helps you doing ... and ..."
6
+ argument :path, :type => :string, :required => true, :desc => "Path to generate the new generator"
7
+
8
+ def create_stuff
9
+ # your stuff
10
+ end
11
+ end
12
+
13
+ HtmlMockup::Generators::Base.register <%= name.capitalize %>Generator
@@ -33,8 +33,6 @@ module HtmlMockup
33
33
  self.layouts_path = @options[:layouts_path]
34
34
  self.shell = @options[:shell]
35
35
 
36
-
37
- load_dependencies!
38
36
  load_mockup!
39
37
  end
40
38
 
@@ -57,27 +55,29 @@ module HtmlMockup
57
55
  end
58
56
 
59
57
  def partial_path=(p)
60
- @partial_path = self.realpath_or_path(p)
58
+ @partial_path = self.single_or_multiple_paths(p)
61
59
  end
62
60
  alias :partials_path :partial_path
63
61
  alias :partials_path= :partial_path=
64
62
 
65
63
  def layouts_path=(p)
66
- @layouts_path = self.realpath_or_path(p)
64
+ @layouts_path = self.single_or_multiple_paths(p)
67
65
  end
68
66
 
69
67
  protected
70
-
71
- def load_dependencies!
72
- if Object.const_defined?(:Bundler)
73
- Bundler.require(:default)
74
- end
75
- end
76
-
68
+
77
69
  def load_mockup!
78
70
  @mockupfile = Mockupfile.new(self)
79
71
  @mockupfile.load
80
72
  end
73
+
74
+ def single_or_multiple_paths(p)
75
+ if p.kind_of?(Array)
76
+ p.map{|tp| self.realpath_or_path(tp) }
77
+ else
78
+ self.realpath_or_path(p)
79
+ end
80
+ end
81
81
 
82
82
  def realpath_or_path(path)
83
83
  path = Pathname.new(path)
@@ -339,8 +339,8 @@ module HtmlMockup
339
339
 
340
340
  commenters = {
341
341
  :html => Proc.new{|s| "<!-- #{s} -->" },
342
- :css => Proc.new{|s| "/* #{s} */" },
343
- :js => Proc.new{|s| "/* #{s} */" }
342
+ :css => Proc.new{|s| "/*! #{s} */" },
343
+ :js => Proc.new{|s| "/*! #{s} */" }
344
344
  }
345
345
 
346
346
  commenter = commenters[options[:style]] || commenters[:js]
@@ -360,4 +360,4 @@ require File.dirname(__FILE__) + "/release/scm"
360
360
  require File.dirname(__FILE__) + "/release/injector"
361
361
  require File.dirname(__FILE__) + "/release/cleaner"
362
362
  require File.dirname(__FILE__) + "/release/finalizers"
363
- require File.dirname(__FILE__) + "/release/processors"
363
+ require File.dirname(__FILE__) + "/release/processors"
@@ -1,36 +1,55 @@
1
1
  module HtmlMockup
2
2
  class Resolver
3
+
4
+ attr_reader :load_paths
3
5
 
4
- def initialize(path)
5
- raise ArgumentError, "Resolver base path can't be nil" if path.nil?
6
- @base = Pathname.new(path)
6
+ def initialize(paths)
7
+ raise ArgumentError, "Resolver base path can't be nil" if paths.nil?
8
+
9
+ # Convert to paths
10
+ @load_paths = [paths].flatten.map{|p| Pathname.new(p) }
7
11
  end
8
12
 
9
13
  # @param [String] url The url to resolve to a path
10
- # @param [true,false] exact_match Wether or not to match exact paths, this is mainly used in the path_to_url method to match .js, .css, etc files.
11
- def find_template(url, exact_match = false)
12
- path, qs, anch = strip_query_string_and_anchor(url.to_s)
13
-
14
- path = File.join(@base, path)
14
+ # @param [Hash] options Options
15
+ #
16
+ # @option options [true,false] :exact_match Wether or not to match exact paths, this is mainly used in the path_to_url method to match .js, .css, etc files.
17
+ # @option options [String] :preferred_extension The part to chop off and re-add to search for more complex double-extensions. (Makes it possible to have context aware partials)
18
+ def find_template(url, options = {})
19
+ orig_path, qs, anch = strip_query_string_and_anchor(url.to_s)
20
+
21
+ options = {
22
+ :exact_match => false,
23
+ :preferred_extension => "html"
24
+ }.update(options)
25
+
26
+ paths = self.load_paths.map{|base| File.join(base, orig_path) }
15
27
 
16
- if exact_match && File.exist?(path)
17
- return Pathname.new(path)
18
- end
19
-
20
- # It's a directory, add "/index"
21
- if File.directory?(path)
22
- path = File.join(path, "index")
23
- end
24
-
25
- # 2. If it's .html,we strip of the extension
26
- if path =~ /\.html\Z/
27
- path.sub!(/\.html\Z/, "")
28
- end
29
-
30
- extensions = Tilt.mappings.keys + Tilt.mappings.keys.map{|ext| "html.#{ext}"}
28
+ paths.find do |path|
29
+ if options[:exact_match] && File.exist?(path)
30
+ return Pathname.new(path)
31
+ end
32
+
33
+ # It's a directory, add "/index"
34
+ if File.directory?(path)
35
+ path = File.join(path, "index")
36
+ end
37
+
38
+ # 2. If it's preferred_extension, we strip of the extension
39
+ if path =~ /\.#{options[:preferred_extension]}\Z/
40
+ path.sub!(/\.#{options[:preferred_extension]}\Z/, "")
41
+ end
42
+
43
+ extensions = Tilt.default_mapping.template_map.keys + Tilt.default_mapping.lazy_map.keys
31
44
 
32
- if found_extension = extensions.find { |ext| File.exist?(path + "." + ext) }
33
- Pathname.new(path + "." + found_extension)
45
+ # We have to re-add preferred_extension again as we have stripped it in in step 2!
46
+ extensions += extensions.map{|ext| "#{options[:preferred_extension]}.#{ext}"}
47
+
48
+ if found_extension = extensions.find { |ext| File.exist?(path + "." + ext) }
49
+ return Pathname.new(path + "." + found_extension)
50
+ end
51
+
52
+ false #Next iteration
34
53
  end
35
54
  end
36
55
  alias :url_to_path :find_template
@@ -39,11 +58,15 @@ module HtmlMockup
39
58
  # Convert a disk path on file to an url
40
59
  def path_to_url(path, relative_to = nil)
41
60
 
42
- path = Pathname.new(path).relative_path_from(@base).cleanpath
61
+ # Find the parent path we're in
62
+ path = Pathname.new(path).realpath
63
+ base = self.load_paths.find{|lp| path.to_s =~ /\A#{Regexp.escape(lp.realpath.to_s)}/ }
64
+
65
+ path = path.relative_path_from(base).cleanpath
43
66
 
44
67
  if relative_to
45
68
  if relative_to.to_s =~ /\A\//
46
- relative_to = Pathname.new(File.dirname(relative_to.to_s)).relative_path_from(@base).cleanpath
69
+ relative_to = Pathname.new(File.dirname(relative_to.to_s)).relative_path_from(base).cleanpath
47
70
  else
48
71
  relative_to = Pathname.new(File.dirname(relative_to.to_s))
49
72
  end
@@ -62,7 +85,7 @@ module HtmlMockup
62
85
  path, qs, anch = strip_query_string_and_anchor(url)
63
86
 
64
87
  # Get disk path
65
- if true_path = self.url_to_path(path, true)
88
+ if true_path = self.url_to_path(path, :exact_match => true)
66
89
  path = self.path_to_url(true_path, relative_to_path)
67
90
  path += qs if qs
68
91
  path += anch if anch
@@ -1,9 +1,13 @@
1
1
  require 'tilt'
2
+ require 'mime/types'
2
3
  require 'yaml'
3
4
  require 'ostruct'
4
5
 
5
6
  require File.dirname(__FILE__) + "/mockup_template"
6
7
 
8
+ # We're enforcing Encoding to UTF-8
9
+ Encoding.default_external = "UTF-8"
10
+
7
11
  module HtmlMockup
8
12
 
9
13
  class Template
@@ -44,28 +48,79 @@ module HtmlMockup
44
48
 
45
49
  def render(env = {})
46
50
  context = TemplateContext.new(self, env)
47
- locals = {:document => OpenStruct.new(self.data)}
48
51
 
49
52
  if @layout_template
50
- @layout_template.render(context, locals) do
51
- self.template.render(context, locals)
53
+ content_for_layout = self.template.render(context, {}) # yields
54
+
55
+ @layout_template.render(context, {}) do |content_for|
56
+ if content_for
57
+ context._content_for_blocks[content_for]
58
+ else
59
+ content_for_layout
60
+ end
52
61
  end
53
62
  else
54
- self.template.render(context, locals)
63
+ self.template.render(context, {})
55
64
  end
56
65
  end
57
66
 
58
67
  def find_template(name, path_type)
59
68
  raise(ArgumentError, "path_type must be one of :partials_path or :layouts_path") unless [:partials_path, :layouts_path].include?(path_type)
60
69
 
70
+ return nil unless @options[path_type]
71
+
61
72
  @resolvers ||= {}
62
73
  @resolvers[path_type] ||= Resolver.new(@options[path_type])
63
74
 
64
- @resolvers[path_type].url_to_path(name)
65
- end
75
+ @resolvers[path_type].find_template(name, :preferred_extension => self.target_extension)
76
+ end
77
+
78
+ # Try to infer the final extension of the output file.
79
+ def target_extension
80
+ return @target_extension if @target_extension
81
+
82
+ if type = MIME::Types[self.target_mime_type].first
83
+ # Dirty little hack to enforce the use of .html instead of .htm
84
+ if type.sub_type == "html"
85
+ @target_extension = "html"
86
+ else
87
+ @target_extension = type.extensions.first
88
+ end
89
+ else
90
+ @target_extension = File.extname(self.source_path.to_s).sub(/^\./, "")
91
+ end
92
+ end
93
+
94
+ def source_extension
95
+ parts = File.basename(File.basename(self.source_path.to_s)).split(".")
96
+ if parts.size > 2
97
+ parts[-2..-1].join(".")
98
+ else
99
+ File.extname(self.source_path.to_s).sub(/^\./, "")
100
+ end
101
+ end
102
+
103
+ # Try to figure out the mime type based on the Tilt class and if that doesn't
104
+ # work we try to infer the type by looking at extensions (needed for .erb)
105
+ def target_mime_type
106
+ mime = self.template.class.default_mime_type
107
+ return mime if mime
108
+
109
+ path = File.basename(self.source_path.to_s)
110
+ mime = MIME::Types.type_for(path).first
111
+ return mime.to_s if mime
112
+
113
+ parts = File.basename(path).split(".")
114
+ if parts.size > 2
115
+ mime = MIME::Types.type_for(parts[0..-2].join(".")).first
116
+ return mime.to_s if mime
117
+ else
118
+ nil
119
+ end
120
+ end
66
121
 
67
122
  protected
68
-
123
+
69
124
  # Get the front matter portion of the file and extract it.
70
125
  def extract_front_matter(source)
71
126
  fm_regex = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
@@ -91,26 +146,55 @@ module HtmlMockup
91
146
  end
92
147
 
93
148
  class TemplateContext
94
-
149
+ attr_accessor :_content_for_blocks
150
+
95
151
  def initialize(template, env={})
96
- @_template, @_env = template, env
152
+ @_template, @_env = template, @_content_for_blocks = {}, env
97
153
  end
98
154
 
155
+ # The current HtmlMockup::Template in use
99
156
  def template
100
157
  @_template
101
158
  end
159
+
160
+ # Access to the front-matter of the document (if any)
161
+ def document
162
+ @_data ||= OpenStruct.new(self.template.data)
163
+ end
102
164
 
165
+ # The current environment variables.
103
166
  def env
104
167
  @_env
105
168
  end
169
+
170
+ # Capture content in blocks in the template for later use in the layout.
171
+ # Currently only works in ERB templates. Use like this in the template:
172
+ #
173
+ # ```
174
+ # <% content_for :name %> bla bla <% end %>
175
+ # ```
176
+ #
177
+ # Place it like this in the layout:
178
+ #
179
+ # ```
180
+ # <%= yield :name %>
181
+ # ```
182
+ def content_for(block_name, &capture)
183
+ raise ArgumentError, "content_for works only with ERB Templates" if !self.template.template.kind_of?(Tilt::ERBTemplate)
184
+ eval "@_erbout_tmp = _erbout", capture.binding
185
+ eval "_erbout = \"\"", capture.binding
186
+ t = Tilt::ERBTemplate.new(){ "<%= yield %>" }
187
+ @_content_for_blocks[block_name] = t.render(&capture)
188
+ return nil
189
+ ensure
190
+ eval "_erbout = @_erbout_tmp", capture.binding
191
+ end
106
192
 
107
193
  def partial(name, options = {})
108
194
  if template_path = self.template.find_template(name, :partials_path)
109
- # puts "Rendering partial #{name}, with template #{template_path}"
110
195
  partial_template = Tilt.new(template_path.to_s)
111
196
  partial_template.render(self, options[:locals] || {})
112
197
  elsif template_path = self.template.find_template(name + ".part", :partials_path)
113
- # puts "Rendering old-style partial #{name}, with template #{template_path}"
114
198
  template = Tilt::ERBTemplate.new(template_path.to_s)
115
199
  context = MockupTemplate::TemplateContext.new(options[:locals] || {})
116
200
  template.render(context, :env => self.env)