diff --git a/cucumber.yml b/cucumber.yml deleted file mode 100644 index 78342a17..00000000 --- a/cucumber.yml +++ /dev/null @@ -1,3 +0,0 @@ -default: --format pretty -travis: --format progress -html_report: --format progress --format html --out=features_report.html diff --git a/features/support/overview.rb b/features/support/overview.rb new file mode 100644 index 00000000..493ef6a6 --- /dev/null +++ b/features/support/overview.rb @@ -0,0 +1,183 @@ +require 'fileutils' +require 'colorator' +require 'cucumber/formatter/console' +require 'cucumber/formatter/io' +require 'gherkin/formatter/escaping' + +module Features + module Support + # The formatter used for --format pretty (the default formatter). + # + # This formatter prints features to plain text - exactly how they were parsed, + # just prettier. That means with proper indentation and alignment of table columns. + # + # If the output is STDOUT (and not a file), there are bright colours to watch too. + # + class Overview + include FileUtils + include Cucumber::Formatter::Console + include Cucumber::Formatter::Io + include Gherkin::Formatter::Escaping + attr_writer :indent + attr_reader :runtime + + def initialize(runtime, path_or_io, options) + @runtime, @io, @options = runtime, ensure_io(path_or_io, "pretty"), options + @exceptions = [] + @indent = 0 + @prefixes = options[:prefixes] || {} + @delayed_messages = [] + end + + def before_features(features) + print_profile_information + end + + def after_features(features) + print_summary(features) + end + + def before_feature(feature) + @exceptions = [] + @indent = 0 + end + + def comment_line(comment_line) + end + + def after_tags(tags) + end + + def tag_name(tag_name) + end + + def before_feature_element(feature_element) + @indent = 2 + @scenario_indent = 2 + end + + def after_feature_element(feature_element) + end + + def before_background(background) + @indent = 2 + @scenario_indent = 2 + @in_background = true + end + + def after_background(background) + @in_background = nil + end + + def background_name(keyword, name, file_colon_line, source_indent) + print_feature_element_name(keyword, name, file_colon_line, source_indent) + end + + def before_examples_array(examples_array) + end + + def examples_name(keyword, name) + end + + def before_outline_table(outline_table) + end + + def after_outline_table(outline_table) + end + + def scenario_name(keyword, name, file_colon_line, source_indent) + print_feature_element_name(keyword, name, file_colon_line, source_indent) + end + + def before_step(step) + @current_step = step + end + + def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line) + @hide_this_step = false + if exception + if @exceptions.include?(exception) + @hide_this_step = true + return + end + @exceptions << exception + end + if status != :failed && @in_background ^ background + @hide_this_step = true + return + end + @status = status + end + + CHARS = { + :failed => "x".red, + :pending => "?".yellow, + :undefined => "x".red, + :passed => ".".green, + :skipped => "-".blue + } + + def step_name(keyword, step_match, status, source_indent, background, file_colon_line) + @io.print CHARS[status] + end + + def doc_string(string) + return if @options[:no_multiline] || @hide_this_step + s = %{"""\n#{string}\n"""}.indent(@indent) + s = s.split("\n").map{|l| l =~ /^\s+$/ ? '' : l}.join("\n") + @io.puts(format_string(s, @current_step.status)) + @io.flush + end + + def exception(exception, status) + return if @hide_this_step + print_exception(exception, status, @indent) + @io.flush + end + + def before_multiline_arg(multiline_arg) + end + + def after_multiline_arg(multiline_arg) + end + + def before_table_row(table_row) + end + + def after_table_row(table_row) + end + + def after_table_cell(cell) + end + + def table_cell_value(value, status) + end + + private + + def print_feature_element_name(keyword, name, file_colon_line, source_indent) + @io.puts + names = name.empty? ? [name] : name.split("\n") + line = " #{keyword}: #{names[0]}" + if @options[:source] + line_comment = "#{file_colon_line}" + @io.print(line_comment) + end + @io.print(line) + @io.print " " + @io.flush + end + + def cell_prefix(status) + @prefixes[status] + end + + def print_summary(features) + @io.puts + print_stats(features, @options) + print_snippets(@options) + print_passing_wip(@options) + end + end + end +end diff --git a/lib/jekyll/collection.rb b/lib/jekyll/collection.rb index 8db6c54c..5edfeef6 100644 --- a/lib/jekyll/collection.rb +++ b/lib/jekyll/collection.rb @@ -22,14 +22,28 @@ module Jekyll @docs ||= [] end + # Fetch the static files in this collection. + # Defaults to an empty array if no static files have been read in. + # + # Returns an array of Jekyll::StaticFile objects. + def files + @files ||= [] + end + # Read the allowed documents into the collection's array of docs. # # Returns the sorted array of docs. def read filtered_entries.each do |file_path| - doc = Jekyll::Document.new(Jekyll.sanitized_path(directory, file_path), { site: site, collection: self }) - doc.read - docs << doc + full_path = Jekyll.sanitized_path(directory, file_path) + if Utils.has_yaml_header? full_path + doc = Jekyll::Document.new(full_path, { site: site, collection: self }) + doc.read + docs << doc + else + relative_dir = File.join(relative_directory, File.dirname(file_path)).chomp("/.") + files << StaticFile.new(site, site.source, relative_dir, File.basename(full_path), self) + end end docs.sort! end @@ -118,6 +132,7 @@ module Jekyll metadata.merge({ "label" => label, "docs" => docs, + "files" => files, "directory" => directory, "output" => write?, "relative_directory" => relative_directory diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb index dc2fdb13..891610fa 100644 --- a/lib/jekyll/document.rb +++ b/lib/jekyll/document.rb @@ -97,20 +97,12 @@ module Jekyll '.coffee'.eql?(extname) end - # Determine whether the document has a YAML header. - # - # Returns true if the file starts with three dashes - def has_yaml_header? - @has_yaml_header unless @has_yaml_header.nil? - @has_yaml_header = !!(File.open(path, 'rb') { |f| f.read(5) } =~ /\A---\r?\n/) - end - # Determine whether the file should be rendered with Liquid. # # Returns false if the document is either an asset file or a yaml file, # true otherwise. def render_with_liquid? - !(coffeescript_file? || yaml_file?) && has_yaml_header? + !(coffeescript_file? || yaml_file?) end # Determine whether the file should be placed into layouts. @@ -118,7 +110,7 @@ module Jekyll # Returns false if the document is either an asset file or a yaml file, # true otherwise. def place_in_layout? - !(asset_file? || yaml_file?) && has_yaml_header? + !(asset_file? || yaml_file?) end # The URL template where the document would be accessible. @@ -214,7 +206,7 @@ module Jekyll unless defaults.empty? @data = defaults end - @content = File.open(path, "rb", merged_file_read_opts(opts)) { |f| f.read } + @content = File.read(path, merged_file_read_opts(opts)) if content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m @content = $POSTMATCH data_file = SafeYAML.load($1) diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index a656b088..5aee3e72 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -144,7 +144,7 @@ module Jekyll if File.directory?(f_abs) f_rel = File.join(dir, f) read_directories(f_rel) unless dest.sub(/\/$/, '') == f_abs - elsif has_yaml_header?(f_abs) + elsif Utils.has_yaml_header?(f_abs) page = Page.new(self, source, dir, f) pages << page if publisher.publish?(page) else @@ -432,7 +432,7 @@ module Jekyll def documents collections.reduce(Set.new) do |docs, (_, collection)| - docs.merge(collection.docs) + docs + collection.docs + collection.files end.to_a end @@ -454,10 +454,6 @@ module Jekyll pages.any? { |page| page.uses_relative_permalinks } end - def has_yaml_header?(file) - !!(File.open(file, 'rb') { |f| f.read(5) } =~ /\A---\r?\n/) - end - def limit_posts! limit = posts.length < limit_posts ? posts.length : limit_posts self.posts = posts[-limit, limit] diff --git a/lib/jekyll/static_file.rb b/lib/jekyll/static_file.rb index 2382d03c..02087e49 100644 --- a/lib/jekyll/static_file.rb +++ b/lib/jekyll/static_file.rb @@ -9,11 +9,12 @@ module Jekyll # base - The String path to the . # dir - The String path between and the file. # name - The String filename of the file. - def initialize(site, base, dir, name) + def initialize(site, base, dir, name, collection = nil) @site = site @base = base @dir = dir @name = name + @collection = collection end # Returns source file path. @@ -23,7 +24,11 @@ module Jekyll # Returns the source file path relative to the site source def relative_path - @relative_path ||= path.sub(/\A#{@site.source}/, '') + @relative_path ||= File.join(*[@dir, @name].compact) + end + + def extname + File.extname(path) end # Obtain destination path. @@ -32,7 +37,15 @@ module Jekyll # # Returns destination file path. def destination(dest) - File.join(*[dest, @dir, @name].compact) + File.join(*[dest, destination_rel_dir, @name].compact) + end + + def destination_rel_dir + if @collection + @dir.gsub(/\A_/, '') + else + @dir + end end # Returns last modification time for this file. @@ -47,6 +60,13 @@ module Jekyll @@mtimes[path] != mtime end + # Whether to write the file to the filesystem + # + # Returns true. + def write? + true + end + # Write the static file to the destination directory (if modified). # # dest - The String path to the destination dir. @@ -75,7 +95,7 @@ module Jekyll def to_liquid { - "path" => relative_path, + "path" => File.join("", relative_path), "modified_time" => mtime.to_s, "extname" => File.extname(relative_path) } diff --git a/lib/jekyll/utils.rb b/lib/jekyll/utils.rb index dc190297..f3dbf9d7 100644 --- a/lib/jekyll/utils.rb +++ b/lib/jekyll/utils.rb @@ -1,100 +1,106 @@ module Jekyll module Utils - class << self + extend self - # Merges a master hash with another hash, recursively. - # - # master_hash - the "parent" hash whose values will be overridden - # other_hash - the other hash whose values will be persisted after the merge - # - # This code was lovingly stolen from some random gem: - # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html - # - # Thanks to whoever made it. - def deep_merge_hashes(master_hash, other_hash) - target = master_hash.dup + # Merges a master hash with another hash, recursively. + # + # master_hash - the "parent" hash whose values will be overridden + # other_hash - the other hash whose values will be persisted after the merge + # + # This code was lovingly stolen from some random gem: + # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html + # + # Thanks to whoever made it. + def deep_merge_hashes(master_hash, other_hash) + target = master_hash.dup - other_hash.keys.each do |key| - if other_hash[key].is_a? Hash and target[key].is_a? Hash - target[key] = Utils.deep_merge_hashes(target[key], other_hash[key]) - next - end - - target[key] = other_hash[key] + other_hash.keys.each do |key| + if other_hash[key].is_a? Hash and target[key].is_a? Hash + target[key] = Utils.deep_merge_hashes(target[key], other_hash[key]) + next end - target - end - - # Read array from the supplied hash favouring the singular key - # and then the plural key, and handling any nil entries. - # - # hash - the hash to read from - # singular_key - the singular key - # plural_key - the plural key - # - # Returns an array - def pluralized_array_from_hash(hash, singular_key, plural_key) - [].tap do |array| - array << (value_from_singular_key(hash, singular_key) || value_from_plural_key(hash, plural_key)) - end.flatten.compact - end - - def value_from_singular_key(hash, key) - hash[key] if (hash.key?(key) || (hash.default_proc && hash[key])) - end - - def value_from_plural_key(hash, key) - if hash.key?(key) || (hash.default_proc && hash[key]) - val = hash[key] - case val - when String - val.split - when Array - val.compact - end - end - end - - def transform_keys(hash) - result = {} - hash.each_key do |key| - result[yield(key)] = hash[key] - end - result - end - - # Apply #to_sym to all keys in the hash - # - # hash - the hash to which to apply this transformation - # - # Returns a new hash with symbolized keys - def symbolize_hash_keys(hash) - transform_keys(hash) { |key| key.to_sym rescue key } - end - - # Apply #to_s to all keys in the Hash - # - # hash - the hash to which to apply this transformation - # - # Returns a new hash with stringified keys - def stringify_hash_keys(hash) - transform_keys(hash) { |key| key.to_s rescue key } - end - - # Parse a date/time and throw an error if invalid - # - # input - the date/time to parse - # msg - (optional) the error message to show the user - # - # Returns the parsed date if successful, throws a FatalException - # if not - def parse_date(input, msg = "Input could not be parsed.") - Time.parse(input) - rescue ArgumentError - raise Errors::FatalException.new("Invalid date '#{input}': " + msg) + target[key] = other_hash[key] end + target end + + # Read array from the supplied hash favouring the singular key + # and then the plural key, and handling any nil entries. + # + # hash - the hash to read from + # singular_key - the singular key + # plural_key - the plural key + # + # Returns an array + def pluralized_array_from_hash(hash, singular_key, plural_key) + [].tap do |array| + array << (value_from_singular_key(hash, singular_key) || value_from_plural_key(hash, plural_key)) + end.flatten.compact + end + + def value_from_singular_key(hash, key) + hash[key] if (hash.key?(key) || (hash.default_proc && hash[key])) + end + + def value_from_plural_key(hash, key) + if hash.key?(key) || (hash.default_proc && hash[key]) + val = hash[key] + case val + when String + val.split + when Array + val.compact + end + end + end + + def transform_keys(hash) + result = {} + hash.each_key do |key| + result[yield(key)] = hash[key] + end + result + end + + # Apply #to_sym to all keys in the hash + # + # hash - the hash to which to apply this transformation + # + # Returns a new hash with symbolized keys + def symbolize_hash_keys(hash) + transform_keys(hash) { |key| key.to_sym rescue key } + end + + # Apply #to_s to all keys in the Hash + # + # hash - the hash to which to apply this transformation + # + # Returns a new hash with stringified keys + def stringify_hash_keys(hash) + transform_keys(hash) { |key| key.to_s rescue key } + end + + # Parse a date/time and throw an error if invalid + # + # input - the date/time to parse + # msg - (optional) the error message to show the user + # + # Returns the parsed date if successful, throws a FatalException + # if not + def parse_date(input, msg = "Input could not be parsed.") + Time.parse(input) + rescue ArgumentError + raise Errors::FatalException.new("Invalid date '#{input}': " + msg) + end + + # Determines whether a given file has + # + # Returns true if the YAML front matter is present. + def has_yaml_header?(file) + !!(File.open(file, 'rb') { |f| f.read(5) } =~ /\A---\r?\n/) + end + end end diff --git a/script/cibuild b/script/cibuild index acc4b70e..d2dd4ae4 100755 --- a/script/cibuild +++ b/script/cibuild @@ -2,6 +2,15 @@ set -e +parallelize_tests() { + ls -1 script/{test,cucumber,proof} | xargs -P 3 -L 1 /bin/bash +} + +serialize_tests() { + script/proof + script/test + script/cucumber +} + script/branding -script/proof -script/test +time parallelize_tests diff --git a/script/cucumber b/script/cucumber new file mode 100755 index 00000000..c6957356 --- /dev/null +++ b/script/cucumber @@ -0,0 +1,5 @@ +#!/bin/bash + +time bundle exec cucumber \ + -f Features::Support::Overview \ + "$@" diff --git a/script/test b/script/test index a97bd1c4..7bb742ef 100755 --- a/script/test +++ b/script/test @@ -2,16 +2,19 @@ # # Usage: # script/test -# script/test - -set -x +# script/test if [ -z "$1" ]; then - TEST_FILES="test/test_*.rb" + TEST_FILES="./test/test_*.rb" else TEST_FILES="$@" fi +RAKE_LIB_DIR=$(ruby -e "puts Gem::Specification.find_by_name('rake').gem_dir + '/lib'") + set -x -bundle exec rake +time bundle exec ruby -I"lib:test" \ + -I"${RAKE_LIB_DIR}" \ + "${RAKE_LIB_DIR}/rake/rake_test_loader.rb" \ + $TEST_FILES diff --git a/test/source/_slides/example-slide-1.html b/test/source/_slides/example-slide-1.html index fcd89b3c..52dd1ab4 100644 --- a/test/source/_slides/example-slide-1.html +++ b/test/source/_slides/example-slide-1.html @@ -2,3 +2,5 @@ title: Example slide layout: slide --- + +Wooot diff --git a/test/source/_with.dots/file.with.dots.md b/test/source/_with.dots/file.with.dots.md index e69de29b..211dfb5b 100644 --- a/test/source/_with.dots/file.with.dots.md +++ b/test/source/_with.dots/file.with.dots.md @@ -0,0 +1,4 @@ +--- +--- + +I'm a file with dots. diff --git a/test/test_document.rb b/test/test_document.rb index 3e3397c6..b352a050 100644 --- a/test/test_document.rb +++ b/test/test_document.rb @@ -62,7 +62,7 @@ class TestDocument < Test::Unit::TestCase }] })) @site.process - @document = @site.collections["slides"].docs.first + @document = @site.collections["slides"].docs.select{|d| d.is_a?(Document) }.first end should "know the frontmatter defaults" do @@ -199,20 +199,24 @@ class TestDocument < Test::Unit::TestCase "destination" => dest_dir })) @site.process - @document = @site.collections["slides"].docs.find { |doc| doc.relative_path == "_slides/octojekyll.png" } + @document = @site.collections["slides"].files.find { |doc| doc.relative_path == "_slides/octojekyll.png" } @dest_file = dest_dir("slides/octojekyll.png") end - should "be a document" do - assert !@document.nil? + should "be a static file" do + assert_equal true, @document.is_a?(StaticFile) end - should "not be rendered with Liquid" do - assert !@document.render_with_liquid? + should "be set to write" do + assert @document.write? + end + + should "be in the list of docs_to_write" do + assert @site.docs_to_write.include?(@document) end should "be output in the correct place" do - assert File.file? @dest_file + assert_equal true, File.file?(@dest_file) end end diff --git a/test/test_site.rb b/test/test_site.rb index a343b1c9..97111971 100644 --- a/test/test_site.rb +++ b/test/test_site.rb @@ -190,12 +190,12 @@ class TestSite < Test::Unit::TestCase should "read pages with yaml front matter" do abs_path = File.expand_path("about.html", @site.source) - assert_equal true, @site.send(:has_yaml_header?, abs_path) + assert_equal true, Utils.has_yaml_header?(abs_path) end should "enforce a strict 3-dash limit on the start of the YAML front-matter" do abs_path = File.expand_path("pgp.key", @site.source) - assert_equal false, @site.send(:has_yaml_header?, abs_path) + assert_equal false, Utils.has_yaml_header?(abs_path) end should "expose jekyll version to site payload" do