From 91b348966ed2725dca7a5cd306dbc446f5928787 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 26 Feb 2016 14:25:29 -0500 Subject: [PATCH 01/34] add theme class --- lib/jekyll.rb | 1 + lib/jekyll/readers/layout_reader.rb | 5 ++++ lib/jekyll/site.rb | 4 ++- lib/jekyll/theme.rb | 42 +++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 lib/jekyll/theme.rb diff --git a/lib/jekyll.rb b/lib/jekyll.rb index 6896bb0b..87dc3b1b 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -67,6 +67,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/readers/layout_reader.rb b/lib/jekyll/readers/layout_reader.rb index f0620294..82989d50 100644 --- a/lib/jekyll/readers/layout_reader.rb +++ b/lib/jekyll/readers/layout_reader.rb @@ -25,6 +25,11 @@ module Jekyll within(layout_directory) do entries = EntryFilter.new(site).filter(Dir['**/*.*']) end + if site.theme + within(site.theme.layouts_path) do + entries.concat EntryFilter.new(site).filter(Dir['**/*.*']) + end + end entries end diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 72d42ad9..d5c1f1c6 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -8,7 +8,7 @@ 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 @@ -38,6 +38,8 @@ module Jekyll self.plugin_manager = Jekyll::PluginManager.new(self) self.plugins = plugin_manager.plugins_path + self.theme = Jekyll::Theme.new(config["theme"]) if config["theme"] + self.file_read_opts = {} self.file_read_opts[:encoding] = config['encoding'] if config['encoding'] diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb new file mode 100644 index 00000000..04bf7aaa --- /dev/null +++ b/lib/jekyll/theme.rb @@ -0,0 +1,42 @@ +module Jekyll + class Theme + attr_reader :name + + def initialize(name) + @name = name.downcase.strip + raise MissingDependencyException unless gemspec + end + + def root + @root ||= gemspec.gem_dir + end + + def version + gemspec.version + end + + def assets_path + path_for "assets" + end + + def includes_path + path_for "includes" + end + + def layouts_path + path_for "layouts" + end + + private + + def path_for(folder) + Jekyll.sanitized_path root, "_#{folder}" + end + + def gemspec + @gemspec ||= Gem::Specification.find_by_name(name) + rescue Gem::LoadError + nil + end + end +end From 47416169c3b5489b60b8520849b86e54cdbb1a7d Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Sat, 5 Mar 2016 13:05:11 -0500 Subject: [PATCH 02/34] add sass path --- lib/jekyll/theme.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 04bf7aaa..45a9aa3c 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -27,6 +27,10 @@ module Jekyll path_for "layouts" end + def sass_path + path_for "sass" + end + private def path_for(folder) From ada7c4f441d603e335bcc18eafe734a82c36386a Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Sat, 5 Mar 2016 13:05:57 -0500 Subject: [PATCH 03/34] revert layout reader --- lib/jekyll/readers/layout_reader.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/jekyll/readers/layout_reader.rb b/lib/jekyll/readers/layout_reader.rb index 82989d50..f0620294 100644 --- a/lib/jekyll/readers/layout_reader.rb +++ b/lib/jekyll/readers/layout_reader.rb @@ -25,11 +25,6 @@ module Jekyll within(layout_directory) do entries = EntryFilter.new(site).filter(Dir['**/*.*']) end - if site.theme - within(site.theme.layouts_path) do - entries.concat EntryFilter.new(site).filter(Dir['**/*.*']) - end - end entries end From 8b880cb993b63f263252beeee9817b8d08aef686 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Sat, 5 Mar 2016 13:35:52 -0500 Subject: [PATCH 04/34] add theme tests --- Gemfile | 1 + lib/jekyll/theme.rb | 7 ++- .../test-theme/_assets/application.coffee | 0 test/fixtures/test-theme/_assets/script.js | 0 .../test-theme/_includes/include.html | 0 .../fixtures/test-theme/_layouts/default.html | 0 test/fixtures/test-theme/_sass/style.scss | 0 test/fixtures/test-theme/test-theme.gemspec | 9 +++ test/test_theme.rb | 55 +++++++++++++++++++ 9 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/test-theme/_assets/application.coffee create mode 100644 test/fixtures/test-theme/_assets/script.js create mode 100644 test/fixtures/test-theme/_includes/include.html create mode 100644 test/fixtures/test-theme/_layouts/default.html create mode 100644 test/fixtures/test-theme/_sass/style.scss create mode 100644 test/fixtures/test-theme/test-theme.gemspec create mode 100644 test/test_theme.rb diff --git a/Gemfile b/Gemfile index 5aad2c4d..dfbbdfd9 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/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 45a9aa3c..13bed36c 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -4,11 +4,11 @@ module Jekyll def initialize(name) @name = name.downcase.strip - raise MissingDependencyException unless gemspec + raise Jekyll::Errors::MissingDependencyException unless gemspec end def root - @root ||= gemspec.gem_dir + @root ||= gemspec.full_gem_path end def version @@ -34,7 +34,8 @@ module Jekyll private def path_for(folder) - Jekyll.sanitized_path root, "_#{folder}" + path = Jekyll.sanitized_path root, "_#{folder}" + path if Dir.exists?(path) end def gemspec diff --git a/test/fixtures/test-theme/_assets/application.coffee b/test/fixtures/test-theme/_assets/application.coffee new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/test-theme/_assets/script.js b/test/fixtures/test-theme/_assets/script.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/test-theme/_includes/include.html b/test/fixtures/test-theme/_includes/include.html new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/test-theme/_layouts/default.html b/test/fixtures/test-theme/_layouts/default.html new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/test-theme/_sass/style.scss b/test/fixtures/test-theme/_sass/style.scss new file mode 100644 index 00000000..e69de29b 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_theme.rb b/test/test_theme.rb new file mode 100644 index 00000000..e996ac6f --- /dev/null +++ b/test/test_theme.rb @@ -0,0 +1,55 @@ +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") + end + end + end + + context "path generation" do + ["assets", "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("_assets", @expected_root) + assert_equal expected, @theme.send(:path_for, "assets") + 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 + end + + should "retrieve the gemspec" do + assert_equal "test-theme-0.1.0", @theme.send(:gemspec).full_name + end +end From ff3df203c4ffc30b3ed899e399cce1e5c2f2cd52 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Sun, 6 Mar 2016 15:32:26 -0500 Subject: [PATCH 05/34] document how to use themes --- History.markdown | 2 + site/_config.yml | 3 ++ site/_data/docs.yml | 1 + site/_docs/themes.md | 107 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 site/_docs/themes.md diff --git a/History.markdown b/History.markdown index 4c329b25..9cdf208b 100644 --- a/History.markdown +++ b/History.markdown @@ -1,3 +1,5 @@ +# History + ## HEAD ### Minor Enhancements diff --git a/site/_config.yml b/site/_config.yml index a7c59653..df2431f8 100644 --- a/site/_config.yml +++ b/site/_config.yml @@ -2,6 +2,9 @@ markdown: kramdown highlighter: pygments permalink: /news/:year/:month/:day/:title/ excerpt_separator: "" +kramdown: + input: GFM + hard_wrap: false gauges_id: 503c5af6613f5d0f19000027 google_analytics_id: UA-50755011-1 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..6a77e008 --- /dev/null +++ b/site/_docs/themes.md @@ -0,0 +1,107 @@ +--- +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, stylesheets, and static assets 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`: + + ```ruby + 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`: + + ```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, stylesheets and static assets, 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` +* `/assets/` + +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`: + +```ruby +Gem::Specification.new do |s| + s.name = 'My Awesome theme' + s.version = '0.1.0' + s.license = 'MIT' + s.summary = 'This is an awesome Jekyll theme!' + s.author = 'Dr. Jekyll' + s.email = 'doc@jekyllrb.com' + s.homepage = 'https://github.com/jekyll/my-awesome-jekyll-theme' + s.files = `git ls-files -z`.split("\x0").grep(%r{^(assets|_sass|_includes|_layouts)/}) +end +``` + +### 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. + +You may also need to output the rendered styles, by adding an `/assets/style.scss` file, which imports the necessary stylesheets, by following the instructions in the [static assets](#static-assets) section below. + +Do not place your theme's styles in the `/assets/style.scss` file. Storing styles in the `/_sass` folder allows users to use SaSS's `@import` directive, to combine their custom styles, with the theme's in a single file. +{: .info .note } + +### Static assets + +You may also bundle static assets within your theme (e.g., javascripts, images, and fonts). Place any files you'd like in your theme's `/assets` folder, which will be included in the published site. It's common to group files into folders, such as `/assets/js`, `/assets/img`, and `/assets/fonts`. + +### 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 + ``` From c86fba6fb388cbb62aa0d65d4a86789f22319b08 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Sun, 6 Mar 2016 15:42:08 -0500 Subject: [PATCH 06/34] use /assets for theme assets, not _assets --- lib/jekyll/theme.rb | 11 ++++++----- .../{_assets => assets}/application.coffee | 0 .../test-theme/{_assets => assets}/script.js | 0 test/test_theme.rb | 12 ++++++++---- 4 files changed, 14 insertions(+), 9 deletions(-) rename test/fixtures/test-theme/{_assets => assets}/application.coffee (100%) rename test/fixtures/test-theme/{_assets => assets}/script.js (100%) diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 13bed36c..9a17de68 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -16,25 +16,26 @@ module Jekyll end def assets_path - path_for "assets" + path_for :assets end def includes_path - path_for "includes" + path_for :includes end def layouts_path - path_for "layouts" + path_for :layouts end def sass_path - path_for "sass" + path_for :sass end private def path_for(folder) - path = Jekyll.sanitized_path root, "_#{folder}" + folder = "_#{folder}" unless folder == :assets + path = Jekyll.sanitized_path root, folder.to_s path if Dir.exists?(path) end diff --git a/test/fixtures/test-theme/_assets/application.coffee b/test/fixtures/test-theme/assets/application.coffee similarity index 100% rename from test/fixtures/test-theme/_assets/application.coffee rename to test/fixtures/test-theme/assets/application.coffee diff --git a/test/fixtures/test-theme/_assets/script.js b/test/fixtures/test-theme/assets/script.js similarity index 100% rename from test/fixtures/test-theme/_assets/script.js rename to test/fixtures/test-theme/assets/script.js diff --git a/test/test_theme.rb b/test/test_theme.rb index e996ac6f..06fc3037 100644 --- a/test/test_theme.rb +++ b/test/test_theme.rb @@ -28,16 +28,20 @@ class TestTheme < JekyllUnitTest end context "path generation" do - ["assets", "layouts", "includes", "sass"].each do |folder| + [:assets, :layouts, :includes, :sass].each do |folder| should "know the #{folder} path" do - expected = File.expand_path("_#{folder}", @expected_root) + if folder == :assets + expected = File.expand_path(folder.to_s, @expected_root) + else + expected = File.expand_path("_#{folder}", @expected_root) + end assert_equal expected, @theme.public_send("#{folder}_path") end end should "generate folder paths" do - expected = File.expand_path("_assets", @expected_root) - assert_equal expected, @theme.send(:path_for, "assets") + expected = File.expand_path("./assets", @expected_root) + assert_equal expected, @theme.send(:path_for, :assets) end should "not allow paths outside of the theme root" do From 06ee9bcff4ada78f068e324320f06c52d3a18bc0 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Tue, 8 Mar 2016 11:52:17 -0500 Subject: [PATCH 07/34] remove title of history file --- History.markdown | 2 -- rake/site.rake | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/History.markdown b/History.markdown index 9cdf208b..4c329b25 100644 --- a/History.markdown +++ b/History.markdown @@ -1,5 +1,3 @@ -# History - ## HEAD ### Minor Enhancements diff --git a/rake/site.rake b/rake/site.rake index 3bff7a16..fb979411 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" From ea84ac9b04f4c7f3de0c308c4a057084c23862e2 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Tue, 8 Mar 2016 11:52:33 -0500 Subject: [PATCH 08/34] use rocket hashes --- rake/site.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rake/site.rake b/rake/site.rake index fb979411..cb329c0e 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', { title: "History" }) + siteify_file('History.markdown', { :title => "History" }) end desc "Copy the Code of Conduct" From e8617cf7cd35100500385b2bd9b222d3d78dea92 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Tue, 8 Mar 2016 11:53:29 -0500 Subject: [PATCH 09/34] better theme-not-found handling --- lib/jekyll/theme.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 9a17de68..4965679b 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -4,7 +4,6 @@ module Jekyll def initialize(name) @name = name.downcase.strip - raise Jekyll::Errors::MissingDependencyException unless gemspec end def root @@ -42,7 +41,7 @@ module Jekyll def gemspec @gemspec ||= Gem::Specification.find_by_name(name) rescue Gem::LoadError - nil + raise Jekyll::Errors::MissingDependencyException, "The #{name} theme could not be found." end end end From eb341514a7fe8329497cd3bdbb6eb1a641d1e569 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Tue, 8 Mar 2016 11:55:16 -0500 Subject: [PATCH 10/34] use def_delegator --- lib/jekyll/theme.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 4965679b..0357a49e 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -1,6 +1,8 @@ module Jekyll class Theme + extend Forwardable attr_reader :name + def_delegator :gemspec, :version, :version def initialize(name) @name = name.downcase.strip @@ -10,10 +12,6 @@ module Jekyll @root ||= gemspec.full_gem_path end - def version - gemspec.version - end - def assets_path path_for :assets end From 678d494d90bfd43ee12a03104ec2247cefabbfbb Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Tue, 8 Mar 2016 12:03:14 -0500 Subject: [PATCH 11/34] no GFM for you --- site/_config.yml | 3 --- site/_docs/history.md | 3 ++- site/_docs/themes.md | 20 ++++++-------------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/site/_config.yml b/site/_config.yml index df2431f8..a7c59653 100644 --- a/site/_config.yml +++ b/site/_config.yml @@ -2,9 +2,6 @@ markdown: kramdown highlighter: pygments permalink: /news/:year/:month/:day/:title/ excerpt_separator: "" -kramdown: - input: GFM - hard_wrap: false gauges_id: 503c5af6613f5d0f19000027 google_analytics_id: UA-50755011-1 diff --git a/site/_docs/history.md b/site/_docs/history.md index 12da9d24..6ebeec3a 100644 --- a/site/_docs/history.md +++ b/site/_docs/history.md @@ -1,5 +1,6 @@ --- -title: History +:title: History +title: layout: docs permalink: "/docs/history/" note: This file is autogenerated. Edit /History.markdown instead. diff --git a/site/_docs/themes.md b/site/_docs/themes.md index 6a77e008..97e8ea81 100644 --- a/site/_docs/themes.md +++ b/site/_docs/themes.md @@ -10,17 +10,13 @@ Jekyll has an extensive theme system, which allows you to leverage community-mai 1. To install a theme, first, add the theme to your site's `Gemfile`: - ```ruby - gem 'my-awesome-jekyll-theme' - ``` + 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`: - ```yml - theme: my-awesome-jekyll-theme - ``` + 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 } @@ -43,7 +39,7 @@ Refer to your selected theme's documentation and source repository for more info 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`: -```ruby +{% highlight ruby %} Gem::Specification.new do |s| s.name = 'My Awesome theme' s.version = '0.1.0' @@ -54,7 +50,7 @@ Gem::Specification.new do |s| s.homepage = 'https://github.com/jekyll/my-awesome-jekyll-theme' s.files = `git ls-files -z`.split("\x0").grep(%r{^(assets|_sass|_includes|_layouts)/}) end -``` +{% endhighlight %} ### Layouts and includes @@ -96,12 +92,8 @@ Themes are published via [RubyGems.org](https://rubygems.org). You'll need a Rub 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 - ``` + 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 - ``` + gem push my-awesome-jekyll-theme-*.gem From 78acdc72e2e45dbfe17a0b8c13312bc701852d0c Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Tue, 8 Mar 2016 12:03:22 -0500 Subject: [PATCH 12/34] fix for history not having a title --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index d4365723..fbbf017e 100644 --- a/Rakefile +++ b/Rakefile @@ -89,7 +89,7 @@ end def siteify_file(file, front_matter = {}) abort "You seem to have misplaced your #{file} file. I can haz?" unless File.exists?(file) - title = File.read(file).match(/\A# (.*)$/)[1] + title = File.read(file)[/\A# (.*)$/, 1] slug = File.basename(file, ".markdown").downcase front_matter = front_matter.merge({ "title" => title, From b29ce257c38db5213a0bd55c7dc1b34b3b1eb0da Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Tue, 8 Mar 2016 12:08:22 -0500 Subject: [PATCH 13/34] fix for history file title --- rake/site.rake | 2 +- site/_docs/history.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/rake/site.rake b/rake/site.rake index cb329c0e..b771e5d6 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', { :title => "History" }) + siteify_file('History.markdown', { "title" => "History" }) end desc "Copy the Code of Conduct" diff --git a/site/_docs/history.md b/site/_docs/history.md index 6ebeec3a..2d7aae77 100644 --- a/site/_docs/history.md +++ b/site/_docs/history.md @@ -1,5 +1,4 @@ --- -:title: History title: layout: docs permalink: "/docs/history/" From 92168954d2082542468cb219844928e63a4abd5b Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 11 Mar 2016 14:41:26 -0500 Subject: [PATCH 14/34] remove asset support --- lib/jekyll/theme.rb | 4 ---- site/_docs/themes.md | 16 +++------------- test/test_theme.rb | 12 ++++-------- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 0357a49e..62b2dae5 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -12,10 +12,6 @@ module Jekyll @root ||= gemspec.full_gem_path end - def assets_path - path_for :assets - end - def includes_path path_for :includes end diff --git a/site/_docs/themes.md b/site/_docs/themes.md index 97e8ea81..6196597e 100644 --- a/site/_docs/themes.md +++ b/site/_docs/themes.md @@ -4,7 +4,7 @@ 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, stylesheets, and static assets in a way that can be overridden by your site's content. +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 @@ -23,14 +23,13 @@ You can have multiple themes listed in your site's Gemfile, but only one theme c ## Overriding theme defaults -Jekyll themes set default layouts, includes, stylesheets and static assets, 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 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` -* `/assets/` Refer to your selected theme's documentation and source repository for more information on what files you can override. {: .note .info} @@ -48,7 +47,7 @@ Gem::Specification.new do |s| s.author = 'Dr. Jekyll' s.email = 'doc@jekyllrb.com' s.homepage = 'https://github.com/jekyll/my-awesome-jekyll-theme' - s.files = `git ls-files -z`.split("\x0").grep(%r{^(assets|_sass|_includes|_layouts)/}) + s.files = `git ls-files -z`.split("\x0").grep(%r{^_(sass|includes|layouts)/}) end {% endhighlight %} @@ -62,15 +61,6 @@ For example, if your theme has a `/_layouts/page.html` file, and a page has `lay 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. -You may also need to output the rendered styles, by adding an `/assets/style.scss` file, which imports the necessary stylesheets, by following the instructions in the [static assets](#static-assets) section below. - -Do not place your theme's styles in the `/assets/style.scss` file. Storing styles in the `/_sass` folder allows users to use SaSS's `@import` directive, to combine their custom styles, with the theme's in a single file. -{: .info .note } - -### Static assets - -You may also bundle static assets within your theme (e.g., javascripts, images, and fonts). Place any files you'd like in your theme's `/assets` folder, which will be included in the published site. It's common to group files into folders, such as `/assets/js`, `/assets/img`, and `/assets/fonts`. - ### 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? diff --git a/test/test_theme.rb b/test/test_theme.rb index 06fc3037..379fe203 100644 --- a/test/test_theme.rb +++ b/test/test_theme.rb @@ -28,20 +28,16 @@ class TestTheme < JekyllUnitTest end context "path generation" do - [:assets, :layouts, :includes, :sass].each do |folder| + [:layouts, :includes, :sass].each do |folder| should "know the #{folder} path" do - if folder == :assets - expected = File.expand_path(folder.to_s, @expected_root) - else - expected = File.expand_path("_#{folder}", @expected_root) - end + 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("./assets", @expected_root) - assert_equal expected, @theme.send(:path_for, :assets) + 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 From 1a203a05981711b0b81e8ddfd278dc515568501c Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 11 Mar 2016 14:43:44 -0500 Subject: [PATCH 15/34] use variables in the docs --- site/_docs/themes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site/_docs/themes.md b/site/_docs/themes.md index 6196597e..2b7155bb 100644 --- a/site/_docs/themes.md +++ b/site/_docs/themes.md @@ -40,12 +40,12 @@ Jekyll themes are distributed as Ruby gems. The only required file is the [Ruby {% highlight ruby %} Gem::Specification.new do |s| - s.name = 'My Awesome theme' + s.name = '' s.version = '0.1.0' s.license = 'MIT' - s.summary = 'This is an awesome Jekyll theme!' - s.author = 'Dr. Jekyll' - s.email = 'doc@jekyllrb.com' + 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 From f89fbbe9e6f8721064b6ba5cc037cb7557b2c49c Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 11 Mar 2016 14:45:14 -0500 Subject: [PATCH 16/34] rebuild site --- site/_docs/history.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/_docs/history.md b/site/_docs/history.md index 5ecb439b..175a3b1a 100644 --- a/site/_docs/history.md +++ b/site/_docs/history.md @@ -1,5 +1,5 @@ --- -title: +title: History layout: docs permalink: "/docs/history/" note: This file is autogenerated. Edit /History.markdown instead. From 0a04932020dfcd57e5732040240cda025d5a2950 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 11 Mar 2016 14:46:05 -0500 Subject: [PATCH 17/34] remove assets dir from theme fixture --- test/fixtures/test-theme/assets/application.coffee | 0 test/fixtures/test-theme/assets/script.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/fixtures/test-theme/assets/application.coffee delete mode 100644 test/fixtures/test-theme/assets/script.js diff --git a/test/fixtures/test-theme/assets/application.coffee b/test/fixtures/test-theme/assets/application.coffee deleted file mode 100644 index e69de29b..00000000 diff --git a/test/fixtures/test-theme/assets/script.js b/test/fixtures/test-theme/assets/script.js deleted file mode 100644 index e69de29b..00000000 From 7b63c8d201043f1136859b4961197b0e891e9d58 Mon Sep 17 00:00:00 2001 From: Saleem Rashid Date: Fri, 11 Mar 2016 20:45:22 +0000 Subject: [PATCH 18/34] theme: fix invalid theme test --- test/test_theme.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_theme.rb b/test/test_theme.rb index 379fe203..c3069a69 100644 --- a/test/test_theme.rb +++ b/test/test_theme.rb @@ -22,7 +22,7 @@ class TestTheme < JekyllUnitTest should "raise an error for invalid themes" do assert_raises Jekyll::Errors::MissingDependencyException do - Theme.new("foo") + Theme.new("foo").gemspec end end end From 64e4984ff59441ec20148314e11f965501f46c91 Mon Sep 17 00:00:00 2001 From: Saleem Rashid Date: Fri, 11 Mar 2016 20:59:54 +0000 Subject: [PATCH 19/34] theme: fix invalid theme test, really --- test/test_theme.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_theme.rb b/test/test_theme.rb index c3069a69..00095528 100644 --- a/test/test_theme.rb +++ b/test/test_theme.rb @@ -22,7 +22,7 @@ class TestTheme < JekyllUnitTest should "raise an error for invalid themes" do assert_raises Jekyll::Errors::MissingDependencyException do - Theme.new("foo").gemspec + Theme.new("foo").version end end end From 285aa54445a5f9aead0cabd79b6e4980f3dd927c Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 11 Mar 2016 16:18:11 -0500 Subject: [PATCH 20/34] remove even more asset support --- lib/jekyll/theme.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 62b2dae5..25160f80 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -27,8 +27,7 @@ module Jekyll private def path_for(folder) - folder = "_#{folder}" unless folder == :assets - path = Jekyll.sanitized_path root, folder.to_s + path = Jekyll.sanitized_path root, "_#{folder}" path if Dir.exists?(path) end From bdaa0c3b5baaa17356be3d5c2516b66aaacf35f5 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 25 Mar 2016 14:46:00 -0400 Subject: [PATCH 21/34] register sass path --- lib/jekyll/theme.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 25160f80..d9f7f52d 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -24,6 +24,10 @@ module Jekyll path_for :sass end + def configure_sass + Sass.load_paths << sass_path if sass_path + end + private def path_for(folder) From 4a70382d5bea80efb552e8b19f7bd9bc0da8d369 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 25 Mar 2016 14:48:44 -0400 Subject: [PATCH 22/34] test that themes register with sass --- test/test_theme.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_theme.rb b/test/test_theme.rb index 00095528..171704ce 100644 --- a/test/test_theme.rb +++ b/test/test_theme.rb @@ -25,6 +25,12 @@ class TestTheme < JekyllUnitTest Theme.new("foo").version end end + + should "add itself to sass's load path" do + refute Sass.load_paths.includes?(@theme.sass_path) + @theme.configure_sass + assert Sass.load_paths.includes?(@theme.sass_path) + end end context "path generation" do From 0f90fe67d26b99b936763178bf4f3f6de7cfae07 Mon Sep 17 00:00:00 2001 From: Ben Balter Date: Fri, 25 Mar 2016 15:00:42 -0400 Subject: [PATCH 23/34] but who will test the testers? --- test/test_theme.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_theme.rb b/test/test_theme.rb index 171704ce..625fe29d 100644 --- a/test/test_theme.rb +++ b/test/test_theme.rb @@ -27,7 +27,6 @@ class TestTheme < JekyllUnitTest end should "add itself to sass's load path" do - refute Sass.load_paths.includes?(@theme.sass_path) @theme.configure_sass assert Sass.load_paths.includes?(@theme.sass_path) end From 6aae64c985a2409003eb7904a7bfa2f3640f2b28 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 25 Mar 2016 15:22:29 -0700 Subject: [PATCH 24/34] Update test_theme.rb --- test/test_theme.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_theme.rb b/test/test_theme.rb index 625fe29d..46c456d4 100644 --- a/test/test_theme.rb +++ b/test/test_theme.rb @@ -28,7 +28,7 @@ class TestTheme < JekyllUnitTest should "add itself to sass's load path" do @theme.configure_sass - assert Sass.load_paths.includes?(@theme.sass_path) + assert Sass.load_paths.include?(@theme.sass_path), "Sass load paths should include the theme sass dir" end end From a1a4b3937accbe593241c1d37c4f034b8c8b4104 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 25 Mar 2016 16:29:37 -0700 Subject: [PATCH 25/34] Add Layout#relative_path so the layout can tell us what its path is for error messages --- lib/jekyll/convertible.rb | 6 +++--- lib/jekyll/layout.rb | 8 ++++++++ lib/jekyll/renderer.rb | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index f58796f0..d563961a 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -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..aed6afe1 100644 --- a/lib/jekyll/layout.rb +++ b/lib/jekyll/layout.rb @@ -45,5 +45,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(site.source)).to_s + end end end diff --git a/lib/jekyll/renderer.rb b/lib/jekyll/renderer.rb index 7d20d452..820d300e 100644 --- a/lib/jekyll/renderer.rb +++ b/lib/jekyll/renderer.rb @@ -146,7 +146,7 @@ module Jekyll layout.content, payload, info, - File.join(site.config['layouts_dir'], layout.name) + layout.relative_path ) # Add layout to dependency tree From 0920d2b48ab67fadf4ca55f137652ac4f5813485 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 25 Mar 2016 17:06:26 -0700 Subject: [PATCH 26/34] LayoutReader#read: read in Theme layouts if they aren't already registered --- lib/jekyll/readers/layout_reader.rb | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/jekyll/readers/layout_reader.rb b/lib/jekyll/readers/layout_reader.rb index f0620294..965daf2a 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.layout_dir 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 From 33255e3ac383709c0f69c2413801b591a884872c Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 25 Mar 2016 17:44:29 -0700 Subject: [PATCH 27/34] IncludeTag: implement multiple load paths --- lib/jekyll/site.rb | 6 +++- lib/jekyll/tags/include.rb | 64 ++++++++++++++++++++------------------ test/test_tags.rb | 8 ++--- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index e13a6bda..9ceda0a0 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -11,7 +11,7 @@ module Jekyll :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,8 +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'] 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/test/test_tags.rb b/test/test_tags.rb index bd481265..81089dab 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -615,7 +615,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 [\"/Users/parkr/jekyll/jekyll/test/source/_includes\"].", ex.message end end @@ -756,7 +756,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 @@ -838,7 +838,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 @@ -892,7 +892,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 From 655ffd45be4fc7144043b048f390569fb8964ccd Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 21 Apr 2016 11:26:33 -0700 Subject: [PATCH 28/34] Use source_dir instead of my own computer's path --- test/test_tags.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_tags.rb b/test/test_tags.rb index 81089dab..e4438a3e 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -615,7 +615,7 @@ title: Include symlink CONTENT create_post(content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true }) end - assert_match "Could not locate the included file 'tmp/pages-test-does-not-exist' in any of [\"/Users/parkr/jekyll/jekyll/test/source/_includes\"].", 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 From d5dd423bfde2efff8c4c34e17f67f6473f99dc85 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 21 Apr 2016 16:09:23 -0700 Subject: [PATCH 29/34] Add cucumber specs. --- features/theme.feature | 61 +++++++++++++++++++ .../test-theme/_includes/include.html | 1 + .../fixtures/test-theme/_layouts/default.html | 1 + test/fixtures/test-theme/_sass/style.scss | 3 + 4 files changed, 66 insertions(+) create mode 100644 features/theme.feature 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/test/fixtures/test-theme/_includes/include.html b/test/fixtures/test-theme/_includes/include.html index e69de29b..98608392 100644 --- a/test/fixtures/test-theme/_includes/include.html +++ 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 index e69de29b..902c61c3 100644 --- a/test/fixtures/test-theme/_layouts/default.html +++ 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 index e69de29b..a1e07da6 100644 --- a/test/fixtures/test-theme/_sass/style.scss +++ b/test/fixtures/test-theme/_sass/style.scss @@ -0,0 +1,3 @@ +.sample { + color: black; +} From 2b73696d0305d44b903f64bf06296e574fa61856 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 21 Apr 2016 16:10:19 -0700 Subject: [PATCH 30/34] Set @path and @base_dir on the Layout per whether it's in the theme --- lib/jekyll/convertible.rb | 2 +- lib/jekyll/layout.rb | 11 +++++++++-- lib/jekyll/page.rb | 1 + lib/jekyll/site.rb | 13 +++++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index 0a2e3106..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 diff --git a/lib/jekyll/layout.rb b/lib/jekyll/layout.rb index aed6afe1..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 = {} @@ -51,7 +58,7 @@ module Jekyll # 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(site.source)).to_s + @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/site.rb b/lib/jekyll/site.rb index 9ceda0a0..9afdbde3 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -373,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 From 3b2d68d3337fec9645b721c67eef999765cd30db Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 21 Apr 2016 16:10:43 -0700 Subject: [PATCH 31/34] LayoutReader: read proper item from theme class --- lib/jekyll/readers/layout_reader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jekyll/readers/layout_reader.rb b/lib/jekyll/readers/layout_reader.rb index 965daf2a..df8931cb 100644 --- a/lib/jekyll/readers/layout_reader.rb +++ b/lib/jekyll/readers/layout_reader.rb @@ -23,7 +23,7 @@ module Jekyll end def theme_layout_directory - @theme_layout_directory ||= site.theme.layout_dir if site.theme + @theme_layout_directory ||= site.theme.layouts_path if site.theme end private From 49a3f543f3853c44894c82d8cc70997ef737c5b3 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 21 Apr 2016 16:11:09 -0700 Subject: [PATCH 32/34] Theme: configure sass at initialize time so we don't forget --- lib/jekyll/theme.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index d9f7f52d..15ca8c85 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -6,6 +6,7 @@ module Jekyll def initialize(name) @name = name.downcase.strip + configure_sass end def root @@ -25,7 +26,9 @@ module Jekyll end def configure_sass - Sass.load_paths << sass_path if sass_path + return unless sass_path + require 'sass' + Sass.load_paths << sass_path end private From bac65ea8c5f42502eee0f1571b057564aa74bed4 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 21 Apr 2016 16:16:46 -0700 Subject: [PATCH 33/34] C'mon travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) 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: From 57ccbe08ea8553e31d4234380277865a3d6569ad Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Thu, 21 Apr 2016 16:32:52 -0700 Subject: [PATCH 34/34] Ensure symlinks work as expected (and secured). --- lib/jekyll/theme.rb | 11 ++++++++++- test/fixtures/test-theme/_symlink | 1 + test/test_theme.rb | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 120000 test/fixtures/test-theme/_symlink diff --git a/lib/jekyll/theme.rb b/lib/jekyll/theme.rb index 15ca8c85..ed9cc68d 100644 --- a/lib/jekyll/theme.rb +++ b/lib/jekyll/theme.rb @@ -34,10 +34,19 @@ module Jekyll private def path_for(folder) - path = Jekyll.sanitized_path root, "_#{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 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/test_theme.rb b/test/test_theme.rb index 46c456d4..9767946e 100644 --- a/test/test_theme.rb +++ b/test/test_theme.rb @@ -52,6 +52,11 @@ class TestTheme < JekyllUnitTest 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