nanoc 2.0.4 → 2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/ChangeLog +31 -1
  2. data/LICENSE +1 -1
  3. data/README +63 -3
  4. data/Rakefile +59 -12
  5. data/bin/nanoc +7 -199
  6. data/lib/nanoc.rb +83 -12
  7. data/lib/nanoc/base/asset.rb +113 -0
  8. data/lib/nanoc/base/asset_defaults.rb +21 -0
  9. data/lib/nanoc/base/asset_rep.rb +277 -0
  10. data/lib/nanoc/base/binary_filter.rb +44 -0
  11. data/lib/nanoc/base/code.rb +41 -0
  12. data/lib/nanoc/base/compiler.rb +46 -34
  13. data/lib/nanoc/base/core_ext/hash.rb +51 -7
  14. data/lib/nanoc/base/core_ext/string.rb +8 -0
  15. data/lib/nanoc/base/data_source.rb +253 -20
  16. data/lib/nanoc/base/defaults.rb +30 -0
  17. data/lib/nanoc/base/enhancements.rb +9 -84
  18. data/lib/nanoc/base/filter.rb +109 -6
  19. data/lib/nanoc/base/layout.rb +91 -0
  20. data/lib/nanoc/base/notification_center.rb +66 -0
  21. data/lib/nanoc/base/page.rb +94 -126
  22. data/lib/nanoc/base/page_defaults.rb +20 -0
  23. data/lib/nanoc/base/page_rep.rb +318 -0
  24. data/lib/nanoc/base/plugin.rb +57 -9
  25. data/lib/nanoc/base/proxies/asset_proxy.rb +29 -0
  26. data/lib/nanoc/base/proxies/asset_rep_proxy.rb +26 -0
  27. data/lib/nanoc/base/proxies/layout_proxy.rb +25 -0
  28. data/lib/nanoc/base/proxies/page_proxy.rb +35 -0
  29. data/lib/nanoc/base/proxies/page_rep_proxy.rb +28 -0
  30. data/lib/nanoc/base/proxy.rb +37 -0
  31. data/lib/nanoc/base/router.rb +72 -0
  32. data/lib/nanoc/base/site.rb +219 -88
  33. data/lib/nanoc/base/template.rb +64 -0
  34. data/lib/nanoc/binary_filters/image_science_thumbnail.rb +28 -0
  35. data/lib/nanoc/cli.rb +1 -0
  36. data/lib/nanoc/cli/base.rb +219 -0
  37. data/lib/nanoc/cli/cli.rb +16 -0
  38. data/lib/nanoc/cli/command.rb +105 -0
  39. data/lib/nanoc/cli/commands/autocompile.rb +80 -0
  40. data/lib/nanoc/cli/commands/compile.rb +273 -0
  41. data/lib/nanoc/cli/commands/create_layout.rb +85 -0
  42. data/lib/nanoc/cli/commands/create_page.rb +85 -0
  43. data/lib/nanoc/cli/commands/create_site.rb +327 -0
  44. data/lib/nanoc/cli/commands/create_template.rb +76 -0
  45. data/lib/nanoc/cli/commands/help.rb +69 -0
  46. data/lib/nanoc/cli/commands/info.rb +114 -0
  47. data/lib/nanoc/cli/commands/switch.rb +141 -0
  48. data/lib/nanoc/cli/commands/update.rb +91 -0
  49. data/lib/nanoc/cli/ext.rb +37 -0
  50. data/lib/nanoc/cli/logger.rb +66 -0
  51. data/lib/nanoc/cli/option_parser.rb +168 -0
  52. data/lib/nanoc/data_sources/filesystem.rb +645 -224
  53. data/lib/nanoc/data_sources/filesystem_combined.rb +495 -0
  54. data/lib/nanoc/extra/auto_compiler.rb +265 -0
  55. data/lib/nanoc/extra/context.rb +22 -0
  56. data/lib/nanoc/extra/core_ext/hash.rb +54 -0
  57. data/lib/nanoc/extra/core_ext/time.rb +13 -0
  58. data/lib/nanoc/extra/file_proxy.rb +29 -0
  59. data/lib/nanoc/extra/vcs.rb +48 -0
  60. data/lib/nanoc/extra/vcses/bazaar.rb +21 -0
  61. data/lib/nanoc/extra/vcses/dummy.rb +20 -0
  62. data/lib/nanoc/extra/vcses/git.rb +21 -0
  63. data/lib/nanoc/extra/vcses/mercurial.rb +21 -0
  64. data/lib/nanoc/extra/vcses/subversion.rb +21 -0
  65. data/lib/nanoc/filters/bluecloth.rb +13 -0
  66. data/lib/nanoc/filters/erb.rb +6 -22
  67. data/lib/nanoc/filters/erubis.rb +14 -0
  68. data/lib/nanoc/filters/haml.rb +7 -23
  69. data/lib/nanoc/filters/markaby.rb +5 -5
  70. data/lib/nanoc/filters/maruku.rb +14 -0
  71. data/lib/nanoc/filters/old.rb +19 -0
  72. data/lib/nanoc/filters/rdiscount.rb +13 -0
  73. data/lib/nanoc/filters/rdoc.rb +5 -4
  74. data/lib/nanoc/filters/redcloth.rb +14 -0
  75. data/lib/nanoc/filters/rubypants.rb +14 -0
  76. data/lib/nanoc/filters/sass.rb +13 -0
  77. data/lib/nanoc/helpers/blogging.rb +170 -0
  78. data/lib/nanoc/helpers/capturing.rb +59 -0
  79. data/lib/nanoc/helpers/html_escape.rb +23 -0
  80. data/lib/nanoc/helpers/link_to.rb +69 -0
  81. data/lib/nanoc/helpers/render.rb +47 -0
  82. data/lib/nanoc/helpers/tagging.rb +52 -0
  83. data/lib/nanoc/helpers/xml_sitemap.rb +58 -0
  84. data/lib/nanoc/routers/default.rb +54 -0
  85. data/lib/nanoc/routers/no_dirs.rb +66 -0
  86. data/lib/nanoc/routers/versioned.rb +79 -0
  87. metadata +112 -22
  88. data/lib/nanoc/base/auto_compiler.rb +0 -132
  89. data/lib/nanoc/base/layout_processor.rb +0 -33
  90. data/lib/nanoc/base/page_proxy.rb +0 -31
  91. data/lib/nanoc/base/plugin_manager.rb +0 -33
  92. data/lib/nanoc/data_sources/database.rb +0 -259
  93. data/lib/nanoc/data_sources/trivial.rb +0 -145
  94. data/lib/nanoc/filters/markdown.rb +0 -13
  95. data/lib/nanoc/filters/smartypants.rb +0 -13
  96. data/lib/nanoc/filters/textile.rb +0 -13
  97. data/lib/nanoc/layout_processors/erb.rb +0 -35
  98. data/lib/nanoc/layout_processors/haml.rb +0 -38
  99. data/lib/nanoc/layout_processors/markaby.rb +0 -16
@@ -0,0 +1,59 @@
1
+ module Nanoc::Helpers
2
+
3
+ # Nanoc::Helpers::Capturing provides a content_for method, which allows
4
+ # content to be "captured" on one page and reused elsewhere.
5
+ #
6
+ # = Example
7
+ #
8
+ # For example, suppose you want the sidebar of your site to contain a short
9
+ # summary of the page. You could put the summary in the meta file, but
10
+ # that’s not possible when the summary contains eRuby. You could also put
11
+ # the sidebar inside the actual page, but that’s not very pretty. Instead,
12
+ # you write the summary on the page itself, but capture it, and print it in
13
+ # the sidebar layout.
14
+ #
15
+ # Captured content becomes part of the page. For example, a sidebar layout
16
+ # could look like this:
17
+ #
18
+ # <div id="sidebar">
19
+ # <h3>Summary</h3>
20
+ # <%= @page.content_for_summary || '(no summary)' %>
21
+ # </div>
22
+ #
23
+ # To put something inside that content_for_summary variable, capture it
24
+ # using the content_for function. In the about page, for example:
25
+ #
26
+ # <% content_for :summary do %>
27
+ # <p>On this page, nanoc is introduced, blah blah.</p>
28
+ # <% end %>
29
+ #
30
+ # When the site is compiled, the sidebar of the about page will say “On
31
+ # this page, the purpose of nanoc is described, blah blah blah,” as
32
+ # expected.
33
+ module Capturing
34
+
35
+ # Captures the content inside the block into a page attribute named
36
+ # "content_for_" followed by the given name. The content of the block
37
+ # itself will not be outputted.
38
+ def content_for(name, &block)
39
+ eval("@page[:content_for_#{name.to_s}] = capture(&block)")
40
+ end
41
+
42
+ private
43
+
44
+ def capture(*args, &block)
45
+ buffer = eval('_erbout', block.binding)
46
+
47
+ pos = buffer.length
48
+ block.call(*args)
49
+
50
+ data = buffer[pos..-1]
51
+
52
+ buffer[pos..-1] = ''
53
+
54
+ data
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,23 @@
1
+ module Nanoc::Helpers
2
+
3
+ # Nanoc::Helpers::HTMLEscape contains functionality for HTML-escaping
4
+ # strings.
5
+ module HTMLEscape
6
+
7
+ # Returns the HTML-escaped representation of the given string. Only &, <,
8
+ # > and " are escaped.
9
+ def html_escape(string)
10
+ string.gsub('&', '&amp;').
11
+ gsub('<', '&lt;').
12
+ gsub('>', '&gt;').
13
+ gsub('"', '&quot;')
14
+ end
15
+
16
+ alias h html_escape
17
+
18
+ end
19
+
20
+ end
21
+
22
+ # Include by default
23
+ include Nanoc::Helpers::HTMLEscape
@@ -0,0 +1,69 @@
1
+ module Nanoc::Helpers
2
+
3
+ # Nanoc::Helpers::LinkTo contains functions for linking to pages.
4
+ module LinkTo
5
+
6
+ include Nanoc::Helpers::HTMLEscape
7
+
8
+ # Creates a HTML link to the given path or page/asset representation, and
9
+ # with the given text.
10
+ #
11
+ # +path_or_rep+:: the URL or path (a String) that should be linked to, or
12
+ # the page or asset representation that should be linked
13
+ # to.
14
+ #
15
+ # +text+:: the visible link text.
16
+ #
17
+ # +attributes+:: a hash containing HTML attributes that will be added to
18
+ # the link.
19
+ #
20
+ # Examples:
21
+ #
22
+ # link_to('Blog', '/blog/')
23
+ # # => '<a href="/blog/">Blog</a>'
24
+ #
25
+ # page_rep = @pages.find { |p| p.page_id == 'special' }.reps(:default)
26
+ # link_to('Special Page', page_rep)
27
+ # # => '<a href="/special_page/">Special Page</a>'
28
+ #
29
+ # link_to('Blog', '/blog/', :title => 'My super cool blog')
30
+ # # => '<a href="/blog/" title="My super cool blog">Blog</a>
31
+ def link_to(text, path_or_rep, attributes={})
32
+ # Find path
33
+ path = path_or_rep.is_a?(String) ? path_or_rep : path_or_rep.path
34
+
35
+ # Join attributes
36
+ attributes = attributes.inject('') do |memo, (key, value)|
37
+ memo + key.to_s + '="' + h(value) + '" '
38
+ end
39
+
40
+ # Create link
41
+ "<a #{attributes}href=\"#{path}\">#{text}</a>"
42
+ end
43
+
44
+ # Creates a HTML link using link_to, except when the linked page is the
45
+ # current one. In this case, a span element with class "active" and with
46
+ # the given text will be returned.
47
+ #
48
+ # Examples:
49
+ #
50
+ # link_to('Blog', '/blog/')
51
+ # # => '<a href="/blog/">Blog</a>'
52
+ #
53
+ # link_to('This Page', @page_rep)
54
+ # # => '<span class="active">This Page</span>'
55
+ def link_to_unless_current(text, path_or_rep, attributes={})
56
+ # Find path
57
+ path = path_or_rep.is_a?(String) ? path_or_rep : path_or_rep.path
58
+
59
+ if @page_rep and @page_rep.path == path
60
+ # Create message
61
+ "<span class=\"active\" title=\"You're here.\">#{text}</span>"
62
+ else
63
+ link_to(text, path_or_rep, attributes)
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,47 @@
1
+ module Nanoc::Helpers
2
+
3
+ # Nanoc::Helpers::Render provides functionality for rendering layouts as
4
+ # partials.
5
+ module Render
6
+
7
+ # Returns a string containing the rendered given layout.
8
+ #
9
+ # +name_or_path+:: the name or the path of the layout that should be
10
+ # rendered.
11
+ #
12
+ # +other_assigns+:: a hash containing assigns that will be made available
13
+ # as instance variables.
14
+ #
15
+ # Example 1: a layout 'head' with content "HEAD" and a layout 'foot' with
16
+ # content "FOOT":
17
+ #
18
+ # <%= render 'head' %> - MIDDLE - <%= render 'foot' %>
19
+ # # => "HEAD - MIDDLE - FOOT"
20
+ #
21
+ # Example 2: a layout named 'head' with content "<h1><%= @title %></h1>":
22
+ #
23
+ # <%= render 'head', :title => 'Foo' %>
24
+ # # => "<h1>Foo</h1>"
25
+ def render(name_or_path, other_assigns={})
26
+ # Find layout
27
+ layout = @_obj.site.layouts.find { |l| l.path == name_or_path.cleaned_path }
28
+ raise Nanoc::Errors::UnknownLayoutError.new(name_or_path.cleaned_path) if layout.nil?
29
+
30
+ # Find filter
31
+ klass = layout.filter_class
32
+ raise Nanoc::Errors::CannotDetermineFilterError.new(layout.path) if klass.nil?
33
+ filter = klass.new(@_obj_rep, other_assigns)
34
+
35
+ # Layout
36
+ @_obj.site.compiler.stack.push(layout)
37
+ result = filter.run(layout.content)
38
+ @_obj.site.compiler.stack.pop
39
+ result
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ # Include by default
47
+ include Nanoc::Helpers::Render
@@ -0,0 +1,52 @@
1
+ module Nanoc::Helpers
2
+
3
+ # Nanoc::Helpers::Tagging provides some support for managing tags added to
4
+ # pages. To add tags to pages, set the +tags+ page attribute to an array of
5
+ # tags that should be applied to the page. For example:
6
+ #
7
+ # tags: [ 'foo', 'bar', 'baz' ]
8
+ module Tagging
9
+
10
+ # Returns a formatted list of tags for the given page as a string. Several
11
+ # parameters allow customization:
12
+ #
13
+ # :base_url:: The URL to which the tag will be appended to construct the
14
+ # link URL. This URL must have a trailing slash. Defaults to
15
+ # "http://technorati.com/tag/".
16
+ #
17
+ # :none_text:: The text to display when the page has no tags. Defaults to
18
+ # "(none)".
19
+ #
20
+ # :separator:: The separator to put between tags. Defaults to ", ".
21
+ def tags_for(page, params={})
22
+ base_url = params[:base_url] || 'http://technorati.com/tag/'
23
+ none_text = params[:none_text] || '(none)'
24
+ separator = params[:separator] || ', '
25
+
26
+ if page.tags.nil? or page.tags.empty?
27
+ none_text
28
+ else
29
+ page.tags.collect { |tag| link_for_tag(tag, base_url) }.join(separator)
30
+ end
31
+ end
32
+
33
+ # Returns all pages with the given tag.
34
+ def pages_with_tag(tag)
35
+ @pages.select { |p| (p.tags || []).include?(tag) }
36
+ end
37
+
38
+ # Returns a link to to the specified tag. The link is marked up using the
39
+ # rel-tag microformat.
40
+ #
41
+ # +tag+:: The name of the tag, which should consist of letters and numbers
42
+ # (no spaces, slashes, or other special characters).
43
+ #
44
+ # +base_url+:: The URL to which the tag will be appended to construct the
45
+ # link URL. This URL must have a trailing slash.
46
+ def link_for_tag(tag, base_url)
47
+ %[<a href="#{base_url}#{tag}" rel="tag">#{tag}</a>]
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,58 @@
1
+ module Nanoc::Helpers
2
+
3
+ # Nanoc::Helpers::XMLSitemap contains functionality for building XML
4
+ # sitemaps that will be crawled by search engines. See the Sitemaps protocol
5
+ # web site, http://www.sitemaps.org, for details.
6
+ module XMLSitemap
7
+
8
+ # Returns the XML sitemap as a string.
9
+ #
10
+ # The following attributes can optionally be set on pages to change the
11
+ # behaviour of the sitemap:
12
+ #
13
+ # * 'changefreq', containing the estimated change frequency as defined by
14
+ # the Sitemaps protocol.
15
+ #
16
+ # * 'priority', containing the page's priority, ranging from 0.0 to 1.0,
17
+ # as defined by the Sitemaps protocol.
18
+ #
19
+ # The sitemap will also include dates on which the pages were updated.
20
+ # These are generated automatically; the way this happens depends on the
21
+ # used data source (the filesystem data source checks the file mtimes, for
22
+ # instance).
23
+ #
24
+ # The sitemap page will need to have the following attributes:
25
+ #
26
+ # * 'base_url', containing the URL to the site, without trailing slash.
27
+ # For example, if the site is at "http://example.com/", the base_url
28
+ # would be "http://example.com". It is probably a good idea to define
29
+ # this in the page defaults, i.e. the 'meta.yaml' file (at least if the
30
+ # filesystem data source is being used, which is probably the case).
31
+ def xml_sitemap
32
+ require 'builder'
33
+
34
+ # Create builder
35
+ buffer = ''
36
+ xml = Builder::XmlMarkup.new(:target => buffer, :indent => 2)
37
+
38
+ # Build sitemap
39
+ xml.instruct!
40
+ xml.urlset(:xmlns => 'http://www.google.com/schemas/sitemap/0.84') do
41
+ # Add page
42
+ @pages.reject { |p| p.is_hidden }.each do |page|
43
+ xml.url do
44
+ xml.loc @page.base_url + page.path
45
+ xml.lastmod page.mtime.to_iso8601_date unless page.mtime.nil?
46
+ xml.changefreq page.changefreq unless page.changefreq.nil?
47
+ xml.priority page.priority unless page.priority.nil?
48
+ end
49
+ end
50
+ end
51
+
52
+ # Return sitemap
53
+ buffer
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,54 @@
1
+ module Nanoc::Routers
2
+
3
+ # The default router organises pages in the most obvious, but sometimes
4
+ # slightly restrictive, way: the hierarchy of compiled pages and assets is
5
+ # the same as the hierarchy of uncompiled pages and assets.
6
+ class Default < Nanoc::Router
7
+
8
+ identifier :default
9
+
10
+ def path_for_page_rep(page_rep)
11
+ # Get data we need
12
+ filename = page_rep.attribute_named(:filename)
13
+ extension = page_rep.attribute_named(:extension)
14
+
15
+ # Initialize path
16
+ path = page_rep.page.path + filename
17
+
18
+ # Add rep name if necessary
19
+ unless page_rep.name == :default
20
+ path += '-' + page_rep.name.to_s
21
+ end
22
+
23
+ # Add extension
24
+ path += '.' + extension
25
+
26
+ # Done
27
+ path
28
+ end
29
+
30
+ def path_for_asset_rep(asset_rep)
31
+ # Get data we need
32
+ extension = asset_rep.attribute_named(:extension)
33
+ modified_path = asset_rep.asset.path[0..-2]
34
+ version = asset_rep.attribute_named(:version)
35
+
36
+ # Initialize path
37
+ assets_prefix = @site.config[:assets_prefix] || '/assets'
38
+ path = assets_prefix + modified_path
39
+
40
+ # Add rep name if necessary
41
+ unless asset_rep.name == :default
42
+ path += '-' + asset_rep.name.to_s
43
+ end
44
+
45
+ # Add extension
46
+ path += '.' + extension
47
+
48
+ # Done
49
+ path
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,66 @@
1
+ module Nanoc::Routers
2
+
3
+ # The no-directories router organises pages very similarly to the default
4
+ # router, but does not create directories unless necessary. This router will
5
+ # therefore generate pages with less pretty URLs.
6
+ #
7
+ # For example, a page with path /about/ will be written to /about.html
8
+ # instead of /about/index.html.
9
+ class NoDirs < Nanoc::Router
10
+
11
+ identifier :no_dirs
12
+
13
+ def path_for_page_rep(page_rep)
14
+ # Get data we need
15
+ filename = page_rep.attribute_named(:filename)
16
+ extension = page_rep.attribute_named(:extension)
17
+
18
+ # Initialize path
19
+ if page_rep.page.path == '/'
20
+ path = '/' + filename
21
+ else
22
+ path = page_rep.page.path[0..-2]
23
+ end
24
+
25
+ # Add rep name if necessary
26
+ unless page_rep.name == :default
27
+ path += '-' + page_rep.name.to_s
28
+ end
29
+
30
+ # Add extension
31
+ path += '.' + extension
32
+
33
+ # Done
34
+ path
35
+ end
36
+
37
+ def path_for_asset_rep(asset_rep)
38
+ # Get data we need
39
+ extension = asset_rep.attribute_named(:extension)
40
+ modified_path = asset_rep.asset.path[0..-2]
41
+ version = asset_rep.attribute_named(:version)
42
+
43
+ # Initialize path
44
+ assets_prefix = @site.config[:assets_prefix] || '/assets'
45
+ path = assets_prefix + modified_path
46
+
47
+ # Add version if necessary
48
+ unless version.nil?
49
+ path += '-v' + version.to_s
50
+ end
51
+
52
+ # Add rep name
53
+ unless asset_rep.name == :default
54
+ path += '-' + asset_rep.name.to_s
55
+ end
56
+
57
+ # Add extension
58
+ path += '.' + extension
59
+
60
+ # Done
61
+ path
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,79 @@
1
+ module Nanoc::Routers
2
+
3
+ # The versioned router behaves pretty much like the default router, with the
4
+ # exception that asset representations (but not page representations) can
5
+ # have versions which will be added to generated URLs.
6
+ #
7
+ # This is very useful if you want to cache assets aggressively by sending an
8
+ # +Expires+ header to clients. An +Expires+ header usually has the issue
9
+ # that clients will not request the asset even if it has changed; giving the
10
+ # asset a different version will cause the URL to be changed, which in turn
11
+ # will cause the client to request the new asset.
12
+ #
13
+ # = Example
14
+ #
15
+ # For example, the URL of a textual asset containing the CSS stylesheet with
16
+ # its 'version' attribute set to 28 will become /assets/style-v28.css.
17
+ #
18
+ # To link to the stylesheet in a DRY way,
19
+ # give the asset a unique name (such as 'style') and then do something along
20
+ # this way:
21
+ #
22
+ # <link rel="stylesheet"
23
+ # type="text/css"
24
+ # media="screen"
25
+ # href="<%= @assets.find { |a| a.asset_id == 'style' }.path %>">
26
+ class Versioned < Nanoc::Router
27
+
28
+ identifier :versioned
29
+
30
+ def path_for_page_rep(page_rep)
31
+ # Get data we need
32
+ filename = page_rep.attribute_named(:filename)
33
+ extension = page_rep.attribute_named(:extension)
34
+
35
+ # Initialize path
36
+ path = page_rep.page.path + filename
37
+
38
+ # Add rep name if necessary
39
+ unless page_rep.name == :default
40
+ path += '-' + page_rep.name.to_s
41
+ end
42
+
43
+ # Add extension
44
+ path += '.' + extension
45
+
46
+ # Done
47
+ path
48
+ end
49
+
50
+ def path_for_asset_rep(asset_rep)
51
+ # Get data we need
52
+ extension = asset_rep.attribute_named(:extension)
53
+ modified_path = asset_rep.asset.path[0..-2]
54
+ version = asset_rep.attribute_named(:version)
55
+
56
+ # Initialize path
57
+ assets_prefix = @site.config[:assets_prefix] || '/assets'
58
+ path = assets_prefix + modified_path
59
+
60
+ # Add version if necessary
61
+ unless version.nil?
62
+ path += '-v' + version.to_s
63
+ end
64
+
65
+ # Add rep name
66
+ unless asset_rep.name == :default
67
+ path += '-' + asset_rep.name.to_s
68
+ end
69
+
70
+ # Add extension
71
+ path += '.' + extension
72
+
73
+ # Done
74
+ path
75
+ end
76
+
77
+ end
78
+
79
+ end