html_mockup 0.8.4 → 0.9.0

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