diff --git a/.travis.yml b/.travis.yml index 56a8b42c..451d94a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ env: branches: only: - master + - themes notifications: irc: diff --git a/Gemfile b/Gemfile index 5a080750..afb4513b 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ group :test do gem "rspec-mocks" gem "nokogiri" gem "rspec" + gem "test-theme", path: File.expand_path("./test/fixtures/test-theme", File.dirname(__FILE__)) end # diff --git a/features/theme.feature b/features/theme.feature new file mode 100644 index 00000000..40877f84 --- /dev/null +++ b/features/theme.feature @@ -0,0 +1,61 @@ +Feature: Writing themes + As a hacker who likes to share my expertise + I want to be able to make a gemified theme + In order to share my awesome style skillz with other Jekyllites + + Scenario: A theme with SCSS + Given I have a configuration file with "theme" set to "test-theme" + And I have a css directory + And I have a "css/main.scss" page that contains "@import 'style';" + When I run jekyll build + Then I should get a zero exit status + And the _site directory should exist + And I should see ".sample {\n color: black; }" in "_site/css/main.css" + + Scenario: A theme with an include + Given I have a configuration file with "theme" set to "test-theme" + And I have an _includes directory + And I have an "_includes/in_project.html" file that contains "I'm in the project." + And I have an "index.html" page that contains "{% include in_project.html %} {% include include.html %}" + When I run jekyll build + Then I should get a zero exit status + And the _site directory should exist + And I should see "I'm in the project." in "_site/index.html" + And I should see "include.html from test-theme" in "_site/index.html" + + Scenario: A theme with a layout + Given I have a configuration file with "theme" set to "test-theme" + And I have an _layouts directory + And I have an "_layouts/post.html" file that contains "post.html from the project: {{ content }}" + And I have an "index.html" page with layout "default" that contains "I'm content." + And I have a "post.html" page with layout "post" that contains "I'm more content." + When I run jekyll build + Then I should get a zero exit status + And the _site directory should exist + And I should see "default.html from test-theme: I'm content." in "_site/index.html" + And I should see "post.html from the project: I'm more content." in "_site/post.html" + + Scenario: Complicated site that puts it all together + Given I have a configuration file with "theme" set to "test-theme" + And I have a _posts directory + And I have the following posts: + | title | date | layout | content | + | entry1 | 2016-04-21 | post | I am using a local layout. {% include include.html %} | + | entry2 | 2016-04-21 | default | I am using a themed layout. {% include include.html %} {% include in_project.html %} | + And I have a _layouts directory + And I have a "_layouts/post.html" page with layout "default" that contains "I am a post layout! {{ content }}" + And I have an _includes directory + And I have an "_includes/in_project.html" file that contains "I am in the project, not the theme." + When I run jekyll build + Then I should get a zero exit status + And the _site directory should exist + And I should see "I am in the project, not the theme." in "_site/2016/04/21/entry2.html" + And I should see "include.html from test-theme" in "_site/2016/04/21/entry2.html" + And I should see "default.html from test-theme:" in "_site/2016/04/21/entry2.html" + And I should see "I am using a themed layout." in "_site/2016/04/21/entry2.html" + And I should not see "I am a post layout!" in "_site/2016/04/21/entry2.html" + And I should not see "I am in the project, not the theme." in "_site/2016/04/21/entry1.html" + And I should see "include.html from test-theme" in "_site/2016/04/21/entry1.html" + And I should see "default.html from test-theme:" in "_site/2016/04/21/entry1.html" + And I should see "I am using a local layout." in "_site/2016/04/21/entry1.html" + And I should see "I am a post layout!" in "_site/2016/04/21/entry1.html" diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 6dc42a92..8afdf06a 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -66,6 +66,7 @@ module Jekyll autoload :Site, 'jekyll/site' autoload :StaticFile, 'jekyll/static_file' autoload :Stevenson, 'jekyll/stevenson' + autoload :Theme, 'jekyll/theme' autoload :URL, 'jekyll/url' autoload :Utils, 'jekyll/utils' autoload :VERSION, 'jekyll/version' diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index 26c88fd6..a473b183 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -39,7 +39,7 @@ module Jekyll filename = File.join(base, name) begin - self.content = File.read(site.in_source_dir(base, name), + self.content = File.read(@path || site.in_source_dir(base, name), Utils.merged_file_read_opts(site, opts)) if content =~ Document::YAML_FRONT_MATTER_REGEXP self.content = $POSTMATCH @@ -215,9 +215,9 @@ module Jekyll payload["layout"] = Utils.deep_merge_hashes(payload["layout"] || {}, layout.data) self.output = render_liquid(layout.content, - payload, - info, - File.join(site.config['layouts_dir'], layout.name)) + payload, + info, + layout.relative_path) # Add layout to dependency tree site.regenerator.add_dependency( diff --git a/lib/jekyll/layout.rb b/lib/jekyll/layout.rb index c29f353f..72005277 100644 --- a/lib/jekyll/layout.rb +++ b/lib/jekyll/layout.rb @@ -29,7 +29,14 @@ module Jekyll @site = site @base = base @name = name - @path = site.in_source_dir(base, name) + + if site.theme && site.theme.layouts_path.eql?(base) + @base_dir = site.theme.root + @path = site.in_theme_dir(base, name) + else + @base_dir = site.source + @path = site.in_source_dir(base, name) + end self.data = {} @@ -45,5 +52,13 @@ module Jekyll def process(name) self.ext = File.extname(name) end + + # The path to the layout, relative to the site source. + # + # Returns a String path which represents the relative path + # from the site source to this layout + def relative_path + @relative_path ||= Pathname.new(path).relative_path_from(Pathname.new(@base_dir)).to_s + end end end diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index 6c402ee3..2bdcdf6f 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -40,6 +40,7 @@ module Jekyll @base = base @dir = dir @name = name + @path = site.in_source_dir(base, dir, name) process(name) read_yaml(File.join(base, dir), name) diff --git a/lib/jekyll/readers/layout_reader.rb b/lib/jekyll/readers/layout_reader.rb index f0620294..df8931cb 100644 --- a/lib/jekyll/readers/layout_reader.rb +++ b/lib/jekyll/readers/layout_reader.rb @@ -7,8 +7,12 @@ module Jekyll end def read - layout_entries.each do |f| - @layouts[layout_name(f)] = Layout.new(site, layout_directory, f) + layout_entries.each do |layout_file| + @layouts[layout_name(layout_file)] = Layout.new(site, layout_directory, layout_file) + end + + theme_layout_entries.each do |layout_file| + @layouts[layout_name(layout_file)] ||= Layout.new(site, theme_layout_directory, layout_file) end @layouts @@ -18,11 +22,23 @@ module Jekyll @layout_directory ||= (layout_directory_in_cwd || layout_directory_inside_source) end + def theme_layout_directory + @theme_layout_directory ||= site.theme.layouts_path if site.theme + end + private def layout_entries + entries_in layout_directory + end + + def theme_layout_entries + theme_layout_directory ? entries_in(theme_layout_directory) : [] + end + + def entries_in(dir) entries = [] - within(layout_directory) do + within(dir) do entries = EntryFilter.new(site).filter(Dir['**/*.*']) end entries diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb index b158f421..f116cf60 100644 --- a/lib/jekyll/renderer.rb +++ b/lib/jekyll/renderer.rb @@ -145,7 +145,7 @@ module Jekyll layout.content, payload, info, - File.join(site.config['layouts_dir'], layout.name) + layout.relative_path ) # Add layout to dependency tree diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index c4fe9f0c..9afdbde3 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -8,10 +8,10 @@ module Jekyll :exclude, :include, :lsi, :highlighter, :permalink_style, :time, :future, :unpublished, :safe, :plugins, :limit_posts, :show_drafts, :keep_files, :baseurl, :data, :file_read_opts, - :gems, :plugin_manager + :gems, :plugin_manager, :theme attr_accessor :converters, :generators, :reader - attr_reader :regenerator, :liquid_renderer + attr_reader :regenerator, :liquid_renderer, :includes_load_paths # Public: Initialize a new Site. # @@ -52,6 +52,12 @@ module Jekyll self.plugin_manager = Jekyll::PluginManager.new(self) self.plugins = plugin_manager.plugins_path + self.theme = nil + self.theme = Jekyll::Theme.new(config["theme"]) if config["theme"] + + @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s)) + @includes_load_paths << theme.includes_path if self.theme + self.file_read_opts = {} self.file_read_opts[:encoding] = config['encoding'] if config['encoding'] @@ -367,6 +373,19 @@ module Jekyll end end + # Public: Prefix a given path with the theme directory. + # + # paths - (optional) path elements to a file or directory within the + # theme directory + # + # Returns a path which is prefixed with the theme root directory. + def in_theme_dir(*paths) + return nil unless theme + paths.reduce(theme.root) do |base, path| + Jekyll.sanitized_path(base, path) + end + end + # Public: Prefix a given path with the destination directory. # # paths - (optional) path elements to a file or directory within the diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 847e6638..d4f31f2a 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -12,8 +12,6 @@ module Jekyll end class IncludeTag < Liquid::Tag - attr_reader :includes_dir - VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/ VARIABLE_SYNTAX = /(?[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)(?.*)/ @@ -98,20 +96,29 @@ eos end end - def tag_includes_dir(context) - context.registers[:site].config['includes_dir'].freeze + def tag_includes_dirs(context) + context.registers[:site].includes_load_paths.freeze + end + + def locate_include_file(context, file, safe) + includes_dirs = tag_includes_dirs(context) + includes_dirs.each do |dir| + path = File.join(dir, file) + return path if valid_include_file?(path, dir, safe) + end + raise IOError, "Could not locate the included file '#{file}' in any of #{includes_dirs}." \ + " Ensure it exists in one of those directories and, if it is a symlink, " \ + "does not point outside your site source." end def render(context) site = context.registers[:site] - @includes_dir = tag_includes_dir(context) - dir = resolved_includes_dir(context) file = render_variable(context) || @file validate_file_name(file) - path = File.join(dir, file) - validate_path(path, dir, site.safe) + path = locate_include_file(context, file, site.safe) + return unless path # Add include to dependency tree if context.registers[:page] && context.registers[:page].key?("path") @@ -121,16 +128,16 @@ eos ) end - begin + #begin partial = load_cached_partial(path, context) context.stack do context['include'] = parse_params(context) if @params partial.render!(context) end - rescue => e - raise IncludeTagError.new e.message, File.join(@includes_dir, @file) - end + #rescue => e + #raise IncludeTagError.new e.message, path + #end end def load_cached_partial(path, context) @@ -144,24 +151,18 @@ eos end end - def resolved_includes_dir(context) - context.registers[:site].in_source_dir(@includes_dir) + def valid_include_file?(path, dir, safe) + !(outside_site_source?(path, dir, safe) || !File.exist?(path)) end - def validate_path(path, dir, safe) - if safe && !realpath_prefixed_with?(path, dir) - raise IOError.new "The included file '#{path}' should exist and should not be a symlink" - elsif !File.exist?(path) - raise IOError.new "Included file '#{path_relative_to_source(dir, path)}' not found" - end - end - - def path_relative_to_source(dir, path) - File.join(@includes_dir, path.sub(Regexp.new("^#{dir}"), "")) + def outside_site_source?(path, dir, safe) + safe && !realpath_prefixed_with?(path, dir) end def realpath_prefixed_with?(path, dir) File.exist?(path) && File.realpath(path).start_with?(dir) + rescue + false end # This method allows to modify the file content by inheriting from the class. @@ -171,16 +172,17 @@ eos end class IncludeRelativeTag < IncludeTag - def tag_includes_dir(context) - '.'.freeze + def tag_includes_dirs(context) + Array(page_path(context)).freeze end def page_path(context) - context.registers[:page].nil? ? includes_dir : File.dirname(context.registers[:page]["path"]) - end - - def resolved_includes_dir(context) - context.registers[:site].in_source_dir(page_path(context)) + if context.registers[:page].nil? + context.registers[:site].source + else + current_doc_dir = File.dirname(context.registers[:page]["path"]) + context.registers[:site].in_source_dir current_doc_dir + end end end end diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb new file mode 100644 index 00000000..ed9cc68d --- /dev/null +++ b/lib/jekyll/theme.rb @@ -0,0 +1,56 @@ +module Jekyll + class Theme + extend Forwardable + attr_reader :name + def_delegator :gemspec, :version, :version + + def initialize(name) + @name = name.downcase.strip + configure_sass + end + + def root + @root ||= gemspec.full_gem_path + end + + def includes_path + path_for :includes + end + + def layouts_path + path_for :layouts + end + + def sass_path + path_for :sass + end + + def configure_sass + return unless sass_path + require 'sass' + Sass.load_paths << sass_path + end + + private + + def path_for(folder) + resolved_dir = realpath_for(folder) + return unless resolved_dir + + path = Jekyll.sanitized_path(root, resolved_dir) + path if Dir.exists?(path) + end + + def realpath_for(folder) + File.realpath(Jekyll.sanitized_path(root, "_#{folder}")) + rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP + nil + end + + def gemspec + @gemspec ||= Gem::Specification.find_by_name(name) + rescue Gem::LoadError + raise Jekyll::Errors::MissingDependencyException, "The #{name} theme could not be found." + end + end +end diff --git a/rake/site.rake b/rake/site.rake index f1af29ba..4d1a4c24 100644 --- a/rake/site.rake +++ b/rake/site.rake @@ -103,7 +103,7 @@ namespace :site do desc "Create a nicely formatted history page for the jekyll site based on the repo history." task :history do - siteify_file('History.markdown') + siteify_file('History.markdown', { "title" => "History" }) end desc "Copy the Code of Conduct" diff --git a/site/_data/docs.yml b/site/_data/docs.yml index 7cb85198..071b5a14 100644 --- a/site/_data/docs.yml +++ b/site/_data/docs.yml @@ -26,6 +26,7 @@ - permalinks - pagination - plugins + - themes - extras - title: Deployment diff --git a/site/_docs/themes.md b/site/_docs/themes.md new file mode 100644 index 00000000..2b7155bb --- /dev/null +++ b/site/_docs/themes.md @@ -0,0 +1,89 @@ +--- +layout: docs +title: Themes +permalink: /docs/themes/ +--- + +Jekyll has an extensive theme system, which allows you to leverage community-maintained templates and styles to customize your site's presentation. Jekyll themes package layouts, includes, and stylesheets in a way that can be overridden by your site's content. + +## Installing a theme + +1. To install a theme, first, add the theme to your site's `Gemfile`: + + gem 'my-awesome-jekyll-theme' + +2. Save the changes to your `Gemfile` +3. Run the command `bundle install` to install the theme +4. Finally, activate the theme by adding the following to your site's `_config.yml`: + + theme: my-awesome-jekyll-theme + +You can have multiple themes listed in your site's Gemfile, but only one theme can be selected in your site's `_config.yml`. +{: .note .info } + +## Overriding theme defaults + +Jekyll themes set default layouts, includes, and stylesheets, that can be overridden by your site's content. For example, if your selected theme has a `page` layout, you can override the theme's layout by creating your own `page` layout in the `_layouts` folder (e.g., `_layouts/page.html`). + +Jekyll will look first to your site's content, before looking to the theme's defaults, for any requested file in the following folders: + +* `/_layouts` +* `/_includes` +* `/_sass` + +Refer to your selected theme's documentation and source repository for more information on what files you can override. +{: .note .info} + +## Creating a theme + +Jekyll themes are distributed as Ruby gems. The only required file is the [Ruby Gemspec](http://guides.rubygems.org/specification-reference/). Here's an example of a minimal Gemspec for the `my-awesome-jekyll-theme` theme, saved as `/my-awsome-jekyll-theme.gemspec`: + +{% highlight ruby %} +Gem::Specification.new do |s| + s.name = '' + s.version = '0.1.0' + s.license = 'MIT' + s.summary = '' + s.author = '' + s.email = '' + s.homepage = 'https://github.com/jekyll/my-awesome-jekyll-theme' + s.files = `git ls-files -z`.split("\x0").grep(%r{^_(sass|includes|layouts)/}) +end +{% endhighlight %} + +### Layouts and includes + +Theme layouts and includes work just like they work in any Jekyll site. Place layouts in your theme's `/_layouts` folder, and place includes in your themes `/_includes` folder. + +For example, if your theme has a `/_layouts/page.html` file, and a page has `layout: page` in its YAML front matter, Jekyll will first look to the site's `_layouts` folder for a the `page` layout, and if none exists, will use your theme's `page` layout. + +### Stylesheets + +Your theme's stylesheets should be placed in your theme's `/_sass` folder, again, just as you would when authoring a Jekyll site. Your theme's styles can be included in the user's stylesheet using the `@import` directive. + +### Documenting your theme + +Your theme should include a `/README.md` file, which explains how site authors can install and use your theme. What layouts are included? What includes? Do they need to add anything special to their site's configuration file? + +### Adding a screenshot + +Themes are visual. Show users what your theme looks like by including a screenshot as `/screenshot.png` within your theme's repository where it can be retrieved programatically. You can also include this screenshot within your theme's documentation. + +### Previewing your theme + +To preview your theme as you're authoring it, it may be helpful to add dummy content in, for example, `/index.html` and `/page.html` files. This will allow you to use the `jekyll build` and `jekyll serve` commands to preview your theme, just as you'd preview a Jekyll site. + +If you do preview your theme locally, be sure to add `/_site` to your theme's `.gitignore` file to prevent the compiled site from also being included when you distribute your theme. +{: .info .note} + +### Publishing your theme + +Themes are published via [RubyGems.org](https://rubygems.org). You'll need a RubyGems account, which you can [create for free](https://rubygems.org/sign_up). + +1. First, package your theme, by running the following command, replacing `my-awesome-jekyll-theme` with the name of your theme: + + gem build my-awesome-jekyll-theme.gemspec + +2. Next, push your packaged theme up to the RubyGems service, by running the following command, again replacing `my-awesome-jekyll-theme` with the name of your theme: + + gem push my-awesome-jekyll-theme-*.gem diff --git a/test/fixtures/test-theme/_includes/include.html b/test/fixtures/test-theme/_includes/include.html new file mode 100644 index 00000000..98608392 --- /dev/null +++ b/test/fixtures/test-theme/_includes/include.html @@ -0,0 +1 @@ +include.html from test-theme diff --git a/test/fixtures/test-theme/_layouts/default.html b/test/fixtures/test-theme/_layouts/default.html new file mode 100644 index 00000000..902c61c3 --- /dev/null +++ b/test/fixtures/test-theme/_layouts/default.html @@ -0,0 +1 @@ +default.html from test-theme: {{ content }} diff --git a/test/fixtures/test-theme/_sass/style.scss b/test/fixtures/test-theme/_sass/style.scss new file mode 100644 index 00000000..a1e07da6 --- /dev/null +++ b/test/fixtures/test-theme/_sass/style.scss @@ -0,0 +1,3 @@ +.sample { + color: black; +} diff --git a/test/fixtures/test-theme/_symlink b/test/fixtures/test-theme/_symlink new file mode 120000 index 00000000..d2d6aac5 --- /dev/null +++ b/test/fixtures/test-theme/_symlink @@ -0,0 +1 @@ +_layouts \ No newline at end of file diff --git a/test/fixtures/test-theme/test-theme.gemspec b/test/fixtures/test-theme/test-theme.gemspec new file mode 100644 index 00000000..5f950ae1 --- /dev/null +++ b/test/fixtures/test-theme/test-theme.gemspec @@ -0,0 +1,9 @@ +Gem::Specification.new do |s| + s.name = 'test-theme' + s.version = '0.1.0' + s.licenses = ['MIT'] + s.summary = "This is a theme used to test Jekyll" + s.authors = ["Jekyll"] + s.files = ["lib/example.rb"] + s.homepage = 'https://github.com/jekyll/jekyll' +end diff --git a/test/test_tags.rb b/test/test_tags.rb index ee7b5074..1dab0feb 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -616,7 +616,7 @@ title: Include symlink CONTENT create_post(content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true }) end - assert_match(/should exist and should not be a symlink/, ex.message) + assert_match "Could not locate the included file 'tmp/pages-test-does-not-exist' in any of [\"#{source_dir}/_includes\"].", ex.message end end @@ -757,7 +757,7 @@ CONTENT exception = assert_raises IOError do create_post(@content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true}) end - assert_equal 'Included file \'_includes/missing.html\' not found', exception.message + assert_match "Could not locate the included file 'missing.html' in any of [\"#{source_dir}/_includes\"].", exception.message end end @@ -839,7 +839,7 @@ CONTENT exception = assert_raises IOError do create_post(@content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true}) end - assert_equal 'Included file \'./missing.html\' not found', exception.message + assert_match "Could not locate the included file 'missing.html' in any of [\"#{source_dir}\"].", exception.message end end @@ -894,7 +894,7 @@ title: Include symlink CONTENT create_post(content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true }) end - assert_match(/should exist and should not be a symlink/, ex.message) + assert_match /Ensure it exists in one of those directories and, if it is a symlink, does not point outside your site source./, ex.message end end end diff --git a/test/test_theme.rb b/test/test_theme.rb new file mode 100644 index 00000000..9767946e --- /dev/null +++ b/test/test_theme.rb @@ -0,0 +1,65 @@ +require 'helper' + +class TestTheme < JekyllUnitTest + def setup + @theme = Theme.new('test-theme') + @expected_root = File.expand_path "./fixtures/test-theme", File.dirname(__FILE__) + end + + context "initializing" do + should "normalize the theme name" do + theme = Theme.new(' Test-Theme ') + assert_equal "test-theme", theme.name + end + + should "know the theme root" do + assert_equal @expected_root, @theme.root + end + + should "know the theme version" do + assert_equal Gem::Version.new("0.1.0"), @theme.version + end + + should "raise an error for invalid themes" do + assert_raises Jekyll::Errors::MissingDependencyException do + Theme.new("foo").version + end + end + + should "add itself to sass's load path" do + @theme.configure_sass + assert Sass.load_paths.include?(@theme.sass_path), "Sass load paths should include the theme sass dir" + end + end + + context "path generation" do + [:layouts, :includes, :sass].each do |folder| + should "know the #{folder} path" do + expected = File.expand_path("_#{folder}", @expected_root) + assert_equal expected, @theme.public_send("#{folder}_path") + end + end + + should "generate folder paths" do + expected = File.expand_path("./_sass", @expected_root) + assert_equal expected, @theme.send(:path_for, :sass) + end + + should "not allow paths outside of the theme root" do + assert_equal nil, @theme.send(:path_for, "../../source") + end + + should "return nil for paths that don't exist" do + assert_equal nil, @theme.send(:path_for, "foo") + end + + should "return the resolved path when a symlink & resolved path exists" do + expected = File.expand_path("./_layouts", @expected_root) + assert_equal expected, @theme.send(:path_for, :symlink) + end + end + + should "retrieve the gemspec" do + assert_equal "test-theme-0.1.0", @theme.send(:gemspec).full_name + end +end