class FrontmatterTests

Public Class Methods

basepath() click to toggle source
# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_validator.rb, line 30
def basepath
  File.join(Dir.pwd, 'tests', 'schema')
end
check_keys(target, keys, title) click to toggle source

Public: checks a hash for expected keys

target - the hash under test keys - an array of keys the data is expected to have, usually loaded from

a schema file by loadschema()

title - A string representing `data`'s name

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_validator.rb, line 12
def check_keys(target, keys, title)
  keys -= ['config']
  unless target.respond_to?('keys')
    puts "The file #{title} is missing all frontmatter.".red
    return false
  end
  diff = keys - target.keys
  if diff.empty?
    return true
  else
    puts "\nThe file #{title} is missing the following keys:".red
    for k in diff
      puts "    * #{k}".red
    end
    return false
  end
end
check_one_of(data, key, value) click to toggle source
# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_validator.rb, line 75
def check_one_of(data, key, value)
  if value.keys.include?('one_of') && !one_of?(data[key], value['one_of'])
    puts "    * One of error: One of '#{data[key]}' was not".red
    puts "                    in the list of expected values in".red
    puts "                    #{File.join(basepath, value['one_of'])}\n".yellow

    false
  end
end
check_rules(data, key, value) click to toggle source
# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_validator.rb, line 85
def check_rules(data, key, value)
  if value.keys.include?('rules') && !follows_rules?(data[key], value['rules'])
    puts "    * Rules error: One of '#{data[key]}'".red
    puts "                   doesn't follow the rules defined in".red
    puts "                   #{basepath}/rules.yml\n".yellow
    false
  end
end
check_types(data, schema, file) click to toggle source

Internal: eventually, validate that the values match expected types

For example, if we expect the `date` key to be in yyyy-mm-dd format, validate that it's been entered in that format. If we expect authors to be an array, make sure we're getting an array.

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_validator.rb, line 39
def check_types(data, schema, file)
  return false unless data.respond_to?('keys')
  schema.each do |s|
    key = s[0]
    value = s[1]
    type = if value.class == Hash
             value['type']
           else
             value
           end

    next unless required?(key, schema)
    if key == 'config'
      next
    elsif value.class == Hash
      next unless value.keys.include?('one_of') || value.keys.include?('rules')
      violate_one_of = check_one_of(data, key, value) == false
      violate_rules = check_rules(data, key, value) == false
      return false if violate_one_of || violate_rules

    elsif type == 'Array' && data[key].class == Array
      next
    elsif type == 'Boolean' && data[key].is_a?(Boolean)
      next
    elsif type == 'String' && data[key].class == String
      next
    elsif type == 'Date'
      next
    else
      puts "    * invalid value for '#{key}' in #{file}. " \
           "Expected #{type} but was #{data[key].class}\n\n"
      return false
    end
  end
end
follows_rules?(value, rules) click to toggle source
# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_helper.rb, line 19
def follows_rules?(value, rules)
  if rules.include?('no-dash') && rules.include?('lowercase')
    FrontmatterRules.dashless?(value) && FrontmatterRules.lowercase?(value)
  elsif rules.include?('no-dash') && !rules.include?('lowercase')
    FrontmatterRules.dashless?(value)
  elsif !rules.include?('no-dash') && rules.include?('lowercase')
    FrontmatterRules.lowercase?(value)
  end
end
init_with_program(prog) click to toggle source

Internal: fired when `jekyll test` is run.

When `jekyll test` runs, `test_frontmatter` is fired with options and args passed from the command line.

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_initializer.rb, line 11
def init_with_program(prog)
  prog.command(:test) do |c|
    c.syntax 'test [options]'
    c.description 'Test your site for frontmatter.'

    c.option 'posts', '-p', 'Target only posts'
    c.option 'collections', '-c [COLLECTION]', 'Target a specific collection'
    c.option 'all', '-a', 'Test all collections (Default)'

    c.action do |args, options|
      options = { 'all' => true } if options.empty?
      test_frontmatter(args, options)
    end
  end
end
load_schema(file) click to toggle source

Public: Load a schema from file.

file - a string containing a filename

Used throughout to load a specific file. In the future the directories where these schema files are located could be loaded from _config.yml

Returns a hash loaded from the YAML doc or exits 1 if no schema file exists.

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_loader.rb, line 15
def load_schema(file)
  # binding.pry
  schema = File.join(Dir.pwd, schema_config['path'], file)
  # binding.pry
  if File.exist?(schema)
    YAML.load_file(schema)
  else
    puts "No schema for #{file}"
    exit 1
  end
end
one_of?(data, schema) click to toggle source
# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_helper.rb, line 6
def one_of?(data, schema)
  if schema.instance_of?(Array) && data.instance_of?(Array)
    (schema & data).count == data.count
  elsif schema.include? '.yml'
    schema_list = YAML.load_file(File.join(Dir.pwd, 'tests', 'schema', schema))
    (schema_list & data).count == data.count
  elsif schema.instance_of?(String) && data.instance_of?(Array)
    false
  else
    schema == data
  end
end
process(schema) click to toggle source

Public: processes a collection against a schema

schema - the hash-representation of a schema file

Opens each file in the collection's expected directory and checks the file's frontmatter for the expected keys and the expected format of the values.

NOTE - As it iterates through files, subdirectories will be ignored

Returns true or false depending on the success of the check.

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_processor.rb, line 17
def process(schema)
  dir = File.join(schema['config']['path'])
  passfail = []
  Dir.open(dir).each do |f|
    next if File.directory?(File.join(dir, f))
    file = File.open(File.join(dir, f))
    next if schema['config']['ignore'].include?(f)
    data = YAML.load_file(file)

    passfail.push check_keys(data, schema.keys, f)
    passfail.push check_types(data, schema, File.join(dir, f))
  end
  passfail.keep_if { |p| p == false }
  if passfail.empty?
    return true
  else
    puts "There were #{passfail.count} errors".red
    return false
  end
end
required?(key, schema) click to toggle source
# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_helper.rb, line 29
def required?(key, schema)
  is_required = true
  is_primary = schema[key]
  schema['config'] = schema['config'] || { 'optional': [] }
  is_optional = schema['config']['optional'].include?(key)

  if is_primary && !is_optional
    is_required
  elsif (is_primary && is_optional) || (!is_primary && is_optional)
    !is_required
  else
    raise 'The key provided is not in the schema.'
  end
end
schema_config() click to toggle source
# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_config.rb, line 5
def schema_config
  config = Jekyll.configuration
  unless config.key?('frontmatter_tests')
    config['frontmatter_tests'] = { 'path' => File.join('deploy', 'tests', 'schema') }
  end
  config['frontmatter_tests']
end
test_collections(collections) click to toggle source

Public: Tests only specific collection documents

collections - a comma separated string of collection names.

`collections` is split into an array and each document is loaded and processed against its respective schema.

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_tester.rb, line 55
def test_collections(collections)
  yepnope = []
  for c in collections
    puts "Testing #{c}".green
    yepnope.push process(load_schema("_#{c}.yml"))
    puts "Finished testing #{c}".green
  end
  yepnope
end
test_everything() click to toggle source

Public: Tests all collections described by a schema file at `deploy/tests/schema`

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_tester.rb, line 67
def test_everything
  schema = Dir.open(schema_config['path'])
  yepnope = []
  schema.each do |s|
    next unless s.start_with?('_')
    puts "Testing #{s}".green
    yepnope.push process(load_schema(s))
    puts "Finished testing #{s}".green
  end
  yepnope
end
test_frontmatter(_args, options) click to toggle source

Public: Processes options passed throguh the command line, runs the appropriate tests.

args - command line arguments (example: jekyll test [ARG]) options - command line options (example: jekyll test -[option] [value])

Depending on the flag passed (see `init_with_program`), runs the expected # test.

Example: the following comamnd `jekyll test -p` will pass “{'posts' =>

true}` as `options`. This will cause `test_frontmatter` to
compare all docs in _posts with the provided schema.

The test runner pushes the result of each test into a `results` array and # exits `1` if any tests fail or `0` if all is well.

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_tester.rb, line 19
def test_frontmatter(_args, options)
  puts 'starting tests'
  if options['posts']
    results = test_posts
  elsif options['collections']
    collections = options['collections'].split(',')
    results = test_collections(collections)
  else
    results = test_everything
  end
  if results.find_index { |r| r == false }
    puts 'The test exited with errors, see above.'
    exit 1
  else
    puts 'Tests finished!'
    exit 0
  end
end
test_posts() click to toggle source

Public: tests all documents that are “posts”

Loads a schema called _posts.yml and processes all post documents against it.

# File lib/jekyll_frontmatter_tests/jekyll_frontmatter_tests_tester.rb, line 42
def test_posts
  puts 'testing posts'.green
  yepnope = [].push process(load_schema('_posts.yml'))
  puts 'Finished testing'.green
  yepnope
end