Full Code of javan/whenever for AI

main 75f19b09ca65 cached
55 files
151.3 KB
45.3k tokens
148 symbols
1 requests
Download .txt
Repository: javan/whenever
Branch: main
Commit: 75f19b09ca65
Files: 55
Total size: 151.3 KB

Directory structure:
gitextract_3e33pqhl/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── Appraisals
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE
├── Makefile
├── README.md
├── Rakefile
├── bin/
│   ├── whenever
│   └── wheneverize
├── gemfiles/
│   ├── activesupport5.0.gemfile
│   ├── activesupport5.1.gemfile
│   ├── activesupport5.2.gemfile
│   ├── activesupport6.0.gemfile
│   ├── activesupport6.1.gemfile
│   ├── activesupport7.0.gemfile
│   ├── activesupport7.1.gemfile
│   ├── activesupport7.2.gemfile
│   ├── activesupport8.0.gemfile
│   └── activesupport8.1.gemfile
├── lib/
│   ├── whenever/
│   │   ├── capistrano/
│   │   │   ├── v2/
│   │   │   │   ├── hooks.rb
│   │   │   │   ├── recipes.rb
│   │   │   │   └── support.rb
│   │   │   └── v3/
│   │   │       └── tasks/
│   │   │           └── whenever.rake
│   │   ├── capistrano.rb
│   │   ├── command_line.rb
│   │   ├── cron.rb
│   │   ├── job.rb
│   │   ├── job_list.rb
│   │   ├── numeric.rb
│   │   ├── numeric_seconds.rb
│   │   ├── os.rb
│   │   ├── output_redirection.rb
│   │   ├── setup.rb
│   │   └── version.rb
│   └── whenever.rb
├── test/
│   ├── functional/
│   │   ├── command_line_test.rb
│   │   ├── output_at_test.rb
│   │   ├── output_default_defined_jobs_test.rb
│   │   ├── output_defined_job_test.rb
│   │   ├── output_description_test.rb
│   │   ├── output_env_test.rb
│   │   ├── output_jobs_for_roles_test.rb
│   │   ├── output_jobs_with_mailto_test.rb
│   │   └── output_redirection_test.rb
│   ├── test_case.rb
│   ├── test_helper.rb
│   └── unit/
│       ├── capistrano_support_test.rb
│       ├── cron_test.rb
│       ├── executable_test.rb
│       └── job_test.rb
└── whenever.gemspec

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "bundler"
    directory: "/"
    schedule:
      interval: "monthly"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"


================================================
FILE: .github/workflows/ci.yml
================================================
name: Ruby CI
on:
  pull_request:
  push:
    branches: [ main ]
permissions:
  contents: read
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
        ruby-version: [ '2.4', '2.5', '2.6', '2.7', '3.0', '3.0', '3.2', '3.3', '3.4', '4.0', 'jruby-9', 'jruby-10']
        # CRuby < 2.6 does not support macos-arm64, so test those on amd64 instead
        exclude:
          - os: macos-latest
            ruby-version: '2.4'
          - os: macos-latest
            ruby-version: '2.5'
        include:
          - os: macos-15-intel
            ruby-version: '2.4'
          - os: macos-15-intel
            ruby-version: '2.5'
    steps:
      - uses: actions/checkout@v6
        name: Set up Ruby ${{ matrix.ruby-version }}
      - uses: ruby/setup-ruby@v1
        env:
          JRUBY_OPTS: "--debug"
        with:
          ruby-version: ${{ matrix.ruby-version }}
          bundler-cache: true # runs 'bundle install' and caches installed gems automatically
      - name: Run tests
        run: bundle exec rake test
        env:
          JRUBY_OPTS: "--debug"

  # Test with activesupport
  test-activesupport:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        ruby-version: ['2.7', '3.0', '3.0', '3.2', '3.3', '3.4', '4.0']
        rails-version: ['6.0', '6.1', '7.0', '7.1', '7.2', '8.0', '8.1']
        include:
          - ruby-version: 2.7
            rails-version: '5.0'
          - ruby-version: 2.7
            rails-version: '5.1'
          - ruby-version: 2.7
            rails-version: '5.2'
        exclude:
          # rails 8.1: support ruby 3.2+
          - ruby-version: 2.7
            rails-version: '8.1'
          - ruby-version: 3.0
            rails-version: '8.1'
          - ruby-version: 3.1
            rails-version: '8.1'
          # rails 8.0: support ruby 3.2+
          - ruby-version: 2.7
            rails-version: '8.0'
          - ruby-version: 3.0
            rails-version: '8.0'
          - ruby-version: 3.1
            rails-version: '8.0'
          # rails 7.2: support ruby 3.1+
          - ruby-version: 2.7
            rails-version: '7.2'
          - ruby-version: 3.0
            rails-version: '7.2'
    env:
      BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/activesupport${{ matrix.rails-version }}.gemfile
    steps:
      - uses: actions/checkout@v6
        name: Set up Ruby ${{ matrix.ruby-version }}
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby-version }}
          bundler-cache: true
      - name: Run tests
        run: bundle exec rake test


================================================
FILE: .gitignore
================================================
.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
tmp/

/Gemfile.lock
gemfiles/*.lock
.ruby-version

.DS_Store
.*.sw[a-z]


================================================
FILE: Appraisals
================================================
if RUBY_VERSION < "3.0"
  appraise 'activesupport5.0' do
    gem "activesupport", "~> 5.0.0"
  end

  appraise 'activesupport5.1' do
    gem "activesupport", "~> 5.1.0"
  end

  appraise 'activesupport5.2' do
    gem "activesupport", "~> 5.2.0"
  end
end

appraise 'activesupport6.0' do
  gem "activesupport", "~> 6.0.0"

  # ruby 3.3+
  gem "base64"
  gem "bigdecimal"
  gem "mutex_m"
  # ruby 3.4+
  gem "benchmark"
  gem "logger"

  # Fix https://github.com/rails/rails/issues/54260
  gem 'concurrent-ruby', "1.3.4"
end

appraise 'activesupport6.1' do
  gem "activesupport", "~> 6.1.0"

  # ruby 3.3+
  gem "base64"
  gem "bigdecimal"
  gem "mutex_m"
  # ruby 3.4+
  gem "benchmark"
  gem "logger"

  # Fix https://github.com/rails/rails/issues/54260
  gem 'concurrent-ruby', "1.3.4"
end

if RUBY_VERSION >= "2.7"
  appraise 'activesupport7.0' do
    gem "activesupport", "~> 7.0.0"
  end
end

if RUBY_VERSION >= "3.1"
  appraise 'activesupport7.1' do
    gem "activesupport", "~> 7.1.0"
  end
end

if RUBY_VERSION >= "3.2"
  appraise 'activesupport7.2' do
    gem "activesupport", "~> 7.2.0"
  end

  appraise 'activesupport8.0' do
    gem "activesupport", "~> 8.0.0"
  end

  appraise 'activesupport8.1' do
    gem "activesupport", "~> 8.1.0"
  end
end


================================================
FILE: CHANGELOG.md
================================================
### unreleased


### 1.1.2 / January 18, 2026

* Add description as comment in crontab https://github.com/javan/whenever/pull/776

* Override global option with group option https://github.com/javan/whenever/pull/822

* CI: Add Ruby 4.0 to CI Matrix https://github.com/javan/whenever/pull/872

* CI: Use macos-15-intel instead of macos-13 for Ruby 2.4 and 2.5 https://github.com/javan/whenever/pull/871

### 1.1.1 / December 8, 2025

* job_list `.respond_to?` updated to an instance method https://github.com/javan/whenever/pull/830

* Update README.md https://github.com/javan/whenever/pull/789

* CI: Bump actions/checkout from 5 to 6 https://github.com/javan/whenever/pull/869

* CI: Add tests using ActiveSupport to CI https://github.com/javan/whenever/pull/868

* Require MFA for gem releases https://github.com/javan/whenever/pull/867

### 1.1.0 / November 21, 2025

* Splat whenever_roles when passing to roles https://github.com/javan/whenever/pull/777

* Update README.md https://github.com/javan/whenever/pull/785

* Clarify in README that tasks are run in parallel https://github.com/javan/whenever/pull/787

* Add note in README that cron is generated with the current user. [Raquel Queiroz] https://github.com/javan/whenever/pull/788

* Clarify options and warn about overwriting https://github.com/javan/whenever/pull/791

* Set the environment for cron jobs based off the currently set RAILS_ENV env variable. If RAILS_ENV is not set it defaults to production https://github.com/javan/whenever/pull/832

* [CHORE] DX: easify getting started contributing https://github.com/javan/whenever/pull/846

* CI: Replace travis-ci by Github-Actions https://github.com/javan/whenever/pull/847

* Added capability for updating cron without cmd https://github.com/javan/whenever/pull/515

* CI: Add Ruby 2.7-3.4 to the CI matrix https://github.com/javan/whenever/pull/858

* CI: Add dependabot config for bundler / github-actions https://github.com/javan/whenever/pull/862

* Handle crontab output to prevent SIGPIPE when running on CentOS Stream 10 https://github.com/javan/whenever/pull/861

* CI: Bump actions/checkout from 4 to 5 https://github.com/javan/whenever/pull/863

* Fix typo In test https://github.com/javan/whenever/pull/851

* Add changelog_uri to metadata to easily link from rubygems.org https://github.com/javan/whenever/pull/786

* Update gemspec format to match Bundler v3.x gemspec template https://github.com/javan/whenever/pull/864

* Add to require `whenever/version` https://github.com/javan/whenever/pull/865

### 1.0.0 / Jun 13, 2019

* First stable release per SemVer.

* Removes support for versions of Ruby which are no longer supported by the Ruby project.

* Bugfix: Splat `whenever_roles` when passing to `roles` to allow definition of multiple whenever roles [samuelokrent](https://github.com/javan/whenever/pull/777)

### 0.11.0 / April 23, 2019

* Add support for mapping Range objects to cron range syntax [Tim Craft](https://github.com/javan/whenever/pull/725)

* Bugfix: Avoid modifying Capistrano `default_env` when setting the whenever environment. [ta1kt0me](https://github.com/javan/whenever/pull/728)

* Enable to execute whenever's task independently without setting :release_path or :whenever_path [ta1kt0me](https://github.com/javan/whenever/pull/729)

* Make error message clearer when parsing cron syntax fails due to a trailing space [ignisf](https://github.com/javan/whenever/pull/744)

### 0.10.0 / November 19, 2017

* Modify wheneverize to allow for the creating of 'config' directory when not present

* Add --crontab-command to whenever binary for overriding the crontab command. [Martin Grandrath]

* Allow setting the path within which Capistrano will execute whenever. [Samuel Johnson](https://github.com/javan/whenever/pull/619)

* Allow the use of string literals for month and day-of-week in raw cron syntax.. [Potamianos Gregory](https://github.com/javan/whenever/pull/711)

* Include Capistrano default environment variables when executing Whenever. [Karl Li](https://github.com/javan/whenever/pull/719)

* Allow configuring an alternative schedule file in Capistrano. [Shinichi Okamoto](https://github.com/javan/whenever/pull/666)

* Add customizing email recipient option with the MAILTO environment variable. [Chikahiro Tokoro](https://github.com/javan/whenever/pull/678)

### 0.9.7 / June 14, 2016

* Restore compatibility with Capistrano v3; it has a bug which we have to work around [Ben Langfeld, Chris Gunther, Shohei Yamasaki]

### 0.9.6 / June 13, 2016

* Bypass symlinks when loading Capistrano v3 code, since these symlinks don't work in recent gem releases [Justin Ramos]

### 0.9.5 / June 12, 2016

* Improve documentation [Ben Langfeld, Spencer Fry]

* Properly support Solaris / SmartOS [Steven Williamson]

* Drop support for Ruby < 1.9.3. Test newer Ruby versions. [Javan Makhmali, Bartłomiej Kozal]

* Suport Ruby 2.3.0 and Rails 4 [Vincent Boisard]

* Set `RAILS_ENV` correctly in schedule when writing crontab from Capistrano [Ben Langfeld, Lorenzo Manacorda]

* Minor refactoring, avoidance of Ruby warnings, etc [Ben Langfeld, DV Dasari]

* Correctly pass through date expressions (e.g. `1.day`) inside job definitions [Rafael Sales]

* Prevent writing invalid cron strings [Danny Fallon, Ben Langfeld]

* Execute runner with `bundle exec` to ensure presence of app dependencies [Judith Roth]


### 0.9.4 / October 24, 2014

* Fix duplicated command line arguments when deploying to multiple servers with Cap 3. [betesh]

* Set `whenever_environment` to the current stage before defaulting to production in Cap 3 tasks. [Karthik T]


### 0.9.3 / October 5, 2014

* Drop ActiveSupport dependency [James Healy, Javan Makhmali]

* Drop shoulda for tests

* Fix `whenever:clear_crontab` Cap 3 task [Javan Makhmali]

* Avoid using tempfiles [ahoward]


### 0.9.2 / March 4, 2014

* Fix issues generating arguments for `execute` in Capistrano 3 tasks. [Javan Makhmali]


### 0.9.1 / March 2, 2014

* Pass `--roles` option to `whenever` in Capistrano 3 tasks. [betesh, Javan Makhmali]

* Allow setting `:whenever_command` for Capistrano 3. [Javan Makhmali]

* Allow `:whenever` command to be mapped in SSHKit. [Javan Makhmali]


### 0.9.0 / December 17, 2013

* Capistrano V3 support. [Philip Hallstrom]

* Process params in job templates. [Austin Ziegler]


### 0.8.4 / July 22, 2013

* Don't require schedule file when clearing. [Javan Makhmali]

* Use bin/rails when available. [Javan Makhmali]


### 0.8.3 / July 11, 2013

* Improve Cap rollback logic. [Jeroen Jacobs]

* Allow configuration of the environment variable. [andfx]

* Output option can be a callable Proc. [Li Xiao]


### 0.8.2 / January 10, 2013

* Fix Capistrano host options. [Igor Yamolov, Wes Morgan]

* Improve JRuby test support. [Igor Yamolov]

* Use correct release path in Cap task. [Wes Morgan]


### 0.8.1 / December 22nd, 2012

* Fix multiserver roles bug. [Wes Morgan]

* Refactor Cap recipes and add tests for them. [Wes Morgan]

* Fix file not found error when running under JRuby. [Wes Morgan]

* Stop interpolating template attributes with no corresponding value. [Vincent Boisard]

* Support for raw cron separated by tabs. [Étienne Barrié]


### 0.8.0 / November 8th, 2012

* Separate Capistrano recipes to allow custom execution. [Bogdan Gusiev]

* Execute `whenever:update_crontab` before `deploy:finalize_update`, not `deploy:restart`. [Michal Wrobel]

* Added a new `script` job type. [Ján Suchal]

* Use correct path in Cap task. [Alex Dean]

* Fix that setup.rb and schedule.rb were eval'd together. [Niklas H]

* New Capistrano roles feature. [Wes Morgan]

* Stop clearing the crontab during a deploy. [Javan Makhmali]

* Bump Chronic gem dependency. [rainchen]


### 0.7.3 / February 23rd, 2012

* Make included Capistrano task compatible with both new and old versions of Cap. [Giacomo Macrì]


### 0.7.2 / December 23rd, 2011

* Accept @reboot and friends as raw cron syntax. [Felix Buenemann]

* Fix clear_crontab task so it will work both standalone and during deploy. [Justin Giancola]


### 0.7.1 / December 19th, 2011

* Require thread before active_support for compatibility with Rails < 2.3.11 and RubyGems >= 1.6.0. [Micah Geisel]

* More advanced role filtering in Cap task. [Brad Gessler]

* Added whenever_variables as a configuration variable in Cap task. [Steve Agalloco]

* Escape percent signs and reject newlines in jobs. [Amir Yalon]

* Escape paths so spaces don't trip up cron. [Javan Makhmali]

* Fix ambiguous handling of 1.month with :at. #99 [Javan Makhmali]


### 0.7.0 / September 2nd, 2011

* Use mojombo's chronic, it's active again. [Javan Makhmali]

* Capistrano task enhancements. [Chris Griego]

* wheneverize command defaults to '.' directory. [Andrew Nesbitt]

* rake job_type uses bundler if detected. [Michał Szajbe]

* Indicate filename in exceptions stemming from schedule file. [Javan Makhmali]

* Don't require rubygems, bundler where possible. [Oleg Pudeyev]

* Documentation and code cleanup. [many nice people]


### 0.6.8 / May 24th, 2011

* Convert most shortcuts to seconds. every :day -> every 1.day. #129 [Javan Makhmali]

* Allow commas in raw cron syntax. #130 [Marco Bergantin, Javan Makhmali]

* Output no update message as comments. #135 [Javan Makhmali]

* require 'thread' to support Rubygems >= 1.6.0. #132 [Javan Makhmali]


### 0.6.7 / March 23rd, 2011

* Fix issue with comment block being corrupted during subsequent insertion of duplicate entries to the crontab. #123 [Jeremy (@lingmann)]

* Removed -i from default job template. #118 [Javan Makhmali]


### 0.6.6 / March 8th, 2011

* Fix unclosed identifier bug. #119 [Javan Makhmali]


### 0.6.5 / March 8th, 2011

* Preserve whitespace at the end of crontab file. #95 [Rich Meyers]

* Setting nil or blank environment variables now properly formats output. [T.J. VanSlyke]

* Allow raw cron sytax, added -i to bash job template, general cleanup. [Javan Makhmali]


### 0.6.2 / October 26th, 2010

* --clear-crontab option completely removes entries. #63 [Javan Makhmali]

* Set default :environment and :path earlier in the new setup.rb (formerly job_types/default.rb). [Javan Makhmali]

* Converted README and CHANGELOG to markdown. [Javan Makhmali]


### 0.6.1 / October 20th, 2010

* Detect script/rails file and change runner to Rails 3 style if found. [Javan Makhmali]

* Created a new :job_template system that can be applied to all commands. Wraps all in bash -l -c 'command..' by default now for better RVM support. Stopped automatically setting the PATH too. [Javan Makhmali]

* Added a built-in Capistrano recipe. [Javan Makhmali]


### 0.5.3 / September 24th, 2010

* Better regexes for replacing Whenever blocks in the crontab. #45 [Javan Makhmali]

* Preserving backslashes when updating existing crontab. #82 [Javan Makhmali]


### 0.5.2 / September 15th, 2010

* Quotes automatically escaped in jobs. [Jay Adkisson]

* Added --cut option to the command line to allow pruning of the crontab. [Peer Allan]

* Switched to aaronh-chronic which is ruby 1.9.2 compatible. [Aaron Hurley, Javan Makhmali]

* Lots of internal reorganizing; tests broken into unit and functional. [Javan Makhmali]


### 0.5.0 / June 28th, 2010

* New job_type API for writing custom jobs. Internals use this to define command, runner, and rake. [Javan Makhmali - inspired by idlefingers (Damien)]

* Jobs < 1.hour can specify an :at. [gorenje]

* --clear option to remove crontab entries for a specific [identifier]. [mraidel (Michael Raidel)]


### 0.4.2 / April 26th, 2010

* runners now cd into the app's directory and then execute. [Michael Guterl]

* Fix STDERR output redirection to file to append instead of overwrite. [weplay]

* Move require of tempfile lib to file that actually uses it. [Finn Smith]

* bugfix: comparison Time with 0 failed. #32 [Dan Hixon]


### 0.4.1 / November 30th, 2009

* exit(0) instead of just exit to make JRuby happy. [Elan Meng]

* Fixed activesupport deprecation warning by requiring active_support. #37 [Andrew Nesbitt]


### 0.4.0 / October 20th, 2009

* New output option replaces the old cron_log option for output redirection and is much more flexible. #31 [Peer Allan]

* Reorganized the lib files (http://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices) and switched to Jeweler from Echoe.


### 0.3.7 / September 4th, 2009

* No longer tries (and fails) to combine @shortcut jobs. #20 [Javan Makhmali]


### 0.3.6 / June 15th, 2009

* Setting a PATH in the crontab automatically based on the user's PATH. [Javan Makhmali]


### 0.3.5 / June 13th, 2009

* Added ability to accept lists of every's and at's and intelligently group them. (ex: every 'monday, wednesday', :at => ['3pm', '6am']). [Sam Ruby]

* Fixed issue with new lines. #18 [Javan Makhmali]

### 0.3.1 / June 25th, 2009

* Removed activesupport gem dependency. #1 [Javan Makhmali]

* Switched to numeric days of the week for Solaris support (and probably others). #8 [Roger Ertesvåg]


### 0.3.0 / June 2nd, 2009

* Added ability to set variables on the fly from the command line (ex: whenever --set environment=staging). [Javan Makhmali]


### 0.2.2 / April 30th, 2009

* Days of week jobs can now accept an :at directive (ex: every :monday, :at => '5pm'). [David Eisinger]

* Fixed command line test so it runs without a config/schedule.rb present. [Javan Makhmali]

* Raising an exception if someone tries to specify an :at with a cron shortcut (:day, :reboot, etc) so there are no false hopes. [Javan Makhmali]


### 0.1.7 / March 5th, 2009

* Added ability to update the crontab file non-destuctively instead of only overwriting it. [Javan Makhmali -- Inspired by code submitted individually from: Tien Dung (tiendung), Tom Lea (cwninja), Kyle Maxwell (fizx), and Andrew Timberlake (andrewtimberlake) on github]


### 0.1.5 / February 19th, 2009

* Fixed load path so Whenever's files don't conflict with anything in Rails. Thanks Ryan Koopmans. [Javan Makhmali]


### 0.1.4 / February 17th, 2009

* Added --load-file and --user opts to whenever binary. [Javan Makhmali]


### 0.1.3 / February 16th, 2009

* Added 'rake' helper for defining scheduled rake tasks. [Javan Makhmali]

* Renamed :cron_environment and :cron_path to :enviroment and :path for better (word) compatibility with rake tasks. [Javan Makhmali]

* Improved test load paths so tests can be run individually. [Javan Makhmali]

* Got rid of already initialized constant warning. [Javan Makhmali]

* Requiring specific gem versions: Chronic >=0.2.3 and activesupport >= 1.3.0 [Javan Makhmali]


### 0.1.0 / February 15th, 2009

* Initial release [Javan Makhmali]


================================================
FILE: CONTRIBUTING.md
================================================
## How to contribute to Whenever

#### **Did you find a bug?**

* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/your/whenever/issues).

* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/your/whenever/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.

#### **Did you write a patch that fixes a bug?**

* Open a new GitHub pull request with the patch.

* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.


#### **Do you have questions about the source code?**

* Ask any question about how to use Whenever in the [whenever issues](https://github.com/javan/whenever/issues).

#### **Do you want to contribute to the Whenever documentation?**

Whenever is a volunteer effort. We encourage you to pitch in! 

Thanks! :heart: :heart: :heart:

Whenever Team

---
## Setup

```bash
$ bundle install
```
## Run tests

```bash
$ make test
```


================================================
FILE: Gemfile
================================================
# frozen_string_literal: true

source "https://rubygems.org"

# Specify your gem's dependencies in whenever.gemspec
gemspec

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"


================================================
FILE: LICENSE
================================================
Copyright (c) 2017 Javan Makhmali

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: Makefile
================================================
.DEFAULT_GOAL := test

test:
	bundle exec rake test
.PHONY: test


================================================
FILE: README.md
================================================
Whenever is a Ruby gem that provides a clear syntax for writing and deploying cron jobs.

### Installation

```sh
$ gem install whenever
```

Or with Bundler in your Gemfile.

```ruby
gem 'whenever', require: false
```

### Getting started

```sh
$ cd /apps/my-great-project
$ bundle exec wheneverize .
```

This will create an initial `config/schedule.rb` file for you (as long as the config folder is already present in your project).

### The `whenever` command

The `whenever` command will simply show you your `schedule.rb` file converted to cron syntax. It does not read or write your crontab file.

```sh
$ cd /apps/my-great-project
$ bundle exec whenever
```

To write your crontab file for your jobs, execute this command:

```sh
$ whenever --update-crontab
```

Other commonly used options include:
```sh
$ whenever --user app # set a user as which to install the crontab
$ whenever --load-file config/my_schedule.rb # set the schedule file
$ whenever --crontab-command 'sudo crontab' # override the crontab command
```

> Note: If you run the whenever --update-crontab without passing the --user attribute, cron will be generated by the current user. This mean tasks that needs other user permission will fail.

You can list installed cron jobs using `crontab -l`.

Run `whenever --help` for a complete list of options for selecting the schedule to use, setting variables in the schedule, etc.

### Example schedule.rb file

```ruby
every 3.hours do # 1.minute 1.day 1.week 1.month 1.year is also supported
  # the following tasks are run in parallel (not in sequence)
  runner "MyModel.some_process"
  rake "my:rake:task"
  command "/usr/bin/my_great_command"
end

every 1.day, at: '4:30 am' do
  runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
end

every 1.day, at: ['4:30 am', '6:00 pm'] do
  runner "Mymodel.task_to_run_in_two_times_every_day"
end

every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
  runner "SomeModel.ladeeda"
end

every :sunday, at: '12pm' do # Use any day of the week or :weekend, :weekday
  runner "Task.do_something_great"
end

every '0 0 27-31 * *' do
  command "echo 'you can use raw cron syntax too'"
end

# run this task only on servers with the :app role in Capistrano
# see Capistrano roles section below
every :day, at: '12:20am', roles: [:app] do
  rake "app_server:task"
end
```

### Define your own job types

Whenever ships with three pre-defined job types: command, runner, and rake. You can define your own with `job_type`.

For example:

```ruby
job_type :awesome, '/usr/local/bin/awesome :task :fun_level'

every 2.hours do
  awesome "party", fun_level: "extreme"
end
```

Would run `/usr/local/bin/awesome party extreme` every two hours. `:task` is always replaced with the first argument, and any additional `:whatevers` are replaced with the options passed in or by variables that have been defined with `set`.

The default job types that ship with Whenever are defined like so:

```ruby
job_type :command, ":task :output"
job_type :rake,    "cd :path && :environment_variable=:environment :bundle_command rake :task --silent :output"
job_type :script,  "cd :path && :environment_variable=:environment :bundle_command script/:task :output"
job_type :runner,  "cd :path && :bundle_command :runner_command -e :environment ':task' :output"
```

Pre-Rails 3 apps and apps that don't use Bundler will redefine the `rake` and `runner` jobs respectively to function correctly.

If a `:path` is not set it will default to the directory in which `whenever` was executed. `:environment_variable` will default to 'RAILS_ENV'. `:environment` will default to 'production'. `:output` will be replaced with your output redirection settings which you can read more about here: <http://github.com/javan/whenever/wiki/Output-redirection-aka-logging-your-cron-jobs>

All jobs are by default run with `bash -l -c 'command...'`. Among other things, this allows your cron jobs to play nice with RVM by loading the entire environment instead of cron's somewhat limited environment. Read more: <http://blog.scoutapp.com/articles/2010/09/07/rvm-and-cron-in-production>

You can change this by setting your own `:job_template`.

```ruby
set :job_template, "bash -l -c ':job'"
```

Or set the job_template to nil to have your jobs execute normally.

```ruby
set :job_template, nil
```

### Parsing dates and times

Whenever uses the [Chronic](https://github.com/mojombo/chronic) gem to parse the specified dates and times.

You can set your custom Chronic configuration if the defaults don't fit you.

For example, to assume a 24 hour clock instead of the default 12 hour clock:

```ruby
set :chronic_options, hours24: true

# By default this would run the job every day at 3am
every 1.day, at: '3:00' do
  runner "MyModel.nightly_archive_job"
end
```

You can see a list of all available options here: <https://github.com/mojombo/chronic/blob/master/lib/chronic/parser.rb>

### Customize email recipient with the `MAILTO` environment variable

Output from the jobs is sent to the email address configured in the `MAILTO` environment variable.

There are many ways to further configure the recipient.

Example: A global configuration, overriding the environment's value:

```ruby
env 'MAILTO', 'output_of_cron@example.com'

every 3.hours do
  command "/usr/bin/my_great_command"
end
```

Example: A `MAILTO` configured for all the jobs in an interval block:

```ruby
every 3.hours, mailto: 'my_super_command@example.com'  do
  command "/usr/bin/my_super_command"
end
```

Example: A `MAILTO` configured for a single job:

```ruby
every 3.hours do
  command "/usr/bin/my_super_command", mailto: 'my_super_command_output@example.com'
end
```

### Adding comments to crontab

A description can be added to a job that will be included in the crontab as a comment above the cron entry.

Example: A single line description:

```ruby
every 1.hours, description: "My job description" do
  command "/usr/bin/my_great_command"
end
```

Example: A multi line description:

```ruby
every 1.hours, description: "My job description\nhas multiple lines" do
  command "/usr/bin/my_great_command"
end
```

### Capistrano integration

Use the built-in Capistrano recipe for easy crontab updates with deploys. For Capistrano V3, see the next section.

In your "config/deploy.rb" file:

```ruby
require "whenever/capistrano"
```

Take a look at the recipe for options you can set. <https://github.com/javan/whenever/blob/main/lib/whenever/capistrano/v2/recipes.rb>
For example, if you're using bundler do this:

```ruby
set :whenever_command, "bundle exec whenever"
require "whenever/capistrano"
```

If you are using different environments (such as staging, production), then you may want to do this:

```ruby
set :whenever_environment, defer { stage }
require "whenever/capistrano"
```

The capistrano variable `:stage` should be the one holding your environment name. This will make the correct `:environment` available in your `schedule.rb`.

If both your environments are on the same server you'll want to namespace them, or they'll overwrite each other when you deploy:

```ruby
set :whenever_environment, defer { stage }
set :whenever_identifier, defer { "#{application}_#{stage}" }
require "whenever/capistrano"
```

If you use a schedule at an alternative path, you may configure it like so:

```ruby
set :whenever_load_file, defer { "#{release_path}/somewhere/else/schedule.rb" }
require "whenever/capistrano"
```

### Capistrano V3 Integration

In your "Capfile" file:

```ruby
require "whenever/capistrano"
```

Take a look at the [load:defaults task](https://github.com/javan/whenever/blob/main/lib/whenever/capistrano/v3/tasks/whenever.rake) (bottom of file) for options you can set. For example, to namespace the crontab entries by application and stage do this in your "config/deploy.rb" file:

```ruby
set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" }
```

The Capistrano integration by default expects the `:application` variable to be set in order to scope jobs in the crontab.

### Capistrano roles

The first thing to know about the new roles support is that it is entirely
optional and backwards-compatible. If you don't need different jobs running on
different servers in your capistrano deployment, then you can safely stop reading
now and everything should just work the same way it always has.

When you define a job in your schedule.rb file, by default it will be deployed to
all servers in the whenever_roles list (which defaults to `[:db]`).

However, if you want to restrict certain jobs to only run on subset of servers,
you can add a `roles: [...]` argument to their definitions. **Make sure to add
that role to the whenever_roles list in your deploy.rb.**

When you run `cap deploy`, jobs with a :roles list specified will only be added to
the crontabs on servers with one or more of the roles in that list.

Jobs with no :roles argument will be deployed to all servers in the whenever_roles
list. This is to maintain backward compatibility with previous releases of whenever.

So, for example, with the default whenever_roles of `[:db]`, a job like this would be
deployed to all servers with the `:db` role:

```ruby
every :day, at: '12:20am' do
  rake 'foo:bar'
end
```

If we set whenever_roles to `[:db, :app]` in deploy.rb, and have the following
jobs in schedule.rb:

```ruby
every :day, at: '1:37pm', roles: [:app] do
  rake 'app:task' # will only be added to crontabs of :app servers
end

every :hour, roles: [:db] do
  rake 'db:task' # will only be added to crontabs of :db servers
end

every :day, at: '12:02am' do
  command "run_this_everywhere" # will be deployed to :db and :app servers
end
```

Here are the basic rules:

  1. If a server's role isn't listed in whenever_roles, it will *never* have jobs
     added to its crontab.
  1. If a server's role is listed in the whenever_roles, then it will have all
     jobs added to its crontab that either list that role in their :roles arg or
     that don't have a :roles arg.
  1. If a job has a :roles arg but that role isn't in the whenever_roles list,
     that job *will not* be deployed to any server.

### RVM Integration

If your production environment uses RVM (Ruby Version Manager) you will run into a gotcha that causes your cron jobs to hang.  This is not directly related to Whenever, and can be tricky to debug.  Your .rvmrc files must be trusted or else the cron jobs will hang waiting for the file to be trusted.  A solution is to disable the prompt by adding this line to your user rvm file in `~/.rvmrc`

`rvm_trust_rvmrcs_flag=1`

This tells rvm to trust all rvmrc files.

### Heroku?

No. Heroku does not support cron, instead providing [Heroku Scheduler](https://devcenter.heroku.com/articles/scheduler). If you deploy to Heroku, you should use that rather than Whenever.

### Testing

[whenever-test](https://github.com/heartbits/whenever-test) is an extension to Whenever for testing a Whenever schedule.

### Credit

Whenever was created for use at Inkling (<http://inklingmarkets.com>). Their take on it: <http://blog.inklingmarkets.com/2009/02/whenever-easy-way-to-do-cron-jobs-from.html>

Thanks to all the contributors who have made it even better: <http://github.com/javan/whenever/contributors>

### Discussion / Feedback / Issues / Bugs

For general discussion and questions, please use the google group: <http://groups.google.com/group/whenever-gem>

If you've found a genuine bug or issue, please use the Issues section on github: <http://github.com/javan/whenever/issues>

Ryan Bates created a great Railscast about Whenever: <http://railscasts.com/episodes/164-cron-in-ruby>
It's a little bit dated now, but remains a good introduction.

----

Copyright &copy; 2017 Javan Makhmali


================================================
FILE: Rakefile
================================================
require 'bundler/gem_tasks'
require 'rake/testtask'

Rake::TestTask.new(:test) do |test|
  test.libs      << 'lib' << 'test'
  test.pattern   = 'test/{functional,unit}/**/*_test.rb'
  test.verbose   = true
end

task :default => :test


================================================
FILE: bin/whenever
================================================
#!/usr/bin/env ruby

require 'optparse'
require 'whenever'

options = {}

OptionParser.new do |opts|
  opts.banner = "Usage: whenever [options]"
  opts.on('-i', '--update-crontab [schedule]',
          'Install the schedule to crontab.',
          '  Default: full path to schedule.rb file') do |identifier|
    options[:update] = true
    options[:identifier] = identifier if identifier
  end
  opts.on('-w', '--write-crontab [schedule]',
          'Clear current crontab, and overwrite it with only the given schedule.',
          '  Default: full path to schedule.rb file') do |identifier|
     options[:write] = true
     options[:identifier] = identifier if identifier
  end
  opts.on('-c', '--clear-crontab [schedule]',
          'Uninstall the schedule from crontab.',
          '  Default: full path to schedule.rb file') do |identifier|    
    options[:clear] = true
    options[:identifier] = identifier if identifier
  end
  opts.on('-s', '--set [variables]', 'Example: --set \'environment=staging&path=/my/sweet/path\'') do |set|
    options[:set] = set if set
  end
  opts.on('-f', '--load-file [schedule file]', 'Default: config/schedule.rb') do |file|
    options[:file] = file if file
  end
  opts.on('-u', '--user [user]', 'Default: current user') do |user|
    options[:user] = user if user
  end
  opts.on('-k', '--cut [lines]', 'Cut lines from the top of the cronfile') do |lines|
    options[:cut] = lines.to_i if lines
  end
  opts.on('-r', '--roles [role1,role2]', 'Comma-separated list of server roles to generate cron jobs for') do |roles|
    options[:roles] = roles.split(',').map(&:to_sym) if roles
  end
  opts.on('-x', '--crontab-command [command]', 'Default: crontab') do |crontab_command|
    options[:crontab_command] = crontab_command if crontab_command
  end
  opts.on('-v', '--version') { puts "Whenever v#{Whenever::VERSION}"; exit(0) }
end.parse!

Whenever::CommandLine.execute(options)


================================================
FILE: bin/wheneverize
================================================
#!/usr/bin/env ruby

# This file is based heavily on Capistrano's `capify` command

require 'optparse'
require 'fileutils'

OptionParser.new do |opts|
  opts.banner = "Usage: #{File.basename($0)} [path]"

  begin
    opts.parse!(ARGV)
  rescue OptionParser::ParseError => e
    warn e.message
    puts opts
    exit 1
  end
end

unless ARGV.empty?
  if !File.exist?(ARGV.first)
    abort "`#{ARGV.first}' does not exist."
  elsif !File.directory?(ARGV.first)
    abort "`#{ARGV.first}' is not a directory."
  elsif ARGV.length > 1
    abort "Too many arguments; please specify only the directory to wheneverize."
  end
end

content = <<-FILE
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron

# Example:
#
# set :output, "/path/to/my/cron_log.log"
#
# every 2.hours do
#   command "/usr/bin/some_great_command"
#   runner "MyModel.some_method"
#   rake "some:great:rake:task"
# end
#
# every 4.days do
#   runner "AnotherModel.prune_old_records"
# end

# Learn more: http://github.com/javan/whenever
FILE

file = 'config/schedule.rb'
base = ARGV.empty? ? '.' : ARGV.shift

file = File.join(base, file)
if File.exist?(file)
  warn "[skip] `#{file}' already exists"
elsif File.exist?(file.downcase)
  warn "[skip] `#{file.downcase}' exists, which could conflict with `#{file}'"
else
  dir = File.dirname(file)
  if !File.exist?(dir)
    warn "[add] creating `#{dir}'"
    FileUtils.mkdir_p(dir)
  end
  puts "[add] writing `#{file}'"
  File.open(file, "w") { |f| f.write(content) }
end

puts "[done] wheneverized!"


================================================
FILE: gemfiles/activesupport5.0.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 5.0.0"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport5.1.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 5.1.0"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport5.2.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 5.2.0"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport6.0.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 6.0.0"
gem "base64"
gem "bigdecimal"
gem "mutex_m"
gem "benchmark"
gem "logger"
gem "concurrent-ruby", "1.3.4"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport6.1.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 6.1.0"
gem "base64"
gem "bigdecimal"
gem "mutex_m"
gem "benchmark"
gem "logger"
gem "concurrent-ruby", "1.3.4"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport7.0.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 7.0.0"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport7.1.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 7.1.0"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport7.2.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 7.2.0"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport8.0.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 8.0.0"

gemspec path: "../"


================================================
FILE: gemfiles/activesupport8.1.gemfile
================================================
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake"
gem "mocha"
gem "minitest"
gem "appraisal"
gem "activesupport", "~> 8.1.0"

gemspec path: "../"


================================================
FILE: lib/whenever/capistrano/v2/hooks.rb
================================================
require "whenever/capistrano/v2/recipes"

Capistrano::Configuration.instance(:must_exist).load do
  # Write the new cron jobs near the end.
  before "deploy:finalize_update", "whenever:update_crontab"
  # If anything goes wrong, undo.
  after "deploy:rollback", "whenever:update_crontab"
end


================================================
FILE: lib/whenever/capistrano/v2/recipes.rb
================================================
require 'whenever/capistrano/v2/support'

Capistrano::Configuration.instance(:must_exist).load do
  Whenever::CapistranoSupport.load_into(self)

  _cset(:whenever_roles)        { :db }
  _cset(:whenever_options)      { {:roles => fetch(:whenever_roles)} }
  _cset(:whenever_command)      { "whenever" }
  _cset(:whenever_identifier)   { fetch :application }
  _cset(:whenever_environment)  { fetch :rails_env, fetch(:stage, "production") }
  _cset(:whenever_variables)    { "environment=#{fetch :whenever_environment}" }
  _cset(:whenever_update_flags) { "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables}" }
  _cset(:whenever_clear_flags)  { "--clear-crontab #{fetch :whenever_identifier}" }
  _cset(:whenever_path)         { fetch :latest_release }

  namespace :whenever do
    desc "Update application's crontab entries using Whenever"
    task :update_crontab do
      args = {
        :command => fetch(:whenever_command),
        :flags   => fetch(:whenever_update_flags),
        :path    => fetch(:whenever_path)
      }

      if whenever_servers.any?
        args = whenever_prepare_for_rollback(args) if task_call_frames[0].task.fully_qualified_name == 'deploy:rollback'
        whenever_run_commands(args)

        on_rollback do
          args = whenever_prepare_for_rollback(args)
          whenever_run_commands(args)
        end
      end
    end

    desc "Clear application's crontab entries using Whenever"
    task :clear_crontab do
      if whenever_servers.any?
        args = {
          :command => fetch(:whenever_command),
          :flags   => fetch(:whenever_clear_flags),
          :path    => fetch(:whenever_path)
        }

        whenever_run_commands(args)
      end
    end
  end
end


================================================
FILE: lib/whenever/capistrano/v2/support.rb
================================================
module Whenever
  module CapistranoSupport
    def self.load_into(capistrano_configuration)
      capistrano_configuration.load do

        def whenever_options
          fetch(:whenever_options)
        end

        def whenever_roles
          Array(whenever_options[:roles])
        end

        def whenever_servers
          find_servers(whenever_options)
        end

        def whenever_server_roles
          whenever_servers.inject({}) do |map, server|
            map[server] = role_names_for_host(server) & whenever_roles
            map
          end
        end

        def whenever_prepare_for_rollback args
          if fetch(:previous_release)
            # rollback to the previous release's crontab
            args[:path] = fetch(:previous_release)
          else
            # clear the crontab if no previous release
            args[:path]  = fetch(:release_path)
            args[:flags] = fetch(:whenever_clear_flags)
          end
          args
        end

        def whenever_run_commands(args)
          unless [:command, :path, :flags].all? { |a| args.include?(a) }
            raise ArgumentError, ":command, :path, & :flags are required"
          end

          whenever_server_roles.each do |server, roles|
            roles_arg = roles.empty? ? "" : " --roles #{roles.join(',')}"

            command = "cd #{args[:path]} && #{args[:command]} #{args[:flags]}#{roles_arg}"
            run command, whenever_options.merge(:hosts => server)
          end
        end

      end
    end
  end
end


================================================
FILE: lib/whenever/capistrano/v3/tasks/whenever.rake
================================================
namespace :whenever do
  def setup_whenever_task(*args, &block)
    args = Array(fetch(:whenever_command)) + args

    on roles *fetch(:whenever_roles) do |host|
      args_for_host = block_given? ? args + Array(yield(host)) : args
      within fetch(:whenever_path) do
        with fetch(:whenever_command_environment_variables) do
          execute(*args_for_host)
        end
      end
    end
  end

  def load_file
    file = fetch(:whenever_load_file)
    if file
      "-f #{file}"
    else
      ''
    end
  end

  desc "Update application's crontab entries using Whenever"
  task :update_crontab do
    setup_whenever_task do |host|
      roles = host.roles_array.join(",")
      [fetch(:whenever_update_flags), "--roles=#{roles}", load_file]
    end
  end

  desc "Clear application's crontab entries using Whenever"
  task :clear_crontab do
    setup_whenever_task do |host|
      [fetch(:whenever_clear_flags), load_file]
    end
  end

  after "deploy:updated",  "whenever:update_crontab"
  after "deploy:reverted", "whenever:update_crontab"
end

namespace :load do
  task :defaults do
    set :whenever_roles,        ->{ :db }
    set :whenever_command,      ->{ [:bundle, :exec, :whenever] }
    set :whenever_command_environment_variables, ->{ fetch(:default_env).merge(rails_env: fetch(:whenever_environment)) }
    set :whenever_identifier,   ->{ fetch :application }
    set :whenever_environment,  ->{ fetch :rails_env, fetch(:stage, "production") }
    set :whenever_variables,    ->{ "environment=#{fetch :whenever_environment}" }
    set :whenever_load_file,    ->{ nil }
    set :whenever_update_flags, ->{ "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables}" }
    set :whenever_clear_flags,  ->{ "--clear-crontab #{fetch :whenever_identifier}" }
    set :whenever_path,         ->{ release_path }
  end
end


================================================
FILE: lib/whenever/capistrano.rb
================================================
require 'capistrano/version'

if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
  load File.expand_path("../capistrano/v3/tasks/whenever.rake", __FILE__)
else
  require 'whenever/capistrano/v2/hooks'
end


================================================
FILE: lib/whenever/command_line.rb
================================================
require 'fileutils'

module Whenever
  class CommandLine
    def self.execute(options={})
      new(options).run
    end

    def initialize(options={})
      @options = options

      @options[:crontab_command] ||= 'crontab'
      @options[:file]            ||= 'config/schedule.rb'
      @options[:cut]             ||= 0
      @options[:identifier]      ||= default_identifier
      @options[:console]    = true if @options[:console].nil?

      if !File.exist?(@options[:file]) && @options[:clear].nil?
        warn("[fail] Can't find file: #{@options[:file]}")
        return_or_exit(false)
      end

      if [@options[:update], @options[:write], @options[:clear]].compact.length > 1
        warn("[fail] Can only update, write or clear. Choose one.")
        return_or_exit(false)
      end

      unless @options[:cut].to_s =~ /[0-9]*/
        warn("[fail] Can't cut negative lines from the crontab #{options[:cut]}")
        return_or_exit(false)
      end
      @options[:cut] = @options[:cut].to_i

      @timestamp = Time.now.to_s
    end

    def run
      if @options[:update] || @options[:clear]
        write_crontab(updated_crontab)
      elsif @options[:write]
        write_crontab(whenever_cron)
      else
        puts Whenever.cron(@options)
        puts "## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated."
        puts "## [message] Run `whenever --help' for more options."
        return_or_exit(true)
      end
    end

  protected

    def default_identifier
      File.expand_path(@options[:file])
    end

    def whenever_cron
      return '' if @options[:clear]
      @whenever_cron ||= [comment_open, Whenever.cron(@options), comment_close].compact.join("\n") + "\n"
    end

    def read_crontab
      return @current_crontab if instance_variable_defined?(:@current_crontab)

      command = [@options[:crontab_command]]
      command << '-l'
      command << "-u #{@options[:user]}" if @options[:user]

      command_results  = %x[#{command.join(' ')} 2> /dev/null]
      @current_crontab = $?.exitstatus.zero? ? prepare(command_results) : ''
    end

    def write_crontab(contents)
      command = [@options[:crontab_command]]
      command << "-u #{@options[:user]}" if @options[:user]
      # Solaris/SmartOS cron does not support the - option to read from stdin.
      command << "-" unless OS.solaris?

      IO.popen(command.join(' '), 'r+') do |crontab|
        crontab.write(contents)
        crontab.close_write
        stdout = crontab.read
        puts stdout unless stdout == ''
      end

      success = $?.exitstatus.zero?

      if success
        action = 'written' if @options[:write]
        action = 'updated' if @options[:update]
        puts "[write] crontab file #{action}"
        return_or_exit(true)
      else
        warn "[fail] Couldn't write crontab; try running `whenever' with no options to ensure your schedule file is valid."
        return_or_exit(false)
      end
    end

    def updated_crontab
      # Check for unopened or unclosed identifier blocks
      if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && (read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")).nil?
        warn "[fail] Unclosed indentifier; Your crontab file contains '#{comment_open(false)}', but no '#{comment_close(false)}'"
        return_or_exit(false)
      elsif (read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$")).nil? && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
        warn "[fail] Unopened indentifier; Your crontab file contains '#{comment_close(false)}', but no '#{comment_open(false)}'"
        return_or_exit(false)
      end

      # If an existing identifier block is found, replace it with the new cron entries
      if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
        # If the existing crontab file contains backslashes they get lost going through gsub.
        # .gsub('\\', '\\\\\\') preserves them. Go figure.
        read_crontab.gsub(Regexp.new("^#{comment_open_regex}\s*$.+^#{comment_close_regex}\s*$", Regexp::MULTILINE), whenever_cron.chomp.gsub('\\', '\\\\\\'))
      else # Otherwise, append the new cron entries after any existing ones
        [read_crontab, whenever_cron].join("\n\n")
      end.gsub(/\n{3,}/, "\n\n") # More than two newlines becomes just two.
    end

    def prepare(contents)
      # Strip n lines from the top of the file as specified by the :cut option.
      # Use split with a -1 limit option to ensure the join is able to rebuild
      # the file with all of the original seperators in-tact.
      stripped_contents = contents.split($/,-1)[@options[:cut]..-1].join($/)

      # Some cron implementations require all non-comment lines to be newline-
      # terminated. (issue #95) Strip all newlines and replace with the default
      # platform record seperator ($/)
      stripped_contents.gsub!(/\s+$/, $/)
    end

    def comment_base(include_timestamp = true)
      if include_timestamp
        "Whenever generated tasks for: #{@options[:identifier]} at: #{@timestamp}"
      else
        "Whenever generated tasks for: #{@options[:identifier]}"
      end
    end

    def comment_open(include_timestamp = true)
      "# Begin #{comment_base(include_timestamp)}"
    end

    def comment_close(include_timestamp = true)
      "# End #{comment_base(include_timestamp)}"
    end

    def comment_open_regex
      "#{comment_open(false)}(#{timestamp_regex}|)"
    end

    def comment_close_regex
      "#{comment_close(false)}(#{timestamp_regex}|)"
    end

    def timestamp_regex
      " at: \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} ([+-]\\d{4}|UTC)"
    end

  private

    def return_or_exit success
      result = 1
      result = 0 if success
      if @options[:console]
        exit(result)
      else
        result
      end
    end

  end
end


================================================
FILE: lib/whenever/cron.rb
================================================
require 'chronic'

module Whenever
  module Output
    class Cron
      DAYS = %w(sun mon tue wed thu fri sat)
      MONTHS = %w(jan feb mar apr may jun jul aug sep oct nov dec)
      KEYWORDS = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly]
      REGEX = /^(@(#{KEYWORDS.join '|'})|((\*?[\d\/,\-]*)\s){3}(\*?([\d\/,\-]|(#{MONTHS.join '|'}))*\s)(\*?([\d\/,\-]|(#{DAYS.join '|'}))*))$/i

      attr_accessor :time, :task

      def initialize(time = nil, task = nil, at = nil, options = {})
        chronic_options = options[:chronic_options] || {}

        @at_given = at
        @time = time
        @task = task
        @at   = at.is_a?(String) ? (Chronic.parse(at, chronic_options) || 0) : (at || 0)
      end

      def self.enumerate(item, detect_cron = true)
        if item and item.is_a?(String)
          items =
            if detect_cron && item =~ REGEX
              [item]
            else
              item.split(',')
            end
        else
          items = item
          items = [items] unless items and items.respond_to?(:each)
        end
        items
      end

      def self.output(times, job, options = {})
        enumerate(times).each do |time|
          enumerate(job.at, false).each do |at|
            yield new(time, job.output, at, options).output
          end
        end
      end

      def output
        [time_in_cron_syntax, task].compact.join(' ').strip
      end

      def time_in_cron_syntax
        @time = @time.to_i if @time.is_a?(Numeric) # Compatibility with `1.day` format using ruby 2.3 and activesupport
        case @time
          when REGEX  then @time # raw cron syntax given
          when Symbol then parse_symbol
          when String then parse_as_string
          else parse_time
        end
      end

    protected
      def day_given?
        @at_given.is_a?(String) && (MONTHS.any? { |m| @at_given.downcase.index(m) } || @at_given[/\d\/\d/])
      end

      def parse_symbol
        shortcut = case @time
          when *KEYWORDS then "@#{@time}" # :reboot => '@reboot'
          when :year     then Whenever.seconds(1, :year)
          when :day      then Whenever.seconds(1, :day)
          when :month    then Whenever.seconds(1, :month)
          when :week     then Whenever.seconds(1, :week)
          when :hour     then Whenever.seconds(1, :hour)
          when :minute   then Whenever.seconds(1, :minute)
        end

        if shortcut.is_a?(Numeric)
          @time = shortcut
          parse_time
        elsif shortcut
          if @at.is_a?(Time) || (@at.is_a?(Numeric) && @at > 0)
            raise ArgumentError, "You cannot specify an ':at' when using the shortcuts for times."
          else
            return shortcut
          end
        else
          parse_as_string
        end
      end

      def parse_time
        timing = Array.new(5, '*')
        case @time
          when Whenever.seconds(0, :seconds)...Whenever.seconds(1, :minute)
            raise ArgumentError, "Time must be in minutes or higher"
          when Whenever.seconds(1, :minute)...Whenever.seconds(1, :hour)
            minute_frequency = @time / 60
            timing[0] = comma_separated_timing(minute_frequency, 59, @at || 0)
          when Whenever.seconds(1, :hour)...Whenever.seconds(1, :day)
            hour_frequency = (@time / 60 / 60).round
            timing[0] = @at.is_a?(Time) ? @at.min : range_or_integer(@at, 0..59, 'Minute')
            timing[1] = comma_separated_timing(hour_frequency, 23)
          when Whenever.seconds(1, :day)...Whenever.seconds(1, :month)
            day_frequency = (@time / 24 / 60 / 60).round
            timing[0] = @at.is_a?(Time) ? @at.min  : 0
            timing[1] = @at.is_a?(Time) ? @at.hour : range_or_integer(@at, 0..23, 'Hour')
            timing[2] = comma_separated_timing(day_frequency, 31, 1)
          when Whenever.seconds(1, :month)...Whenever.seconds(1, :year)
            month_frequency = (@time / 30 / 24 / 60 / 60).round
            timing[0] = @at.is_a?(Time) ? @at.min  : 0
            timing[1] = @at.is_a?(Time) ? @at.hour : 0
            timing[2] = if @at.is_a?(Time)
              day_given? ? @at.day : 1
            else
              @at == 0 ? 1 : range_or_integer(@at, 1..31, 'Day')
            end
            timing[3] = comma_separated_timing(month_frequency, 12, 1)
          when Whenever.seconds(1, :year)
            timing[0] = @at.is_a?(Time) ? @at.min  : 0
            timing[1] = @at.is_a?(Time) ? @at.hour : 0
            timing[2] = if @at.is_a?(Time)
              day_given? ? @at.day : 1
            else
              1
            end
            timing[3] = if @at.is_a?(Time)
              day_given? ? @at.month : 1
            else
              @at == 0 ? 1 : range_or_integer(@at, 1..12, 'Month')
            end
          else
            return parse_as_string
        end
        timing.join(' ')
      end

      def parse_as_string
        return unless @time
        string = @time.to_s

        timing = Array.new(4, '*')
        timing[0] = @at.is_a?(Time) ? @at.min  : 0
        timing[1] = @at.is_a?(Time) ? @at.hour : 0

        return (timing << '1-5') * " " if string.downcase.index('weekday')
        return (timing << '6,0') * " " if string.downcase.index('weekend')

        DAYS.each_with_index do |day, i|
          return (timing << i) * " " if string.downcase.index(day)
        end

        raise ArgumentError, "Couldn't parse: #{@time.inspect}"
      end

      def range_or_integer(at, valid_range, name)
        must_be_between = "#{name} must be between #{valid_range.min}-#{valid_range.max}"
        if at.is_a?(Range)
          raise ArgumentError, "#{must_be_between}, #{at.min} given" unless valid_range.include?(at.min)
          raise ArgumentError, "#{must_be_between}, #{at.max} given" unless valid_range.include?(at.max)
          return "#{at.min}-#{at.max}"
        end
        raise ArgumentError, "#{must_be_between}, #{at} given" unless valid_range.include?(at)
        at
      end

      def comma_separated_timing(frequency, max, start = 0)
        return start     if frequency.nil? || frequency == "" || frequency.zero?
        return '*'       if frequency == 1
        return frequency if frequency > (max * 0.5).ceil

        original_start = start

        start += frequency unless (max + 1).modulo(frequency).zero? || start > 0
        output = (start..max).step(frequency).to_a

        max_occurances = (max.to_f  / (frequency.to_f)).round
        max_occurances += 1 if original_start.zero?

        output[0, max_occurances].join(',')
      end
    end
  end
end


================================================
FILE: lib/whenever/job.rb
================================================
require 'shellwords'

module Whenever
  class Job
    attr_reader :at, :roles, :mailto, :description

    def initialize(options = {})
      @options = options
      @at                               = options.delete(:at)
      @template                         = options.delete(:template)
      @mailto                           = options.fetch(:mailto, :default_mailto)
      @job_template                     = options.delete(:job_template) || ":job"
      @roles                            = Array(options.delete(:roles))
      @description                      = options.delete(:description)
      @options[:output]                 = options.has_key?(:output) ? Whenever::Output::Redirection.new(options[:output]).to_s : ''
      @options[:environment_variable] ||= "RAILS_ENV"
      @options[:environment]          ||= :production
      @options[:path]                   = Shellwords.shellescape(@options[:path] || Whenever.path)
    end

    def output
      job = process_template(@template, @options)
      out = process_template(@job_template, @options.merge(:job => job))
      out.gsub(/%/, '\%')
    end

    def has_role?(role)
      roles.empty? || roles.include?(role)
    end

  protected

    def process_template(template, options)
      template.gsub(/:\w+/) do |key|
        before_and_after = [$`[-1..-1], $'[0..0]]
        option = options[key.sub(':', '').to_sym] || key

        if before_and_after.all? { |c| c == "'" }
          escape_single_quotes(option)
        elsif before_and_after.all? { |c| c == '"' }
          escape_double_quotes(option)
        else
          option
        end
      end.gsub(/\s+/m, " ").strip
    end

    def escape_single_quotes(str)
      str.gsub(/'/) { "'\\''" }
    end

    def escape_double_quotes(str)
      str.gsub(/"/) { '\"' }
    end
  end
end


================================================
FILE: lib/whenever/job_list.rb
================================================
module Whenever
  class JobList
    attr_reader :roles

    def initialize(options)
      @jobs, @env, @set_variables, @pre_set_variables = {}, {}, {}, {}

      if options.is_a? String
        options = { :string => options }
      end

      pre_set(options[:set])

      @roles = options[:roles] || []

      setup_file = File.expand_path('../setup.rb', __FILE__)
      setup = File.read(setup_file)
      schedule = if options[:string]
        options[:string]
      elsif options[:file]
        File.read(options[:file])
      end

      instance_eval(setup, setup_file)
      instance_eval(schedule, options[:file] || '<eval>')
    end

    def set(variable, value)
      variable = variable.to_sym
      return if @pre_set_variables[variable]

      instance_variable_set("@#{variable}".to_sym, value)
      @set_variables[variable] = value
    end

    def method_missing(name, *args, &block)
      @set_variables.has_key?(name) ? @set_variables[name] : super
    end

    def respond_to?(name, include_private = false)
      @set_variables.has_key?(name) || super
    end

    def env(variable, value)
      @env[variable.to_s] = value
    end

    def every(frequency, options = {})
      @current_time_scope = frequency
      @options = options
      yield
    end

    def job_type(name, template)
      singleton_class.class_eval do
        define_method(name) do |task, *args|
          options = { :task => task, :template => template }
          options.merge!(args[0]) if args[0].is_a? Hash

          options[:mailto] ||= @options.fetch(:mailto, :default_mailto)

          # :cron_log was an old option for output redirection, it remains for backwards compatibility
          options[:output] = (options[:cron_log] || @cron_log) if defined?(@cron_log) || options.has_key?(:cron_log)
          # :output is the newer, more flexible option.
          options[:output] = @output if defined?(@output) && !options.has_key?(:output)

          @jobs[options.fetch(:mailto)] ||= {}
          @jobs[options.fetch(:mailto)][@current_time_scope] ||= []
          @jobs[options.fetch(:mailto)][@current_time_scope] << Whenever::Job.new(@set_variables.merge(@options).merge(options))
        end
      end
    end

    def generate_cron_output
      [environment_variables, cron_jobs].compact.join
    end

  private

    #
    # Takes a string like: "variable1=something&variable2=somethingelse"
    # and breaks it into variable/value pairs. Used for setting variables at runtime from the command line.
    # Only works for setting values as strings.
    #
    def pre_set(variable_string = nil)
      return if variable_string.nil? || variable_string == ""

      pairs = variable_string.split('&')
      pairs.each do |pair|
        next unless pair.index('=')
        variable, value = *pair.split('=')
        unless variable.nil? || variable == "" || value.nil? || value == ""
          variable = variable.strip.to_sym
          set(variable, value.strip)
          @pre_set_variables[variable] = value
        end
      end
    end

    def environment_variables
      return if @env.empty?

      output = []
      @env.each do |key, val|
        output << "#{key}=#{val.nil? || val == "" ? '""' : val}\n"
      end
      output << "\n"

      output.join
    end

    #
    # Takes the standard cron output that Whenever generates and finds
    # similar entries that can be combined. For example: If a job should run
    # at 3:02am and 4:02am, instead of creating two jobs this method combines
    # them into one that runs on the 2nd minute at the 3rd and 4th hour.
    #
    def combine(entries)
      entries.map! { |entry| entry.split(/ +/, 6) }
      0.upto(4) do |f|
        (entries.length-1).downto(1) do |i|
          next if entries[i][f] == '*'
          comparison = entries[i][0...f] + entries[i][f+1..-1]
          (i-1).downto(0) do |j|
            next if entries[j][f] == '*'
            if comparison == entries[j][0...f] + entries[j][f+1..-1]
              entries[j][f] += ',' + entries[i][f]
              entries.delete_at(i)
              break
            end
          end
        end
      end

      entries.map { |entry| entry.join(' ') }
    end

    def cron_jobs_of_time(time, jobs)
      shortcut_jobs, regular_jobs = [], []

      jobs.each do |job|
        next unless roles.empty? || roles.any? do |r|
          job.has_role?(r)
        end
        Whenever::Output::Cron.output(time, job, :chronic_options => @chronic_options) do |cron|
          cron << "\n\n"
          cron = (job.description.strip + "\n").gsub(/^(.*)$/, '# \1') + cron unless job.description.to_s.empty?

          if cron[0,1] == "@"
            shortcut_jobs << cron
          else
            regular_jobs << cron
          end
        end
      end

      shortcut_jobs.join + combine(regular_jobs).join
    end

    def cron_jobs
      return if @jobs.empty?

      output = []

      # jobs with default mailto's must be output before the ones with non-default mailto's.
      @jobs.delete(:default_mailto) { Hash.new }.each do |time, jobs|
        output << cron_jobs_of_time(time, jobs)
      end

      @jobs.each do |mailto, time_and_jobs|
        output_jobs = []

        time_and_jobs.each do |time, jobs|
          output_jobs << cron_jobs_of_time(time, jobs)
        end

        output_jobs.reject! { |output_job| output_job.empty? }

        output << "MAILTO=#{mailto}\n\n" unless output_jobs.empty?
        output << output_jobs
      end

      output.join
    end
  end
end


================================================
FILE: lib/whenever/numeric.rb
================================================
Numeric.class_eval do
  def respond_to?(method, include_private = false)
    super || Whenever::NumericSeconds.public_method_defined?(method)
  end

  def method_missing(method, *args, &block)
    if Whenever::NumericSeconds.public_method_defined?(method)
      Whenever::NumericSeconds.new(self).send(method)
    else
      super
    end
  end
end


================================================
FILE: lib/whenever/numeric_seconds.rb
================================================
module Whenever
  class NumericSeconds
    attr_reader :number

    def self.seconds(number, units)
      new(number).send(units)
    end

    def initialize(number)
      @number = number.to_i
    end

    def seconds
      number
    end
    alias :second :seconds

    def minutes
      number * 60
    end
    alias :minute :minutes

    def hours
      number * 3_600
    end
    alias :hour :hours

    def days
      number * 86_400
    end
    alias :day :days

    def weeks
      number * 604_800
    end
    alias :week :weeks

    def months
      number * 2_592_000
    end
    alias :month :months

    def years
      number * 31_557_600
    end
    alias :year :years
  end
end


================================================
FILE: lib/whenever/os.rb
================================================
module Whenever
  module OS
    def self.solaris?
      (/solaris/ =~ RUBY_PLATFORM)
    end
  end
end


================================================
FILE: lib/whenever/output_redirection.rb
================================================
module Whenever
  module Output
    class Redirection
      def initialize(output)
        @output = output
      end
      
      def to_s
        return '' unless defined?(@output)
        case @output
          when String   then redirect_from_string
          when Hash     then redirect_from_hash
          when NilClass then ">> /dev/null 2>&1"
          when Proc     then @output.call
          else ''
        end 
      end
      
    protected
      
      def stdout
        return unless @output.has_key?(:standard)
        @output[:standard].nil? ? '/dev/null' : @output[:standard]
      end

      def stderr
        return unless @output.has_key?(:error)
        @output[:error].nil? ? '/dev/null' : @output[:error]
      end

      def redirect_from_hash
        case
          when stdout == '/dev/null' && stderr == '/dev/null'
            "> /dev/null 2>&1"
          when stdout && stderr == '/dev/null'
            ">> #{stdout} 2> /dev/null"
          when stdout && stderr
            ">> #{stdout} 2>> #{stderr}"
          when stderr == '/dev/null'
            "2> /dev/null"
          when stderr
            "2>> #{stderr}"
          when stdout == '/dev/null'
            "> /dev/null"
          when stdout
            ">> #{stdout}"
          else
            ''
        end
      end

      def redirect_from_string
        ">> #{@output} 2>&1"
      end
    end
  end
end


================================================
FILE: lib/whenever/setup.rb
================================================
# Environment variable defaults to RAILS_ENV
set :environment_variable, "RAILS_ENV"
# Environment defaults to the value of RAILS_ENV in the current environment if
# it's set or production otherwise
set :environment, ENV.fetch("RAILS_ENV", "production")
# Path defaults to the directory `whenever` was run from
set :path, Whenever.path

# Custom Chronic configuration for time parsing, empty by default
# Full list of options at: https://github.com/mojombo/chronic/blob/master/lib/chronic/parser.rb
set :chronic_options, {}

# All jobs are wrapped in this template.
# http://blog.scoutapp.com/articles/2010/09/07/rvm-and-cron-in-production
set :job_template, "/bin/bash -l -c ':job'"

set :runner_command, case
  when Whenever.bin_rails?
    "bin/rails runner"
  when Whenever.script_rails?
    "script/rails runner"
  else
    "script/runner"
  end

set :bundle_command, Whenever.bundler? ? "bundle exec" : ""

job_type :command, ":task :output"
job_type :rake,    "cd :path && :environment_variable=:environment :bundle_command rake :task --silent :output"
job_type :script,  "cd :path && :environment_variable=:environment :bundle_command script/:task :output"
job_type :runner,  "cd :path && :bundle_command :runner_command -e :environment ':task' :output"


================================================
FILE: lib/whenever/version.rb
================================================
module Whenever
  VERSION = '1.1.2'
end


================================================
FILE: lib/whenever.rb
================================================
require "whenever/version"
require 'whenever/numeric'
require 'whenever/numeric_seconds'
require 'whenever/job_list'
require 'whenever/job'
require 'whenever/command_line'
require 'whenever/cron'
require 'whenever/output_redirection'
require 'whenever/os'

module Whenever
  def self.cron(options)
    Whenever::JobList.new(options).generate_cron_output
  end

  def self.seconds(number, units)
    Whenever::NumericSeconds.seconds(number, units)
  end

  def self.path
    Dir.pwd
  end

  def self.bin_rails?
    File.exist?(File.join(path, 'bin', 'rails'))
  end

  def self.script_rails?
    File.exist?(File.join(path, 'script', 'rails'))
  end

  def self.bundler?
    File.exist?(File.join(path, 'Gemfile'))
  end

  def self.update_cron options
    o = { 'update' => true, 'console' => false}
    o.merge! options if options
    Whenever::CommandLine.execute o
  end

end


================================================
FILE: test/functional/command_line_test.rb
================================================
require 'test_helper'

class CommandLineWriteTest < Whenever::TestCase
  setup do
    Time.stubs(:now).returns(Time.new(2017, 2, 24, 16, 21, 30, '+01:00'))
    File.expects(:exist?).with('config/schedule.rb').returns(true)
    @command = Whenever::CommandLine.new(:write => true, :identifier => 'My identifier')
    @task = "#{two_hours} /my/command"
    Whenever.expects(:cron).returns(@task)
  end

  should "output the cron job with identifier blocks" do
    output = <<-EXPECTED
# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
#{@task}
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
EXPECTED

    assert_equal output, @command.send(:whenever_cron)
  end

  should "write the crontab when run" do
    @command.expects(:write_crontab).returns(true)
    assert @command.run
  end
end

class CommandLineUpdateTest < Whenever::TestCase
  setup do
    Time.stubs(:now).returns(Time.new(2017, 2, 24, 16, 21, 30, '+01:00'))
    File.expects(:exist?).with('config/schedule.rb').returns(true)
    @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier')
    @task = "#{two_hours} /my/command"
    Whenever.expects(:cron).returns(@task)
  end

  should "add the cron to the end of the file if there is no existing identifier block" do
    existing = '# Existing crontab'
    @command.expects(:read_crontab).at_least_once.returns(existing)

    new_cron = <<-EXPECTED
#{existing}

# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
#{@task}
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
EXPECTED

    assert_equal new_cron, @command.send(:updated_crontab)

    @command.expects(:write_crontab).with(new_cron).returns(true)
    assert @command.run
  end

  should "replace an existing block if the identifier matches and the timestamp doesn't" do
    existing = <<-EXISTING_CRON
# Something

# Begin Whenever generated tasks for: My identifier at: 2017-01-03 08:02:22 +0500
My whenever job that was already here
# End Whenever generated tasks for: My identifier at: 2017-01-03 08:22:22 +0500

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON

    new_cron = <<-NEW_CRON
# Something

# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
#{@task}
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
NEW_CRON

    @command.expects(:read_crontab).at_least_once.returns(existing)
    assert_equal new_cron, @command.send(:updated_crontab)

    @command.expects(:write_crontab).with(new_cron).returns(true)
    assert @command.run
  end

  should "replace an existing block if the identifier matches and the UTC timestamp doesn't" do
    existing = <<-EXISTING_CRON
# Something

# Begin Whenever generated tasks for: My identifier at: 2017-01-03 08:02:22 UTC
My whenever job that was already here
# End Whenever generated tasks for: My identifier at: 2017-01-03 08:22:22 UTC

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON

    new_cron = <<-NEW_CRON
# Something

# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
#{@task}
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
NEW_CRON

    @command.expects(:read_crontab).at_least_once.returns(existing)
    assert_equal new_cron, @command.send(:updated_crontab)

    @command.expects(:write_crontab).with(new_cron).returns(true)
    assert @command.run
  end

  should "replace an existing block if the identifier matches and it doesn't contain a timestamp" do
    existing = <<-EXISTING_CRON
# Something

# Begin Whenever generated tasks for: My identifier
My whenever job that was already here
# End Whenever generated tasks for: My identifier

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON

    new_cron = <<-NEW_CRON
# Something

# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
#{@task}
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
NEW_CRON

    @command.expects(:read_crontab).at_least_once.returns(existing)
    assert_equal new_cron, @command.send(:updated_crontab)

    @command.expects(:write_crontab).with(new_cron).returns(true)
    assert @command.run
  end
end

class CommandLineUpdateWithBackslashesTest < Whenever::TestCase
  setup do
    Time.stubs(:now).returns(Time.new(2017, 2, 24, 16, 21, 30, '+01:00'))
    @existing = <<-EXISTING_CRON
# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
script/runner -e production 'puts '\\''hello'\\'''
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON
    File.expects(:exist?).with('config/schedule.rb').returns(true)
    @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier')
    @command.expects(:read_crontab).at_least_once.returns(@existing)
    @command.expects(:whenever_cron).returns(@existing)
  end

  should "replace the existing block with the backslashes in tact" do
    assert_equal @existing, @command.send(:updated_crontab)
  end
end

class CommandLineUpdateToSimilarCrontabTest < Whenever::TestCase
  setup do
    @existing = <<-EXISTING_CRON
# Begin Whenever generated tasks for: WheneverExisting at: 2017-02-24 16:21:30 +0100
# End Whenever generated tasks for: WheneverExisting at: 2017-02-24 16:21:30 +0100
EXISTING_CRON
    @new = <<-NEW_CRON
# Begin Whenever generated tasks for: Whenever at: 2017-02-24 16:21:30 +0100
# End Whenever generated tasks for: Whenever at: 2017-02-24 16:21:30 +0100
NEW_CRON
    File.expects(:exist?).with('config/schedule.rb').returns(true)
    @command = Whenever::CommandLine.new(:update => true, :identifier => 'Whenever')
    @command.expects(:read_crontab).at_least_once.returns(@existing)
    @command.expects(:whenever_cron).returns(@new)
  end

  should "append the similarly named command" do
    assert_equal @existing + "\n" + @new, @command.send(:updated_crontab)
  end
end

class CommandLineClearTest < Whenever::TestCase
  setup do
    Time.stubs(:now).returns(Time.new(2017, 2, 24, 16, 21, 30, '+01:00'))
    File.expects(:exist?).with('config/schedule.rb').returns(true)
    @command = Whenever::CommandLine.new(:clear => true, :identifier => 'My identifier')
    @task = "#{two_hours} /my/command"
  end

  should "clear an existing block if the identifier matches and the timestamp doesn't" do
    existing = <<-EXISTING_CRON
# Something

# Begin Whenever generated tasks for: My identifier at: 2017-01-03 08:20:02 +0500
My whenever job that was already here
# End Whenever generated tasks for: My identifier at: 2017-01-03 08:20:02 +0500

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON

    @command.expects(:read_crontab).at_least_once.returns(existing)

    new_cron = <<-NEW_CRON
# Something

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
NEW_CRON

    assert_equal new_cron, @command.send(:updated_crontab)

    @command.expects(:write_crontab).with(new_cron).returns(true)
    assert @command.run
  end

  should "clear an existing block if the identifier matches and the UTC timestamp doesn't" do
    existing = <<-EXISTING_CRON
# Something

# Begin Whenever generated tasks for: My identifier at: 2017-01-03 08:20:02 UTC
My whenever job that was already here
# End Whenever generated tasks for: My identifier at: 2017-01-03 08:20:02 UTC

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON

    @command.expects(:read_crontab).at_least_once.returns(existing)

    new_cron = <<-NEW_CRON
# Something

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
NEW_CRON

    assert_equal new_cron, @command.send(:updated_crontab)

    @command.expects(:write_crontab).with(new_cron).returns(true)
    assert @command.run
  end

  should "clear an existing block if the identifier matches and it doesn't have a timestamp" do
    existing = <<-EXISTING_CRON
# Something

# Begin Whenever generated tasks for: My identifier
My whenever job that was already here
# End Whenever generated tasks for: My identifier

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON

    @command.expects(:read_crontab).at_least_once.returns(existing)

    new_cron = <<-NEW_CRON
# Something

# Begin Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
This shouldn't get replaced
# End Whenever generated tasks for: Other identifier at: 2017-02-24 16:21:30 +0100
NEW_CRON

    assert_equal new_cron, @command.send(:updated_crontab)

    @command.expects(:write_crontab).with(new_cron).returns(true)
    assert @command.run
  end
end

class CommandLineClearWithNoScheduleTest < Whenever::TestCase
  setup do
    File.expects(:exist?).with('config/schedule.rb').returns(false)
    @command = Whenever::CommandLine.new(:clear => true, :identifier => 'My identifier')
  end

  should "run successfully" do
    @command.expects(:write_crontab).returns(true)
    assert @command.run
  end
end

class CommandLineUpdateWithNoIdentifierTest < Whenever::TestCase
  setup do
    Time.stubs(:now).returns(Time.new(2017, 2, 24, 16, 21, 30, '+01:00'))
    File.expects(:exist?).with('config/schedule.rb').returns(true)
    Whenever::CommandLine.any_instance.expects(:default_identifier).returns('DEFAULT')
    @command = Whenever::CommandLine.new(:update => true)
  end

  should "use the default identifier" do
    assert_equal "Whenever generated tasks for: DEFAULT at: 2017-02-24 16:21:30 +0100", @command.send(:comment_base)
  end
end

class CombinedParamsTest < Whenever::TestCase
  setup do
    Whenever::CommandLine.any_instance.expects(:exit)
    Whenever::CommandLine.any_instance.expects(:warn)
    File.expects(:exist?).with('config/schedule.rb').returns(true)
  end

  should "exit with write and clear" do
    @command = Whenever::CommandLine.new(:write => true, :clear => true)
  end

  should "exit with write and update" do
    @command = Whenever::CommandLine.new(:write => true, :update => true)
  end

  should "exit with update and clear" do
    @command = Whenever::CommandLine.new(:update => true, :clear => true)
  end
end

class RunnerOverwrittenWithSetOptionTest < Whenever::TestCase
  setup do
    @output = Whenever.cron :set => 'environment=serious', :string => \
    <<-file
      set :job_template, nil
      set :environment, :silly
      set :path, '/my/path'
      every 2.hours do
        runner "blahblah"
      end
    file
  end

  should "output the runner using the override environment" do
    assert_match two_hours + %( cd /my/path && bundle exec script/runner -e serious 'blahblah'), @output
  end
end


class EnvironmentAndPathOverwrittenWithSetOptionTest < Whenever::TestCase
  setup do
    @output = Whenever.cron :set => 'environment=serious&path=/serious/path', :string => \
    <<-file
      set :job_template, nil
      set :environment, :silly
      set :path, '/silly/path'
      every 2.hours do
        runner "blahblah"
      end
    file
  end

  should "output the runner using the overridden path and environment" do
    assert_match two_hours + %( cd /serious/path && bundle exec script/runner -e serious 'blahblah'), @output
  end
end

class EnvironmentAndPathOverwrittenWithSetOptionWithSpacesTest < Whenever::TestCase
  setup do
    @output = Whenever.cron :set => ' environment = serious&  path =/serious/path', :string => \
    <<-file
      set :job_template, nil
      set :environment, :silly
      set :path, '/silly/path'
      every 2.hours do
        runner "blahblah"
      end
    file
  end

  should "output the runner using the overridden path and environment" do
    assert_match two_hours + %( cd /serious/path && bundle exec script/runner -e serious 'blahblah'), @output
  end
end

class EnvironmentOverwrittenWithoutValueTest < Whenever::TestCase
  setup do
    @output = Whenever.cron :set => ' environment=', :string => \
    <<-file
      set :job_template, nil
      set :environment, :silly
      set :path, '/silly/path'
      every 2.hours do
        runner "blahblah"
      end
    file
  end

  should "output the runner using the original environment" do
    assert_match two_hours + %( cd /silly/path && bundle exec script/runner -e silly 'blahblah'), @output
  end
end

class PreparingOutputTest < Whenever::TestCase
  setup do
    File.expects(:exist?).with('config/schedule.rb').returns(true)
  end

  should "not trim off the top lines of the file" do
    @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier', :cut => 0)
    existing = <<-EXISTING_CRON
# Useless Comments
# at the top of the file

# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
My whenever job that was already here
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON

    assert_equal existing, @command.send(:prepare, existing)
  end

  should "trim off the top lines of the file" do
    @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier', :cut => '3')
    existing = <<-EXISTING_CRON
# Useless Comments
# at the top of the file

# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
My whenever job that was already here
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
EXISTING_CRON

    new_cron = <<-NEW_CRON
# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
My whenever job that was already here
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
NEW_CRON

    assert_equal new_cron, @command.send(:prepare, existing)
  end

  should "preserve terminating newlines in files" do
    @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier')
    existing = <<-EXISTING_CRON
# Begin Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100
My whenever job that was already here
# End Whenever generated tasks for: My identifier at: 2017-02-24 16:21:30 +0100

# A non-Whenever task
My non-whenever job that was already here
EXISTING_CRON

    assert_equal existing, @command.send(:prepare, existing)
  end
end


================================================
FILE: test/functional/output_at_test.rb
================================================
require 'test_helper'

class OutputAtTest < Whenever::TestCase
  test "weekday at a (single) given time" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every "weekday", :at => '5:02am' do
        command "blahblah"
      end
    file

    assert_match '2 5 * * 1-5 blahblah', output
  end

  test "weekday at a multiple diverse times, via an array" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every "weekday", :at => %w(5:02am 3:52pm) do
        command "blahblah"
      end
    file

    assert_match '2 5 * * 1-5 blahblah', output
    assert_match '52 15 * * 1-5 blahblah', output
  end

  test "weekday at a multiple diverse times, comma separated" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every "weekday", :at => '5:02am, 3:52pm' do
        command "blahblah"
      end
    file

    assert_match '2 5 * * 1-5 blahblah', output
    assert_match '52 15 * * 1-5 blahblah', output
  end

  test "weekday at a multiple aligned times" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every "weekday", :at => '5:02am, 3:02pm' do
        command "blahblah"
      end
    file

    assert_match '2 5,15 * * 1-5 blahblah', output
  end

  test "various days at a various aligned times" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every "mon,wed,fri", :at => '5:02am, 3:02pm' do
        command "blahblah"
      end
    file

    assert_match '2 5,15 * * 1,3,5 blahblah', output
  end

  test "various days at a various aligned times using a runner" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/your/path'
      every "mon,wed,fri", :at => '5:02am, 3:02pm' do
        runner "Worker.perform_async(1.day.ago)"
      end
    file

    assert_match %(2 5,15 * * 1,3,5 cd /your/path && bundle exec script/runner -e production 'Worker.perform_async(1.day.ago)'), output
  end

  test "various days at a various aligned times using a rake task" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/your/path'
      every "mon,wed,fri", :at => '5:02am, 3:02pm' do
        rake "blah:blah"
      end
    file

    assert_match '2 5,15 * * 1,3,5 cd /your/path && RAILS_ENV=production bundle exec rake blah:blah --silent', output
  end

  test "A command every 1.month at very diverse times" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every [1.month, 1.day], :at => 'january 5:02am, june 17th at 2:22pm, june 3rd at 3:33am' do
        command "blahblah"
      end
    file

    # The 1.month commands
    assert_match '2 5 1 * * blahblah', output
    assert_match '22 14 17 * * blahblah', output
    assert_match '33 3 3 * * blahblah', output

    # The 1.day commands
    assert_match '2 5 * * * blahblah', output
    assert_match '22 14 * * * blahblah', output
    assert_match '33 3 * * * blahblah', output
  end

  test "Multiple commands output every :reboot" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every :reboot do
        command "command_1"
        command "command_2"
      end
    file

    assert_match "@reboot command_1", output
    assert_match "@reboot command_2", output
  end

  test "Many different job types output every :day" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/your/path'
      every :daily do
        rake "blah:blah"
        runner "runner_1"
        command "command_1"
        runner "runner_2"
        command "command_2"
      end
    file

    assert_match '@daily cd /your/path && RAILS_ENV=production bundle exec rake blah:blah --silent', output
    assert_match %(@daily cd /your/path && bundle exec script/runner -e production 'runner_1'), output
    assert_match '@daily command_1', output
    assert_match %(@daily cd /your/path && bundle exec script/runner -e production 'runner_2'), output
    assert_match '@daily command_2', output
  end

  test "every 5 minutes but but starting at 1" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 5.minutes, :at => 1 do
        command "blahblah"
      end
    file

    assert_match '1,6,11,16,21,26,31,36,41,46,51,56 * * * * blahblah', output
  end

  test "every 4 minutes but starting at 2" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 4.minutes, :at => 2 do
        command "blahblah"
      end
    file

    assert_match '2,6,10,14,18,22,26,30,34,38,42,46,50,54,58 * * * * blahblah', output
  end

  test "every 3 minutes but starting at 7" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 3.minutes, :at => 7 do
        command "blahblah"
      end
    file


    assert_match '7,10,13,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58 * * * * blahblah', output
  end

  test "every 2 minutes but starting at 27" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.minutes, :at => 27 do
        command "blahblah"
      end
    file

    assert_match '27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59 * * * * blahblah', output
  end

  test "using raw cron syntax" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every '0 0 27,31 * *' do
        command "blahblah"
      end
    file

    assert_match '0 0 27,31 * * blahblah', output
  end

  test "using custom Chronic configuration to specify time using 24 hour clock" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :chronic_options, :hours24 => true
      every 1.day, :at => '03:00' do
        command "blahblah"
      end
    file

    assert_match '0 3 * * * blahblah', output
  end

  test "using custom Chronic configuration to specify date using little endian preference" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :chronic_options, :endian_precedence => :little
      every 1.month, :at => '02/03 10:15' do
        command "blahblah"
      end
    file

    assert_match '15 10 2 * * blahblah', output
  end

  test "using custom Chronic configuration to specify time using 24 hour clock and date using little endian preference" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :chronic_options, :hours24 => true, :endian_precedence => :little
      every 1.month, :at => '01/02 04:30' do
        command "blahblah"
      end
    file

    assert_match '30 4 1 * * blahblah', output
  end
end


================================================
FILE: test/functional/output_default_defined_jobs_test.rb
================================================
require 'test_helper'

class OutputDefaultDefinedJobsTest < Whenever::TestCase

  # command

  test "A plain command with the job template set to nil" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah$/, output)
  end

  test "A plain command with no job template set" do
    output = Whenever.cron \
    <<-file
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ \/bin\/bash -l -c 'blahblah'$/, output)
  end

  test "A plain command with a job_template using a normal parameter" do
    output = Whenever.cron \
    <<-file
      set :job_template, "/bin/bash -l -c 'cd :path && :job'"
      every 2.hours do
        set :path, "/tmp"
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ \/bin\/bash -l -c 'cd \/tmp \&\& blahblah'$/, output)
  end

  test "A plain command that overrides the job_template set" do
    output = Whenever.cron \
    <<-file
      set :job_template, "/bin/bash -l -c ':job'"
      every 2.hours do
        command "blahblah", :job_template => "/bin/sh -l -c ':job'"
      end
    file


    assert_match(/^.+ .+ .+ .+ \/bin\/sh -l -c 'blahblah'$/, output)
    assert_no_match(/bash/, output)
  end

  test "A plain command that overrides the job_template set using a parameter" do
    output = Whenever.cron \
    <<-file
      set :job_template, "/bin/bash -l -c 'cd :path && :job'"
      every 2.hours do
        set :path, "/tmp"
        command "blahblah", :job_template => "/bin/sh -l -c 'cd :path && :job'"
      end
    file


    assert_match(/^.+ .+ .+ .+ \/bin\/sh -l -c 'cd \/tmp && blahblah'$/, output)
    assert_no_match(/bash/, output)
  end

  test "A plain command that is conditional on default environent and path" do
    Whenever.expects(:path).at_least_once.returns('/what/you/want')
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      if environment == 'production' && path == '/what/you/want'
        every 2.hours do
          command "blahblah"
        end
      end
    file

    assert_match(/blahblah/, output)
  end

  # runner

  test "A runner with path set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      every 2.hours do
        runner 'blahblah'
      end
    file

    assert_match two_hours + %( cd /my/path && bundle exec script/runner -e production 'blahblah'), output
  end

  test "A runner that overrides the path set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      every 2.hours do
        runner "blahblah", :path => '/some/other/path'
      end
    file

    assert_match two_hours + %( cd /some/other/path && bundle exec script/runner -e production 'blahblah'), output
  end

  test "A runner for a non-bundler app" do
    Whenever.expects(:bundler?).returns(false)
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      every 2.hours do
        runner 'blahblah'
      end
    file

    assert_match two_hours + %( cd /my/path && script/runner -e production 'blahblah'), output
  end

  test "A runner for an app with bin/rails" do
    Whenever.expects(:path).at_least_once.returns('/my/path')
    Whenever.expects(:bin_rails?).returns(true)
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        runner 'blahblah'
      end
    file

    assert_match two_hours + %( cd /my/path && bin/rails runner -e production 'blahblah'), output
  end

  test "A runner for an app with script/rails" do
    Whenever.expects(:path).at_least_once.returns('/my/path')
    Whenever.expects(:script_rails?).returns(true)
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        runner 'blahblah'
      end
    file

    assert_match two_hours + %( cd /my/path && script/rails runner -e production 'blahblah'), output
  end

  # rake

  test "A rake command with path set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      every 2.hours do
        rake "blahblah"
      end
    file

    assert_match two_hours + ' cd /my/path && RAILS_ENV=production bundle exec rake blahblah --silent', output
  end

  test "A rake command with arguments" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      every 2.hours do
        rake "blahblah[foobar]"
      end
    file

    assert_match two_hours + ' cd /my/path && RAILS_ENV=production bundle exec rake blahblah[foobar] --silent', output
  end

  test "A rake for a non-bundler app" do
    Whenever.expects(:path).at_least_once.returns('/my/path')
    Whenever.expects(:bundler?).returns(false)
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        rake 'blahblah'
      end
    file

    assert_match two_hours + ' cd /my/path && RAILS_ENV=production rake blahblah --silent', output
  end

  test "A rake command that overrides the path set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      every 2.hours do
        rake "blahblah", :path => '/some/other/path'
      end
    file

    assert_match two_hours + ' cd /some/other/path && RAILS_ENV=production bundle exec rake blahblah --silent', output
  end

  test "A rake command that uses the default environment variable when RAILS_ENV is set" do
    ENV.expects(:fetch).with("RAILS_ENV", "production").returns("development")
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      every 2.hours do
        rake "blahblah"
      end
    file

    assert_match two_hours + ' cd /my/path && RAILS_ENV=development bundle exec rake blahblah --silent', output
  end

  test "A rake command that sets the environment variable" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      set :environment_variable, 'RAKE_ENV'
      every 2.hours do
        rake "blahblah"
      end
    file

    assert_match two_hours + ' cd /my/path && RAKE_ENV=production bundle exec rake blahblah --silent', output
  end

  test "A rake command that overrides the environment variable" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      set :environment_variable, 'RAKE_ENV'
      every 2.hours do
        rake "blahblah", :environment_variable => 'SOME_ENV'
      end
    file

    assert_match two_hours + ' cd /my/path && SOME_ENV=production bundle exec rake blahblah --silent', output
  end

    # script

  test "A script command with path set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :path, '/my/path'
      every 2.hours do
        script "blahblah"
      end
    file

    assert_match two_hours + ' cd /my/path && RAILS_ENV=production bundle exec script/blahblah', output
  end

  test "A script command for a non-bundler app" do
    Whenever.expects(:path).at_least_once.returns('/my/path')
    Whenever.expects(:bundler?).returns(false)
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        script 'blahblah'
      end
    file

    assert_match two_hours + ' cd /my/path && RAILS_ENV=production script/blahblah', output
  end

  test "A script command that uses output" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, '/log/file'
      set :path, '/my/path'
      every 2.hours do
        script "blahblah", :path => '/some/other/path'
      end
    file

    assert_match two_hours + ' cd /some/other/path && RAILS_ENV=production bundle exec script/blahblah >> /log/file 2>&1', output
  end

  test "A script command that uses an environment variable" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :environment_variable, 'RAKE_ENV'
      set :path, '/my/path'
      every 2.hours do
        script "blahblah"
      end
    file

    assert_match two_hours + ' cd /my/path && RAKE_ENV=production bundle exec script/blahblah', output
  end
end


================================================
FILE: test/functional/output_defined_job_test.rb
================================================
require 'test_helper'

class OutputDefinedJobTest < Whenever::TestCase
  test "defined job with a :task" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      job_type :some_job, "before :task after"
      every 2.hours do
        some_job "during"
      end
    file

    assert_match(/^.+ .+ .+ .+ before during after$/, output)
  end

  test "defined job with a :task and some options" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      job_type :some_job, "before :task after :option1 :option2"
      every 2.hours do
        some_job "during", :option1 => 'happy', :option2 => 'birthday'
      end
    file

    assert_match(/^.+ .+ .+ .+ before during after happy birthday$/, output)
  end

  test "defined job with a :task and an option where the option is set globally" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      job_type :some_job, "before :task after :option1"
      set :option1, 'happy'
      every 2.hours do
        some_job "during"
      end
    file

    assert_match(/^.+ .+ .+ .+ before during after happy$/, output)
  end

  test "defined job with a :task and an option where the option is set globally and locally" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      job_type :some_job, "before :task after :option1"
      set :option1, 'global'
      every 2.hours do
        some_job "during", :option1 => 'local'
      end
    file

    assert_match(/^.+ .+ .+ .+ before during after local$/, output)
  end

  test "defined job with a :task and an option where the option is set globally and on the group" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      job_type :some_job, "before :task after :option1"
      set :option1, 'global'
      every 2.hours, :option1 => 'group' do
        some_job "during"
      end
    file

    assert_match(/^.+ .+ .+ .+ before during after group$/, output)
  end

  test "defined job with a :task and an option where the option is set globally, on the group, and locally" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      job_type :some_job, "before :task after :option1"
      set :option1, 'global'
      every 2.hours, :option1 => 'group' do
        some_job "during", :option1 => 'local'
      end
    file

    assert_match(/^.+ .+ .+ .+ before during after local$/, output)
  end

  test "defined job with a :task and an option that is not set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      job_type :some_job, "before :task after :option1"
      every 2.hours do
        some_job "during", :option2 => 'happy'
      end
    file

    assert_match(/^.+ .+ .+ .+ before during after :option1$/, output)
  end

  test "defined job that uses a :path where none is explicitly set" do
    Whenever.stubs(:path).returns('/my/path')

    output = Whenever.cron \
    <<-file
      set :job_template, nil
      job_type :some_job, "cd :path && :task"
      every 2.hours do
        some_job 'blahblah'
      end
    file

    assert_match two_hours + %( cd /my/path && blahblah), output
  end
end


================================================
FILE: test/functional/output_description_test.rb
================================================
require 'test_helper'

class OutputDescriptionTest < Whenever::TestCase
  test "single line description" do
    output = Whenever.cron \
    <<-file
      every "weekday", :description => "A description" do
        command "blahblah"
      end
    file

    assert_match "# A description\n0 0 * * 1-5 /bin/bash -l -c 'blahblah'\n\n", output
  end

  test "multi line description" do
    output = Whenever.cron \
    <<-file
      every "weekday", :description => "A description\nhas mulitple lines" do
        command "blahblah"
      end
    file

    assert_match "# A description\n# has mulitple lines\n0 0 * * 1-5 /bin/bash -l -c 'blahblah'\n\n", output
  end
end


================================================
FILE: test/functional/output_env_test.rb
================================================
require 'test_helper'

class OutputEnvTest < Whenever::TestCase
  setup do
    @output = Whenever.cron \
    <<-file
      env :MYVAR, 'blah'
      env 'MAILTO', "someone@example.com"
      env :BLANKVAR, ''
      env :NILVAR, nil
    file
  end

  should "output MYVAR environment variable" do
    assert_match "MYVAR=blah", @output
  end

  should "output MAILTO environment variable" do
    assert_match "MAILTO=someone@example.com", @output
  end

  should "output BLANKVAR environment variable" do
    assert_match "BLANKVAR=\"\"", @output
  end

  should "output NILVAR environment variable" do
    assert_match "NILVAR=\"\"", @output
  end
end


================================================
FILE: test/functional/output_jobs_for_roles_test.rb
================================================
require 'test_helper'

class OutputJobsForRolesTest < Whenever::TestCase
  test "one role requested and specified on the job" do
    output = Whenever.cron :roles => [:role1], :string => \
    <<-file
      every 2.hours, :roles => [:role1] do
        command "blahblah"
      end
    file

    assert_equal two_hours + " /bin/bash -l -c 'blahblah'\n\n", output
  end

  test "one role requested but none specified on the job" do
    output = Whenever.cron :roles => [:role1], :string => \
    <<-file
      every 2.hours do
        command "blahblah"
      end
    file

    # this should output the job because not specifying a role means "all roles"
    assert_equal two_hours + " /bin/bash -l -c 'blahblah'\n\n", output
  end

  test "no roles requested but one specified on the job" do
    output = Whenever.cron \
    <<-file
      every 2.hours, :roles => [:role1] do
        command "blahblah"
      end
    file

    # this should output the job because not requesting roles means "all roles"
    assert_equal two_hours + " /bin/bash -l -c 'blahblah'\n\n", output
  end

  test "a different role requested than the one specified on the job" do
    output = Whenever.cron :roles => [:role1], :string => \
    <<-file
      every 2.hours, :roles => [:role2] do
        command "blahblah"
      end
    file

    assert_equal "", output
  end

  test "with 2 roles requested and a job defined for each" do
    output = Whenever.cron :roles => [:role1, :role2], :string => \
    <<-file
      every 2.hours, :roles => [:role1] do
        command "role1_cmd"
      end

      every :hour, :roles => [:role2] do
        command "role2_cmd"
      end
    file

    assert_match two_hours + " /bin/bash -l -c 'role1_cmd'", output
    assert_match "0 * * * * /bin/bash -l -c 'role2_cmd'", output
  end
end


================================================
FILE: test/functional/output_jobs_with_mailto_test.rb
================================================
require 'test_helper'

class OutputJobsWithMailtoTest < Whenever::TestCase
  test "defined job with a mailto argument" do
    output = Whenever.cron \
    <<-file
      every 2.hours do
        command "blahblah", mailto: 'someone@example.com'
      end
    file

    output_without_empty_line = lines_without_empty_line(output.lines)

    assert_equal 'MAILTO=someone@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift
  end

  test "defined job with every method's block and a mailto argument" do
    output = Whenever.cron \
    <<-file
      every 2.hours, mailto: 'someone@example.com' do
        command "blahblah"
      end
    file

    output_without_empty_line = lines_without_empty_line(output.lines)

    assert_equal 'MAILTO=someone@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift
  end

  test "defined job which overrided mailto argument in the block" do
    output = Whenever.cron \
    <<-file
      every 2.hours, mailto: 'of_the_block@example.com' do
        command "blahblah", mailto: 'overrided_in_the_block@example.com'
      end
    file

    output_without_empty_line = lines_without_empty_line(output.lines)

    assert_equal 'MAILTO=overrided_in_the_block@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift
  end

  test "defined some jobs with various mailto argument" do
    output = Whenever.cron \
    <<-file
      every 2.hours do
        command "blahblah"
      end

      every 2.hours, mailto: 'john@example.com' do
        command "blahblah_of_john"
        command "blahblah2_of_john"
      end

      every 2.hours, mailto: 'sarah@example.com' do
        command "blahblah_of_sarah"
      end

      every 2.hours do
        command "blahblah_of_martin", mailto: 'martin@example.com'
        command "blahblah2_of_sarah", mailto: 'sarah@example.com'
      end

      every 2.hours do
        command "blahblah2"
      end
    file

    output_without_empty_line = lines_without_empty_line(output.lines)

    assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah2'", output_without_empty_line.shift

    assert_equal 'MAILTO=john@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah_of_john'", output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah2_of_john'", output_without_empty_line.shift

    assert_equal 'MAILTO=sarah@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah_of_sarah'", output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah2_of_sarah'", output_without_empty_line.shift

    assert_equal 'MAILTO=martin@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah_of_martin'", output_without_empty_line.shift
  end

  test "defined some jobs with no mailto argument jobs and mailto argument jobs(no mailto jobs should be first line of cron output" do
    output = Whenever.cron \
    <<-file
      every 2.hours, mailto: 'john@example.com' do
        command "blahblah_of_john"
        command "blahblah2_of_john"
      end

      every 2.hours do
        command "blahblah"
      end
    file

    output_without_empty_line = lines_without_empty_line(output.lines)

    assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift

    assert_equal 'MAILTO=john@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah_of_john'", output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah2_of_john'", output_without_empty_line.shift
  end

  test "defined some jobs with environment mailto define and various mailto argument" do
    output = Whenever.cron \
    <<-file
      env 'MAILTO', 'default@example.com'

      every 2.hours do
        command "blahblah"
      end

      every 2.hours, mailto: 'sarah@example.com' do
        command "blahblah_by_sarah"
      end

      every 2.hours do
        command "blahblah_by_john", mailto: 'john@example.com'
      end

      every 2.hours do
        command "blahblah2"
      end
    file

    output_without_empty_line = lines_without_empty_line(output.lines)

    assert_equal 'MAILTO=default@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah2'", output_without_empty_line.shift

    assert_equal 'MAILTO=sarah@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah_by_sarah'", output_without_empty_line.shift

    assert_equal 'MAILTO=john@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah_by_john'", output_without_empty_line.shift
  end
end

class OutputJobsWithMailtoForRolesTest < Whenever::TestCase
  test "one role requested and specified on the job with mailto argument" do
    output = Whenever.cron roles: [:role1], :string => \
    <<-file
      env 'MAILTO', 'default@example.com'

      every 2.hours, :roles => [:role1] do
        command "blahblah"
      end

      every 2.hours, mailto: 'sarah@example.com', :roles => [:role2] do
        command "blahblah_by_sarah"
      end
    file

    output_without_empty_line = lines_without_empty_line(output.lines)

    assert_equal 'MAILTO=default@example.com', output_without_empty_line.shift
    assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift
    assert_nil output_without_empty_line.shift
  end
end


================================================
FILE: test/functional/output_redirection_test.rb
================================================
require 'test_helper'

class OutputRedirectionTest < Whenever::TestCase
  test "command when the output is set to nil" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, nil
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> \/dev\/null 2>&1$/, output)
  end


  test "command when the output is set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, 'logfile.log'
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> logfile.log 2>&1$/, output)
  end

  test "command when the error and standard output is set by the command" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah", :output => {:standard => 'dev_null', :error => 'dev_err'}
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> dev_null 2>> dev_err$/, output)
  end

  test "command when the output is set and the comand overrides it" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, 'logfile.log'
      every 2.hours do
        command "blahblah", :output => 'otherlog.log'
      end
    file

    assert_no_match(/.+ .+ .+ .+ blahblah >> logfile.log 2>&1/, output)
    assert_match(/^.+ .+ .+ .+ blahblah >> otherlog.log 2>&1$/, output)
  end

  test "command when the output is set and the comand overrides with standard and error" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, 'logfile.log'
      every 2.hours do
        command "blahblah", :output => {:error => 'dev_err', :standard => 'dev_null' }
      end
    file

    assert_no_match(/.+ .+ .+ .+ blahblah >> logfile.log 2>&1/, output)
    assert_match(/^.+ .+ .+ .+ blahblah >> dev_null 2>> dev_err$/, output)
  end

  test "command when the output is set and the comand rejects it" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, 'logfile.log'
      every 2.hours do
        command "blahblah", :output => false
      end
    file

    assert_no_match(/.+ .+ .+ .+ blahblah >> logfile.log 2>&1/, output)
    assert_match(/^.+ .+ .+ .+ blahblah$/, output)
  end

  test "command when the output is set and is overridden by the :set option" do
    output = Whenever.cron :set => 'output=otherlog.log', :string => \
    <<-file
      set :job_template, nil
      set :output, 'logfile.log'
      every 2.hours do
        command "blahblah"
      end
    file

    assert_no_match(/.+ .+ .+ .+ blahblah >> logfile.log 2>&1/, output)
    assert_match(/^.+ .+ .+ .+ blahblah >> otherlog.log 2>&1/, output)
  end

  test "command when the error and standard output is set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, {:error => 'dev_err', :standard => 'dev_null' }
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> dev_null 2>> dev_err$/, output)
  end

  test "command when error output is set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, {:error => 'dev_null'}
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah 2>> dev_null$/, output)
  end

  test "command when the standard output is set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, {:standard => 'dev_out'}
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> dev_out$/, output)
  end

  test "command when error output is set by the command" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah", :output => {:error => 'dev_err'}
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah 2>> dev_err$/, output)
  end

  test "command when standard output is set by the command" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah", :output => {:standard => 'dev_out'}
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> dev_out$/, output)
  end

  test "command when standard output is set to nil" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah", :output => {:standard => nil}
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah > \/dev\/null$/, output)
  end

  test "command when standard error is set to nil" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah", :output => {:error => nil}
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah 2> \/dev\/null$/, output)
  end

  test "command when standard output and standard error is set to nil" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah", :output => {:error => nil, :standard => nil}
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah > \/dev\/null 2>&1$/, output)
  end

  test "command when standard output is set and standard error is set to nil" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah", :output => {:error => nil, :standard => 'my.log'}
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> my.log 2> \/dev\/null$/, output)
  end

  test "command when standard output is nil and standard error is set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      every 2.hours do
        command "blahblah", :output => {:error => 'my_error.log', :standard => nil}
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> \/dev\/null 2>> my_error.log$/, output)
  end

  test "command when the deprecated :cron_log is set" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :cron_log, "cron.log"
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah >> cron.log 2>&1$/, output)
  end


  test "a command when the standard output is set to a lambda" do
    output = Whenever.cron \
    <<-file
      set :job_template, nil
      set :output, lambda { "2>&1 | logger -t whenever_cron" }
      every 2.hours do
        command "blahblah"
      end
    file

    assert_match(/^.+ .+ .+ .+ blahblah 2>&1 | logger -t whenever_cron$/, output)
  end
end


================================================
FILE: test/test_case.rb
================================================
module Whenever
  require 'minitest/autorun'
  begin
    # 2.0.0
    class TestCase < Minitest::Test; end
  rescue NameError
    # 1.9.3
    class TestCase < Minitest::Unit::TestCase; end
  end


  class TestCase
    class << self
      def setup(&block)
        define_method(:setup) do
          super()
          instance_eval(&block)
        end
      end

      def test(name, &block)
        define_method("test_#{name}".to_sym, &block)
      end
      alias should test
    end

    def assert_no_match(regexp, string)
      message = "<#{regexp}> expected to not match\n<#{string}>"
      assert regexp !~ string, message
    end
  end
end


================================================
FILE: test/test_helper.rb
================================================
require 'whenever'
require 'test_case'
require 'mocha/minitest'
begin
  require 'active_support/all'
rescue LoadError
end

module Whenever::TestHelpers
  protected
    def new_job(options={})
      Whenever::Job.new(options)
    end

    def parse_time(time = nil, task = nil, at = nil, options = {})
      Whenever::Output::Cron.new(time, task, at, options).time_in_cron_syntax
    end

    def two_hours
      "0 0,2,4,6,8,10,12,14,16,18,20,22 * * *"
    end

    def assert_months_and_days_and_hours_and_minutes_equals(expected, time, options = {})
      cron = parse_time(Whenever.seconds(1, :year), 'some task', time, options)
      minutes, hours, days, months = cron.split(' ')
      assert_equal expected, [months, days, hours, minutes]
    end

    def assert_days_and_hours_and_minutes_equals(expected, time, options = {})
      cron = parse_time(Whenever.seconds(2, :months), 'some task', time, options)
      minutes, hours, days, _ = cron.split(' ')
      assert_equal expected, [days, hours, minutes]
    end

    def assert_hours_and_minutes_equals(expected, time, options = {})
      cron = parse_time(Whenever.seconds(2, :days), 'some task', time, options)
      minutes, hours, _ = cron.split(' ')
      assert_equal expected, [hours, minutes]
    end

    def assert_minutes_equals(expected, time, options = {})
      cron = parse_time(Whenever.seconds(2, :hours), 'some task', time, options)
      assert_equal expected, cron.split(' ')[0]
    end

    def lines_without_empty_line(lines)
      lines.map { |line| line.chomp }.reject { |line| line.empty? }
    end
end

Whenever::TestCase.send(:include, Whenever::TestHelpers)


================================================
FILE: test/unit/capistrano_support_test.rb
================================================
require 'test_helper'
require 'whenever/capistrano/v2/support'

class CapistranoSupportTestSubject
  include Whenever::CapistranoSupport
end

class CapistranoTestCase < Whenever::TestCase
  setup do
    @capistrano = CapistranoSupportTestSubject.new
    configuration = mock()
    configuration.stubs(:load).yields(@capistrano)
    Whenever::CapistranoSupport.load_into(configuration)
  end
end

class CapistranoSupportTest < CapistranoTestCase
  should "return fetch(:whenever_options) from #whenever_options" do
    @capistrano.expects(:fetch).with(:whenever_options)
    @capistrano.whenever_options
  end

  should "return whenever_options[:roles] as an array from #whenever_roles with one role" do
    @capistrano.stubs(:whenever_options).returns({:roles => :role1})
    assert_equal [:role1], @capistrano.whenever_roles
  end

  should "return an empty array from #whenever_roles with no defined roles" do
    @capistrano.stubs(:whenever_options).returns({})
    assert_equal [], @capistrano.whenever_roles
  end

  should "return the list of servers returned by find_servers from #whenever_servers" do
    @capistrano.stubs(:whenever_options).returns({})
    @capistrano.stubs(:find_servers).returns([:server1, :server2])

    assert_equal [:server1, :server2], @capistrano.whenever_servers
  end

  should "#whenever_prepare_for_rollback: set path to previous_release if there is a previous release" do
    args = {}
    @capistrano.stubs(:fetch).with(:previous_release).returns("/some/path/20121221010000")
    assert_equal({:path => "/some/path/20121221010000"}, @capistrano.whenever_prepare_for_rollback(args))
  end

  should "#whenever_prepare_for_rollback: set path to release_path and flags to whenever_clear_flags if there is no previous release" do
    args = {}
    @capistrano.stubs(:fetch).with(:previous_release).returns(nil)
    @capistrano.stubs(:fetch).with(:release_path).returns("/some/path/20121221010000")
    @capistrano.stubs(:fetch).with(:whenever_clear_flags).returns("--clear-crontab whenever_identifier")
    assert_equal({:path => "/some/path/20121221010000", :flags => "--clear-crontab whenever_identifier"}, @capistrano.whenever_prepare_for_rollback(args))
  end

  should "#whenever_run_commands: require :command arg" do
    assert_raises ArgumentError do
      @capistrano.whenever_run_commands(:options => {}, :path => {}, :flags => {})
    end
  end

  should "#whenever_run_commands: require :path arg" do
    assert_raises ArgumentError do
      @capistrano.whenever_run_commands(:options => {}, :command => {}, :flags => {})
    end
  end

  should "#whenever_run_commands: require :flags arg" do
    assert_raises ArgumentError do
      @capistrano.whenever_run_commands(:options => {}, :path => {}, :command => {})
    end
  end
end

class ServerRolesTest < CapistranoTestCase
  setup do
    @mock_servers = ["foo", "bar"]
    @capistrano.stubs(:whenever_servers).returns(@mock_servers)

    @mock_server1, @mock_server2, @mock_server3 = mock("Server1"), mock("Server2"), mock("Server3")
    @mock_server1.stubs(:host).returns("server1.foo.com")
    @mock_server2.stubs(:host).returns("server2.foo.com")
    @mock_server3.stubs(:host => "server3.foo.com", :port => 1022, :user => 'test')
    @mock_servers = [@mock_server1, @mock_server2]
  end

  should "return a map of servers to their role(s)" do
    @capistrano.stubs(:whenever_roles).returns([:role1, :role2])
    @capistrano.stubs(:role_names_for_host).with("foo").returns([:role1])
    @capistrano.stubs(:role_names_for_host).with("bar").returns([:role2])
    assert_equal({"foo" => [:role1], "bar" => [:role2]}, @capistrano.whenever_server_roles)
  end

  should "exclude non-requested roles" do
    @capistrano.stubs(:whenever_roles).returns([:role1, :role2])
    @capistrano.stubs(:role_names_for_host).with("foo").returns([:role1, :role3])
    @capistrano.stubs(:role_names_for_host).with("bar").returns([:role2])
    assert_equal({"foo" => [:role1], "bar" => [:role2]}, @capistrano.whenever_server_roles)
  end

  should "include all roles for servers w/ >1 when they're requested" do
    @capistrano.stubs(:whenever_roles).returns([:role1, :role2, :role3])
    @capistrano.stubs(:role_names_for_host).with("foo").returns([:role1, :role3])
    @capistrano.stubs(:role_names_for_host).with("bar").returns([:role2])
    assert_equal({"foo" => [:role1, :role3], "bar" => [:role2]}, @capistrano.whenever_server_roles)
  end

  should "call run for each host w/ appropriate role args" do
    @capistrano.stubs(:role_names_for_host).with(@mock_server1).returns([:role1])
    @capistrano.stubs(:role_names_for_host).with(@mock_server2).returns([:role2])
    @capistrano.stubs(:whenever_servers).returns(@mock_servers)
    roles = [:role1, :role2]
    @capistrano.stubs(:whenever_options).returns({:roles => roles})

    @capistrano.expects(:run).once.with('cd /foo/bar && whenever --flag1 --flag2 --roles role1', {:roles => roles, :hosts => @mock_server1})
    @capistrano.expects(:run).once.with('cd /foo/bar && whenever --flag1 --flag2 --roles role2', {:roles => roles, :hosts => @mock_server2})

    @capistrano.whenever_run_commands(:command => "whenever",
                                      :path => "/foo/bar",
                                      :flags => "--flag1 --flag2")
  end

  should "call run w/ all role args for servers w/ >1 role" do
    @capistrano.stubs(:role_names_for_host).with(@mock_server1).returns([:role1, :role3])
    @capistrano.stubs(:whenever_servers).returns([@mock_server1])
    roles = [:role1, :role2, :role3]
    @capistrano.stubs(:whenever_options).returns({:roles => roles})

    @capistrano.expects(:run).once.with('cd /foo/bar && whenever --flag1 --flag2 --roles role1,role3', {:roles => roles, :hosts => @mock_server1})

    @capistrano.whenever_run_commands(:command => "whenever",
                                      :path => "/foo/bar",
                                      :flags => "--flag1 --flag2")
  end

  should "call run w/ proper server options (port, user)" do
    @capistrano.stubs(:role_names_for_host).with(@mock_server3).returns([:role3])
    @capistrano.stubs(:whenever_servers).returns([@mock_server3])
    @capistrano.stubs(:whenever_options).returns({:roles => [:role3]})

    @capistrano.expects(:run).once.with do |command, options|
      options[:hosts].user == "test" && options[:hosts].port == 1022
    end

    @capistrano.whenever_run_commands(:command => "whenever",
                                      :path => "/foo/bar",
                                      :flags => "--flag1 --flag2")
  end
end


================================================
FILE: test/unit/cron_test.rb
================================================
require 'test_helper'

class CronTest < Whenever::TestCase
  should "raise if less than 1 minute" do
    assert_raises ArgumentError do
      parse_time(Whenever.seconds(59, :seconds))
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(0, :minutes))
    end
  end

  # For sanity, do some tests on straight cron-syntax strings
  should "parse correctly" do
    assert_equal '* * * * *', parse_time(Whenever.seconds(1, :minute))
    assert_equal '0,5,10,15,20,25,30,35,40,45,50,55 * * * *', parse_time(Whenever.seconds(5, :minutes))
    assert_equal '7,14,21,28,35,42,49,56 * * * *', parse_time(Whenever.seconds(7, :minutes))
    assert_equal '0,30 * * * *', parse_time(Whenever.seconds(30, :minutes))
    assert_equal '32 * * * *', parse_time(Whenever.seconds(32, :minutes))
    assert '60 * * * *' != parse_time(Whenever.seconds(60, :minutes)) # 60 minutes bumps up into the hour range
  end

  # Test all minutes
  (2..59).each do |num|
    should "parse correctly for #{num} minutes" do
      start = 0
      start += num unless 60.modulo(num).zero?
      minutes = (start..59).step(num).to_a

      assert_equal "#{minutes.join(',')} * * * *", parse_time(Whenever.seconds(num, :minutes))
    end
  end
end

class CronParseHoursTest < Whenever::TestCase
  should "parse correctly" do
    assert_equal '0 * * * *', parse_time(Whenever.seconds(1, :hour))
    assert_equal '0 0,2,4,6,8,10,12,14,16,18,20,22 * * *', parse_time(Whenever.seconds(2, :hours))
    assert_equal '0 0,3,6,9,12,15,18,21 * * *', parse_time(Whenever.seconds(3, :hours))
    assert_equal '0 5,10,15,20 * * *', parse_time(Whenever.seconds(5, :hours))
    assert_equal '0 17 * * *', parse_time(Whenever.seconds(17, :hours))
    assert '0 24 * * *' != parse_time(Whenever.seconds(24, :hours)) # 24 hours bumps up into the day range
  end

  (2..23).each do |num|
    should "parse correctly for #{num} hours" do
      start = 0
      start += num unless 24.modulo(num).zero?
      hours = (start..23).step(num).to_a

      assert_equal "0 #{hours.join(',')} * * *", parse_time(Whenever.seconds(num, :hours))
    end
  end

  should "parse correctly when given an 'at' with minutes as an Integer" do
    assert_minutes_equals "1",  1
    assert_minutes_equals "14", 14
    assert_minutes_equals "27", 27
    assert_minutes_equals "55", 55
  end

  should "parse correctly when given an 'at' with minutes as a Time" do
    # Basically just testing that Chronic parses some times and we get the minutes out of it
    assert_minutes_equals "1",  '3:01am'
    assert_minutes_equals "1",  'January 21 2:01 PM'
    assert_minutes_equals "0",  'midnight'
    assert_minutes_equals "59", '13:59'
  end

  should "parse correctly when given an 'at' with minutes as a Time and custom Chronic options are set" do
    assert_minutes_equals "15", '3:15'
    assert_minutes_equals "15", '3:15', :chronic_options => { :hours24 => true }
    assert_minutes_equals "15", '3:15', :chronic_options => { :hours24 => false }

    assert_minutes_equals "30", '6:30'
    assert_minutes_equals "30", '6:30', :chronic_options => { :hours24 => true }
    assert_minutes_equals "30", '6:30', :chronic_options => { :hours24 => false }
  end

  should "parse correctly when given an 'at' with minutes as a Range" do
    assert_minutes_equals "15-30", 15..30
  end

  should "raise an exception when given an 'at' with an invalid minute value" do
    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :hour), nil, 60)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :hour), nil, -1)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :hour), nil, 0..60)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :hour), nil, -1..59)
    end
  end
end

class CronParseDaysTest < Whenever::TestCase
  should "parse correctly" do
    assert_equal '0 0 * * *', parse_time(Whenever.seconds(1, :days))
    assert_equal '0 0 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31 * *', parse_time(Whenever.seconds(2, :days))
    assert_equal '0 0 1,5,9,13,17,21,25,29 * *', parse_time(Whenever.seconds(4, :days))
    assert_equal '0 0 1,8,15,22 * *', parse_time(Whenever.seconds(7, :days))
    assert_equal '0 0 1,17 * *', parse_time(Whenever.seconds(16, :days))
    assert_equal '0 0 17 * *', parse_time(Whenever.seconds(17, :days))
    assert_equal '0 0 29 * *', parse_time(Whenever.seconds(29, :days))
    assert '0 0 30 * *' != parse_time(Whenever.seconds(30, :days)) # 30 days bumps into the month range
  end

  should "parse correctly when given an 'at' with hours, minutes as a Time" do
    # first param is an array with [hours, minutes]
    assert_hours_and_minutes_equals %w(3 45),  '3:45am'
    assert_hours_and_minutes_equals %w(20 1),  '8:01pm'
    assert_hours_and_minutes_equals %w(0 0),   'midnight'
    assert_hours_and_minutes_equals %w(1 23),  '1:23 AM'
    assert_hours_and_minutes_equals %w(23 59), 'March 21 11:59 pM'
  end

  should "parse correctly when given an 'at' with hours, minutes as a Time and custom Chronic options are set" do
    # first param is an array with [hours, minutes]
    assert_hours_and_minutes_equals %w(15 15), '3:15'
    assert_hours_and_minutes_equals %w(3 15),  '3:15', :chronic_options => { :hours24 => true }
    assert_hours_and_minutes_equals %w(15 15), '3:15', :chronic_options => { :hours24 => false }

    assert_hours_and_minutes_equals %w(6 30),  '6:30'
    assert_hours_and_minutes_equals %w(6 30),  '6:30', :chronic_options => { :hours24 => true }
    assert_hours_and_minutes_equals %w(6 30),  '6:30', :chronic_options => { :hours24 => false }
  end

  should "parse correctly when given an 'at' with hours as an Integer" do
    # first param is an array with [hours, minutes]
    assert_hours_and_minutes_equals %w(1 0),  1
    assert_hours_and_minutes_equals %w(3 0),  3
    assert_hours_and_minutes_equals %w(15 0), 15
    assert_hours_and_minutes_equals %w(19 0), 19
    assert_hours_and_minutes_equals %w(23 0), 23
  end

  should "parse correctly when given an 'at' with hours as a Range" do
    assert_hours_and_minutes_equals %w(3-23 0), 3..23
  end

  should "raise an exception when given an 'at' with an invalid hour value" do
    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :day), nil, 24)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :day), nil, -1)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :day), nil, 0..24)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :day), nil, -1..23)
    end
  end
end

class CronParseMonthsTest < Whenever::TestCase
  should "parse correctly" do
    assert_equal '0 0 1 * *', parse_time(Whenever.seconds(1, :month))
    assert_equal '0 0 1 1,3,5,7,9,11 *', parse_time(Whenever.seconds(2, :months))
    assert_equal '0 0 1 1,4,7,10 *', parse_time(Whenever.seconds(3, :months))
    assert_equal '0 0 1 1,5,9 *', parse_time(Whenever.seconds(4, :months))
    assert_equal '0 0 1 1,6 *', parse_time(Whenever.seconds(5, :months))
    assert_equal '0 0 1 7 *', parse_time(Whenever.seconds(7, :months))
    assert_equal '0 0 1 8 *', parse_time(Whenever.seconds(8, :months))
    assert_equal '0 0 1 9 *', parse_time(Whenever.seconds(9, :months))
    assert_equal '0 0 1 10 *', parse_time(Whenever.seconds(10, :months))
    assert_equal '0 0 1 11 *', parse_time(Whenever.seconds(11, :months))
    assert_equal '0 0 1 12 *', parse_time(Whenever.seconds(12, :months))
  end

  should "parse months with a date and/or time" do
    # should set the day to 1 if no date is given
    assert_equal '0 17 1 * *', parse_time(Whenever.seconds(1, :month), nil, "5pm")
    # should use the date if one is given
    assert_equal '0 2 23 * *', parse_time(Whenever.seconds(1, :month), nil, "February 23rd at 2am")
    # should use an iteger as the day
    assert_equal '0 0 5 * *', parse_time(Whenever.seconds(1, :month), nil, 5)
  end

  should "parse correctly when given an 'at' with days, hours, minutes as a Time" do
    # first param is an array with [days, hours, minutes]
    assert_days_and_hours_and_minutes_equals %w(1 3 45),  'January 1st 3:45am'
    assert_days_and_hours_and_minutes_equals %w(11 23 0), 'Feb 11 11PM'
    assert_days_and_hours_and_minutes_equals %w(22 1 1), 'march 22nd at 1:01 am'
    assert_days_and_hours_and_minutes_equals %w(23 0 0), 'march 22nd at midnight' # looks like midnight means the next day
  end

  should "parse correctly when given an 'at' with days, hours, minutes as a Time and custom Chronic options are set" do
    # first param is an array with [days, hours, minutes]
    assert_days_and_hours_and_minutes_equals %w(22 15 45), 'February 22nd 3:45'
    assert_days_and_hours_and_minutes_equals %w(22 15 45), '02/22 3:45'
    assert_days_and_hours_and_minutes_equals %w(22 3 45),  'February 22nd 3:45', :chronic_options => { :hours24 => true }
    assert_days_and_hours_and_minutes_equals %w(22 15 45), 'February 22nd 3:45', :chronic_options => { :hours24 => false }

    assert_days_and_hours_and_minutes_equals %w(3 8 15), '02/03 8:15'
    assert_days_and_hours_and_minutes_equals %w(3 8 15), '02/03 8:15', :chronic_options => { :endian_precedence => :middle }
    assert_days_and_hours_and_minutes_equals %w(2 8 15), '02/03 8:15', :chronic_options => { :endian_precedence => :little }

    assert_days_and_hours_and_minutes_equals %w(4 4 50),  '03/04 4:50', :chronic_options => { :endian_precedence => :middle, :hours24 => true }
    assert_days_and_hours_and_minutes_equals %w(4 16 50), '03/04 4:50', :chronic_options => { :endian_precedence => :middle, :hours24 => false }
    assert_days_and_hours_and_minutes_equals %w(3 4 50),  '03/04 4:50', :chronic_options => { :endian_precedence => :little, :hours24 => true }
    assert_days_and_hours_and_minutes_equals %w(3 16 50), '03/04 4:50', :chronic_options => { :endian_precedence => :little, :hours24 => false }
  end

  should "parse correctly when given an 'at' with days as an Integer" do
    # first param is an array with [days, hours, minutes]
    assert_days_and_hours_and_minutes_equals %w(1 0 0),  1
    assert_days_and_hours_and_minutes_equals %w(15 0 0), 15
    assert_days_and_hours_and_minutes_equals %w(29 0 0), 29
  end

  should "parse correctly when given an 'at' with days as a Range" do
    assert_days_and_hours_and_minutes_equals %w(1-7 0 0), 1..7
  end

  should "raise an exception when given an 'at' with an invalid day value" do
    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :month), nil, 32)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :month), nil, -1)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :month), nil, 0..30)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :month), nil, 1..32)
    end
  end
end

class CronParseYearTest < Whenever::TestCase
  should "parse correctly" do
    assert_equal '0 0 1 1 *', parse_time(Whenever.seconds(1, :year))
  end

  should "parse year with a date and/or time" do
    # should set the day and month to 1 if no date is given
    assert_equal '0 17 1 1 *', parse_time(Whenever.seconds(1, :year), nil, "5pm")
    # should use the date if one is given
    assert_equal '0 2 23 2 *', parse_time(Whenever.seconds(1, :year), nil, "February 23rd at 2am")
    # should use an iteger as the month
    assert_equal '0 0 1 5 *', parse_time(Whenever.seconds(1, :year), nil, 5)
  end

  should "parse correctly when given an 'at' with days, hours, minutes as a Time" do
    # first param is an array with [months, days, hours, minutes]
    assert_months_and_days_and_hours_and_minutes_equals %w(1 1 3 45),  'January 1st 3:45am'
    assert_months_and_days_and_hours_and_minutes_equals %w(2 11 23 0), 'Feb 11 11PM'
    assert_months_and_days_and_hours_and_minutes_equals %w(3 22 1 1),  'march 22nd at 1:01 am'
    assert_months_and_days_and_hours_and_minutes_equals %w(3 23 0 0),  'march 22nd at midnight' # looks like midnight means the next day
  end

  should "parse correctly when given an 'at' with days, hours, minutes as a Time and custom Chronic options are set" do
    # first param is an array with [months, days, hours, minutes]
    assert_months_and_days_and_hours_and_minutes_equals %w(2 22 15 45), 'February 22nd 3:45'
    assert_months_and_days_and_hours_and_minutes_equals %w(2 22 15 45), '02/22 3:45'
    assert_months_and_days_and_hours_and_minutes_equals %w(2 22 3 45),  'February 22nd 3:45', :chronic_options => { :hours24 => true }
    assert_months_and_days_and_hours_and_minutes_equals %w(2 22 15 45), 'February 22nd 3:45', :chronic_options => { :hours24 => false }

    assert_months_and_days_and_hours_and_minutes_equals %w(2 3 8 15), '02/03 8:15'
    assert_months_and_days_and_hours_and_minutes_equals %w(2 3 8 15), '02/03 8:15', :chronic_options => { :endian_precedence => :middle }
    assert_months_and_days_and_hours_and_minutes_equals %w(3 2 8 15), '02/03 8:15', :chronic_options => { :endian_precedence => :little }

    assert_months_and_days_and_hours_and_minutes_equals %w(3 4 4 50),  '03/04 4:50', :chronic_options => { :endian_precedence => :middle, :hours24 => true }
    assert_months_and_days_and_hours_and_minutes_equals %w(3 4 16 50), '03/04 4:50', :chronic_options => { :endian_precedence => :middle, :hours24 => false }
    assert_months_and_days_and_hours_and_minutes_equals %w(4 3 4 50),  '03/04 4:50', :chronic_options => { :endian_precedence => :little, :hours24 => true }
    assert_months_and_days_and_hours_and_minutes_equals %w(4 3 16 50), '03/04 4:50', :chronic_options => { :endian_precedence => :little, :hours24 => false }
  end

  should "parse correctly when given an 'at' with month as an Integer" do
    # first param is an array with [months, days, hours, minutes]
    assert_months_and_days_and_hours_and_minutes_equals %w(1 1 0 0),  1
    assert_months_and_days_and_hours_and_minutes_equals %w(5 1 0 0),  5
    assert_months_and_days_and_hours_and_minutes_equals %w(12 1 0 0), 12
  end

  should "parse correctly when given an 'at' with month as a Range" do
    assert_months_and_days_and_hours_and_minutes_equals %w(1-3 1 0 0), 1..3
  end

  should "raise an exception when given an 'at' with an invalid month value" do
    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :year), nil, 13)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :year), nil, -1)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :year), nil, 0..12)
    end

    assert_raises ArgumentError do
      parse_time(Whenever.seconds(1, :year), nil, 1..13)
    end
  end
end

class CronParseDaysOfWeekTest < Whenever::TestCase
  should "parse days of the week correctly" do
    {
      '0' => %w(sun Sunday SUNDAY SUN),
      '1' => %w(mon Monday MONDAY MON),
      '2' => %w(tue tues Tuesday TUESDAY TUE),
      '3' => %w(wed Wednesday WEDNESDAY WED),
      '4' => %w(thu thurs thur Thursday THURSDAY THU),
      '5' => %w(fri Friday FRIDAY FRI),
      '6' => %w(sat Saturday SATURDAY SAT)
    }.each do |day, day_tests|
      day_tests.each do |day_test|
        assert_equal "0 0 * * #{day}", parse_time(day_test)
      end
    end
  end

  should "allow additional directives" do
    assert_equal '30 13 * * 5', parse_time('friday', nil, "1:30 pm")
    assert_equal '22 2 * * 1', parse_time('Monday', nil, "2:22am")
    assert_equal '55 17 * * 4', parse_time('THU', nil, "5:55PM")
  end

  should "parse weekday correctly" do
    assert_equal '0 0 * * 1-5', parse_time('weekday')
    assert_equal '0 0 * * 1-5', parse_time('Weekdays')
    assert_equal '0 1 * * 1-5', parse_time('Weekdays', nil, "1:00 am")
    assert_equal '59 5 * * 1-5', parse_time('Weekdays', nil, "5:59 am")
  end

  should "parse weekend correctly" do
    assert_equal '0 0 * * 6,0', parse_time('weekend')
    assert_equal '0 0 * * 6,0', parse_time('Weekends')
    assert_equal '0 7 * * 6,0', parse_time('Weekends', nil, "7am")
    assert_equal '2 18 * * 6,0', parse_time('Weekends', nil, "6:02PM")
  end
end

class CronParseShortcutsTest < Whenever::TestCase
  should "parse a :symbol into the correct shortcut" do
    assert_equal '@reboot',   parse_time(:reboot)
    assert_equal '@annually', parse_time(:annually)
    assert_equal '@yearly',   parse_time(:yearly)
    assert_equal '@daily',    parse_time(:daily)
    assert_equal '@midnight', parse_time(:midnight)
    assert_equal '@monthly',  parse_time(:monthly)
    assert_equal '@weekly',   parse_time(:weekly)
    assert_equal '@hourly',   parse_time(:hourly)
  end

  should "convert time-based shortcuts to times" do
    assert_equal '0 0 1 * *', parse_time(:month)
    assert_equal '0 0 * * *', parse_time(:day)
    assert_equal '0 * * * *', parse_time(:hour)
    assert_equal '0 0 1 1 *', parse_time(:year)
    assert_equal '0 0 1,8,15,22 * *', parse_time(:week)
  end

  should "raise an exception if a valid shortcut is given but also an :at" do
    assert_raises ArgumentError do
      parse_time(:hourly, nil, "1:00 am")
    end

    assert_raises ArgumentError do
      parse_time(:reboot, nil, 5)
    end

    assert_raises ArgumentError do
      parse_time(:daily, nil, '4:20pm')
    end
  end
end

class CronParseRubyTimeTest < Whenever::TestCase
  should "process things like `1.day` correctly" do
    assert_equal "0 0 * * *", parse_time(1.day)
  end
end

class CronParseRawTest < Whenever::TestCase
  should "raise if cron-syntax string is too long" do
    assert_raises ArgumentError do
      parse_time('* * * * * *')
    end
  end

  should "raise if cron-syntax string is invalid" do
    assert_raises ArgumentError do
      parse_time('** * * * *')
    end
  end

  should "return the same cron sytax" do
    crons = ['0 0 27-31 * *', '* * * * *', '2/3 1,9,22 11-26 1-6 *', '*/5 6-23 * * *',
             "*\t*\t*\t*\t*",
             '7 17 * * FRI', '7 17 * * Mon-Fri', '30 12 * Jun *', '30 12 * Jun-Aug *',
             '@reboot', '@yearly', '@annually', '@monthly', '@weekly',
             '@daily', '@midnight', '@hourly']
    crons.each do |cron|
      assert_equal cron, parse_time(cron)
    end
  end
end


================================================
FILE: test/unit/executable_test.rb
================================================
require 'test_helper'

describe 'Executable' do
  describe 'bin/wheneverize' do
    describe 'ARGV is not empty' do
      describe 'file does not exist' do
        file = '/tmp/this_does_not_exist'

        it 'prints STDERR' do
          out, err = capture_subprocess_io do
            system('wheneverize', file)
          end

          assert_empty(out)
          assert_match(/`#{file}' does not exist./, err)
        end
      end

      describe 'file exists, but not a directory' do
        file = '/tmp/this_is_a_file.txt'
        before { FileUtils.touch(file) }

        it 'prints STDERR' do
          begin
            out, err = capture_subprocess_io do
              system('wheneverize', file)
            end

            assert_empty(out)
            assert_match(/`#{file}' is not a directory./, err)
          ensure
            FileUtils.rm(file)
          end
        end
      end

      describe 'file is a directory, but another param(s) are given as well' do
        file = '/tmp/this_is_a_directory'
        before { FileUtils.mkdir(file) }

        it 'prints STDERR' do
          begin
            out, err = capture_subprocess_io do
              system('wheneverize', file, 'another', 'parameters')
            end

            assert_empty(out)
            assert_match(/#{"Too many arguments; please specify only the " \
                         "directory to wheneverize."}/, err)
          ensure
            FileUtils.rmdir(file)
          end
        end
      end
    end

    describe 'ARGV is empty' do
      dir  = '.'
      file = 'config/schedule.rb'
      path = File.join(dir, file)

      describe 'config file already exists' do
        before do
          FileUtils.mkdir(File.dirname(path))
          FileUtils.touch(path)
        end

        it 'prints STDOUT and STDERR' do
          begin
            out, err = capture_subprocess_io do
              system('wheneverize')
            end

            assert_match(/\[done\] wheneverized!/, out)
            assert_match(/\[skip\] `#{path}' already exists/, err)
          ensure
            FileUtils.rm_rf(File.dirname(path))
          end
        end
      end

      describe 'config directory does not exist' do
        it 'prints STDOUT and STDERR' do
          begin
            out, err = capture_subprocess_io do
              system('wheneverize')
            end

            assert_match(/\[add\] creating `#{File.dirname(path)}'\n/, err)
            assert_match(/\[done\] wheneverized!/, out)
          ensure
            FileUtils.rm_rf(File.dirname(path))
          end
        end
      end

      describe 'config directory exists, but file does not' do
        before { FileUtils.mkdir(File.dirname(path)) }

        it 'writes config file and prints STDOUT' do
          begin
            out, err = capture_subprocess_io do
              system('wheneverize')
            end

            assert_empty(err)
            assert_match(
              /\[add\] writing `#{path}'\n\[done\] wheneverized!/,
              out
            )

            assert_match((<<-FILE
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron

# Example:
#
# set :output, "/path/to/my/cron_log.log"
#
# every 2.hours do
#   command "/usr/bin/some_great_command"
#   runner "MyModel.some_method"
#   rake "some:great:rake:task"
# end
#
# every 4.days do
#   runner "AnotherModel.prune_old_records"
# end

# Learn more: http://github.com/javan/whenever
FILE
                         ), IO.read(path))
          ensure
            FileUtils.rm_rf(File.dirname(path))
          end
        end
      end
    end
  end
end


================================================
FILE: test/unit/job_test.rb
================================================
require 'test_helper'

class JobTest < Whenever::TestCase
  should "return the :at set when #at is called" do
    assert_equal 'foo', new_job(:at => 'foo').at
  end

  should "return the :roles set when #roles is called" do
    assert_equal ['foo', 'bar'], new_job(:roles => ['foo', 'bar']).roles
  end

  should "return whether it has a role from #has_role?" do
    assert new_job(:roles => 'foo').has_role?('foo')
    assert_equal false, new_job(:roles => 'bar').has_role?('foo')
  end

  should "substitute the :task when #output is called" do
    job = new_job(:template => ":task", :task => 'abc123')
    assert_equal 'abc123', job.output
  end

  should "substitute the :path when #output is called" do
    assert_equal 'foo', new_job(:template => ':path', :path => 'foo').output
  end

  should "substitute the :path with the default Whenever.path if none is provided when #output is called" do
    Whenever.expects(:path).returns('/my/path')
    assert_equal '/my/path', new_job(:template => ':path').output
  end

  should "not substitute parameters for which no value is set" do
    assert_equal 'Hello :world', new_job(:template => ':matching :world', :matching => 'Hello').output
  end

  should "escape the :path" do
    assert_equal '/my/spacey\ path', new_job(:template => ':path', :path => '/my/spacey path').output
  end

  should "escape percent signs" do
    job = new_job(
      :template => "before :foo after",
      :foo => "percent -> % <- percent"
    )
    assert_equal %q(before percent -> \% <- percent after), job.output
  end

  should "assume percent signs are not already escaped" do
    job = new_job(
      :template => "before :foo after",
      :foo => %q(percent preceded by a backslash -> \% <-)
    )
    assert_equal %q(before percent preceded by a backslash -> \\\% <- after), job.output
  end

  should "squish spaces and newlines" do
    job = new_job(
      :template => "before :foo after",
      :foo => "newline -> \n <- newline space ->     <- space"
    )

    assert_equal "before newline -> <- newline space -> <- space after", job.output
  end
end


class JobWithQuotesTest < Whenever::TestCase
  should "output the :task if it's in single quotes" do
    job = new_job(:template => "':task'", :task => 'abc123')
    assert_equal %q('abc123'), job.output
  end

  should "output the :task if it's in double quotes" do
    job = new_job(:template => '":task"', :task => 'abc123')
    assert_equal %q("abc123"), job.output
  end

  should "output escaped single quotes in when it's wrapped in them" do
    job = new_job(
      :template => "before ':foo' after",
      :foo => "quote -> ' <- quote"
    )
    assert_equal %q(before 'quote -> '\'' <- quote' after), job.output
  end

  should "output escaped double quotes when it's wrapped in them" do
    job = new_job(
      :template => 'before ":foo" after',
      :foo => 'quote -> " <- quote'
    )
    assert_equal %q(before "quote -> \" <- quote" after), job.output
  end
end

class JobWithJobTemplateTest < Whenever::TestCase
  should "use the job template" do
    job = new_job(:template => ':task', :task => 'abc123', :job_template => 'left :job right')
    assert_equal 'left abc123 right', job.output
  end

  should "reuse parameter in the job template" do
    job = new_job(:template => ':path :task', :path => 'path', :task => "abc123", :job_template => ':path left :job right')
    assert_equal 'path left path abc123 right', job.output
  end

  should "escape single quotes" do
    job = new_job(:template => "before ':task' after", :task => "quote -> ' <- quote", :job_template => "left ':job' right")
    assert_equal %q(left 'before '\''quote -> '\\''\\'\\'''\\'' <- quote'\'' after' right), job.output
  end

  should "escape double quotes" do
    job = new_job(:template => 'before ":task" after', :task => 'quote -> " <- quote', :job_template => 'left ":job" right')
    assert_equal %q(left "before \"quote -> \\\" <- quote\" after" right), job.output
  end
end


================================================
FILE: whenever.gemspec
================================================
# frozen_string_literal: true

require_relative "lib/whenever/version"

Gem::Specification.new do |spec|
  spec.name        = "whenever"
  spec.version     = Whenever::VERSION
  spec.platform    = Gem::Platform::RUBY
  spec.authors     = ["Javan Makhmali"]
  spec.email       = ["javan@javan.us"]

  spec.summary     = %q{Cron jobs in ruby.}
  spec.description = %q{Clean ruby syntax for writing and deploying cron jobs.}
  spec.homepage    = "https://github.com/javan/whenever"
  spec.license     = "MIT"
  spec.required_ruby_version = ">= 1.9.3"

  spec.metadata["homepage_uri"] = spec.homepage
  spec.metadata["source_code_uri"] = "https://github.com/javan/whenever"
  spec.metadata["changelog_uri"] = "https://github.com/javan/whenever/blob/main/CHANGELOG.md"
  spec.metadata["rubygems_mfa_required"] = "true"

  spec.files         = `git ls-files`.split("\n")
  spec.test_files    = `git ls-files -- test/{functional,unit}/*`.split("\n")
  spec.executables   = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.add_dependency "chronic", ">= 0.6.3"
end
Download .txt
gitextract_3e33pqhl/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── Appraisals
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE
├── Makefile
├── README.md
├── Rakefile
├── bin/
│   ├── whenever
│   └── wheneverize
├── gemfiles/
│   ├── activesupport5.0.gemfile
│   ├── activesupport5.1.gemfile
│   ├── activesupport5.2.gemfile
│   ├── activesupport6.0.gemfile
│   ├── activesupport6.1.gemfile
│   ├── activesupport7.0.gemfile
│   ├── activesupport7.1.gemfile
│   ├── activesupport7.2.gemfile
│   ├── activesupport8.0.gemfile
│   └── activesupport8.1.gemfile
├── lib/
│   ├── whenever/
│   │   ├── capistrano/
│   │   │   ├── v2/
│   │   │   │   ├── hooks.rb
│   │   │   │   ├── recipes.rb
│   │   │   │   └── support.rb
│   │   │   └── v3/
│   │   │       └── tasks/
│   │   │           └── whenever.rake
│   │   ├── capistrano.rb
│   │   ├── command_line.rb
│   │   ├── cron.rb
│   │   ├── job.rb
│   │   ├── job_list.rb
│   │   ├── numeric.rb
│   │   ├── numeric_seconds.rb
│   │   ├── os.rb
│   │   ├── output_redirection.rb
│   │   ├── setup.rb
│   │   └── version.rb
│   └── whenever.rb
├── test/
│   ├── functional/
│   │   ├── command_line_test.rb
│   │   ├── output_at_test.rb
│   │   ├── output_default_defined_jobs_test.rb
│   │   ├── output_defined_job_test.rb
│   │   ├── output_description_test.rb
│   │   ├── output_env_test.rb
│   │   ├── output_jobs_for_roles_test.rb
│   │   ├── output_jobs_with_mailto_test.rb
│   │   └── output_redirection_test.rb
│   ├── test_case.rb
│   ├── test_helper.rb
│   └── unit/
│       ├── capistrano_support_test.rb
│       ├── cron_test.rb
│       ├── executable_test.rb
│       └── job_test.rb
└── whenever.gemspec
Download .txt
SYMBOL INDEX (148 symbols across 26 files)

FILE: lib/whenever.rb
  type Whenever (line 11) | module Whenever
    function cron (line 12) | def self.cron(options)
    function seconds (line 16) | def self.seconds(number, units)
    function path (line 20) | def self.path
    function bin_rails? (line 24) | def self.bin_rails?
    function script_rails? (line 28) | def self.script_rails?
    function bundler? (line 32) | def self.bundler?
    function update_cron (line 36) | def self.update_cron options

FILE: lib/whenever/capistrano/v2/support.rb
  type Whenever (line 1) | module Whenever
    type CapistranoSupport (line 2) | module CapistranoSupport
      function load_into (line 3) | def self.load_into(capistrano_configuration)

FILE: lib/whenever/capistrano/v3/tasks/whenever.rake
  function setup_whenever_task (line 2) | def setup_whenever_task(*args, &block)
  function load_file (line 15) | def load_file

FILE: lib/whenever/command_line.rb
  type Whenever (line 3) | module Whenever
    class CommandLine (line 4) | class CommandLine
      method execute (line 5) | def self.execute(options={})
      method initialize (line 9) | def initialize(options={})
      method run (line 37) | def run
      method default_identifier (line 52) | def default_identifier
      method whenever_cron (line 56) | def whenever_cron
      method read_crontab (line 61) | def read_crontab
      method write_crontab (line 72) | def write_crontab(contents)
      method updated_crontab (line 98) | def updated_crontab
      method prepare (line 118) | def prepare(contents)
      method comment_base (line 130) | def comment_base(include_timestamp = true)
      method comment_open (line 138) | def comment_open(include_timestamp = true)
      method comment_close (line 142) | def comment_close(include_timestamp = true)
      method comment_open_regex (line 146) | def comment_open_regex
      method comment_close_regex (line 150) | def comment_close_regex
      method timestamp_regex (line 154) | def timestamp_regex
      method return_or_exit (line 160) | def return_or_exit success

FILE: lib/whenever/cron.rb
  type Whenever (line 3) | module Whenever
    type Output (line 4) | module Output
      class Cron (line 5) | class Cron
        method initialize (line 13) | def initialize(time = nil, task = nil, at = nil, options = {})
        method enumerate (line 22) | def self.enumerate(item, detect_cron = true)
        method output (line 37) | def self.output(times, job, options = {})
        method output (line 45) | def output
        method time_in_cron_syntax (line 49) | def time_in_cron_syntax
        method day_given? (line 60) | def day_given?
        method parse_symbol (line 64) | def parse_symbol
        method parse_time (line 89) | def parse_time
        method parse_as_string (line 135) | def parse_as_string
        method range_or_integer (line 153) | def range_or_integer(at, valid_range, name)
        method comma_separated_timing (line 164) | def comma_separated_timing(frequency, max, start = 0)

FILE: lib/whenever/job.rb
  type Whenever (line 3) | module Whenever
    class Job (line 4) | class Job
      method initialize (line 7) | def initialize(options = {})
      method output (line 21) | def output
      method has_role? (line 27) | def has_role?(role)
      method process_template (line 33) | def process_template(template, options)
      method escape_single_quotes (line 48) | def escape_single_quotes(str)
      method escape_double_quotes (line 52) | def escape_double_quotes(str)

FILE: lib/whenever/job_list.rb
  type Whenever (line 1) | module Whenever
    class JobList (line 2) | class JobList
      method initialize (line 5) | def initialize(options)
      method set (line 28) | def set(variable, value)
      method method_missing (line 36) | def method_missing(name, *args, &block)
      method respond_to? (line 40) | def respond_to?(name, include_private = false)
      method env (line 44) | def env(variable, value)
      method every (line 48) | def every(frequency, options = {})
      method job_type (line 54) | def job_type(name, template)
      method generate_cron_output (line 74) | def generate_cron_output
      method pre_set (line 85) | def pre_set(variable_string = nil)
      method environment_variables (line 100) | def environment_variables
      method combine (line 118) | def combine(entries)
      method cron_jobs_of_time (line 138) | def cron_jobs_of_time(time, jobs)
      method cron_jobs (line 160) | def cron_jobs

FILE: lib/whenever/numeric.rb
  function respond_to? (line 2) | def respond_to?(method, include_private = false)
  function method_missing (line 6) | def method_missing(method, *args, &block)

FILE: lib/whenever/numeric_seconds.rb
  type Whenever (line 1) | module Whenever
    class NumericSeconds (line 2) | class NumericSeconds
      method seconds (line 5) | def self.seconds(number, units)
      method initialize (line 9) | def initialize(number)
      method seconds (line 13) | def seconds
      method minutes (line 18) | def minutes
      method hours (line 23) | def hours
      method days (line 28) | def days
      method weeks (line 33) | def weeks
      method months (line 38) | def months
      method years (line 43) | def years

FILE: lib/whenever/os.rb
  type Whenever (line 1) | module Whenever
    type OS (line 2) | module OS
      function solaris? (line 3) | def self.solaris?

FILE: lib/whenever/output_redirection.rb
  type Whenever (line 1) | module Whenever
    type Output (line 2) | module Output
      class Redirection (line 3) | class Redirection
        method initialize (line 4) | def initialize(output)
        method to_s (line 8) | def to_s
        method stdout (line 21) | def stdout
        method stderr (line 26) | def stderr
        method redirect_from_hash (line 31) | def redirect_from_hash
        method redirect_from_string (line 52) | def redirect_from_string

FILE: lib/whenever/version.rb
  type Whenever (line 1) | module Whenever

FILE: test/functional/command_line_test.rb
  class CommandLineWriteTest (line 3) | class CommandLineWriteTest < Whenever::TestCase
  class CommandLineUpdateTest (line 28) | class CommandLineUpdateTest < Whenever::TestCase
  class CommandLineUpdateWithBackslashesTest (line 152) | class CommandLineUpdateWithBackslashesTest < Whenever::TestCase
  class CommandLineUpdateToSimilarCrontabTest (line 171) | class CommandLineUpdateToSimilarCrontabTest < Whenever::TestCase
  class CommandLineClearTest (line 192) | class CommandLineClearTest < Whenever::TestCase
  class CommandLineClearWithNoScheduleTest (line 288) | class CommandLineClearWithNoScheduleTest < Whenever::TestCase
  class CommandLineUpdateWithNoIdentifierTest (line 300) | class CommandLineUpdateWithNoIdentifierTest < Whenever::TestCase
  class CombinedParamsTest (line 313) | class CombinedParamsTest < Whenever::TestCase
  class RunnerOverwrittenWithSetOptionTest (line 333) | class RunnerOverwrittenWithSetOptionTest < Whenever::TestCase
  class EnvironmentAndPathOverwrittenWithSetOptionTest (line 352) | class EnvironmentAndPathOverwrittenWithSetOptionTest < Whenever::TestCase
  class EnvironmentAndPathOverwrittenWithSetOptionWithSpacesTest (line 370) | class EnvironmentAndPathOverwrittenWithSetOptionWithSpacesTest < Wheneve...
  class EnvironmentOverwrittenWithoutValueTest (line 388) | class EnvironmentOverwrittenWithoutValueTest < Whenever::TestCase
  class PreparingOutputTest (line 406) | class PreparingOutputTest < Whenever::TestCase

FILE: test/functional/output_at_test.rb
  class OutputAtTest (line 3) | class OutputAtTest < Whenever::TestCase

FILE: test/functional/output_default_defined_jobs_test.rb
  class OutputDefaultDefinedJobsTest (line 3) | class OutputDefaultDefinedJobsTest < Whenever::TestCase

FILE: test/functional/output_defined_job_test.rb
  class OutputDefinedJobTest (line 3) | class OutputDefinedJobTest < Whenever::TestCase

FILE: test/functional/output_description_test.rb
  class OutputDescriptionTest (line 3) | class OutputDescriptionTest < Whenever::TestCase

FILE: test/functional/output_env_test.rb
  class OutputEnvTest (line 3) | class OutputEnvTest < Whenever::TestCase

FILE: test/functional/output_jobs_for_roles_test.rb
  class OutputJobsForRolesTest (line 3) | class OutputJobsForRolesTest < Whenever::TestCase

FILE: test/functional/output_jobs_with_mailto_test.rb
  class OutputJobsWithMailtoTest (line 3) | class OutputJobsWithMailtoTest < Whenever::TestCase
  class OutputJobsWithMailtoForRolesTest (line 147) | class OutputJobsWithMailtoForRolesTest < Whenever::TestCase

FILE: test/functional/output_redirection_test.rb
  class OutputRedirectionTest (line 3) | class OutputRedirectionTest < Whenever::TestCase

FILE: test/test_case.rb
  type Whenever (line 1) | module Whenever
    class TestCase (line 5) | class TestCase < Minitest::Test; end
      method setup (line 14) | def setup(&block)
      method test (line 21) | def test(name, &block)
      method assert_no_match (line 27) | def assert_no_match(regexp, string)
    class TestCase (line 8) | class TestCase < Minitest::Unit::TestCase; end
      method setup (line 14) | def setup(&block)
      method test (line 21) | def test(name, &block)
      method assert_no_match (line 27) | def assert_no_match(regexp, string)
    class TestCase (line 12) | class TestCase
      method setup (line 14) | def setup(&block)
      method test (line 21) | def test(name, &block)
      method assert_no_match (line 27) | def assert_no_match(regexp, string)

FILE: test/test_helper.rb
  type Whenever::TestHelpers (line 9) | module Whenever::TestHelpers
    function new_job (line 11) | def new_job(options={})
    function parse_time (line 15) | def parse_time(time = nil, task = nil, at = nil, options = {})
    function two_hours (line 19) | def two_hours
    function assert_months_and_days_and_hours_and_minutes_equals (line 23) | def assert_months_and_days_and_hours_and_minutes_equals(expected, time...
    function assert_days_and_hours_and_minutes_equals (line 29) | def assert_days_and_hours_and_minutes_equals(expected, time, options =...
    function assert_hours_and_minutes_equals (line 35) | def assert_hours_and_minutes_equals(expected, time, options = {})
    function assert_minutes_equals (line 41) | def assert_minutes_equals(expected, time, options = {})
    function lines_without_empty_line (line 46) | def lines_without_empty_line(lines)

FILE: test/unit/capistrano_support_test.rb
  class CapistranoSupportTestSubject (line 4) | class CapistranoSupportTestSubject
  class CapistranoTestCase (line 8) | class CapistranoTestCase < Whenever::TestCase
  class CapistranoSupportTest (line 17) | class CapistranoSupportTest < CapistranoTestCase
  class ServerRolesTest (line 73) | class ServerRolesTest < CapistranoTestCase

FILE: test/unit/cron_test.rb
  class CronTest (line 3) | class CronTest < Whenever::TestCase
  class CronParseHoursTest (line 36) | class CronParseHoursTest < Whenever::TestCase
  class CronParseDaysTest (line 104) | class CronParseDaysTest < Whenever::TestCase
  class CronParseMonthsTest (line 168) | class CronParseMonthsTest < Whenever::TestCase
  class CronParseYearTest (line 247) | class CronParseYearTest < Whenever::TestCase
  class CronParseDaysOfWeekTest (line 316) | class CronParseDaysOfWeekTest < Whenever::TestCase
  class CronParseShortcutsTest (line 354) | class CronParseShortcutsTest < Whenever::TestCase
  class CronParseRubyTimeTest (line 389) | class CronParseRubyTimeTest < Whenever::TestCase
  class CronParseRawTest (line 395) | class CronParseRawTest < Whenever::TestCase

FILE: test/unit/job_test.rb
  class JobTest (line 3) | class JobTest < Whenever::TestCase
  class JobWithQuotesTest (line 66) | class JobWithQuotesTest < Whenever::TestCase
  class JobWithJobTemplateTest (line 94) | class JobWithJobTemplateTest < Whenever::TestCase
Condensed preview — 55 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (165K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 211,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"bundler\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n  - pack"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 2675,
    "preview": "name: Ruby CI\non:\n  pull_request:\n  push:\n    branches: [ main ]\npermissions:\n  contents: read\njobs:\n  test:\n    runs-on"
  },
  {
    "path": ".gitignore",
    "chars": 138,
    "preview": ".bundle/\n/.yardoc\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\ntmp/\n\n/Gemfile.lock\ngemfiles/*.lock\n.ruby-version\n\n.DS"
  },
  {
    "path": "Appraisals",
    "chars": 1258,
    "preview": "if RUBY_VERSION < \"3.0\"\n  appraise 'activesupport5.0' do\n    gem \"activesupport\", \"~> 5.0.0\"\n  end\n\n  appraise 'activesu"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 14641,
    "preview": "### unreleased\n\n\n### 1.1.2 / January 18, 2026\n\n* Add description as comment in crontab https://github.com/javan/whenever"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1180,
    "preview": "## How to contribute to Whenever\n\n#### **Did you find a bug?**\n\n* **Ensure the bug was not already reported** by searchi"
  },
  {
    "path": "Gemfile",
    "chars": 179,
    "preview": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\n\n# Specify your gem's dependencies in whenever.gemspec\ngems"
  },
  {
    "path": "LICENSE",
    "chars": 1058,
    "preview": "Copyright (c) 2017 Javan Makhmali\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this "
  },
  {
    "path": "Makefile",
    "chars": 65,
    "preview": ".DEFAULT_GOAL := test\n\ntest:\n\tbundle exec rake test\n.PHONY: test\n"
  },
  {
    "path": "README.md",
    "chars": 11785,
    "preview": "Whenever is a Ruby gem that provides a clear syntax for writing and deploying cron jobs.\n\n### Installation\n\n```sh\n$ gem "
  },
  {
    "path": "Rakefile",
    "chars": 234,
    "preview": "require 'bundler/gem_tasks'\nrequire 'rake/testtask'\n\nRake::TestTask.new(:test) do |test|\n  test.libs      << 'lib' << 't"
  },
  {
    "path": "bin/whenever",
    "chars": 1926,
    "preview": "#!/usr/bin/env ruby\n\nrequire 'optparse'\nrequire 'whenever'\n\noptions = {}\n\nOptionParser.new do |opts|\n  opts.banner = \"Us"
  },
  {
    "path": "bin/wheneverize",
    "chars": 1638,
    "preview": "#!/usr/bin/env ruby\n\n# This file is based heavily on Capistrano's `capify` command\n\nrequire 'optparse'\nrequire 'fileutil"
  },
  {
    "path": "gemfiles/activesupport5.0.gemfile",
    "chars": 178,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport5.1.gemfile",
    "chars": 178,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport5.2.gemfile",
    "chars": 178,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport6.0.gemfile",
    "chars": 282,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport6.1.gemfile",
    "chars": 282,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport7.0.gemfile",
    "chars": 178,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport7.1.gemfile",
    "chars": 178,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport7.2.gemfile",
    "chars": 178,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport8.0.gemfile",
    "chars": 178,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "gemfiles/activesupport8.1.gemfile",
    "chars": 178,
    "preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"rake\"\ngem \"mocha\"\ngem \"minitest\"\ngem \"apprai"
  },
  {
    "path": "lib/whenever/capistrano/v2/hooks.rb",
    "chars": 292,
    "preview": "require \"whenever/capistrano/v2/recipes\"\n\nCapistrano::Configuration.instance(:must_exist).load do\n  # Write the new cron"
  },
  {
    "path": "lib/whenever/capistrano/v2/recipes.rb",
    "chars": 1753,
    "preview": "require 'whenever/capistrano/v2/support'\n\nCapistrano::Configuration.instance(:must_exist).load do\n  Whenever::Capistrano"
  },
  {
    "path": "lib/whenever/capistrano/v2/support.rb",
    "chars": 1531,
    "preview": "module Whenever\n  module CapistranoSupport\n    def self.load_into(capistrano_configuration)\n      capistrano_configurati"
  },
  {
    "path": "lib/whenever/capistrano/v3/tasks/whenever.rake",
    "chars": 1865,
    "preview": "namespace :whenever do\n  def setup_whenever_task(*args, &block)\n    args = Array(fetch(:whenever_command)) + args\n\n    o"
  },
  {
    "path": "lib/whenever/capistrano.rb",
    "chars": 265,
    "preview": "require 'capistrano/version'\n\nif defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).release >= Gem::"
  },
  {
    "path": "lib/whenever/command_line.rb",
    "chars": 5929,
    "preview": "require 'fileutils'\n\nmodule Whenever\n  class CommandLine\n    def self.execute(options={})\n      new(options).run\n    end"
  },
  {
    "path": "lib/whenever/cron.rb",
    "chars": 6623,
    "preview": "require 'chronic'\n\nmodule Whenever\n  module Output\n    class Cron\n      DAYS = %w(sun mon tue wed thu fri sat)\n      MON"
  },
  {
    "path": "lib/whenever/job.rb",
    "chars": 1818,
    "preview": "require 'shellwords'\n\nmodule Whenever\n  class Job\n    attr_reader :at, :roles, :mailto, :description\n\n    def initialize"
  },
  {
    "path": "lib/whenever/job_list.rb",
    "chars": 5522,
    "preview": "module Whenever\n  class JobList\n    attr_reader :roles\n\n    def initialize(options)\n      @jobs, @env, @set_variables, @"
  },
  {
    "path": "lib/whenever/numeric.rb",
    "chars": 349,
    "preview": "Numeric.class_eval do\n  def respond_to?(method, include_private = false)\n    super || Whenever::NumericSeconds.public_me"
  },
  {
    "path": "lib/whenever/numeric_seconds.rb",
    "chars": 694,
    "preview": "module Whenever\n  class NumericSeconds\n    attr_reader :number\n\n    def self.seconds(number, units)\n      new(number).se"
  },
  {
    "path": "lib/whenever/os.rb",
    "chars": 103,
    "preview": "module Whenever\n  module OS\n    def self.solaris?\n      (/solaris/ =~ RUBY_PLATFORM)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/whenever/output_redirection.rb",
    "chars": 1405,
    "preview": "module Whenever\n  module Output\n    class Redirection\n      def initialize(output)\n        @output = output\n      end\n  "
  },
  {
    "path": "lib/whenever/setup.rb",
    "chars": 1260,
    "preview": "# Environment variable defaults to RAILS_ENV\nset :environment_variable, \"RAILS_ENV\"\n# Environment defaults to the value "
  },
  {
    "path": "lib/whenever/version.rb",
    "chars": 40,
    "preview": "module Whenever\n  VERSION = '1.1.2'\nend\n"
  },
  {
    "path": "lib/whenever.rb",
    "chars": 880,
    "preview": "require \"whenever/version\"\nrequire 'whenever/numeric'\nrequire 'whenever/numeric_seconds'\nrequire 'whenever/job_list'\nreq"
  },
  {
    "path": "test/functional/command_line_test.rb",
    "chars": 15940,
    "preview": "require 'test_helper'\n\nclass CommandLineWriteTest < Whenever::TestCase\n  setup do\n    Time.stubs(:now).returns(Time.new("
  },
  {
    "path": "test/functional/output_at_test.rb",
    "chars": 6594,
    "preview": "require 'test_helper'\n\nclass OutputAtTest < Whenever::TestCase\n  test \"weekday at a (single) given time\" do\n    output ="
  },
  {
    "path": "test/functional/output_default_defined_jobs_test.rb",
    "chars": 8319,
    "preview": "require 'test_helper'\n\nclass OutputDefaultDefinedJobsTest < Whenever::TestCase\n\n  # command\n\n  test \"A plain command wit"
  },
  {
    "path": "test/functional/output_defined_job_test.rb",
    "chars": 3162,
    "preview": "require 'test_helper'\n\nclass OutputDefinedJobTest < Whenever::TestCase\n  test \"defined job with a :task\" do\n    output ="
  },
  {
    "path": "test/functional/output_description_test.rb",
    "chars": 668,
    "preview": "require 'test_helper'\n\nclass OutputDescriptionTest < Whenever::TestCase\n  test \"single line description\" do\n    output ="
  },
  {
    "path": "test/functional/output_env_test.rb",
    "chars": 651,
    "preview": "require 'test_helper'\n\nclass OutputEnvTest < Whenever::TestCase\n  setup do\n    @output = Whenever.cron \\\n    <<-file\n   "
  },
  {
    "path": "test/functional/output_jobs_for_roles_test.rb",
    "chars": 1806,
    "preview": "require 'test_helper'\n\nclass OutputJobsForRolesTest < Whenever::TestCase\n  test \"one role requested and specified on the"
  },
  {
    "path": "test/functional/output_jobs_with_mailto_test.rb",
    "chars": 5930,
    "preview": "require 'test_helper'\n\nclass OutputJobsWithMailtoTest < Whenever::TestCase\n  test \"defined job with a mailto argument\" d"
  },
  {
    "path": "test/functional/output_redirection_test.rb",
    "chars": 6631,
    "preview": "require 'test_helper'\n\nclass OutputRedirectionTest < Whenever::TestCase\n  test \"command when the output is set to nil\" d"
  },
  {
    "path": "test/test_case.rb",
    "chars": 648,
    "preview": "module Whenever\n  require 'minitest/autorun'\n  begin\n    # 2.0.0\n    class TestCase < Minitest::Test; end\n  rescue NameE"
  },
  {
    "path": "test/test_helper.rb",
    "chars": 1647,
    "preview": "require 'whenever'\nrequire 'test_case'\nrequire 'mocha/minitest'\nbegin\n  require 'active_support/all'\nrescue LoadError\nen"
  },
  {
    "path": "test/unit/capistrano_support_test.rb",
    "chars": 6584,
    "preview": "require 'test_helper'\nrequire 'whenever/capistrano/v2/support'\n\nclass CapistranoSupportTestSubject\n  include Whenever::C"
  },
  {
    "path": "test/unit/cron_test.rb",
    "chars": 18239,
    "preview": "require 'test_helper'\n\nclass CronTest < Whenever::TestCase\n  should \"raise if less than 1 minute\" do\n    assert_raises A"
  },
  {
    "path": "test/unit/executable_test.rb",
    "chars": 3741,
    "preview": "require 'test_helper'\n\ndescribe 'Executable' do\n  describe 'bin/wheneverize' do\n    describe 'ARGV is not empty' do\n    "
  },
  {
    "path": "test/unit/job_test.rb",
    "chars": 3986,
    "preview": "require 'test_helper'\n\nclass JobTest < Whenever::TestCase\n  should \"return the :at set when #at is called\" do\n    assert"
  },
  {
    "path": "whenever.gemspec",
    "chars": 1099,
    "preview": "# frozen_string_literal: true\n\nrequire_relative \"lib/whenever/version\"\n\nGem::Specification.new do |spec|\n  spec.name    "
  }
]

About this extraction

This page contains the full source code of the javan/whenever GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 55 files (151.3 KB), approximately 45.3k tokens, and a symbol index with 148 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!