├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .travis.yml ├── Gemfile ├── History.markdown ├── LICENSE.txt ├── README.md ├── Rakefile ├── appveyor.yml ├── jekyll-watch.gemspec ├── lib ├── jekyll-watch.rb ├── jekyll-watch │ └── version.rb └── jekyll │ ├── commands │ └── watch.rb │ └── watcher.rb ├── script ├── bootstrap ├── cibuild ├── fmt ├── release ├── test └── test-watcher └── spec ├── spec_helper.rb ├── test-sité ├── .gitignore ├── _config.dev.toml ├── _config.yml ├── _includes │ ├── footer.html │ ├── head.html │ └── header.html ├── _layouts │ ├── default.html │ ├── page.html │ └── post.html ├── _posts │ ├── 2014-08-08-welcome-to-jekyll.markdown │ └── 2014-08-08-歡迎使用jekyll.markdown ├── _sass │ ├── _base.scss │ ├── _layout.scss │ └── _syntax-highlighting.scss ├── about.md ├── css │ └── main.scss ├── feed.xml └── index.html └── watcher_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | vendor 24 | 25 | .sass-cache 26 | _site 27 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | require: rubocop-jekyll 4 | inherit_gem: 5 | rubocop-jekyll: .rubocop.yml 6 | 7 | AllCops: 8 | TargetRubyVersion: 2.3 9 | Include: 10 | - lib/**/*.rb 11 | 12 | Exclude: 13 | - vendor/**/* 14 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config --auto-gen-only-exclude` 3 | # on 2020-03-19 22:20:35 +0100 using RuboCop version 0.80.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 2 10 | # Configuration parameters: AllowComments. 11 | Lint/SuppressedException: 12 | Exclude: 13 | - 'lib/jekyll/watcher.rb' 14 | 15 | # Offense count: 1 16 | # Configuration parameters: Max. 17 | Metrics/AbcSize: 18 | Exclude: 19 | - 'lib/jekyll/watcher.rb' 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | rvm: 4 | - &ruby1 2.7 5 | - &ruby2 2.5 6 | matrix: 7 | include: 8 | - rvm: *ruby1 9 | env: JEKYLL_VERSION="~> 4.0" 10 | - rvm: *ruby1 11 | env: JEKYLL_VERSION="~> 3.9" 12 | branches: 13 | only: 14 | - master 15 | 16 | before_install: 17 | - gem update --system 18 | - gem install bundler 19 | install: 20 | - travis_retry script/bootstrap 21 | script: script/cibuild 22 | 23 | notifications: 24 | irc: 25 | on_success: change 26 | on_failure: change 27 | channels: 28 | - irc.freenode.org#jekyll 29 | template: 30 | - '%{repository}#%{build_number} %{message} %{build_url}' 31 | email: 32 | on_success: never 33 | on_failure: change 34 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | gemspec 5 | 6 | gem "jekyll", ENV["JEKYLL_VERSION"] if ENV["JEKYLL_VERSION"] 7 | gem "kramdown-parser-gfm" if ENV["JEKYLL_VERSION"] == "~> 3.9" 8 | -------------------------------------------------------------------------------- /History.markdown: -------------------------------------------------------------------------------- 1 | ## 2.2.1 / 2019-03-22 2 | 3 | ### Bug Fixes 4 | 5 | * Fix encoding discrepancy in excluded Windows paths (#76) 6 | * Ignore directories rather than all similar paths (#65) 7 | 8 | ### Development Fixes 9 | 10 | * Test against Ruby 2.6 11 | * Relax version constraint on bundler to allow using 1.x or 2.x 12 | * dependencies: rubocop-jekyll 0.5 13 | * style: target Ruby 2.4 14 | 15 | ## 2.2.0 (YANKED) 16 | 17 | ## 2.1.2 / 2018-10-17 18 | 19 | ### Development Fixes 20 | 21 | * Initialize AppVeyor CI to test plugin on Windows (#77) 22 | 23 | ### Bug Fixes 24 | 25 | * Fix watcher failure due to incorrect file name encoding (#78) 26 | 27 | ## 2.1.1 / 2018-10-10 28 | 29 | ### Bug Fixes 30 | 31 | * Replace non-existent local variable (#73) 32 | 33 | ## 2.1.0 / 2018-10-09 34 | 35 | ### Bug Fixes 36 | 37 | * Normalize watched-path encoding (#69) 38 | 39 | ### Development Fixes 40 | 41 | * Test against Ruby 2.5 (#62) 42 | * Drop support for Ruby 2.2 (EOL) 43 | * Style: lint with rubocop-jekyll 44 | 45 | ## 2.0.0 / 2016-12-02 46 | 47 | ### Development Fixes 48 | 49 | * Update versions for Travis (#43) 50 | * Define path with __dir__ (#48) 51 | * Remove version lock for dependency listen (#50) 52 | * Inherit Jekyll's rubocop config for consistency (#51) 53 | * Update jekyll-watch (#53) 54 | * Drop support for old Ruby and old Jekyll (#55) 55 | 56 | ### Minor Enhancements 57 | 58 | * Output regenerated file paths to terminal (#57) 59 | 60 | ### Major Enhancements 61 | 62 | * Remove unnecessary method (#56) 63 | 64 | ## 1.5.0 / 2016-07-20 65 | 66 | * reuse provided site instance if available (#40) 67 | 68 | ## 1.4.0 / 2016-04-25 69 | 70 | * Lock Listen to less than 3.1. (#38) 71 | 72 | ## 1.3.1 / 2016-01-19 73 | 74 | * Test against Jekyll 2 and 3. (#30) 75 | * watcher: set `LISTEN_GEM_DEBUGGING` if `--verbose` flag set (#31) 76 | * Apply Rubocop auditing and fix up (#32) 77 | 78 | ## 1.3.0 / 2015-09-23 79 | 80 | * Lock to Listen 3.x (#25) 81 | 82 | ## 1.2.1 / 2015-01-24 83 | 84 | * Show regen time & use the same `Site` object across regens (#21) 85 | 86 | ## 1.2.0 / 2014-12-05 87 | 88 | * *Always* ignore `.jekyll-metadata`, even if it doesn't exist. (#18) 89 | * Ignore `.jekyll-metadata` by default if it exists (#15) 90 | 91 | ## 1.1.2 / 2014-11-08 92 | 93 | * Only ignore a file or directory if it exists (#13) 94 | 95 | ## 1.1.1 / 2014-09-05 96 | 97 | * Exclude test files from the gem build (#9) 98 | 99 | ## 1.1.0 / 2014-08-10 100 | 101 | ### Minor Enhancements 102 | 103 | * Refactor the whole watching thing and compartmentalize it. (#5) 104 | * Don't listen to things in the `exclude` configuration option. (#5) 105 | 106 | ### Development Fixes 107 | 108 | * Add github stuff and the beginnings of the test suite (#6) 109 | * Flesh out the test suite (#7) 110 | 111 | ## 1.0.0 / 2014-06-27 112 | 113 | * Birthday! 114 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-present Parker Moore and the jekyll-watch contributors 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jekyll Watch 2 | 3 | Rebuild your Jekyll site when a file changes with the `--watch` switch. 4 | 5 | [![Linux Build Status](https://img.shields.io/travis/jekyll/jekyll-watch/master.svg?label=Linux%20build)][travis] 6 | [![Windows Build status](https://img.shields.io/appveyor/ci/jekyll/jekyll-watch/master.svg?label=Windows%20build)][appveyor] 7 | 8 | [travis]: https://travis-ci.org/jekyll/jekyll-watch 9 | [appveyor]: https://ci.appveyor.com/project/jekyll/jekyll-watch 10 | 11 | ## Installation 12 | 13 | **`jekyll-watch` comes pre-installed with Jekyll 2.1 or greater.** 14 | 15 | Add this line to your application's Gemfile: 16 | 17 | gem 'jekyll-watch' 18 | 19 | And then execute: 20 | 21 | $ bundle 22 | 23 | Or install it yourself as: 24 | 25 | $ gem install jekyll-watch 26 | 27 | ## Usage 28 | 29 | Pass the `--watch` flag to `jekyll build` or `jekyll serve`: 30 | 31 | ```bash 32 | $ jekyll build --watch 33 | $ jekyll serve --watch # this flag is the default, so no need to specify it here for the 'serve' command 34 | ``` 35 | 36 | The `--watch` flag can be used in combination with any other flags for those 37 | two commands, except `--detach` for the `serve` command. 38 | 39 | ## Contributing 40 | 41 | 1. Fork it ( https://github.com/jekyll/jekyll-watch/fork ) 42 | 2. Create your feature branch (`git checkout -b my-new-feature`) 43 | 3. Commit your changes (`git commit -am 'Add some feature'`) 44 | 4. Push to the branch (`git push origin my-new-feature`) 45 | 5. Create a new Pull Request 46 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | 5 | require "rspec/core/rake_task" 6 | 7 | RSpec::Core::RakeTask.new(:spec) do |t| 8 | t.verbose = false 9 | end 10 | 11 | task :default => :spec 12 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | clone_depth: 5 3 | branches: 4 | only: 5 | - master 6 | 7 | build: off 8 | 9 | environment: 10 | matrix: 11 | - RUBY_FOLDER_VER: "26" 12 | JEKYLL_VERSION : "~> 4.0" 13 | - RUBY_FOLDER_VER: "26" 14 | JEKYLL_VERSION : "~> 3.9" 15 | - RUBY_FOLDER_VER: "25" 16 | JEKYLL_VERSION : "~> 4.0" 17 | - RUBY_FOLDER_VER: "25" 18 | JEKYLL_VERSION : "~> 3.9" 19 | 20 | install: 21 | - SET PATH=C:\Ruby%RUBY_FOLDER_VER%-x64\bin;%PATH% 22 | - bundle install --retry 5 --jobs=%NUMBER_OF_PROCESSORS% --clean --path vendor\bundle 23 | 24 | test_script: 25 | - ruby --version 26 | - gem --version 27 | - bundler --version 28 | - bundle exec rspec 29 | 30 | cache: 31 | # If one of the files after the right arrow changes, cache will be invalidated 32 | - 'vendor\bundle -> appveyor.yml,Gemfile,jekyll-watch.gemspec' 33 | -------------------------------------------------------------------------------- /jekyll-watch.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/jekyll-watch/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "jekyll-watch" 7 | spec.version = Jekyll::Watch::VERSION 8 | spec.authors = ["Parker Moore"] 9 | spec.email = ["parkrmoore@gmail.com"] 10 | spec.summary = "Rebuild your Jekyll site when a file changes with the `--watch` switch." 11 | spec.homepage = "https://github.com/jekyll/jekyll-watch" 12 | spec.license = "MIT" 13 | 14 | spec.files = `git ls-files -z`.split("\x0").grep(%r!(bin|lib)/!) 15 | spec.executables = spec.files.grep(%r!^bin/!) { |f| File.basename(f) } 16 | spec.require_paths = ["lib"] 17 | 18 | spec.required_ruby_version = ">= 2.3.0" 19 | 20 | spec.add_runtime_dependency "listen", "~> 3.0" 21 | 22 | require "rbconfig" 23 | if RbConfig::CONFIG["host_os"] =~ %r!mswin|mingw|cygwin! 24 | spec.add_runtime_dependency "wdm", "~> 0.1.0" 25 | end 26 | 27 | spec.add_development_dependency "bundler" 28 | spec.add_development_dependency "jekyll", ">= 3.6", "< 5.0" 29 | spec.add_development_dependency "rake" 30 | spec.add_development_dependency "rspec", "~> 3.0" 31 | spec.add_development_dependency "rubocop-jekyll", "~> 0.11" 32 | end 33 | -------------------------------------------------------------------------------- /lib/jekyll-watch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll-watch/version" 4 | require_relative "jekyll/watcher" 5 | require_relative "jekyll/commands/watch" 6 | -------------------------------------------------------------------------------- /lib/jekyll-watch/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Watch 5 | VERSION = "2.2.1" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/jekyll/commands/watch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Commands 5 | module Watch 6 | extend self 7 | 8 | def init_with_program(prog); end 9 | 10 | # Build your jekyll site 11 | # Continuously watch if `watch` is set to true in the config. 12 | def process(options) 13 | Jekyll.logger.log_level = :error if options["quiet"] 14 | Jekyll::Watcher.watch(options) if options["watch"] 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/jekyll/watcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "listen" 4 | 5 | module Jekyll 6 | module Watcher 7 | extend self 8 | 9 | # Public: Continuously watch for file changes and rebuild the site 10 | # whenever a change is detected. 11 | # 12 | # If the optional site argument is populated, that site instance will be 13 | # reused and the options Hash ignored. Otherwise, a new site instance will 14 | # be instantiated from the options Hash and used. 15 | # 16 | # options - A Hash containing the site configuration 17 | # site - The current site instance (populated starting with Jekyll 3.2) 18 | # (optional, default: nil) 19 | # 20 | # Returns nothing. 21 | def watch(options, site = nil) 22 | ENV["LISTEN_GEM_DEBUGGING"] ||= "1" if options["verbose"] 23 | 24 | site ||= Jekyll::Site.new(options) 25 | listener = build_listener(site, options) 26 | listener.start 27 | 28 | Jekyll.logger.info "Auto-regeneration:", "enabled for '#{options["source"]}'" 29 | 30 | unless options["serving"] 31 | trap("INT") do 32 | listener.stop 33 | Jekyll.logger.info "", "Halting auto-regeneration." 34 | exit 0 35 | end 36 | 37 | sleep_forever 38 | end 39 | rescue ThreadError 40 | # You pressed Ctrl-C, oh my! 41 | end 42 | 43 | private 44 | 45 | def build_listener(site, options) 46 | Listen.to( 47 | options["source"], 48 | :ignore => listen_ignore_paths(options), 49 | :force_polling => options["force_polling"], 50 | &listen_handler(site) 51 | ) 52 | end 53 | 54 | def listen_handler(site) 55 | proc do |modified, added, removed| 56 | t = Time.now 57 | c = modified + added + removed 58 | n = c.length 59 | 60 | Jekyll.logger.info "Regenerating:", 61 | "#{n} file(s) changed at #{t.strftime("%Y-%m-%d %H:%M:%S")}" 62 | 63 | c.each { |path| Jekyll.logger.info "", path["#{site.source}/".length..-1] } 64 | process(site, t) 65 | end 66 | end 67 | 68 | def normalize_encoding(obj, desired_encoding) 69 | case obj 70 | when Array 71 | obj.map { |entry| entry.encode!(desired_encoding, entry.encoding) } 72 | when String 73 | obj.encode!(desired_encoding, obj.encoding) 74 | end 75 | end 76 | 77 | def custom_excludes(options) 78 | Array(options["exclude"]).map { |e| Jekyll.sanitized_path(options["source"], e) } 79 | end 80 | 81 | def config_files(options) 82 | %w(yml yaml toml).map do |ext| 83 | Jekyll.sanitized_path(options["source"], "_config.#{ext}") 84 | end 85 | end 86 | 87 | def to_exclude(options) 88 | [ 89 | config_files(options), 90 | options["destination"], 91 | custom_excludes(options), 92 | ].flatten 93 | end 94 | 95 | # Paths to ignore for the watch option 96 | # 97 | # options - A Hash of options passed to the command 98 | # 99 | # Returns a list of relative paths from source that should be ignored 100 | def listen_ignore_paths(options) 101 | source = Pathname.new(options["source"]).expand_path 102 | paths = to_exclude(options) 103 | 104 | paths.map do |p| 105 | absolute_path = Pathname.new(normalize_encoding(p, options["source"].encoding)).expand_path 106 | next unless absolute_path.exist? 107 | 108 | begin 109 | relative_path = absolute_path.relative_path_from(source).to_s 110 | relative_path = File.join(relative_path, "") if absolute_path.directory? 111 | unless relative_path.start_with?("../") 112 | path_to_ignore = %r!^#{Regexp.escape(relative_path)}! 113 | Jekyll.logger.debug "Watcher:", "Ignoring #{path_to_ignore}" 114 | path_to_ignore 115 | end 116 | rescue ArgumentError 117 | # Could not find a relative path 118 | end 119 | end.compact + [%r!^\.jekyll\-metadata!] 120 | end 121 | 122 | def sleep_forever 123 | loop { sleep 1000 } 124 | end 125 | 126 | def process(site, time) 127 | begin 128 | site.process 129 | Jekyll.logger.info "", "...done in #{Time.now - time} seconds." 130 | rescue StandardError => e 131 | Jekyll.logger.warn "Error:", e.message 132 | Jekyll.logger.warn "Error:", "Run jekyll build --trace for more information." 133 | end 134 | Jekyll.logger.info "" 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [[ "$TRAVIS" == "true" ]]; then 4 | echo "We're on Travis! Installing to vendor." 5 | time bundle install --path vendor 6 | else 7 | bundle install --system 8 | fi 9 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | time script/test 4 | time script/fmt 5 | -------------------------------------------------------------------------------- /script/fmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | bundle exec rubocop -D -E 3 | -------------------------------------------------------------------------------- /script/release: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | rake release 4 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | bundle exec rspec "$@" 3 | -------------------------------------------------------------------------------- /script/test-watcher: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | local-jekyll serve --watch \ 4 | --source spec/test-sité \ 5 | --destination spec/test-sité/_site 6 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll" 4 | require File.expand_path("../lib/jekyll-watch.rb", __dir__) 5 | TEST_DIR = __dir__ 6 | 7 | RSpec.configure do |config| 8 | # These two settings work together to allow you to limit a spec run 9 | # to individual examples or groups you care about by tagging them with 10 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 11 | # get run. 12 | config.filter_run :focus 13 | config.run_all_when_everything_filtered = true 14 | 15 | # Many RSpec users commonly either run the entire suite or an individual 16 | # file, and it's useful to allow more verbose output when running an 17 | # individual spec file. 18 | if config.files_to_run.one? 19 | # Use the documentation formatter for detailed output, 20 | # unless a formatter has already been configured 21 | # (e.g. via a command-line flag). 22 | config.default_formatter = "doc" 23 | end 24 | 25 | # Print the 10 slowest examples and example groups at the 26 | # end of the spec run, to help surface which specs are running 27 | # particularly slow. 28 | config.profile_examples = 10 29 | 30 | # Run specs in random order to surface order dependencies. If you find an 31 | # order dependency and want to debug it, you can fix the order by providing 32 | # the seed, which is printed after each run. 33 | # --seed 1234 34 | config.order = :random 35 | 36 | # Seed global randomization in this process using the `--seed` CLI option. 37 | # Setting this allows you to use `--seed` to deterministically reproduce 38 | # test failures related to randomization by passing the same `--seed` value 39 | # as the one that triggered the failure. 40 | Kernel.srand config.seed 41 | 42 | # rspec-expectations config goes here. You can use an alternate 43 | # assertion/expectation library such as wrong or the stdlib/minitest 44 | # assertions if you prefer. 45 | config.expect_with :rspec do |expectations| 46 | # Enable only the newer, non-monkey-patching expect syntax. 47 | # For more details, see: 48 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 49 | expectations.syntax = :expect 50 | end 51 | 52 | # rspec-mocks config goes here. You can use an alternate test double 53 | # library (such as bogus or mocha) by changing the `mock_with` option here. 54 | config.mock_with :rspec do |mocks| 55 | # Enable only the newer, non-monkey-patching expect syntax. 56 | # For more details, see: 57 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 58 | mocks.syntax = :expect 59 | 60 | # Prevents you from mocking or stubbing a method that does not exist on 61 | # a real object. This is generally recommended. 62 | mocks.verify_partial_doubles = true 63 | end 64 | 65 | def test_dir(*files) 66 | File.join(TEST_DIR, *files) 67 | end 68 | 69 | def source_dir(*files) 70 | test_dir("test-sité", *files) 71 | end 72 | 73 | def dest_dir(*files) 74 | source_dir("_site", *files) 75 | end 76 | 77 | def ignore_path?(patterns, path) 78 | path = path.to_s 79 | patterns.each do |pattern| 80 | return true if path =~ pattern 81 | next 82 | end 83 | false 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/test-sité/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | -------------------------------------------------------------------------------- /spec/test-sité/_config.dev.toml: -------------------------------------------------------------------------------- 1 | hello = "there" 2 | -------------------------------------------------------------------------------- /spec/test-sité/_config.yml: -------------------------------------------------------------------------------- 1 | # Site settings 2 | title: jekyll-watch test site 3 | email: your-email@domain.com 4 | description: > # this means to ignore newlines until "baseurl:" 5 | Write an awesome description for your new site here. You can edit this 6 | line in _config.yml. It will appear in your document head meta (for 7 | Google search results) and in your feed.xml site description. 8 | baseurl: "" # the subpath of your site, e.g. /blog/ 9 | url: "http://yourdomain.com" # the base hostname & protocol for your site 10 | twitter_username: jekyllrb 11 | github_username: jekyll 12 | exclude: [".gitignore"] 13 | 14 | # Build settings 15 | markdown: kramdown 16 | -------------------------------------------------------------------------------- /spec/test-sité/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /spec/test-sité/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /spec/test-sité/_includes/header.html: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /spec/test-sité/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include header.html %} 9 | 10 |
11 |
12 | {{ content }} 13 |
14 |
15 | 16 | {% include footer.html %} 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /spec/test-sité/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | 6 |
7 |

{{ page.title }}

8 |
9 | 10 |
11 | {{ content }} 12 |
13 | 14 |
15 | -------------------------------------------------------------------------------- /spec/test-sité/_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | 6 |
7 |

{{ page.title }}

8 | 9 |
10 | 11 |
12 | {{ content }} 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /spec/test-sité/_posts/2014-08-08-welcome-to-jekyll.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Welcome to Jekyll!" 4 | date: 2014-08-08 18:00:36 5 | categories: jekyll update 6 | --- 7 | You’ll find this post in your `_posts` directory – edit it and re-build (or run with the `--watch` switch) to see your changes. 8 | 9 | To add new posts, simply add a file in the `_posts` directory that follows the convention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works. 10 | 11 | Jekyll also offers powerful support for code snippets: 12 | 13 | {% highlight ruby %} 14 | def print_hi(name) 15 | puts "Hi, #{name}" 16 | end 17 | print_hi('Tom') 18 | #=> prints 'Hi, Tom' to STDOUT. 19 | {% endhighlight %} 20 | 21 | Check out the [Jekyll docs][jekyll] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll’s dedicated Help repository][jekyll-help]. 22 | 23 | [jekyll]: http://jekyllrb.com 24 | [jekyll-gh]: https://github.com/jekyll/jekyll 25 | [jekyll-help]: https://github.com/jekyll/jekyll-help 26 | -------------------------------------------------------------------------------- /spec/test-sité/_posts/2014-08-08-歡迎使用jekyll.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "歡迎使用Jekyll" 4 | date: 2014-08-08 18:00:36 5 | categories: jekyll update 6 | --- 7 | 你會在你的`_posts`目錄中找到這篇文章 - 編輯它並重新構建(或使用`--watch`開關運行)來查看你的更改。 8 | 9 | 要添加新帖子,只需在`_posts`目錄中添加一個文件,該文件遵循慣例`YYYY-MM-DD-name-of-post.ext`並包含必要的前置事項。 看看這篇文章的來源,了解它的工作原理。 10 | 11 | Jekyll還為代碼片段提供強大的支持: 12 | 13 | {% highlight ruby %} 14 | def print_hi(name) 15 | puts "Hi, #{name}" 16 | end 17 | print_hi('Tom') 18 | #=> prints 'Hi, Tom' to STDOUT. 19 | {% endhighlight %} 20 | 21 | 查看[Jekyll docs] [jekyll]了解更多關於如何充分利用Jekyll的信息。 在[Jekyll的GitHub repo] [jekyll-gh]提交所有錯誤/功能請求。 如果您有疑問,可以在[Jekyll的專用幫助存儲庫] [jekyll-help]上詢問。 22 | 23 | [jekyll]: http://jekyllrb.com 24 | [jekyll-gh]: https://github.com/jekyll/jekyll 25 | [jekyll-help]: https://github.com/jekyll/jekyll-help 26 | -------------------------------------------------------------------------------- /spec/test-sité/_sass/_base.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Reset some basic elements 3 | */ 4 | body, h1, h2, h3, h4, h5, h6, 5 | p, blockquote, pre, hr, 6 | dl, dd, ol, ul, figure { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | /** 12 | * Basic styling 13 | */ 14 | body { 15 | font-family: $base-font-family; 16 | font-size: $base-font-size; 17 | line-height: $base-line-height; 18 | font-weight: 300; 19 | color: $text-color; 20 | background-color: $background-color; 21 | } 22 | 23 | 24 | 25 | /** 26 | * Set `margin-bottom` to maintain vertycal rhythm 27 | */ 28 | h1, h2, h3, h4, h5, h6, 29 | p, blockquote, pre, 30 | ul, ol, dl, figure, 31 | %vertical-rhythm { 32 | margin-bottom: $spacing-unit / 2; 33 | } 34 | 35 | 36 | 37 | /** 38 | * Images 39 | */ 40 | img { 41 | max-width: 100%; 42 | vertical-align: middle; 43 | } 44 | 45 | 46 | 47 | /** 48 | * Figures 49 | */ 50 | figure > img { 51 | display: block; 52 | } 53 | 54 | figcaption { 55 | font-size: $small-font-size; 56 | } 57 | 58 | 59 | 60 | /** 61 | * Lists 62 | */ 63 | ul, ol { 64 | margin-left: $spacing-unit; 65 | } 66 | 67 | li { 68 | > ul, 69 | > ol { 70 | margin-bottom: 0; 71 | } 72 | } 73 | 74 | 75 | 76 | /** 77 | * Headings 78 | */ 79 | h1, h2, h3, h4, h5, h6 { 80 | font-weight: 300; 81 | } 82 | 83 | 84 | 85 | /** 86 | * Links 87 | */ 88 | a { 89 | color: $brand-color; 90 | text-decoration: none; 91 | 92 | &:visited { 93 | color: darken($brand-color, 15%); 94 | } 95 | 96 | &:hover { 97 | color: $text-color; 98 | text-decoration: underline; 99 | } 100 | } 101 | 102 | 103 | 104 | /** 105 | * Blockquotes 106 | */ 107 | blockquote { 108 | color: $grey-color; 109 | border-left: 4px solid $grey-color-light; 110 | padding-left: $spacing-unit / 2; 111 | font-size: 18px; 112 | letter-spacing: -1px; 113 | font-style: italic; 114 | 115 | > :last-child { 116 | margin-bottom: 0; 117 | } 118 | } 119 | 120 | 121 | 122 | /** 123 | * Code formatting 124 | */ 125 | pre, 126 | code { 127 | font-size: 15px; 128 | border: 1px solid $grey-color-light; 129 | border-radius: 3px; 130 | background-color: #eef; 131 | } 132 | 133 | code { 134 | padding: 1px 5px; 135 | } 136 | 137 | pre { 138 | padding: 8px 12px; 139 | overflow-x: scroll; 140 | 141 | > code { 142 | border: 0; 143 | padding-right: 0; 144 | padding-left: 0; 145 | } 146 | } 147 | 148 | 149 | 150 | /** 151 | * Wrapper 152 | */ 153 | .wrapper { 154 | max-width: -webkit-calc(800px - (#{$spacing-unit} * 2)); 155 | max-width: calc(800px - (#{$spacing-unit} * 2)); 156 | margin-right: auto; 157 | margin-left: auto; 158 | padding-right: $spacing-unit; 159 | padding-left: $spacing-unit; 160 | @extend %clearfix; 161 | 162 | @include media-query($on-laptop) { 163 | max-width: -webkit-calc(800px - (#{$spacing-unit})); 164 | max-width: calc(800px - (#{$spacing-unit})); 165 | padding-right: $spacing-unit / 2; 166 | padding-left: $spacing-unit / 2; 167 | } 168 | } 169 | 170 | 171 | 172 | /** 173 | * Clearfix 174 | */ 175 | %clearfix { 176 | 177 | &:after { 178 | content: ""; 179 | display: table; 180 | clear: both; 181 | } 182 | } 183 | 184 | 185 | 186 | /** 187 | * Icons 188 | */ 189 | .icon { 190 | 191 | > svg { 192 | display: inline-block; 193 | width: 16px; 194 | height: 16px; 195 | vertical-align: middle; 196 | 197 | path { 198 | fill: $grey-color; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /spec/test-sité/_sass/_layout.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Site header 3 | */ 4 | .site-header { 5 | border-top: 5px solid $grey-color-dark; 6 | border-bottom: 1px solid $grey-color-light; 7 | min-height: 56px; 8 | 9 | // Positioning context for the mobile navigation icon 10 | position: relative; 11 | } 12 | 13 | .site-title { 14 | font-size: 26px; 15 | line-height: 56px; 16 | letter-spacing: -1px; 17 | margin-bottom: 0; 18 | float: left; 19 | 20 | &, 21 | &:visited { 22 | color: $grey-color-dark; 23 | } 24 | } 25 | 26 | .site-nav { 27 | float: right; 28 | line-height: 56px; 29 | 30 | .menu-icon { 31 | display: none; 32 | } 33 | 34 | .page-link { 35 | color: $text-color; 36 | line-height: $base-line-height; 37 | 38 | // Gaps between nav items, but not on the first one 39 | &:not(:first-child) { 40 | margin-left: 20px; 41 | } 42 | } 43 | 44 | @include media-query($on-palm) { 45 | position: absolute; 46 | top: 9px; 47 | right: 30px; 48 | background-color: $background-color; 49 | border: 1px solid $grey-color-light; 50 | border-radius: 5px; 51 | text-align: right; 52 | 53 | .menu-icon { 54 | display: block; 55 | float: right; 56 | width: 36px; 57 | height: 26px; 58 | line-height: 0; 59 | padding-top: 10px; 60 | text-align: center; 61 | 62 | > svg { 63 | width: 18px; 64 | height: 15px; 65 | 66 | path { 67 | fill: $grey-color-dark; 68 | } 69 | } 70 | } 71 | 72 | .trigger { 73 | clear: both; 74 | display: none; 75 | } 76 | 77 | &:hover .trigger { 78 | display: block; 79 | padding-bottom: 5px; 80 | } 81 | 82 | .page-link { 83 | display: block; 84 | padding: 5px 10px; 85 | } 86 | } 87 | } 88 | 89 | 90 | 91 | /** 92 | * Site footer 93 | */ 94 | .site-footer { 95 | border-top: 1px solid $grey-color-light; 96 | padding: $spacing-unit 0; 97 | } 98 | 99 | .footer-heading { 100 | font-size: 18px; 101 | margin-bottom: $spacing-unit / 2; 102 | } 103 | 104 | .contact-list, 105 | .social-media-list { 106 | list-style: none; 107 | margin-left: 0; 108 | } 109 | 110 | .footer-col-wrapper { 111 | font-size: 15px; 112 | color: $grey-color; 113 | margin-left: -$spacing-unit / 2; 114 | @extend %clearfix; 115 | } 116 | 117 | .footer-col { 118 | float: left; 119 | margin-bottom: $spacing-unit / 2; 120 | padding-left: $spacing-unit / 2; 121 | } 122 | 123 | .footer-col-1 { 124 | width: -webkit-calc(35% - (#{$spacing-unit} / 2)); 125 | width: calc(35% - (#{$spacing-unit} / 2)); 126 | } 127 | 128 | .footer-col-2 { 129 | width: -webkit-calc(20% - (#{$spacing-unit} / 2)); 130 | width: calc(20% - (#{$spacing-unit} / 2)); 131 | } 132 | 133 | .footer-col-3 { 134 | width: -webkit-calc(45% - (#{$spacing-unit} / 2)); 135 | width: calc(45% - (#{$spacing-unit} / 2)); 136 | } 137 | 138 | @include media-query($on-laptop) { 139 | .footer-col-1, 140 | .footer-col-2 { 141 | width: -webkit-calc(50% - (#{$spacing-unit} / 2)); 142 | width: calc(50% - (#{$spacing-unit} / 2)); 143 | } 144 | 145 | .footer-col-3 { 146 | width: -webkit-calc(100% - (#{$spacing-unit} / 2)); 147 | width: calc(100% - (#{$spacing-unit} / 2)); 148 | } 149 | } 150 | 151 | @include media-query($on-palm) { 152 | .footer-col { 153 | float: none; 154 | width: -webkit-calc(100% - (#{$spacing-unit} / 2)); 155 | width: calc(100% - (#{$spacing-unit} / 2)); 156 | } 157 | } 158 | 159 | 160 | 161 | /** 162 | * Page content 163 | */ 164 | .page-content { 165 | padding: $spacing-unit 0; 166 | } 167 | 168 | .page-heading { 169 | font-size: 20px; 170 | } 171 | 172 | .post-list { 173 | margin-left: 0; 174 | list-style: none; 175 | 176 | > li { 177 | margin-bottom: $spacing-unit; 178 | } 179 | } 180 | 181 | .post-meta { 182 | font-size: $small-font-size; 183 | color: $grey-color; 184 | } 185 | 186 | .post-link { 187 | display: block; 188 | font-size: 24px; 189 | } 190 | 191 | 192 | 193 | /** 194 | * Posts 195 | */ 196 | .post-header { 197 | margin-bottom: $spacing-unit; 198 | } 199 | 200 | .post-title { 201 | font-size: 42px; 202 | letter-spacing: -1px; 203 | line-height: 1; 204 | 205 | @include media-query($on-laptop) { 206 | font-size: 36px; 207 | } 208 | } 209 | 210 | .post-content { 211 | margin-bottom: $spacing-unit; 212 | 213 | h2 { 214 | font-size: 32px; 215 | 216 | @include media-query($on-laptop) { 217 | font-size: 28px; 218 | } 219 | } 220 | 221 | h3 { 222 | font-size: 26px; 223 | 224 | @include media-query($on-laptop) { 225 | font-size: 22px; 226 | } 227 | } 228 | 229 | h4 { 230 | font-size: 20px; 231 | 232 | @include media-query($on-laptop) { 233 | font-size: 18px; 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /spec/test-sité/_sass/_syntax-highlighting.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Syntax highlighting styles 3 | */ 4 | .highlight { 5 | background: #fff; 6 | @extend %vertical-rhythm; 7 | 8 | .c { color: #998; font-style: italic } // Comment 9 | .err { color: #a61717; background-color: #e3d2d2 } // Error 10 | .k { font-weight: bold } // Keyword 11 | .o { font-weight: bold } // Operator 12 | .cm { color: #998; font-style: italic } // Comment.Multiline 13 | .cp { color: #999; font-weight: bold } // Comment.Preproc 14 | .c1 { color: #998; font-style: italic } // Comment.Single 15 | .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special 16 | .gd { color: #000; background-color: #fdd } // Generic.Deleted 17 | .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific 18 | .ge { font-style: italic } // Generic.Emph 19 | .gr { color: #a00 } // Generic.Error 20 | .gh { color: #999 } // Generic.Heading 21 | .gi { color: #000; background-color: #dfd } // Generic.Inserted 22 | .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific 23 | .go { color: #888 } // Generic.Output 24 | .gp { color: #555 } // Generic.Prompt 25 | .gs { font-weight: bold } // Generic.Strong 26 | .gu { color: #aaa } // Generic.Subheading 27 | .gt { color: #a00 } // Generic.Traceback 28 | .kc { font-weight: bold } // Keyword.Constant 29 | .kd { font-weight: bold } // Keyword.Declaration 30 | .kp { font-weight: bold } // Keyword.Pseudo 31 | .kr { font-weight: bold } // Keyword.Reserved 32 | .kt { color: #458; font-weight: bold } // Keyword.Type 33 | .m { color: #099 } // Literal.Number 34 | .s { color: #d14 } // Literal.String 35 | .na { color: #008080 } // Name.Attribute 36 | .nb { color: #0086B3 } // Name.Builtin 37 | .nc { color: #458; font-weight: bold } // Name.Class 38 | .no { color: #008080 } // Name.Constant 39 | .ni { color: #800080 } // Name.Entity 40 | .ne { color: #900; font-weight: bold } // Name.Exception 41 | .nf { color: #900; font-weight: bold } // Name.Function 42 | .nn { color: #555 } // Name.Namespace 43 | .nt { color: #000080 } // Name.Tag 44 | .nv { color: #008080 } // Name.Variable 45 | .ow { font-weight: bold } // Operator.Word 46 | .w { color: #bbb } // Text.Whitespace 47 | .mf { color: #099 } // Literal.Number.Float 48 | .mh { color: #099 } // Literal.Number.Hex 49 | .mi { color: #099 } // Literal.Number.Integer 50 | .mo { color: #099 } // Literal.Number.Oct 51 | .sb { color: #d14 } // Literal.String.Backtick 52 | .sc { color: #d14 } // Literal.String.Char 53 | .sd { color: #d14 } // Literal.String.Doc 54 | .s2 { color: #d14 } // Literal.String.Double 55 | .se { color: #d14 } // Literal.String.Escape 56 | .sh { color: #d14 } // Literal.String.Heredoc 57 | .si { color: #d14 } // Literal.String.Interpol 58 | .sx { color: #d14 } // Literal.String.Other 59 | .sr { color: #009926 } // Literal.String.Regex 60 | .s1 { color: #d14 } // Literal.String.Single 61 | .ss { color: #990073 } // Literal.String.Symbol 62 | .bp { color: #999 } // Name.Builtin.Pseudo 63 | .vc { color: #008080 } // Name.Variable.Class 64 | .vg { color: #008080 } // Name.Variable.Global 65 | .vi { color: #008080 } // Name.Variable.Instance 66 | .il { color: #099 } // Literal.Number.Integer.Long 67 | } 68 | -------------------------------------------------------------------------------- /spec/test-sité/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: About 4 | permalink: /about/ 5 | --- 6 | 7 | This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](http://jekyllrb.com/) 8 | 9 | You can find the source code for the Jekyll new theme at: [github.com/jglovier/jekyll-new](https://github.com/jglovier/jekyll-new) 10 | 11 | You can find the source code for Jekyll at [github.com/jekyll/jekyll](https://github.com/jekyll/jekyll) 12 | -------------------------------------------------------------------------------- /spec/test-sité/css/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Only the main Sass file needs front matter (the dashes are enough) 3 | layout: null 4 | --- 5 | @charset "utf-8"; 6 | 7 | 8 | 9 | // Our variables 10 | $base-font-family: Helvetica, Arial, sans-serif; 11 | $base-font-size: 16px; 12 | $small-font-size: $base-font-size * 0.875; 13 | $base-line-height: 1.5; 14 | 15 | $spacing-unit: 30px; 16 | 17 | $text-color: #111; 18 | $background-color: #fdfdfd; 19 | $brand-color: #2a7ae2; 20 | 21 | $grey-color: #828282; 22 | $grey-color-light: lighten($grey-color, 40%); 23 | $grey-color-dark: darken($grey-color, 25%); 24 | 25 | $on-palm: 600px; 26 | $on-laptop: 800px; 27 | 28 | 29 | 30 | // Using media queries with like this: 31 | // @include media-query($palm) { 32 | // .wrapper { 33 | // padding-right: $spacing-unit / 2; 34 | // padding-left: $spacing-unit / 2; 35 | // } 36 | // } 37 | @mixin media-query($device) { 38 | @media screen and (max-width: $device) { 39 | @content; 40 | } 41 | } 42 | 43 | 44 | 45 | // Import partials from `sass_dir` (defaults to `_sass`) 46 | @import 47 | "base", 48 | "layout", 49 | "syntax-highlighting" 50 | ; 51 | -------------------------------------------------------------------------------- /spec/test-sité/feed.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | --- 4 | 5 | 6 | 7 | {{ site.title | xml_escape }} 8 | {{ site.description | xml_escape }} 9 | {{ site.url }}{{ site.baseurl }}/ 10 | 11 | {{ site.time | date_to_rfc822 }} 12 | {{ site.time | date_to_rfc822 }} 13 | Jekyll v{{ jekyll.version }} 14 | {% for post in site.posts limit:10 %} 15 | 16 | {{ post.title | xml_escape }} 17 | {{ post.content | xml_escape }} 18 | {{ post.date | date_to_rfc822 }} 19 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 20 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 21 | {% for tag in post.tags %} 22 | {{ tag | xml_escape }} 23 | {% endfor %} 24 | {% for cat in post.categories %} 25 | {{ cat | xml_escape }} 26 | {% endfor %} 27 | 28 | {% endfor %} 29 | 30 | 31 | -------------------------------------------------------------------------------- /spec/test-sité/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 | 7 |

Posts

8 | 9 | 20 | 21 |

subscribe via RSS

22 | 23 |
24 | -------------------------------------------------------------------------------- /spec/watcher_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe(Jekyll::Watcher) do 4 | let(:base_opts) do 5 | { 6 | "source" => source_dir, 7 | "destination" => dest_dir, 8 | } 9 | end 10 | 11 | let(:options) { base_opts } 12 | let(:site) { instance_double(Jekyll::Site) } 13 | let(:default_ignored) { [%r!^_config\.yml!, %r!^_site/!, %r!^\.jekyll\-metadata!] } 14 | subject { described_class } 15 | before(:each) do 16 | FileUtils.mkdir(options["destination"]) if options["destination"] 17 | end 18 | 19 | after(:each) do 20 | FileUtils.rm_rf(options["destination"]) if options["destination"] 21 | end 22 | 23 | describe "#watch" do 24 | let(:listener) { instance_double(Listen::Listener) } 25 | 26 | let(:opts) do 27 | { :ignore => default_ignored, :force_polling => options["force_polling"] } 28 | end 29 | 30 | before do 31 | allow(Listen).to receive(:to).with(options["source"], opts).and_return(listener) 32 | 33 | allow(listener).to receive(:start) 34 | 35 | allow(Jekyll::Site).to receive(:new).with(options).and_return(site) 36 | allow(Jekyll.logger).to receive(:info) 37 | 38 | allow(subject).to receive(:sleep_forever) 39 | 40 | subject.watch(options) 41 | end 42 | 43 | it "starts the listener" do 44 | expect(listener).to have_received(:start) 45 | end 46 | 47 | it "sleeps" do 48 | expect(subject).to have_received(:sleep_forever) 49 | end 50 | 51 | it "ignores the config and site by default" do 52 | expect(Listen) 53 | .to have_received(:to) 54 | .with(anything, hash_including(:ignore => default_ignored)) 55 | end 56 | 57 | it "defaults to no force_polling" do 58 | expect(Listen) 59 | .to have_received(:to) 60 | .with(anything, hash_including(:force_polling => nil)) 61 | end 62 | 63 | context "with force_polling turned on" do 64 | let(:options) { base_opts.merge("force_polling" => true) } 65 | 66 | it "respects the custom value of force_polling" do 67 | expect(Listen) 68 | .to have_received(:to) 69 | .with(anything, hash_including(:force_polling => true)) 70 | end 71 | end 72 | end 73 | 74 | describe "#watch using site instance" do 75 | let(:listener) { instance_double(Listen::Listener) } 76 | 77 | let(:opts) { { :ignore => default_ignored, :force_polling => nil } } 78 | 79 | before do 80 | allow(Listen) 81 | .to receive(:to) 82 | .with(options["source"], opts) 83 | .and_return(listener) 84 | 85 | allow(listener).to receive(:start) 86 | 87 | allow(Jekyll.logger).to receive(:info) 88 | 89 | allow(subject).to receive(:sleep_forever) 90 | 91 | subject.watch(options, site) 92 | end 93 | 94 | it "does not create a new site instance" do 95 | expect(listener).to have_received(:start) 96 | end 97 | end 98 | 99 | context "#listen_ignore_paths" do 100 | let(:ignored) { subject.send(:listen_ignore_paths, options) } 101 | let(:metadata_path) { Jekyll.sanitized_path(options["source"], ".jekyll-metadata") } 102 | 103 | before(:each) { FileUtils.touch(metadata_path) } 104 | after(:each) { FileUtils.rm(metadata_path) } 105 | 106 | it "ignores config.yml, .jekyll-metadata, and _site by default" do 107 | expect(ignored).to eql(default_ignored) 108 | expect(ignore_path?(ignored, "_site/foo.html")).to eql(true) 109 | expect(ignore_path?(ignored, "_sitemapper/foo.html")).to eql(false) 110 | expect(ignore_path?(ignored, "bar/_site/foo.html")).to eql(false) 111 | expect(ignore_path?(ignored, "bar/_site-mapper.html")).to eql(false) 112 | end 113 | 114 | context "with something excluded" do 115 | let(:excluded) { ["README.md", "LICENSE"] } 116 | let(:excluded_absolute) do 117 | excluded.map { |p| Jekyll.sanitized_path(options["source"], p) } 118 | end 119 | let(:options) { base_opts.merge("exclude" => excluded) } 120 | before(:each) { FileUtils.touch(excluded_absolute) } 121 | after(:each) { FileUtils.rm(excluded_absolute) } 122 | 123 | it "ignores the excluded files" do 124 | expect(ignore_path?(ignored, "README.md")).to eql(true) 125 | expect(ignore_path?(ignored, "LICENSE")).to eql(true) 126 | end 127 | end 128 | 129 | context "with a custom destination" do 130 | let(:default_ignored) { [%r!^_config\.yml!, %r!^_dest/!, %r!^\.jekyll\-metadata!] } 131 | 132 | context "when source is absolute" do 133 | context "when destination is absolute" do 134 | let(:options) { base_opts.merge("destination" => source_dir("_dest")) } 135 | it "ignores the destination" do 136 | expect(ignored).to eql(default_ignored) 137 | expect(ignore_path?(ignored, "_dest/foo.html")).to eql(true) 138 | expect(ignore_path?(ignored, "_destination/foo.html")).to eql(false) 139 | expect(ignore_path?(ignored, "bar/_dest/foo.html")).to eql(false) 140 | expect(ignore_path?(ignored, "bar/_dest-nation.html")).to eql(false) 141 | end 142 | end 143 | 144 | context "when destination is relative" do 145 | let(:options) { base_opts.merge("destination" => "spec/test-sité/_dest") } 146 | it "ignores the destination" do 147 | expect(ignored).to eql(default_ignored) 148 | expect(ignore_path?(ignored, "_dest/foo.html")).to eql(true) 149 | expect(ignore_path?(ignored, "_destination/foo.html")).to eql(false) 150 | expect(ignore_path?(ignored, "bar/_dest/foo.html")).to eql(false) 151 | expect(ignore_path?(ignored, "bar/_dest-nation.html")).to eql(false) 152 | end 153 | end 154 | end 155 | 156 | context "when source is relative" do 157 | let(:base_opts) do 158 | { "source" => Pathname 159 | .new(source_dir) 160 | .relative_path_from(Pathname.new(".") 161 | .expand_path).to_s, } 162 | end 163 | 164 | context "when destination is absolute" do 165 | let(:options) { base_opts.merge("destination" => source_dir("_dest")) } 166 | it "ignores the destination" do 167 | expect(ignored).to eql(default_ignored) 168 | expect(ignore_path?(ignored, "_dest/foo.html")).to eql(true) 169 | expect(ignore_path?(ignored, "_destination/foo.html")).to eql(false) 170 | expect(ignore_path?(ignored, "bar/_dest/foo.html")).to eql(false) 171 | expect(ignore_path?(ignored, "bar/_dest-nation.html")).to eql(false) 172 | end 173 | end 174 | 175 | context "when destination is relative" do 176 | let(:options) { base_opts.merge("destination" => "spec/test-sité/_dest") } 177 | it "ignores the destination" do 178 | expect(ignored).to eql(default_ignored) 179 | expect(ignore_path?(ignored, "_dest/foo.html")).to eql(true) 180 | expect(ignore_path?(ignored, "_destination/foo.html")).to eql(false) 181 | expect(ignore_path?(ignored, "bar/_dest/foo.html")).to eql(false) 182 | expect(ignore_path?(ignored, "bar/_dest-nation.html")).to eql(false) 183 | end 184 | end 185 | end 186 | end 187 | end 188 | end 189 | --------------------------------------------------------------------------------