Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support data sources #1003

Merged
merged 1 commit into from
Oct 1, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions features/data.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Feature: Data
In order to use well-formatted data in my blog
As a blog's user
I want to use _data directory in my site

Scenario: autoload *.yaml files in _data directory
Given I have a _data directory
And I have a "_data/products.yaml" file with content:
"""
- name: sugar
price: 5.3
- name: salt
price: 2.5
"""
And I have an "index.html" page that contains "{% for product in site.data.products %}{{product.name}}{% endfor %}"
When I run jekyll
Then the "_site/index.html" file should exist
And I should see "sugar" in "_site/index.html"
And I should see "salt" in "_site/index.html"

Scenario: autoload *.yml files in _data directory
Given I have a _data directory
And I have a "_data/members.yml" file with content:
"""
- name: Jack
age: 28
- name: Leon
age: 34
"""
And I have an "index.html" page that contains "{% for member in site.data.members %}{{member.name}}{% endfor %}"
When I run jekyll
Then the "_site/index.html" file should exist
And I should see "Jack" in "_site/index.html"
And I should see "Leon" in "_site/index.html"

Scenario: autoload *.yml files in _data directory with space in file name
Given I have a _data directory
And I have a "_data/team members.yml" file with content:
"""
- name: Jack
age: 28
- name: Leon
age: 34
"""
And I have an "index.html" page that contains "{% for member in site.data.team_members %}{{member.name}}{% endfor %}"
When I run jekyll
Then the "_site/index.html" file should exist
And I should see "Jack" in "_site/index.html"
And I should see "Leon" in "_site/index.html"

Scenario: should be backward compatible with site.data in _config.yml
Given I have a "_config.yml" file with content:
"""
data:
- name: Jack
age: 28
- name: Leon
age: 34
"""
And I have an "index.html" page that contains "{% for member in site.data %}{{member.name}}{% endfor %}"
When I run jekyll
Then the "_site/index.html" file should exist
And I should see "Jack" in "_site/index.html"
And I should see "Leon" in "_site/index.html"

6 changes: 6 additions & 0 deletions features/step_definitions/jekyll_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
end
end

Given /^I have an? "(.*)" file with content:$/ do |file, text|
File.open(file, 'w') do |f|
f.write(text)
end
end

Given /^I have an? (.*) directory$/ do |dir|
FileUtils.mkdir_p(dir)
end
Expand Down
2 changes: 2 additions & 0 deletions jekyll.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Gem::Specification.new do |s|
bin/jekyll
cucumber.yml
features/create_sites.feature
features/data.feature
features/drafts.feature
features/embed_filters.feature
features/include_tag.feature
Expand Down Expand Up @@ -203,6 +204,7 @@ Gem::Specification.new do |s|
test/helper.rb
test/source/+/foo.md
test/source/.htaccess
test/source/_data/members.yaml
test/source/_includes/params.html
test/source/_includes/sig.markdown
test/source/_layouts/default.html
Expand Down
1 change: 1 addition & 0 deletions lib/jekyll/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Configuration < Hash
'destination' => File.join(Dir.pwd, '_site'),
'plugins' => '_plugins',
'layouts' => '_layouts',
'data_source' => '_data',
'keep_files' => ['.git','.svn'],

'timezone' => nil, # use the local timezone
Expand Down
40 changes: 38 additions & 2 deletions lib/jekyll/site.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class Site
attr_accessor :config, :layouts, :posts, :pages, :static_files,
:categories, :exclude, :include, :source, :dest, :lsi, :pygments,
:permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts,
:show_drafts, :keep_files, :baseurl, :file_read_opts
:show_drafts, :keep_files, :baseurl, :data, :file_read_opts

attr_accessor :converters, :generators

Expand Down Expand Up @@ -56,6 +56,7 @@ def reset
self.static_files = []
self.categories = Hash.new { |hash, key| hash[key] = [] }
self.tags = Hash.new { |hash, key| hash[key] = [] }
self.data = {}

if self.limit_posts < 0
raise ArgumentError, "limit_posts must be a non-negative number"
Expand Down Expand Up @@ -110,6 +111,7 @@ def plugins_path
def read
self.read_layouts
self.read_directories
self.read_data(config['data_source'])
end

# Read all the files in <source>/<layouts> and create a new Layout object
Expand Down Expand Up @@ -197,6 +199,25 @@ def read_drafts(dir)
end
end

# Read and parse all yaml files under <source>/<dir>
#
# Returns nothing
def read_data(dir)
base = File.join(self.source, dir)
return unless File.directory?(base) && (!self.safe || !File.symlink?(base))

entries = Dir.chdir(base) { Dir['*.{yaml,yml}'] }
entries.delete_if { |e| File.directory?(File.join(base, e)) }

entries.each do |entry|
path = File.join(self.source, dir, entry)
next if File.symlink?(path) && self.safe

key = sanitize_filename(File.basename(entry, '.*'))
self.data[key] = YAML.safe_load_file(path)
end
end

# Run each of the Generators.
#
# Returns nothing.
Expand Down Expand Up @@ -262,6 +283,14 @@ def post_attr_hash(post_attr)
hash
end

# Prepare site data for site payload. The method maintains backward compatibility
# if the key 'data' is already used in _config.yml.
#
# Returns the Hash to be hooked to site.data.
def site_data
self.config['data'] || self.data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a test for this bit of logic around the backward compatibility? If we're going to maintain backwards compatibility, I'd like to make sure we don't break it.

Not a blocker, so I think I'll go ahead and merge this, but it's something that would be nice to have for later. 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right you are! I totally missed that before. Thank you!

end

# The Hash payload containing site-wide data.
#
# Returns the Hash: { "site" => data } where data is a Hash with keys:
Expand All @@ -283,7 +312,8 @@ def site_payload
"pages" => self.pages,
"html_pages" => self.pages.reject { |page| !page.html? },
"categories" => post_attr_hash('categories'),
"tags" => post_attr_hash('tags')})}
"tags" => post_attr_hash('tags'),
"data" => site_data})}
end

# Filter out any files/directories that are hidden or backup files (start
Expand Down Expand Up @@ -393,5 +423,11 @@ def limit_posts!
def site_cleaner
@site_cleaner ||= Cleaner.new(self)
end

def sanitize_filename(name)
name = name.gsub(/[^\w\s_-]+/, '')
name = name.gsub(/(^|\b\s)\s+($|\s?\b)/, '\\1\\2')
name = name.gsub(/\s+/, '_')
end
end
end
17 changes: 17 additions & 0 deletions site/docs/structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ A basic Jekyll site usually looks something like this:
├── _posts
| ├── 2007-10-29-why-every-programmer-should-play-nethack.textile
| └── 2009-04-26-barcamp-boston-4-roundup.textile
├── _data
| └── members.yml
├── _site
└── index.html
{% endhighlight %}
Expand Down Expand Up @@ -121,6 +123,21 @@ An overview of what each of these does:
</p>
</td>
</tr>
<tr>
<td>
<p><code>_data</code></p>
</td>
<td>
<p>

Well-formatted site data should be placed here. The jekyll engine will
autoload all yaml files (ends with <code>.yml</code> or <code>.yaml</code>)
in this directory. If there's a file <code>members.yml</code> under the directory,
then you can access contents of the file through <code>site.data.members</code>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plese change to <code>site.data.members</code>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already <code>site.data.members</code> here, you mean something else ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve seen <code>site.members</code>. My window wasn’t reloaded. Nevermind.


</p>
</td>
</tr>
<tr>
<td>
<p><code>_site</code></p>
Expand Down
2 changes: 2 additions & 0 deletions test/source/_data/languages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- java
- ruby
7 changes: 7 additions & 0 deletions test/source/_data/members.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- name: Jack
age: 27
blog: http://example.com/jack

- name: John
age: 32
blog: http://example.com/john
1 change: 1 addition & 0 deletions test/source/_data/products.yml
4 changes: 4 additions & 0 deletions test/source/products.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: sugar
price: 5.3
- name: salt
price: 2.5
1 change: 1 addition & 0 deletions test/source/symlink-test/_data
57 changes: 57 additions & 0 deletions test/test_site.rb
Original file line number Diff line number Diff line change
Expand Up @@ -335,5 +335,62 @@ def generate(site)
end
end

context 'data directory' do
should 'auto load yaml files' do
site = Site.new(Jekyll.configuration)
site.process

file_content = YAML.safe_load_file(File.join(source_dir, '_data', 'members.yaml'))

assert_equal site.data['members'], file_content
assert_equal site.site_payload['site']['data']['members'], file_content
end

should 'auto load yml files' do
site = Site.new(Jekyll.configuration)
site.process

file_content = YAML.safe_load_file(File.join(source_dir, '_data', 'languages.yml'))

assert_equal site.data['languages'], file_content
assert_equal site.site_payload['site']['data']['languages'], file_content
end

should "load symlink files in unsafe mode" do
site = Site.new(Jekyll.configuration.merge({'safe' => false}))
site.process

file_content = YAML.safe_load_file(File.join(source_dir, '_data', 'products.yml'))

assert_equal site.data['products'], file_content
assert_equal site.site_payload['site']['data']['products'], file_content
end

should "not load symlink files in safe mode" do
site = Site.new(Jekyll.configuration.merge({'safe' => true}))
site.process

assert_nil site.data['products']
assert_nil site.site_payload['site']['data']['products']
end

should "load symlink directory in unsafe mode" do
site = Site.new(Jekyll.configuration.merge({'safe' => false, 'data_source' => File.join('symlink-test', '_data')}))
site.process

assert_not_nil site.data['products']
assert_not_nil site.data['languages']
assert_not_nil site.data['members']
end

should "not load symlink directory in safe mode" do
site = Site.new(Jekyll.configuration.merge({'safe' => true, 'data_source' => File.join('symlink-test', '_data')}))
site.process

assert_nil site.data['products']
assert_nil site.data['languages']
assert_nil site.data['members']
end
end
end
end