[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: bundler\n    directory: \"/\"\n    schedule:\n      interval: monthly\n      time: \"16:00\"\n      timezone: America/Los_Angeles\n    open-pull-requests-limit: 10\n    labels:\n      - \"🏠 Housekeeping\"\n  - package-ecosystem: github-actions\n    directory: \"/\"\n    schedule:\n      interval: monthly\n      time: \"16:00\"\n      timezone: America/Los_Angeles\n    open-pull-requests-limit: 10\n    labels:\n      - \"🏠 Housekeeping\"\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "name-template: \"$RESOLVED_VERSION\"\ntag-template: \"v$RESOLVED_VERSION\"\ncategories:\n  - title: \"⚠️ Breaking Changes\"\n    label: \"⚠️ Breaking\"\n  - title: \"✨ New Features\"\n    label: \"✨ Feature\"\n  - title: \"🐛 Bug Fixes\"\n    label: \"🐛 Bug Fix\"\n  - title: \"📚 Documentation\"\n    label: \"📚 Docs\"\n  - title: \"🏠 Housekeeping\"\n    label: \"🏠 Housekeeping\"\nversion-resolver:\n  minor:\n    labels:\n      - \"⚠️ Breaking\"\n      - \"✨ Feature\"\n  default: patch\nchange-template: \"- $TITLE (#$NUMBER) @$AUTHOR\"\nno-changes-template: \"- No changes\"\ntemplate: |\n  $CHANGES\n\n  **Full Changelog:** https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  pull_request:\n  push:\n    branches:\n      - main\njobs:\n  rubocop:\n    name: \"RuboCop\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ruby\n          bundler-cache: true\n      - run: bundle exec rubocop\n  test:\n    name: \"Test / Ruby ${{ matrix.ruby }}\"\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        ruby: [\"3.2\", \"3.3\", \"3.4\", \"4.0\", \"head\"]\n    steps:\n      - uses: actions/checkout@v6\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby }}\n          bundler-cache: true\n      - run: bundle exec rake test\n  test_legacy:\n    name: \"Test / Ruby ${{ matrix.ruby }}\"\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        ruby: [\"2.5\", \"2.6\", \"2.7\", \"3.0\", \"3.1\"]\n    env:\n      BUNDLE_GEMFILE: gemfiles/legacy.gemfile\n    steps:\n      - uses: actions/checkout@v6\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby }}\n          bundler-cache: true\n      - run: bundle exec rake test\n  test_all:\n    name: \"Test / Ruby (All)\"\n    runs-on: ubuntu-latest\n    needs: [\"test\", \"test_legacy\"]\n    if: always()\n    steps:\n      - name: All tests ok\n        if: ${{ !(contains(needs.*.result, 'failure')) }}\n        run: exit 0\n      - name: Some tests failed\n        if: ${{ contains(needs.*.result, 'failure') }}\n        run: exit 1\n"
  },
  {
    "path": ".github/workflows/push.yml",
    "content": "name: Release Drafter\non:\n  push:\n    branches:\n      - main\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: release-drafter/release-drafter@v7\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/.yardoc\n/Gemfile.lock\n/_yardoc/\n/coverage/\n/doc/\n/log/\n/pkg/\n/spec/reports/\n/tmp/\n*.bundle\n*.so\n*.o\n*.a\nmkmf.log\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "inherit_from: .rubocop_todo.yml\n\nAllCops:\n  DisplayCopNames: true\n  DisplayStyleGuide: true\n  NewCops: disable\n  SuggestExtensions: false\n  TargetRubyVersion: 2.5\n  Exclude:\n    - \"*.gemspec\"\n    - \"vendor/**/*\"\n\nLayout/EndOfLine:\n  Enabled: false\n\nLayout/LineLength:\n  Exclude:\n    - \"test/airbrussh/formatter_test.rb\"\n\nLayout/SpaceAroundEqualsInParameterDefault:\n  EnforcedStyle: no_space\n\nMetrics/AbcSize:\n  Exclude:\n    - \"test/**/*\"\n\nMetrics/MethodLength:\n  Exclude:\n    - \"test/**/*\"\n\nMetrics/ClassLength:\n  Exclude:\n    - \"test/**/*\"\n\nStyle/BarePercentLiterals:\n  EnforcedStyle: percent_q\n\nStyle/ClassAndModuleChildren:\n  Enabled: false\n\nStyle/Documentation:\n  Enabled: false\n\nStyle/DoubleNegation:\n  Enabled: false\n\nStyle/Encoding:\n  Enabled: false\n\nStyle/FrozenStringLiteralComment:\n  Enabled: false\n\nStyle/HashSyntax:\n  Exclude:\n    - \"Gemfile\"\n    - \"gemfiles/*.gemfile\"\n  EnforcedStyle: hash_rockets\n\nStyle/StringLiterals:\n  EnforcedStyle: double_quotes\n\nStyle/TrivialAccessors:\n  AllowPredicates: true\n"
  },
  {
    "path": ".rubocop_todo.yml",
    "content": "# This configuration was generated by\n# `rubocop --auto-gen-config`\n# on 2025-11-28 18:11:46 UTC using RuboCop version 1.81.7.\n# The point is for the user to remove these configuration records\n# one by one as the offenses are removed from the code base.\n# Note that changes in the inspected code, or installation of new\n# versions of RuboCop, may require this file to be generated again.\n\n# Offense count: 7\n# This cop supports safe autocorrection (--autocorrect).\nLayout/EmptyLineAfterGuardClause:\n  Exclude:\n    - \"lib/airbrussh.rb\"\n    - \"lib/airbrussh/configuration.rb\"\n    - \"lib/airbrussh/console_formatter.rb\"\n    - \"lib/airbrussh/rake/context.rb\"\n\n# Offense count: 1\n# This cop supports safe autocorrection (--autocorrect).\n# Configuration parameters: AllowAliasSyntax, AllowedMethods.\n# AllowedMethods: alias_method, public, protected, private\nLayout/EmptyLinesAroundAttributeAccessor:\n  Exclude:\n    - \"lib/airbrussh/console_formatter.rb\"\n\n# Offense count: 4\n# This cop supports safe autocorrection (--autocorrect).\n# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.\n# SupportedHashRocketStyles: key, separator, table\n# SupportedColonStyles: key, separator, table\n# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit\nLayout/HashAlignment:\n  Exclude:\n    - \"lib/airbrussh/colors.rb\"\n\n# Offense count: 1\n# This cop supports unsafe autocorrection (--autocorrect-all).\nLint/NonDeterministicRequireOrder:\n  Exclude:\n    - \"test/minitest_helper.rb\"\n\n# Offense count: 1\n# This cop supports unsafe autocorrection (--autocorrect-all).\n# Configuration parameters: EnforcedStyleForLeadingUnderscores.\n# SupportedStylesForLeadingUnderscores: disallowed, required, optional\nNaming/MemoizedInstanceVariableName:\n  Exclude:\n    - \"lib/airbrussh/log_file_formatter.rb\"\n\n# Offense count: 1\n# This cop supports safe autocorrection (--autocorrect).\n# Configuration parameters: PreferredName.\nNaming/RescuedExceptionsVariableName:\n  Exclude:\n    - \"test/airbrussh/formatter_test.rb\"\n\n# Offense count: 1\nSecurity/Open:\n  Exclude:\n    - \"lib/airbrussh/capistrano/tasks.rb\"\n\n# Offense count: 1\n# This cop supports unsafe autocorrection (--autocorrect-all).\n# Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols, AllowModifiersOnAttrs, AllowModifiersOnAliasMethod.\n# SupportedStyles: inline, group\nStyle/AccessModifierDeclarations:\n  Exclude:\n    - \"lib/airbrussh/colors.rb\"\n\n# Offense count: 2\n# This cop supports safe autocorrection (--autocorrect).\nStyle/ExpandPathArguments:\n  Exclude:\n    - \"test/minitest_helper.rb\"\n\n# Offense count: 2\n# This cop supports safe autocorrection (--autocorrect).\n# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns.\n# SupportedStyles: annotated, template, unannotated\nStyle/FormatStringToken:\n  EnforcedStyle: unannotated\n\n# Offense count: 1\n# This cop supports unsafe autocorrection (--autocorrect-all).\n# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.\n# SupportedStyles: predicate, comparison\nStyle/NumericPredicate:\n  Exclude:\n    - \"spec/**/*\"\n    - \"lib/airbrussh/console.rb\"\n\n# Offense count: 1\n# This cop supports safe autocorrection (--autocorrect).\nStyle/RedundantBegin:\n  Exclude:\n    - \"test/airbrussh/formatter_test.rb\"\n\n# Offense count: 1\n# This cop supports safe autocorrection (--autocorrect).\nStyle/RedundantFileExtensionInRequire:\n  Exclude:\n    - \"test/sshkit/formatter/airbrussh_test.rb\"\n\n# Offense count: 1\n# This cop supports safe autocorrection (--autocorrect).\nStyle/RedundantFreeze:\n  Exclude:\n    - \"lib/airbrussh/version.rb\"\n\n# Offense count: 4\n# This cop supports safe autocorrection (--autocorrect).\nStyle/RedundantRegexpEscape:\n  Exclude:\n    - \"test/airbrussh/formatter_test.rb\"\n\n# Offense count: 1\n# This cop supports safe autocorrection (--autocorrect).\nStyle/StderrPuts:\n  Exclude:\n    - \"lib/airbrussh/configuration.rb\"\n\n# Offense count: 1\n# This cop supports unsafe autocorrection (--autocorrect-all).\n# Configuration parameters: Mode.\nStyle/StringConcatenation:\n  Exclude:\n    - \"lib/airbrussh/console.rb\"\n\n# Offense count: 3\n# This cop supports safe autocorrection (--autocorrect).\n# Configuration parameters: EnforcedStyle.\n# SupportedStyles: single_quotes, double_quotes\nStyle/StringLiteralsInInterpolation:\n  Exclude:\n    - \"test/airbrussh/formatter_test.rb\"\n\n# Offense count: 2\n# This cop supports safe autocorrection (--autocorrect).\n# Configuration parameters: MinSize.\n# SupportedStyles: percent, brackets\nStyle/SymbolArray:\n  EnforcedStyle: brackets\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Release notes for this project are kept here: https://github.com/mattbrictson/airbrussh/releases\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect all\npeople who contribute through reporting issues, posting feature requests,\nupdating documentation, submitting pull requests or patches, and other\nactivities.\n\nWe are committed to making participation in this project a harassment-free\nexperience for everyone, regardless of level of experience, gender, gender\nidentity and expression, sexual orientation, disability, personal appearance,\nbody size, race, ethnicity, age, or religion.\n\nExamples of unacceptable behavior by participants include the use of sexual\nlanguage or imagery, derogatory comments or personal attacks, trolling, public\nor private harassment, insults, or other unprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct. Project maintainers who do not\nfollow the Code of Conduct may be removed from the project team.\n\nThis code of conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by opening an issue or contacting one or more of the project\nmaintainers.\n\nThis Code of Conduct is adapted from the Contributor Covenant\n(http://contributor-covenant.org), version 1.1.0, available at\nhttp://contributor-covenant.org/version/1/1/0/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to airbrussh\n\nHave a feature idea, bug fix, or refactoring suggestion? Contributions are welcome!\n\n## Pull requests\n\n1. Check [Issues][] to see if your contribution has already been discussed and/or implemented.\n2. If not, open an issue to discuss your contribution. I won't accept all changes and do not want to waste your time.\n3. Once you have the :thumbsup:, fork the repo, make your changes, and open a PR.\n\n## Coding guidelines\n\n* This project has a coding style enforced by [RuboCop][]. Use hash rockets and double-quoted strings, and otherwise try to follow the [Ruby style guide][style].\n* Writing tests is strongly encouraged! This project uses Minitest.\n\n## Getting started\n\nAfter checking out the airbrussh repo, run `bin/setup` to install dependencies. Run `rake` to execute airbrussh's tests and RuboCop checks.\n\nAirbrussh is designed to work against multiple versions of SSHKit and Ruby. In order to test this, we use the environment variable `sshkit` in order to run the tests against a specific version. The combinations of sshkit and ruby we support are specified in [.github/workflows/ci.yml](.github/workflows/ci.yml).\n\n[Issues]: https://github.com/mattbrictson/airbrussh/issues\n[RuboCop]: https://github.com/rubocop/rubocop\n[style]: https://github.com/rubocop/ruby-style-guide\n"
  },
  {
    "path": "Gemfile",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"coveralls_reborn\", \"~> 0.29.0\"\ngem \"irb\"\ngem \"minitest\", \"~> 6.0\"\ngem \"minitest-reporters\", \"~> 1.1\"\ngem \"mocha\", \"~> 3.0\"\ngem \"rake\", \"~> 13.0\"\ngem \"rubocop\", \"1.86.0\"\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2026 Matt Brictson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Airbrussh\n\n[![Gem Version](https://badge.fury.io/rb/airbrussh.svg)](http://badge.fury.io/rb/airbrussh)\n[![Build Status](https://github.com/mattbrictson/airbrussh/actions/workflows/ci.yml/badge.svg)](https://github.com/mattbrictson/airbrussh/actions/workflows/ci.yml)\n[![Build status](https://ci.appveyor.com/api/projects/status/h052rlq54sne3md6/branch/main?svg=true)](https://ci.appveyor.com/project/mattbrictson/airbrussh/branch/main)\n[![Coverage Status](https://coveralls.io/repos/mattbrictson/airbrussh/badge.svg?branch=main)](https://coveralls.io/r/mattbrictson/airbrussh?branch=main)\n\n\nAirbrussh is a concise log formatter for Capistrano and SSHKit. It displays well-formatted, useful log output that is easy to read. Airbrussh also saves Capistrano's verbose output to a separate log file just in case you need additional details for troubleshooting.\n\n**As of April 2016, Airbrussh is bundled with Capistrano 3.5, and is Capistrano's default formatter! There is nothing additional to install or enable.** Continue reading to learn more about Airbrussh's features and configuration options.\n\nIf you aren't yet using Capistrano 3.5 (or wish to use Airbrussh with SSHKit directly), refer to the [advanced/legacy usage](#advancedlegacy-usage) section for installation instructions.\n\n![Sample output](https://raw.github.com/mattbrictson/airbrussh/HEAD/demo.gif)\n\nFor more details on how exactly Airbrussh affects Capistrano's output and the reasoning behind it, check out the blog post: [Introducing Airbrussh](https://mattbrictson.com/airbrussh).\n\n-----\n\n* [Usage](#usage)\n* [Configuration](#configuration)\n* [FAQ](#faq)\n* [Advanced/legacy usage](#advancedlegacy-usage)\n\n## Usage\n\nAirbrussh is enabled by default in Capistrano 3.5 and newer. To manually enable Airbrussh (for example, when upgrading an existing project), set the Capistrano format like this:\n\n```ruby\n# In deploy.rb\nset :format, :airbrussh\n```\n\n### What's displayed\n\nWhen you run a Capistrano command, Airbrussh provides the following information in its output:\n\n![Sample output](https://raw.github.com/mattbrictson/airbrussh/HEAD/formatting.png)\n\n* Name of Capistrano task being executed\n* When each task started (minutes:seconds elapsed since the deploy began)\n* The SSH command-line strings that are executed; for Capistrano tasks that involve running multiple commands, the numeric prefix indicates the command in the sequence, starting from `01`\n* Stdout and stderr output from each command\n* The duration of each command execution, per server\n\n### What's *not* displayed\n\nFor brevity, Airbrussh does not show *everything* that Capistrano is doing. For example, it will omit Capistrano's `test` commands, which can be noisy and confusing. Airbrussh also hides things like environment variables, as well as `cd` and `env` invocations. To see a full audit of Capistrano's execution, including *exactly* what commands were run on each server, look at `log/capistrano.log`.\n\n## Configuration\n\nYou can customize many aspects of Airbrussh's output. In Capistrano 3.5 and newer, this is done via the `:format_options` variable, like this:\n\n```ruby\n# Pass options to Airbrussh\nset :format_options, color: false, truncate: 80\n```\n\nHere are the options you can use, and their effects (note that the defaults may be different depending on where Airbrussh is used; these are the defaults used by Capistrano 3.5):\n\n|Option|Default|Usage|\n|---|---|---|\n|`banner`|`nil`|Provide a string (e.g. \"Capistrano started!\") that will be printed when Capistrano starts up.|\n|`color`|`:auto`|Use `true` or `false` to enable or disable ansi color. If set to `:auto`, Airbrussh automatically uses color based on whether the output is a TTY, or if the SSHKIT_COLOR environment variable is set.|\n|`command_output`|`true`|Set to `:stdout`, `:stderr`, or `true` to display the SSH output received via stdout, stderr, or both, respectively. Set to `false` to not show any SSH output, for a minimal look.|\n|`context`|`Airbrussh::Rake::Context`|Defines the execution context. Targeted towards uses of Airbrussh outside of Rake/Capistrano. Alternate implementations should provide the definition for `current_task_name`, `register_new_command`, and `position`.|\n|`log_file`|`log/capistrano.log`|Capistrano's verbose output is saved to this file to facilitate debugging. Set to `nil` to disable completely.|\n|`truncate`|`:auto`|Set to a number (e.g. 80) to truncate the width of the output to that many characters, or `false` to disable truncation. If `:auto`, output is automatically truncated to the width of the terminal window, if it can be determined.|\n|`task_prefix`|`nil`|A string to prefix to task output. Handy for output collapsing like [buildkite](https://buildkite.com/docs/builds/managing-log-output)'s `---` prefix|\n\n## FAQ\n\n**Airbrussh is not displaying the output of my commands! For example, I run `tail` in one of my capistrano tasks and airbrussh doesn't show anything. How do I fix this?**\n\nMake sure Airbrussh is configured to show SSH output.\n\n```ruby\nset :format_options, command_output: true\n```\n\n**I haven't upgraded to Capistrano 3.5 yet. Can I still use Airbrussh?**\n\nYes! Capistrano 3.4.x is also supported. Refer to the [advanced/legacy usage](#advancedlegacy-usage) section for installation instructions.\n\n**Does Airbrussh work with Capistrano 2?**\n\nNo, Capistrano 3 is required. We recommend Capistrano 3.4.0 or higher. Capistrano 3.5.0 and higher have Airbrussh enabled by default, with no installation needed.\n\n**Does Airbrussh work with JRuby?**\n\nJRuby is not officially supported or tested, but may work. You must disable automatic truncation to work around a known bug in the JRuby 9.0 standard library. See [#62](https://github.com/mattbrictson/airbrussh/issues/62) for more details.\n\n```ruby\nset :format_options, truncate: false\n```\n\n**I have a question that’s not answered here or elsewhere in the README.**\n\nPlease [open a GitHub issue](https://github.com/mattbrictson/airbrussh/issues/new) and we’ll be happy to help!\n\n## Advanced/legacy usage\n\nAlthough Airbrussh is built into Capistrano 3.5.0 and higher, it is also available as a plug-in for older versions. Airbrussh has been tested with MRI 1.9+, Capistrano 3.4.0+, and SSHKit 1.6.1+.\n\n### Capistrano 3.4.x\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"airbrussh\", require: false\n```\n\nAnd then execute:\n\n    $ bundle\n\nFinally, add this line to your application's Capfile:\n\n```ruby\nrequire \"airbrussh/capistrano\"\n```\n\n**Important:** explicitly setting Capistrano's `:format` option in your deploy.rb will override airbrussh. Remove this line if you have it:\n\n```ruby\n# Remove this\nset :format, :pretty\n```\n\nCapistrano 3.4.x doesn't have the `:format_options` configuration system, so you will need to configure Airbrussh using this technique:\n\n```ruby\nAirbrussh.configure do |config|\n  config.color = false\n  config.command_output = true\n  # etc.\nend\n```\n\nRefer to the [configuration](#configuration) section above for the list of supported options.\n\n### SSHKit\n\nIf you are using SSHKit directly (i.e. without Capistrano), you can use Airbrussh like this:\n\n```ruby\nrequire \"airbrussh\"\nSSHKit.config.output = Airbrussh::Formatter.new($stdout)\n\n# You can also pass configuration options like this\nSSHKit.config.output = Airbrussh::Formatter.new($stdout, color: false)\n```\n\n## History\n\nAirbrussh started life as custom logging code within the [capistrano-mb][] collection of opinionated Capistrano recipes. In February 2015, the logging code was refactored into a standalone gem with its own configuration and documentation, and renamed `airbrussh`. In February 2016, Airbrussh was added as the default formatter in Capistrano 3.5.0.\n\n## Roadmap\n\nAirbrussh now has a stable feature set, excellent test coverage, is being used for production deployments, and has reached 1.0.0! If you have ideas for improvements to Airbrussh, please open a [GitHub issue](https://github.com/mattbrictson/airbrussh/issues/new).\n\n## Contributing\n\nContributions are welcome! Read [CONTRIBUTING.md](CONTRIBUTING.md) to get started.\n\n[capistrano-mb]: https://github.com/mattbrictson/capistrano-mb\n"
  },
  {
    "path": "Rakefile",
    "content": "require \"bundler/gem_tasks\"\n\nrequire \"rake/testtask\"\nRake::TestTask.new(:test) do |t|\n  t.libs << \"test\"\n  t.libs << \"lib\"\n  t.test_files = FileList[\"test/**/*_test.rb\"]\nend\n\nbegin\n  require \"rubocop/rake_task\"\n  RuboCop::RakeTask.new\n  task :default => [:test, :rubocop]\nrescue LoadError\n  task :default => :test\nend\n\n# == \"rake release\" enhancements ==============================================\n\nRake::Task[\"release\"].enhance do\n  puts \"Don't forget to publish the release on GitHub!\"\n  system \"open https://github.com/mattbrictson/airbrussh/releases\"\nend\n\ntask :disable_overcommit do\n  ENV[\"OVERCOMMIT_DISABLE\"] = \"1\"\nend\n\nRake::Task[:build].enhance [:disable_overcommit]\n\ntask :verify_gemspec_files do\n  git_files = `git ls-files -z`.split(\"\\x0\")\n  gemspec_files = Gem::Specification.load(\"airbrussh.gemspec\").files.sort\n  ignored_by_git = gemspec_files - git_files\n  next if ignored_by_git.empty?\n\n  raise <<-ERROR.gsub(/^\\s+/, \"\")\n\n    The `spec.files` specified in airbrussh.gemspec include the following files\n    that are being ignored by git. Did you forget to add them to the repo? If\n    not, you may need to delete these files or modify the gemspec to ensure\n    that they are not included in the gem by mistake:\n\n    #{ignored_by_git.join(\"\\n\").gsub(/^/, '  ')}\n\n  ERROR\nend\n\nRake::Task[:build].enhance [:verify_gemspec_files]\n"
  },
  {
    "path": "UPGRADING-CAP-3.5.md",
    "content": "# Capistrano 3.5 upgrade guide for existing Airbrussh users\n\nIf you have been using Airbrussh with Capistrano, and are upgrading to Capistrano 3.5, then this guide is for you.\n\n## What changed?\n\n* Airbrussh is built into Capistrano starting with Capistrano 3.5.0, and is enabled by default.\n* Capistrano 3.5 initializes Airbrussh with a default configuration that is different than Airbrussh+Capistrano 3.4.0.\n* In Capistrano 3.5, `set :format_options, ...` is now the preferred mechanism for configuring Airbrussh.\n\n## How to upgrade\n\n1. Remove `gem \"airbrussh\"` from your Gemfile. Airbrussh is now included automatically by Capistrano.\n2. Likewise, remove `require \"capistrano/airbrussh\"` from your Capfile. Capistrano now does this internally.\n3. Remove `Airbrussh.configure do ... end` from your deploy.rb and replace with `set :format_options, ...` (see below).\n\n## New configuration system\n\nIn Capistrano 3.5, Airbrussh is configured by assigning a configuration Hash to the `:format_options` variable. Here is a comparison of the old and new syntaxes:\n\n```ruby\n# Old syntax\nAirbrussh.configure do |config|\n  config.color = false\nend\n\n# New syntax\nset :format_options, color: false\n```\n\nAlthough the syntax is different, the names of the configuration keys have not changed.\n\n## New defaults\n\nCapistrano 3.5.0 changes Airbrussh defaults as follows:\n\n* `banner: false`\n* `command_output: true`\n\nTherefore, after upgrading to Capistrano 3.5.0, you may notice Airbrussh's output has changed and become more verbose. To restore the defaults to what you were used to in older versions, do this:\n\n```ruby\nset :format_options, banner: :auto, command_output: false\n```\n\n## Trouble?\n\nIf you have any Airbrussh-related trouble with the upgrade, please [open an issue](https://github.com/mattbrictson/airbrussh/issues).\n"
  },
  {
    "path": "airbrussh.gemspec",
    "content": "# coding: utf-8\nlib = File.expand_path(\"../lib\", __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire \"airbrussh/version\"\n\nGem::Specification.new do |spec|\n  spec.name          = \"airbrussh\"\n  spec.version       = Airbrussh::VERSION\n  spec.license       = \"MIT\"\n  spec.authors       = [\"Matt Brictson\"]\n  spec.email         = [\"airbrussh@mattbrictson.com\"]\n  spec.summary       = \"Airbrussh pretties up your SSHKit and Capistrano output\"\n  spec.description   = \"A replacement log formatter for SSHKit that makes \"\\\n                       \"Capistrano output much easier on the eyes. Just add \"\\\n                       \"Airbrussh to your Capfile and enjoy concise, useful \"\\\n                       \"log output that is easy to read.\"\n  spec.homepage      = \"https://github.com/mattbrictson/airbrussh\"\n  spec.metadata      = {\n    \"bug_tracker_uri\" => \"https://github.com/mattbrictson/airbrussh/issues\",\n    \"changelog_uri\" => \"https://github.com/mattbrictson/airbrussh/releases\",\n    \"source_code_uri\" => \"https://github.com/mattbrictson/airbrussh\",\n    \"homepage_uri\" => \"https://github.com/mattbrictson/airbrussh\"\n  }\n\n  spec.files         = Dir.glob(%w[LICENSE.txt README.md {exe,lib}/**/*]).reject { |f| File.directory?(f) }\n  spec.bindir        = \"exe\"\n  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }\n  spec.require_paths = [\"lib\"]\n\n  spec.required_ruby_version = \">= 2.5\"\n\n  spec.add_dependency \"sshkit\", [\">= 1.6.1\", \"!= 1.7.0\"]\nend\n"
  },
  {
    "path": "appveyor.yml",
    "content": "version: \"{build}\"\nskip_tags: true\nskip_branch_with_pr: true\nenvironment:\n  BUNDLE_GEMFILE: \"gemfiles/legacy.gemfile\"\n  MT_COMPAT: \"1\" # Allows old versions of mocha gem to work with minitest\ninstall:\n  - set PATH=C:\\Ruby26-x64\\bin;%PATH%\n  - bundle install --retry=3\ntest_script:\n  - bundle exec rake\nbuild: off\n"
  },
  {
    "path": "bin/console",
    "content": "#!/usr/bin/env ruby\n\nrequire \"bundler/setup\"\nrequire \"airbrussh\"\n\n# You can add fixtures and/or initialization code here to make experimenting\n# with your gem easier. You can also use a different console, if you like.\n\n# (If you use this, don't forget to add pry to your Gemfile!)\n# require \"pry\"\n# Pry.start\n\nrequire \"irb\"\nIRB.start\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/bin/bash\nset -euo pipefail\nIFS=$'\\n\\t'\n\nbundle install\n\n# Do any other automated setup that you need to do here\n"
  },
  {
    "path": "gemfiles/legacy.gemfile",
    "content": "source \"https://rubygems.org\"\n\ngemspec path: \"..\"\n\ngem \"minitest\", \"~> 5.10\"\ngem \"minitest-reporters\", \"~> 1.1\"\ngem \"mocha\", \"~> 2.1\"\ngem \"rake\", \"~> 13.0\"\n"
  },
  {
    "path": "lib/airbrussh/capistrano/tasks.rb",
    "content": "require \"airbrussh\"\nrequire \"airbrussh/colors\"\nrequire \"airbrussh/console\"\nrequire \"forwardable\"\nrequire \"shellwords\"\n\nmodule Airbrussh\n  module Capistrano\n    # Encapsulates the rake behavior that integrates Airbrussh into Capistrano.\n    # This class allows us to easily test the behavior using a mock to stand in\n    # for the Capistrano DSL.\n    #\n    # See airbrussh/capistrano.rb to see how this class is used.\n    #\n    class Tasks\n      extend Forwardable\n      def_delegators :dsl, :set, :env\n      def_delegators :config, :log_file\n\n      include Airbrussh::Colors\n\n      def initialize(dsl, stderr=$stderr, config=Airbrussh.configuration)\n        @dsl = dsl\n        @stderr = stderr\n        @config = config\n\n        configure\n        warn_if_missing_dsl\n      end\n\n      # Behavior for the rake load:defaults task.\n      def load_defaults\n        require \"sshkit/formatter/airbrussh\"\n        set :format, :airbrussh\n      end\n\n      # Behavior for the rake deploy:failed task.\n      def deploy_failed\n        return unless airbrussh_is_being_used?\n        return if log_file.nil?\n\n        error_line\n        error_line(red(\"** DEPLOY FAILED\"))\n        error_line(yellow(\"** Refer to #{log_file} for details. \"\\\n                          \"Here are the last 20 lines:\"))\n        error_line\n        error_line(tail_log_file)\n      end\n\n      private\n\n      attr_reader :dsl, :stderr, :config\n\n      # Change airbrussh's default configuration to be more appropriate for\n      # capistrano.\n      def configure\n        config.log_file = \"log/capistrano.log\"\n        config.monkey_patch_rake = true\n      end\n\n      # Verify that capistrano and rake DSLs are present\n      def warn_if_missing_dsl\n        return if %w[set namespace task].all? { |m| dsl.respond_to?(m, true) }\n\n        error_line(\n          red(\"WARNING: airbrussh/capistrano must be loaded by Capistrano in \"\\\n              \"order to work.\\nRequire this gem within your application's \"\\\n              \"Capfile, as described here:\\n\"\\\n              \"https://github.com/mattbrictson/airbrussh#installation\")\n        )\n      end\n\n      def err_console\n        @err_console ||= begin\n          # Ensure we do not truncate the error output\n          err_config = config.dup\n          err_config.truncate = false\n          Airbrussh::Console.new(stderr, err_config)\n        end\n      end\n\n      def error_line(line=\"\\n\")\n        line.each_line(&err_console.method(:print_line))\n      end\n\n      # Returns a String containing the last 20 lines of the log file. Since\n      # this method uses a fixed-size buffer, fewer than 20 lines may be\n      # returned in cases where the lines are extremely long.\n      def tail_log_file\n        open(log_file) do |file|\n          file.seek(-[8192, file.size].min, IO::SEEK_END)\n          file.readlines.last(20).join\n        end\n      end\n\n      def airbrussh_is_being_used?\n        # Obtain the current formatter from the SSHKit backend\n        output = env.backend.config.output\n        output.is_a?(Airbrussh::Formatter)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/capistrano.rb",
    "content": "require \"airbrussh/capistrano/tasks\"\n\ntasks = Airbrussh::Capistrano::Tasks.new(self)\n\n# Hook into Capistrano's init process to set the formatter\nnamespace :load do\n  task :defaults do\n    tasks.load_defaults\n  end\nend\n\n# Capistrano failure hook\nnamespace :deploy do\n  task :failed do\n    tasks.deploy_failed\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/colors.rb",
    "content": "module Airbrussh\n  # Very basic support for ANSI color, so that we don't have to rely on\n  # any external dependencies.\n  module Colors\n    ANSI_CODES = {\n      :red    => 31,\n      :green  => 32,\n      :yellow => 33,\n      :blue   => 34,\n      :gray   => 90\n    }.freeze\n\n    # Define red, green, blue, etc. methods that return a copy of the\n    # String that is wrapped in the corresponding ANSI color escape\n    # sequence.\n    ANSI_CODES.each do |name, code|\n      define_method(name) do |string|\n        \"\\e[0;#{code};49m#{string}\\e[0m\"\n      end\n      module_function(name)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/command_formatter.rb",
    "content": "# encoding: UTF-8\n\nrequire \"airbrussh/colors\"\nrequire \"delegate\"\n\nmodule Airbrussh\n  # Decorates an SSHKit Command to add string output helpers and the\n  # command's position within currently executing rake task:\n  #\n  # * position - zero-based position of this command in the list of\n  #              all commands that have been run in the current rake task; in\n  #              some cases this could be nil\n  class CommandFormatter < SimpleDelegator\n    include Airbrussh::Colors\n\n    def initialize(command, position)\n      super(command)\n      @position = position\n    end\n\n    # Prefixes the line with the command number and removes the newline.\n    #\n    # format_output(\"hello\\n\") # => \"01 hello\"\n    #\n    def format_output(line)\n      \"#{number} #{line.chomp}\"\n    end\n\n    # Returns the abbreviated command (in yellow) with the number prefix.\n    #\n    # start_message # => \"01 echo hello\"\n    #\n    def start_message\n      \"#{number} #{yellow(abbreviated)}\"\n    end\n\n    # Returns a green (success) or red (failure) message depending on the\n    # exit status.\n    #\n    # exit_message # => \"✔ 01 user@host 0.084s\"\n    # exit_message # => \"✘ 01 user@host 0.084s\"\n    #\n    def exit_message\n      message = if failure?\n                  red(failure_message)\n                else\n                  green(success_message)\n                end\n      message << \" #{runtime}\"\n    end\n\n    private\n\n    def user_at_host\n      user_str = host.user || (host.ssh_options || {})[:user]\n      host_str = host.hostname\n      [user_str, host_str].compact.join(\"@\")\n    end\n\n    def runtime\n      format(\"%5.3fs\", super)\n    end\n\n    def abbreviated\n      to_s.sub(%r{^/usr/bin/env }, \"\")\n    end\n\n    def number\n      format(\"%02d\", @position.to_i + 1)\n    end\n\n    def success_message\n      \"✔ #{number} #{user_at_host}\"\n    end\n\n    def failure_message\n      \"✘ #{number} #{user_at_host}\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/configuration.rb",
    "content": "require \"airbrussh/colors\"\nrequire \"airbrussh/console_formatter\"\nrequire \"airbrussh/log_file_formatter\"\n\nmodule Airbrussh\n  class Configuration\n    attr_accessor :log_file, :monkey_patch_rake, :color, :truncate, :banner,\n                  :command_output, :task_prefix, :context\n\n    def initialize\n      self.log_file = nil\n      self.monkey_patch_rake = false\n      self.color = :auto\n      self.truncate = :auto\n      self.banner = :auto\n      self.command_output = false\n      self.task_prefix = nil\n      self.context = Airbrussh::Rake::Context\n    end\n\n    def apply_options(options)\n      return self if options.nil?\n\n      options.each do |key, value|\n        if respond_to?(writer = \"#{key}=\")\n          public_send(writer, value)\n        else\n          warn_unrecognized_key(key)\n        end\n      end\n      self\n    end\n\n    def banner_message\n      return nil unless banner\n      return banner unless banner == :auto\n      msg = +\"Using airbrussh format.\"\n      if log_file\n        msg << \"\\n\"\n        msg << \"Verbose output is being written to #{Colors.blue(log_file)}.\"\n      end\n      msg\n    end\n\n    # This returns an array of formatters appropriate for the configuration.\n    # Depending on whether a log file is configured, this could be just the\n    # Airbrussh:ConsoleFormatter, or that plus the LogFileFormatter.\n    def formatters(io)\n      fmts = [Airbrussh::ConsoleFormatter.new(io, self)]\n      fmts.unshift(Airbrussh::LogFileFormatter.new(log_file)) if log_file\n      fmts\n    end\n\n    def show_command_output?(sym)\n      command_output == true || Array(command_output).include?(sym)\n    end\n\n    private\n\n    def warn_unrecognized_key(key)\n      $stderr.puts(\"Ignoring unrecognized Airbrussh option: #{key}\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/console.rb",
    "content": "# encoding: UTF-8\n\nrequire \"io/console\"\n\nmodule Airbrussh\n  # Helper class that wraps an IO object and provides methods for truncating\n  # output, assuming the IO object represents a console window.\n  #\n  # This is useful for writing log messages that will typically show up on\n  # an ANSI color-capable console. When a console is not present (e.g. when\n  # running on a CI server) the output will gracefully degrade.\n  class Console\n    attr_reader :output, :config\n\n    def initialize(output, config=Airbrussh.configuration)\n      @output = output\n      @config = config\n    end\n\n    # Writes to the IO after first truncating the output to fit the console\n    # width. If the underlying IO is not a TTY, ANSI colors are removed from\n    # the output. A newline is always added. Color output can be forced by\n    # setting the SSHKIT_COLOR environment variable.\n    def print_line(obj=\"\")\n      string = obj.to_s\n\n      string = truncate_to_console_width(string) if console_width\n      string = strip_ascii_color(string) unless color_enabled?\n\n      write(string + \"\\n\")\n      output.flush\n    end\n\n    # Writes directly through to the IO with no truncation or color logic.\n    # No newline is added.\n    def write(string)\n      output.write(string || \"\")\n    end\n    alias << write\n\n    def truncate_to_console_width(string)\n      string = (string || \"\").rstrip\n      ellipsis = utf8_supported?(string) ? \"…\" : \"...\"\n      width = console_width\n\n      if strip_ascii_color(string).length > width\n        width -= ellipsis.length\n        string.chop! while strip_ascii_color(string).length > width\n        string << \"#{ellipsis}\\e[0m\"\n      else\n        string\n      end\n    end\n\n    def strip_ascii_color(string)\n      string ||= \"\"\n      string = to_utf8(string) unless string.valid_encoding?\n\n      string.gsub(/\\033\\[[0-9;]*m/, \"\")\n    end\n\n    def console_width\n      width = case (truncate = config.truncate)\n              when :auto\n                IO.console.winsize.last if @output.tty? && IO.console\n              when Integer\n                truncate\n              end\n\n      width if width.to_i > 0\n    end\n\n    private\n\n    def color_enabled?\n      case config.color\n      when true\n        true\n      when :auto\n        ENV[\"SSHKIT_COLOR\"] || @output.tty?\n      else\n        false\n      end\n    end\n\n    def utf8_supported?(string)\n      string.encode(\"UTF-8\").valid_encoding?\n    rescue Encoding::UndefinedConversionError\n      false\n    end\n\n    def to_utf8(string)\n      string.force_encoding(\"ASCII-8BIT\")\n            .encode(\"UTF-8\", :invalid => :replace, :undef => :replace)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/console_formatter.rb",
    "content": "require \"airbrussh/colors\"\nrequire \"airbrussh/command_formatter\"\nrequire \"airbrussh/console\"\nrequire \"airbrussh/rake/context\"\nrequire \"sshkit\"\n\nmodule Airbrussh\n  class ConsoleFormatter < SSHKit::Formatter::Abstract\n    include Airbrussh::Colors\n    extend Forwardable\n\n    attr_reader :config, :context\n    def_delegators :context, :current_task_name, :register_new_command\n\n    def initialize(io, config=Airbrussh.configuration)\n      super(io)\n\n      @config = config\n      @context = config.context.new(config)\n      @console = Airbrussh::Console.new(original_output, config)\n\n      write_banner\n    end\n\n    def write_banner\n      print_line(config.banner_message) if config.banner_message\n    end\n\n    def log_command_start(command)\n      return if debug?(command)\n      first_execution = register_new_command(command)\n      command = decorate(command)\n      print_task_if_changed\n      print_indented_line(command.start_message) if first_execution\n    end\n\n    def log_command_data(command, stream_type, string)\n      return if debug?(command)\n      return unless config.show_command_output?(stream_type)\n      command = decorate(command)\n      string.each_line do |line|\n        print_indented_line(command.format_output(line))\n      end\n    end\n\n    def log_command_exit(command)\n      return if debug?(command)\n      command = decorate(command)\n      print_indented_line(command.exit_message, -2)\n    end\n\n    def write(obj)\n      case obj\n      when SSHKit::Command\n        log_command_start(obj)\n        log_and_clear_command_output(obj, :stderr)\n        log_and_clear_command_output(obj, :stdout)\n        log_command_exit(obj) if obj.finished?\n      when SSHKit::LogMessage\n        write_log_message(obj)\n      end\n    end\n    alias << write\n\n    private\n\n    attr_accessor :last_printed_task\n\n    def write_log_message(log_message)\n      return if debug?(log_message)\n      print_task_if_changed\n      print_indented_line(format_log_message(log_message))\n    end\n\n    def format_log_message(log_message)\n      case log_message.verbosity\n      when SSHKit::Logger::WARN\n        \"#{yellow('WARN')}  #{log_message}\"\n      when SSHKit::Logger::ERROR\n        \"#{red('ERROR')} #{log_message}\"\n      when SSHKit::Logger::FATAL\n        \"#{red('FATAL')} #{log_message}\"\n      else\n        log_message.to_s\n      end\n    end\n\n    # For SSHKit versions up to and including 1.7.1, the stdout and stderr\n    # output was available as attributes on the Command. Print the data for\n    # the specified command and stream if enabled and clear the stream.\n    # (see Airbrussh::Configuration#command_output).\n    def log_and_clear_command_output(command, stream)\n      output = command.public_send(stream)\n      log_command_data(command, stream, output)\n      command.public_send(\"#{stream}=\", \"\")\n    end\n\n    def print_task_if_changed\n      return if current_task_name.nil?\n      return if current_task_name == last_printed_task\n\n      self.last_printed_task = current_task_name\n      print_line(\"#{config.task_prefix}#{clock} #{blue(current_task_name)}\")\n    end\n\n    def clock\n      @start_at ||= Time.now\n      duration = Time.now - @start_at\n\n      minutes = (duration / 60).to_i\n      seconds = (duration - minutes * 60).to_i\n\n      format(\"%02d:%02d\", minutes, seconds)\n    end\n\n    def debug?(obj)\n      obj.verbosity <= SSHKit::Logger::DEBUG\n    end\n\n    def decorate(command)\n      Airbrussh::CommandFormatter.new(command, @context.position(command))\n    end\n\n    def print_line(string)\n      @console.print_line(string)\n    end\n\n    def print_indented_line(string, offset=0)\n      indent = \" \" * (6 + offset)\n      print_line([indent, string].join)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/delegating_formatter.rb",
    "content": "require \"sshkit\"\n\nmodule Airbrussh\n  # This class quacks like an SSHKit::Formatter, but when any formatting\n  # methods are called, it simply forwards them to one more more concrete\n  # formatters. This allows us to split out the responsibilities of\n  # ConsoleFormatter and LogFileFormatter into two separate classes, with\n  # DelegatingFormatter forwarding the logging messages to both at once.\n  #\n  class DelegatingFormatter\n    FORWARD_METHODS = %w[\n      fatal error warn info debug log\n      log_command_start log_command_data log_command_exit\n    ].freeze\n    DUP_AND_FORWARD_METHODS = %w[<< write].freeze\n\n    attr_reader :formatters\n\n    def initialize(formatters)\n      @formatters = formatters\n    end\n\n    FORWARD_METHODS.each do |method|\n      define_method(method) do |*args|\n        formatters.map { |f| f.public_send(method, *args) }.last\n      end\n    end\n\n    # For versions of SSHKit up to and including 1.7.1, the LogfileFormatter\n    # and ConsoleFormatter (and all of SSHKit's built in formatters) clear\n    # the stdout and stderr data in the command obj. Therefore, ensure only\n    # one of the formatters (the last one) gets the original command. This is\n    # also the formatter whose return value is passed to the caller.\n    #\n    DUP_AND_FORWARD_METHODS.each do |method|\n      define_method(method) do |command_or_log_message|\n        formatters[0...-1].each do |f|\n          f.public_send(method, command_or_log_message.dup)\n        end\n        formatters.last.public_send(method, command_or_log_message)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/formatter.rb",
    "content": "require \"airbrussh\"\nrequire \"airbrussh/delegating_formatter\"\n\n# This is the formatter class that conforms to the SSHKit Formatter API and\n# provides the airbrussh functionality to SSHKit. Note however that this class\n# doesn't do much by itself; instead, it delegates to the ConsoleFormatter\n# and (optionally) the LogFileFormatter, which handle the bulk of the logic.\n#\nmodule Airbrussh\n  class Formatter < Airbrussh::DelegatingFormatter\n    def initialize(io, options_or_config_object={})\n      config = ::Airbrussh.configuration(options_or_config_object)\n      # Delegate to ConsoleFormatter and (optionally) LogFileFormatter,\n      # based on the configuration.\n      super(config.formatters(io))\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/log_file_formatter.rb",
    "content": "require \"delegate\"\nrequire \"fileutils\"\nrequire \"logger\"\nrequire \"sshkit\"\n\nmodule Airbrussh\n  # A Pretty formatter that sends its output to a specified log file path.\n  # LogFileFormatter takes care of creating the file (and its parent\n  # directory) if it does not already exist, opens it for appending, and writes\n  # a delimiter message. The file is automatically rotated if it reaches 20 MB.\n  #\n  class LogFileFormatter < SimpleDelegator\n    attr_reader :path\n\n    def initialize(path, formatter_class=SSHKit::Formatter::Pretty)\n      @path = path\n      ensure_directory_exists if path.is_a?(String)\n      super(formatter_class.new(log_file_io))\n      write_delimiter\n    end\n\n    private\n\n    def write_delimiter\n      delimiter = []\n      delimiter << \"-\" * 75\n      delimiter << \"START #{Time.now} cap #{ARGV.join(' ')}\"\n      delimiter << \"-\" * 75\n      delimiter.each do |line|\n        write(SSHKit::LogMessage.new(SSHKit::Logger::INFO, line))\n      end\n    end\n\n    def ensure_directory_exists\n      FileUtils.mkdir_p(File.dirname(path))\n    end\n\n    def log_file_io\n      @io ||= ::Logger.new(path, 1, 20_971_520)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/rake/context.rb",
    "content": "module Airbrussh\n  module Rake\n    # Maintains information about what Rake task is currently being invoked,\n    # in order to be able to decorate SSHKit commands with additional\n    # context-sensitive information. Works via a monkey patch to Rake::Task,\n    # which can be disabled via by setting\n    # Airbrussh.configuration.monkey_patch_rake = false.\n    #\n    # Note that this class is not thread-safe. Normally this is not a problem,\n    # but some Capistrano users are known to use `invoke` to switch the Rake\n    # task in the middle of an SSHKit thread, which causes Context to get very\n    # confused. It such scenarios Context is not reliable and may return `nil`\n    # for the `position` of a command.\n    #\n    class Context\n      class << self\n        attr_accessor :current_task_name\n      end\n\n      def initialize(config=Airbrussh.configuration)\n        @history = []\n        @enabled = config.monkey_patch_rake\n        self.class.install_monkey_patch if enabled?\n      end\n\n      # Returns the name of the currently-executing rake task, if it can be\n      # determined. If monkey patching is disabled, this will be nil.\n      def current_task_name\n        return nil unless enabled?\n        self.class.current_task_name\n      end\n\n      # Update the context when a new command starts by:\n      # * Clearing the command history if the rake task has changed\n      # * Recording the command in the history\n      #\n      # Returns whether or not this command was the first execution\n      # of this command in the current rake task\n      def register_new_command(command)\n        reset_history_if_task_changed\n\n        first_execution = !history.include?(command.to_s)\n        history << command.to_s\n        history.uniq!\n        first_execution\n      end\n\n      # The zero-based position of the specified command in the current rake\n      # task. May be `nil` in certain multi-threaded scenarios, so be careful!\n      def position(command)\n        history.index(command.to_s)\n      end\n\n      if Object.respond_to?(:prepend)\n        module Patch\n          def execute(args=nil)\n            ::Airbrussh::Rake::Context.current_task_name = name.to_s\n            super\n          end\n        end\n\n        def self.install_monkey_patch\n          require \"rake\"\n          return if ::Rake::Task.include?(::Airbrussh::Rake::Context::Patch)\n\n          ::Rake::Task.prepend(::Airbrussh::Rake::Context::Patch)\n        end\n      else\n        def self.install_monkey_patch\n          require \"rake\"\n          return if ::Rake::Task.instance_methods.include?(:_airbrussh_execute)\n\n          ::Rake::Task.class_exec do\n            alias_method :_airbrussh_execute, :execute\n            def execute(args=nil)\n              ::Airbrussh::Rake::Context.current_task_name = name.to_s\n              _airbrussh_execute(args)\n            end\n          end\n        end\n      end\n\n      private\n\n      attr_reader :history\n      attr_accessor :last_task_name\n\n      def reset_history_if_task_changed\n        history.clear if last_task_name != current_task_name\n        self.last_task_name = current_task_name\n      end\n\n      def enabled?\n        @enabled\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/airbrussh/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule Airbrussh\n  VERSION = \"1.6.1\".freeze\nend\n"
  },
  {
    "path": "lib/airbrussh.rb",
    "content": "require \"airbrussh/configuration\"\nrequire \"airbrussh/formatter\"\nrequire \"airbrussh/version\"\n\nmodule Airbrussh\n  def self.configuration(options={})\n    return options if options.is_a?(::Airbrussh::Configuration)\n    @configuration ||= Configuration.new\n    @configuration.apply_options(options)\n  end\n\n  def self.configure\n    yield(configuration)\n  end\nend\n"
  },
  {
    "path": "lib/sshkit/formatter/airbrussh.rb",
    "content": "require \"airbrussh/formatter\"\n\n# Capistrano's formatter configuration requires that the formatter class\n# be in the SSHKit::Formatter namespace. So we declare\n# SSHKit::Formatter::Airbrussh that simply functions as an alias for\n# Airbrussh::Formatter.\nmodule SSHKit\n  module Formatter\n    class Airbrussh < Airbrussh::Formatter\n    end\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/capistrano/tasks_test.rb",
    "content": "require \"minitest_helper\"\nrequire \"airbrussh/capistrano/tasks\"\nrequire \"airbrussh/configuration\"\nrequire \"ostruct\"\nrequire \"stringio\"\nrequire \"tempfile\"\n\nclass Airbrussh::Capistrano::TasksTest < Minitest::Test\n  DSL = Struct.new(:formatter) do\n    def set(*); end\n\n    # The Tasks object needs to reach into the backend provided by `env` in\n    # order to obtain the current Formatter, so here we build the complex mock\n    # needed for that.\n    def env\n      OpenStruct.new(\n        :backend => OpenStruct.new(\n          :config => OpenStruct.new(\n            :output => formatter\n          )\n        )\n      )\n    end\n\n    private\n\n    def namespace(*); end\n\n    def task(*); end\n  end\n\n  def setup\n    @config = Airbrussh::Configuration.new\n    @dsl = DSL.new(Airbrussh::Formatter.new(StringIO.new, @config))\n    @stderr = StringIO.new\n    @tasks = Airbrussh::Capistrano::Tasks.new(@dsl, @stderr, @config)\n  end\n\n  def test_no_warning_is_printed_when_proper_dsl_is_present\n    assert_empty(stderr)\n  end\n\n  def test_prints_warning_if_dsl_is_missing\n    bad_dsl = Object.new\n    Airbrussh::Capistrano::Tasks.new(bad_dsl, @stderr, @config)\n    assert_match(/WARNING.*must be loaded by Capistrano/, stderr)\n  end\n\n  def test_configures_for_capistrano\n    assert_equal(\"log/capistrano.log\", @config.log_file)\n    assert(@config.monkey_patch_rake)\n    assert_equal(:auto, @config.color)\n    assert_equal(:auto, @config.truncate)\n    assert_equal(:auto, @config.banner)\n    refute(@config.command_output)\n  end\n\n  def test_sets_airbrussh_formatter_on_load_defaults\n    @dsl.expects(:set).with(:format, :airbrussh)\n    @tasks.load_defaults\n    assert(defined?(SSHKit::Formatter::Airbrussh))\n  end\n\n  def test_prints_last_20_logfile_lines_on_deploy_failure\n    with_log_file do |log_file|\n      log_file.write((11..31).map { |i| \"line #{i}\\n\" }.join)\n      log_file.close\n\n      @tasks.deploy_failed\n\n      assert_match(\"DEPLOY FAILED\", stderr)\n      refute_match(\"line 11\", stderr)\n      (12..31).each { |i| assert_match(\"line #{i}\", stderr) }\n    end\n  end\n\n  def test_does_not_truncate_log_file_lines\n    @config.truncate = 80\n\n    with_log_file do |log_file|\n      long_line = \"a\" * 100\n      log_file.puts(long_line)\n      log_file.close\n\n      @tasks.deploy_failed\n\n      assert_match(long_line, stderr)\n    end\n  end\n\n  def test_does_not_print_anything_on_deploy_failure_if_nil_logfile\n    @config.log_file = nil\n    @tasks.deploy_failed\n    assert_empty(stderr)\n  end\n\n  def test_does_not_print_anything_on_deploy_failure_if_airbrussh_is_not_used\n    pretty_dsl = DSL.new(SSHKit::Formatter::Pretty.new(StringIO.new))\n    tasks = Airbrussh::Capistrano::Tasks.new(pretty_dsl, @stderr, @config)\n    tasks.deploy_failed\n    assert_empty(stderr)\n  end\n\n  private\n\n  def stderr\n    @stderr.string\n  end\n\n  def with_log_file\n    log_file = Tempfile.new(\"airbrussh-test-\")\n    begin\n      @config.log_file = log_file.path\n      yield(log_file)\n    ensure\n      log_file.unlink\n    end\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/capistrano_test.rb",
    "content": "require \"minitest_helper\"\n\nclass Airbrussh::CapistranoTest < Minitest::Test\n  def setup\n    # Mute the warning that is normally printed to $stderr when\n    # airbrussh/capistrano is required outside a capistrano runtime.\n    $stderr.stubs(:write)\n    require \"airbrussh/capistrano\"\n  end\n\n  def teardown\n    Airbrussh::Rake::Context.current_task_name = nil\n  end\n\n  def test_defines_tasks\n    assert_instance_of(Rake::Task, Rake.application[\"load:defaults\"])\n    assert_instance_of(Rake::Task, Rake.application[\"deploy:failed\"])\n  end\n\n  def test_load_defaults_rake_task_delegates_to_tasks_instance\n    Airbrussh::Capistrano::Tasks.any_instance.expects(:load_defaults)\n    Rake.application[\"load:defaults\"].execute\n  end\n\n  def test_deploy_failed_rake_task_delegates_to_tasks_instance\n    Airbrussh::Capistrano::Tasks.any_instance.expects(:deploy_failed)\n    Rake.application[\"deploy:failed\"].execute\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/colors_test.rb",
    "content": "require \"minitest_helper\"\nrequire \"airbrussh/colors\"\n\nclass Airbrussh::ColorsTest < Minitest::Test\n  include Airbrussh::Colors\n\n  def test_red\n    assert_equal(\"\\e[0;31;49mhello\\e[0m\", red(\"hello\"))\n  end\n\n  def test_green\n    assert_equal(\"\\e[0;32;49mhello\\e[0m\", green(\"hello\"))\n  end\n\n  def test_yellow\n    assert_equal(\"\\e[0;33;49mhello\\e[0m\", yellow(\"hello\"))\n  end\n\n  def test_blue\n    assert_equal(\"\\e[0;34;49mhello\\e[0m\", blue(\"hello\"))\n  end\n\n  def test_gray\n    assert_equal(\"\\e[0;90;49mhello\\e[0m\", gray(\"hello\"))\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/command_formatter_test.rb",
    "content": "# encoding: UTF-8\n\nrequire \"minitest_helper\"\nrequire \"ostruct\"\nrequire \"airbrussh/command_formatter\"\n\nclass Airbrussh::CommandFormatterTest < Minitest::Test\n  def setup\n    @sshkit_command = OpenStruct.new(\n      :host => host(\"deployer\", \"12.34.56.78\"),\n      :options => { :user => \"override\" },\n      :runtime => 1.23456,\n      :failure? => false\n    )\n    @sshkit_command.define_singleton_method(:to_s) do\n      \"/usr/bin/env echo hello\"\n    end\n    @command = Airbrussh::CommandFormatter.new(@sshkit_command, 0)\n  end\n\n  def test_format_output\n    assert_equal(\"01 hello\", @command.format_output(\"hello\\n\"))\n  end\n\n  def test_start_message\n    assert_equal(\"01 \\e[0;33;49mecho hello\\e[0m\", @command.start_message)\n  end\n\n  def test_exit_message_success\n    assert_equal(\n      \"\\e[0;32;49m✔ 01 deployer@12.34.56.78\\e[0m 1.235s\",\n      @command.exit_message\n    )\n  end\n\n  def test_exit_message_failure\n    @command.stubs(:failure? => true)\n\n    assert_equal(\n      \"\\e[0;31;49m✘ 01 deployer@12.34.56.78\\e[0m \"\\\n      \"1.235s\",\n      @command.exit_message\n    )\n  end\n\n  def test_uses_ssh_options_if_host_user_is_absent\n    @sshkit_command.host = host(nil, \"12.34.56.78\", :user => \"sshuser\")\n    assert_equal(\n      \"\\e[0;32;49m✔ 01 sshuser@12.34.56.78\\e[0m 1.235s\",\n      @command.exit_message\n    )\n  end\n\n  def test_shows_hostname_only_if_no_user\n    @sshkit_command.host = host(nil, \"12.34.56.78\")\n    assert_equal(\n      \"\\e[0;32;49m✔ 01 12.34.56.78\\e[0m 1.235s\",\n      @command.exit_message\n    )\n  end\n\n  def test_handles_nil_position_gracefully\n    command = Airbrussh::CommandFormatter.new(@sshkit_command, nil)\n    assert_equal(\"01 hello\", command.format_output(\"hello\\n\"))\n  end\n\n  private\n\n  def host(user, hostname, ssh_options={})\n    SSHKit::Host.new(\n      :user => user,\n      :hostname => hostname,\n      :ssh_options => ssh_options\n    )\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/configuration_test.rb",
    "content": "require \"minitest_helper\"\nrequire \"airbrussh/console_formatter\"\nrequire \"airbrussh/log_file_formatter\"\nrequire \"tempfile\"\n\nclass Airbrussh::ConfigurationTest < Minitest::Test\n  def setup\n    @config = Airbrussh::Configuration.new\n  end\n\n  def test_defaults\n    assert_nil(@config.log_file)\n    assert_equal(:auto, @config.color)\n    assert_equal(:auto, @config.truncate)\n    assert_equal(:auto, @config.banner)\n    assert_nil(@config.task_prefix)\n    refute(@config.monkey_patch_rake)\n    refute(@config.command_output)\n    assert_equal(Airbrussh::Rake::Context, @config.context)\n  end\n\n  def test_apply_options\n    @config.apply_options(\n      :log_file => \"test\",\n      :color => true,\n      :truncate => false,\n      :banner => \"hi\",\n      :monkey_patch_rake => true,\n      :command_output => true,\n      :context => Class\n    )\n\n    assert_equal(\"test\", @config.log_file)\n    assert_equal(true, @config.color)\n    assert_equal(false, @config.truncate)\n    assert_equal(\"hi\", @config.banner)\n    assert(@config.monkey_patch_rake)\n    assert(@config.command_output)\n    assert_equal(Class, @config.context)\n  end\n\n  def test_apply_options_warns_on_stderr_of_bad_key\n    _out, err = capture_io { @config.apply_options(:bad => \"test\") }\n    assert_match(/unrecognized/i, err)\n    assert_match(/bad/, err)\n  end\n\n  def test_auto_banner_message_without_log\n    @config.log_file = nil\n    @config.banner = :auto\n    assert_equal(\"Using airbrussh format.\", @config.banner_message)\n  end\n\n  def test_auto_banner_message_with_log\n    @config.log_file = \"log/test.log\"\n    @config.banner = :auto\n    assert_equal(\n      \"Using airbrussh format.\\n\"\\\n      \"Verbose output is being written to \\e[0;34;49mlog/test.log\\e[0m.\",\n      @config.banner_message\n    )\n  end\n\n  def test_nil_or_false_banner_message\n    @config.banner = nil\n    assert_nil(@config.banner_message)\n    @config.banner = false\n    assert_nil(@config.banner_message)\n  end\n\n  def test_custom_banner_message\n    @config.banner = \"Hello!\"\n    assert_equal(\"Hello!\", @config.banner_message)\n  end\n\n  def test_formatters_without_log_file\n    io = StringIO.new\n    formatters = @config.formatters(io)\n    assert_equal(1, formatters.length)\n    assert_instance_of(Airbrussh::ConsoleFormatter, formatters.first)\n    assert_equal(io, formatters.first.original_output)\n    assert_equal(@config, formatters.first.config)\n  end\n\n  def test_formatters_with_log_file\n    @config.log_file = Tempfile.new(\"airbrussh-test\").path\n    io = StringIO.new\n    formatters = @config.formatters(io)\n    assert_equal(2, formatters.length)\n    assert_instance_of(Airbrussh::LogFileFormatter, formatters.first)\n    assert_instance_of(Airbrussh::ConsoleFormatter, formatters.last)\n    assert_equal(@config.log_file, formatters.first.path)\n    assert_equal(io, formatters.last.original_output)\n    assert_equal(@config, formatters.last.config)\n  end\n\n  def test_effects_of_command_output_true\n    @config.command_output = true\n    assert(@config.show_command_output?(:stdout))\n    assert(@config.show_command_output?(:stderr))\n  end\n\n  def test_effects_of_command_output_false\n    @config.command_output = false\n    refute(@config.show_command_output?(:stdout))\n    refute(@config.show_command_output?(:stderr))\n  end\n\n  def test_effects_of_command_output_stdout\n    @config.command_output = :stdout\n    assert(@config.show_command_output?(:stdout))\n    refute(@config.show_command_output?(:stderr))\n  end\n\n  def test_effects_of_command_output_stderr\n    @config.command_output = :stderr\n    refute(@config.show_command_output?(:stdout))\n    assert(@config.show_command_output?(:stderr))\n  end\n\n  def test_effects_of_command_output_stdout_stderr\n    @config.command_output = [:stdout, :stderr]\n    assert(@config.show_command_output?(:stdout))\n    assert(@config.show_command_output?(:stderr))\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/console_formatter_test.rb",
    "content": "require \"minitest_helper\"\nrequire \"stringio\"\nrequire \"airbrussh/configuration\"\nrequire \"airbrussh/console_formatter\"\n\n# ConsoleFormatter is currently tested via a comprehensive acceptance-style\n# test in Airbrussh::FormatterTest. Hence the lack of unit tests here, which\n# would be redundant.\nclass Airbrussh::ConsoleFormatterTest < Minitest::Test\n  def setup\n    @config = Airbrussh::Configuration.new\n    @config.banner = false\n    @config.command_output = true\n    @output = StringIO.new\n    @formatter = Airbrussh::ConsoleFormatter.new(@output, @config)\n  end\n\n  # Make sure that command data containing two lines is formatted as two\n  # indented lines.\n  def test_log_command_data_with_multiline_string\n    command = stub(:verbosity => SSHKit::Logger::INFO, :to_s => \"greet\")\n    data = \"hello\\nworld\\n\"\n    @formatter.log_command_start(command)\n    @formatter.log_command_data(command, :stdout, data)\n    assert_equal(\n      \"      01 greet\\n\"\\\n      \"      01 hello\\n\"\\\n      \"      01 world\\n\",\n      output\n    )\n  end\n\n  private\n\n  def output\n    @output.string\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/console_test.rb",
    "content": "# encoding: UTF-8\n\nrequire \"minitest_helper\"\nrequire \"stringio\"\nrequire \"airbrussh/configuration\"\nrequire \"airbrussh/console\"\n\nclass Airbrussh::ConsoleTest < Minitest::Test\n  def setup\n    @output = StringIO.new\n  end\n\n  def test_color_is_allowed_for_tty\n    console = configured_console(:tty => true) do |config|\n      config.color = :auto\n    end\n    console.print_line(\"The \\e[0;32;49mgreen\\e[0m text\")\n    assert_equal(\"The \\e[0;32;49mgreen\\e[0m text\\n\", output)\n  end\n\n  def test_color_is_can_be_forced\n    console = configured_console(:tty => false) do |config|\n      config.color = true\n    end\n    console.print_line(\"The \\e[0;32;49mgreen\\e[0m text\")\n    assert_equal(\"The \\e[0;32;49mgreen\\e[0m text\\n\", output)\n  end\n\n  def test_color_is_can_be_forced_via_env\n    console = configured_console(:tty => false) do |config|\n      config.color = :auto\n    end\n    ENV.stubs(:[]).with(\"SSHKIT_COLOR\").returns(\"1\")\n    console.print_line(\"The \\e[0;32;49mgreen\\e[0m text\")\n    assert_equal(\"The \\e[0;32;49mgreen\\e[0m text\\n\", output)\n  end\n\n  def test_color_is_stripped_for_non_tty\n    console = configured_console(:tty => false) do |config|\n      config.color = :auto\n    end\n    console.print_line(\"The \\e[0;32;49mgreen\\e[0m text\")\n    assert_equal(\"The green text\\n\", output)\n  end\n\n  def test_color_can_be_disabled_for_tty\n    console = configured_console(:tty => true) do |config|\n      config.color = false\n    end\n    console.print_line(\"The \\e[0;32;49mgreen\\e[0m text\")\n    assert_equal(\"The green text\\n\", output)\n  end\n\n  def test_truncates_to_winsize\n    console = configured_console(:tty => true) do |config|\n      config.color = false\n      config.truncate = :auto\n    end\n    IO.stubs(:console => stub(:winsize => [100, 20]))\n    console.print_line(\"The quick brown fox jumps over the lazy dog.\")\n    assert_equal(\"The quick brown fox…\\n\", output)\n  end\n\n  def test_ignores_ascii_color_codes_when_determing_truncation_amount\n    console = configured_console(:tty => true) do |config|\n      config.color = true\n      config.truncate = :auto\n    end\n    IO.stubs(:console => stub(:winsize => [100, 20]))\n    twenty_chars_plus_color = \"\\e[0;31;49m#{'a' * 20}\\e[0m\"\n    console.print_line(twenty_chars_plus_color)\n    assert_equal(\"#{twenty_chars_plus_color}\\n\", output)\n  end\n\n  def test_truncates_to_explicit_width\n    console = configured_console(:tty => true) do |config|\n      config.color = false\n      config.truncate = 25\n    end\n    console.print_line(\"The quick brown fox jumps over the lazy dog.\")\n    assert_equal(\"The quick brown fox jump…\\n\", output)\n  end\n\n  def test_truncation_can_be_disabled\n    console = configured_console(:tty => true) do |config|\n      config.truncate = false\n    end\n    IO.expects(:console).never\n    console.print_line(\"The quick brown fox jumps over the lazy dog.\")\n    assert_equal(\"The quick brown fox jumps over the lazy dog.\\n\", output)\n  end\n\n  # SSHKit sometimes returns raw ASCII-8BIT data that cannot be converted to\n  # UTF-8, which could frustrate the truncation logic. Make sure that Console\n  # recovers gracefully in this scenario.\n  def test_truncates_improperly_encoded_ascii_string\n    console = configured_console(:tty => true) do |config|\n      config.color = false\n      config.truncate = 10\n    end\n\n    console.print_line(ascii_8bit(\"The ‘quick’ brown fox\"))\n\n    # Note that the left-apostrophe character is actually 3 bytes as raw\n    # ASCII-8BIT, which accounts for the short truncated value.\n    assert_equal(ascii_8bit(\"The ‘...\\n\"), ascii_8bit(output))\n  end\n\n  def test_print_line_handles_invalid_utf8\n    console = configured_console(:tty => false)\n\n    invalid_utf8 = \"The ‘quick’ brown fox\"\n                   .encode(\"Windows-1255\")\n                   .force_encoding(\"UTF-8\")\n\n    console.print_line(invalid_utf8)\n    assert_equal(\"The �quick� brown fox\\n\", output)\n  end\n\n  def test_doesnt_truncates_to_zero_width\n    console = configured_console(:tty => true) do |config|\n      config.color = false\n      config.truncate = 0\n    end\n    console.print_line(\"The quick brown fox jumps over the lazy dog.\")\n    assert_equal(\"The quick brown fox jumps over the lazy dog.\\n\", output)\n  end\n\n  private\n\n  def ascii_8bit(string)\n    string.dup.force_encoding(\"ASCII-8BIT\")\n  end\n\n  def output\n    @output.string\n  end\n\n  def configured_console(opts={})\n    config = Airbrussh::Configuration.new\n    yield(config) if block_given?\n    @output.stubs(:tty? => opts.fetch(:tty, false))\n    Airbrussh::Console.new(@output, config)\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/delegating_formatter_test.rb",
    "content": "require \"minitest_helper\"\nrequire \"airbrussh/delegating_formatter\"\n\nclass Airbrussh::DelegatingFormatterTest < Minitest::Test\n  def setup\n    @fmt1 = stub\n    @fmt2 = stub\n    @delegating = Airbrussh::DelegatingFormatter.new([@fmt1, @fmt2])\n  end\n\n  def test_forwards_logger_methods\n    %w[fatal error warn info debug log].each do |method|\n      @fmt1.expects(method).with(\"string\").returns(6)\n      @fmt2.expects(method).with(\"string\").returns(6)\n      result = @delegating.public_send(method, \"string\")\n      assert_equal(6, result)\n    end\n  end\n\n  def test_forwards_start_and_exit_methods\n    %w[log_command_start log_command_exit].each do |method|\n      @fmt1.expects(method).with(:command).returns(nil)\n      @fmt2.expects(method).with(:command).returns(nil)\n      result = @delegating.public_send(method, :command)\n      assert_nil(result)\n    end\n  end\n\n  def test_forwards_log_command_data\n    @fmt1.expects(:log_command_data).with(:command, :stdout, \"a\").returns(nil)\n    @fmt2.expects(:log_command_data).with(:command, :stdout, \"a\").returns(nil)\n    result = @delegating.log_command_data(:command, :stdout, \"a\")\n    assert_nil(result)\n  end\n\n  def test_forwards_io_methods_to_multiple_formatters\n    # All formatters get duped commands except for the last one. This is\n    # because in SSHKit versions up to and including 1.7.1, the formatters\n    # clear the command output, so each must be given it's own copy.\n    command = stub(:dup => \"I've been duped!\")\n    %w[<< write].each do |method|\n      @fmt1.expects(method).with(\"I've been duped!\").returns(16)\n      @fmt2.expects(method).with(command).returns(10)\n      result = @delegating.public_send(method, command)\n      assert_equal(10, result)\n    end\n  end\n\n  def test_forwards_io_methods_to_a_single_formatter\n    command = stub(:dup => \"I've been duped!\")\n    %w[<< write].each do |method|\n      delegating = Airbrussh::DelegatingFormatter.new([@fmt1])\n      @fmt1.expects(method).with(command).returns(10)\n      result = delegating.public_send(method, command)\n      assert_equal(10, result)\n    end\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/formatter_test.rb",
    "content": "# encoding: utf-8\n\nrequire \"minitest_helper\"\nrequire \"bundler\"\nrequire \"etc\"\n\nclass Airbrussh::FormatterTest < Minitest::Test\n  include RakeTaskDefinition\n\n  def setup\n    @output = StringIO.new\n    @log_file = StringIO.new\n    # sshkit > 1.6.1 && <= 1.7.1 uses Etc.getpwuid.name for local user lookup\n    # which causes a NoMethodError on Windows, so we need to stub it here.\n    Etc.stubs(:getpwuid => stub(:name => \"stubbed_user\")) unless sshkit_after?(\"1.7.1\")\n    @user = SSHKit::Host.new(:local).username # nil on sshkit < 1.7.0\n    @user_at_localhost = [@user, \"localhost\"].compact.join(\"@\")\n  end\n\n  def teardown\n    Airbrussh::Rake::Context.current_task_name = nil\n    SSHKit.reset_configuration!\n  end\n\n  def configure\n    airbrussh_config = Airbrussh::Configuration.new\n    airbrussh_config.log_file = @log_file\n\n    # Replace command map so it doesn't prefix every cmd with /usr/bin/env\n    sshkit_config = SSHKit.config\n    sshkit_config.command_map = Hash.new { |h, cmd| h[cmd] = cmd.to_s }\n\n    yield(airbrussh_config, sshkit_config)\n\n    sshkit_config.output = formatter_class.new(@output, airbrussh_config)\n  end\n\n  def test_can_be_configured_with_options_hash\n    formatter = Airbrussh::Formatter.new(@output, :banner => \"success!\")\n    config = formatter.formatters.last.config\n    assert_equal(\"success!\", config.banner)\n  end\n\n  def test_formats_execute_with_color\n    configure do |airbrussh_config, sshkit_config|\n      sshkit_config.output_verbosity = ::Logger::DEBUG\n      airbrussh_config.command_output = true\n      airbrussh_config.color = true\n    end\n\n    on_local do\n      execute(:echo, \"foo\")\n    end\n\n    assert_output_lines(\n      \"      01 \\e[0;33;49mecho foo\\e[0m\\n\",\n      \"      01 foo\\n\",\n      /    \\e\\[0;32;49m✔ 01 #{@user_at_localhost}\\e\\[0m \\d.\\d+s\\n/\n    )\n\n    assert_log_file_lines(\n      command_running(\"echo foo\"),\n      command_started_debug(\"echo foo\"),\n      command_std_stream(:stdout, \"foo\"),\n      command_success\n    )\n  end\n\n  def test_formats_execute_without_color\n    configure do |airbrussh_config|\n      airbrussh_config.command_output = true\n    end\n\n    on_local do\n      execute(:echo, \"foo\")\n    end\n\n    assert_output_lines(\n      \"      01 echo foo\\n\",\n      \"      01 foo\\n\",\n      /    ✔ 01 #{@user_at_localhost} \\d.\\d+s\\n/\n    )\n\n    assert_log_file_lines(\n      command_running(\"echo foo\"), command_success\n    )\n  end\n\n  def test_formats_without_command_output\n    configure do |airbrussh_config|\n      airbrussh_config.command_output = false\n    end\n\n    on_local do\n      execute(:ls, \"-l\")\n    end\n\n    assert_output_lines(\n      \"      01 ls -l\\n\",\n      /    ✔ 01 #{@user_at_localhost} \\d.\\d+s\\n/\n    )\n  end\n\n  def test_formats_failing_execute_with_color\n    configure do |airbrussh_config, sshkit_config|\n      sshkit_config.output_verbosity = ::Logger::DEBUG\n      airbrussh_config.command_output = true\n      airbrussh_config.color = true\n    end\n\n    error = nil\n    on_local do\n      begin\n        execute(:echo, \"hi\")\n        execute(:ls, \"_file_does_not_exist\")\n      rescue SSHKit::Command::Failed => error # rubocop:disable Lint/SuppressedException\n      end\n    end\n\n    refute_nil error\n\n    expected_output = [\n      \"      01 \\e[0;33;49mecho hi\\e[0m\\n\",\n      \"      01 hi\\n\",\n      /    \\e\\[0;32;49m✔ 01 #{@user_at_localhost}\\e\\[0m \\d.\\d+s\\n/,\n      \"      02 \\e[0;33;49mls _file_does_not_exist\\e[0m\\n\"\n    ]\n\n    error_message = /.*_file_does_not_exist.*No such file or directory/\n\n    # Don't know why this log line doesn't show up in SSHKit 1.6.1\n    expected_output << /      02 #{error_message}\\n/ if sshkit_after?(\"1.6.1\")\n\n    assert_output_lines(*expected_output)\n\n    expected_log_output = [\n      command_running(\"echo hi\"),\n      command_started_debug(\"echo hi\"),\n      command_std_stream(:stdout, \"hi\"),\n      command_success,\n\n      command_running(\"ls _file_does_not_exist\"),\n      command_started_debug(\"ls _file_does_not_exist\")\n    ]\n\n    if sshkit_after?(\"1.6.1\")\n      expected_log_output << command_std_stream(:stderr, error_message)\n      expected_log_output << \"\\e[0m\" if color_output?\n    end\n\n    assert_log_file_lines(*expected_log_output)\n  end\n\n  def test_formats_capture_with_color\n    configure do |airbrussh_config|\n      airbrussh_config.command_output = true\n      airbrussh_config.color = true\n    end\n\n    on_local do\n      capture(:ls, \"-1\", \"airbrussh.gemspec\", :verbosity => SSHKit::Logger::INFO)\n    end\n\n    assert_output_lines(\n      \"      01 \\e[0;33;49mls -1 airbrussh.gemspec\\e[0m\\n\",\n      \"      01 airbrussh.gemspec\\n\",\n      /    \\e\\[0;32;49m✔ 01 #{@user_at_localhost}\\e\\[0m \\d.\\d+s\\n/\n    )\n\n    assert_log_file_lines(\n      command_running(\"ls -1 airbrussh.gemspec\"), command_success\n    )\n  end\n\n  def test_formats_capture_without_color\n    configure do |airbrussh_config|\n      airbrussh_config.command_output = true\n    end\n\n    on_local do\n      capture(:ls, \"-1\", \"airbrussh.gemspec\", :verbosity => SSHKit::Logger::INFO)\n    end\n\n    assert_output_lines(\n      \"      01 ls -1 airbrussh.gemspec\\n\",\n      \"      01 airbrussh.gemspec\\n\",\n      /    ✔ 01 #{@user_at_localhost} \\d.\\d+s\\n/\n    )\n\n    assert_log_file_lines(\n      command_running(\"ls -1 airbrussh.gemspec\"), command_success\n    )\n  end\n\n  def test_does_not_output_test_commands\n    configure do |airbrussh_config, sshkit_config|\n      airbrussh_config.command_output = true\n      sshkit_config.output_verbosity = Logger::DEBUG\n    end\n\n    on_local do\n      test(\"echo hi\")\n    end\n\n    assert_output_lines\n\n    assert_log_file_lines(\n      command_running(\"echo hi\", \"DEBUG\"),\n      command_started_debug(\"echo hi\"),\n      command_std_stream(:stdout, \"hi\"),\n      command_success(\"DEBUG\")\n    )\n  end\n\n  def test_handles_rake_tasks\n    configure do |airbrussh_config|\n      airbrussh_config.monkey_patch_rake = true\n      airbrussh_config.command_output = true\n    end\n\n    on_local(\"special_rake_task\") do\n      execute(:echo, \"command 1\")\n      info(\"Starting command 2\")\n      execute(:echo, \"command 2\")\n    end\n    on_local(\"special_rake_task_2\") do\n      error(\"New task starting\")\n    end\n    on_local(\"special_rake_task_3\") do\n      execute(:echo, \"command 3\")\n      execute(:echo, \"command 4\")\n      warn(\"All done\")\n    end\n\n    assert_output_lines(\n      \"00:00 special_rake_task\\n\",\n      \"      01 echo command 1\\n\",\n      \"      01 command 1\\n\",\n      /    ✔ 01 #{@user_at_localhost} \\d.\\d+s\\n/,\n      \"      Starting command 2\\n\",\n      \"      02 echo command 2\\n\",\n      \"      02 command 2\\n\",\n      /    ✔ 02 #{@user_at_localhost} \\d.\\d+s\\n/,\n      \"00:00 special_rake_task_2\\n\",\n      \"      ERROR New task starting\\n\",\n      \"00:00 special_rake_task_3\\n\",\n      \"      01 echo command 3\\n\",\n      \"      01 command 3\\n\",\n      /    ✔ 01 #{@user_at_localhost} \\d.\\d+s\\n/,\n      \"      02 echo command 4\\n\",\n      \"      02 command 4\\n\",\n      /    ✔ 02 #{@user_at_localhost} \\d.\\d+s\\n/,\n      \"      WARN  All done\\n\"\n    )\n\n    assert_log_file_lines(\n      command_running(\"echo command 1\"), command_success,\n      /#{blue('INFO')} Starting command 2\\n/,\n      command_running(\"echo command 2\"), command_success,\n      /#{red('ERROR')} New task starting\\n/,\n      command_running(\"echo command 3\"), command_success,\n      command_running(\"echo command 4\"), command_success,\n      /#{yellow('WARN')} All done\\n/\n    )\n  end\n\n  def test_log_message_levels\n    configure do |airbrussh_config, sshkit_config|\n      airbrussh_config.color = true\n      sshkit_config.output_verbosity = Logger::DEBUG\n    end\n\n    on_local do\n      %w[log fatal error warn info debug].each do |level|\n        send(level, \"Test\")\n      end\n    end\n\n    assert_output_lines(\n      \"      Test\\n\",\n      \"      \\e[0;31;49mFATAL\\e[0m Test\\n\",\n      \"      \\e[0;31;49mERROR\\e[0m Test\\n\",\n      \"      \\e[0;33;49mWARN\\e[0m  Test\\n\",\n      \"      Test\\n\"\n    )\n\n    assert_log_file_lines(\n      /#{blue('INFO')} Test\\n/,\n      /#{red('FATAL')} Test\\n/,\n      /#{red('ERROR')} Test\\n/,\n      /#{yellow('WARN')} Test\\n/,\n      /#{blue('INFO')} Test\\n/,\n      /#{black('DEBUG')} Test\\n/\n    )\n  end\n\n  def test_interleaved_debug_and_info_commands\n    configure do |airbrussh_config|\n      airbrussh_config.monkey_patch_rake = true\n      airbrussh_config.command_output = true\n    end\n\n    on_local(\"interleaving_test\") do\n      test(\"echo hi\")\n      # test methods are logged at debug level by default\n      execute(:echo, \"command 1\")\n      test(\"echo hello\")\n      debug(\"Debug line should not be output\")\n      info(\"Info line should be output\")\n      execute(:echo, \"command 2\")\n      execute(:echo, \"command 3\", :verbosity => :debug)\n      execute(:echo, \"command 4\")\n    end\n\n    assert_output_lines(\n      \"00:00 interleaving_test\\n\",\n      \"      01 echo command 1\\n\",\n      \"      01 command 1\\n\",\n      /    ✔ 01 #{@user_at_localhost} \\d.\\d+s\\n/,\n      \"      Info line should be output\\n\",\n      \"      02 echo command 2\\n\",\n      \"      02 command 2\\n\",\n      /    ✔ 02 #{@user_at_localhost} \\d.\\d+s\\n/,\n      \"      03 echo command 4\\n\",\n      \"      03 command 4\\n\",\n      /    ✔ 03 #{@user_at_localhost} \\d.\\d+s\\n/\n    )\n  end\n\n  def test_task_prefix\n    configure do |airbrussh_config|\n      airbrussh_config.monkey_patch_rake = true\n      airbrussh_config.task_prefix = \"--- \"\n    end\n\n    on_local(\"task_prefix_test\") do\n      execute(:echo, \"command 1\")\n      execute(:echo, \"command 2\")\n    end\n\n    assert_output_lines(\n      \"--- 00:00 task_prefix_test\\n\",\n      \"      01 echo command 1\\n\",\n      /    ✔ 01 #{@user_at_localhost} \\d.\\d+s\\n/,\n      \"      02 echo command 2\\n\",\n      /    ✔ 02 #{@user_at_localhost} \\d.\\d+s\\n/\n    )\n  end\n\n  private\n\n  def on_local(task_name=nil, &block)\n    define_and_execute_rake_task(task_name) do\n      local_backend = SSHKit::Backend::Local.new(&block)\n      local_backend.run\n    end\n  end\n\n  def assert_output_lines(*expected_output)\n    expected_output = [\n      \"Using airbrussh format.\\n\",\n      /Verbose output is being written to .*\\n/\n    ] + expected_output\n    assert_string_io_lines(expected_output, @output)\n  end\n\n  def assert_string_io_lines(expected_output, string_io)\n    lines = string_io.string.lines.to_a\n    assert_equal expected_output.size, lines.size, lines.map(&:inspect).join(\",\\n\")\n    lines.each.with_index do |line, i|\n      assert_case_equal(expected_output[i], line)\n    end\n  end\n\n  def assert_log_file_lines(*command_lines)\n    preamble_lines = [\n      /#{blue('INFO')} ---------------------------------------------------------------------------\\n/,\n      /#{blue('INFO')} START [\\d\\-]+ [\\d\\:]+ [\\+\\-]\\d+ cap\\n/,\n      /#{blue('INFO')} ---------------------------------------------------------------------------\\n/\n    ]\n\n    assert_string_io_lines(preamble_lines + command_lines, @log_file)\n  end\n\n  def command_running(command, level=\"INFO\")\n    level_tag_color = level == \"INFO\" ? :blue : :black\n    /#{send(level_tag_color, level)} \\[#{green('\\w+')}\\] Running #{bold_yellow(command.to_s)} #{@user ? \"as #{blue(@user)}@\" : \"on \"}#{blue('localhost')}\\n/\n  end\n\n  def command_started_debug(command)\n    /#{black('DEBUG')} \\[#{green('\\w+')}\\] Command: #{blue(command)}/\n  end\n\n  def command_std_stream(stream, output)\n    # Note ansii character end code is omitted due to newline\n    # This is probably a bug in SSHKit\n    color = stream == :stdout ? :green : :red\n    formatted_output = send(color, \"\\\\t#{output}\\\\n\").chomp('\\\\e\\\\[0m')\n    /#{black('DEBUG')} \\[#{green('\\w+')}\\] #{formatted_output}/\n  end\n\n  def command_success(level=\"INFO\")\n    level_tag_color = level == \"INFO\" ? :blue : :black\n    /#{send(level_tag_color, level)} \\[#{green('\\w+')}\\] Finished in \\d.\\d+ seconds with exit status 0 \\(#{bold_green(\"successful\")}\\).\\n/\n  end\n\n  def command_failed(exit_status)\n    /#{black('DEBUG')} \\[#{green('\\w+')}\\] Finished in \\d.\\d+ seconds with exit status #{exit_status} \\(#{bold_red(\"failed\")}\\)/\n  end\n\n  {\n    :black => \"0;30;49\",\n    :red => \"0;31;49\",\n    :green => \"0;32;49\",\n    :yellow => \"0;33;49\",\n    :blue => \"0;34;49\",\n    :bold_red => \"1;31;49\",\n    :bold_green => \"1;32;49\",\n    :bold_yellow => \"1;33;49\"\n  }.each do |color, code|\n    define_method(color) do |string|\n      if color_output?\n        \"\\\\e\\\\[#{code}m#{string}\\\\e\\\\[0m\"\n      else\n        string\n      end\n    end\n  end\n\n  # Whether or not SSHKit emits color depends on the test environment. SSHKit\n  # versions up to 1.7.1 added colors to the log file, but only if\n  # `$stdout.tty?` is true. Later versions never output color to the log file.\n  def color_output?\n    $stdout.tty? && !(sshkit_after?(\"1.7.1\") || sshkit_master?)\n  end\n\n  def sshkit_after?(version)\n    Gem.loaded_specs[\"sshkit\"].version > Gem::Version.new(version)\n  end\n\n  def sshkit_master?\n    gem_source = Gem.loaded_specs[\"sshkit\"].source\n    gem_source.is_a?(Bundler::Source::Git) && gem_source.branch == \"master\"\n  end\n\n  def formatter_class\n    Airbrussh::Formatter\n  end\n\n  module Minitest::Assertions\n    # rubocop:disable Style/CaseEquality\n    def assert_case_equal(matcher, obj, msg=nil)\n      message = message(msg) { \"Expected #{mu_pp matcher} to === #{mu_pp obj}\" }\n      assert matcher === obj, message\n    end\n    # rubocop:enable Style/CaseEquality\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/log_file_formatter_test.rb",
    "content": "require \"minitest_helper\"\nrequire \"airbrussh/log_file_formatter\"\nrequire \"fileutils\"\nrequire \"tempfile\"\n\nclass Airbrussh::LogFileFormatterTest < Minitest::Test\n  def setup\n    @file = Tempfile.new(\"airbrussh-test-\")\n    @file.puts(\"Existing data\")\n    @file.close\n    @formatter = Airbrussh::LogFileFormatter.new(@file.path)\n  end\n\n  def teardown\n    @file.unlink\n    @output = nil\n  end\n\n  def test_appends_to_existing_file\n    assert_match(\"Existing data\", output)\n  end\n\n  def test_writes_delimiter\n    assert_match(\"----------\", output)\n    assert_match(\"START\", output)\n  end\n\n  def test_writes_through_via_pretty_formatter\n    @formatter << SSHKit::LogMessage.new(SSHKit::Logger::INFO, \"hello\")\n    assert_match(/INFO.*hello/, output)\n  end\n\n  def test_creates_log_directory_and_file\n    with_tempdir do |dir|\n      log_file = File.join(dir, \"log\", \"capistrano.log\")\n      Airbrussh::LogFileFormatter.new(log_file)\n      assert(File.exist?(log_file))\n    end\n  end\n\n  private\n\n  def output\n    @output ||= IO.read(@file.path)\n  end\n\n  def with_tempdir\n    dir = Dir.mktmpdir(\"airbrussh-test-\")\n    yield(dir)\n  ensure\n    FileUtils.rm_rf(dir)\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/rake/context_test.rb",
    "content": "require \"minitest_helper\"\nrequire \"airbrussh/rake/context\"\n\nclass Airbrussh::Rake::ContextTest < Minitest::Test\n  include RakeTaskDefinition\n\n  def setup\n    @config = Airbrussh::Configuration.new\n  end\n\n  def teardown\n    Airbrussh::Rake::Context.current_task_name = nil\n  end\n\n  def test_current_task_name_is_nil_when_disabled\n    @config.monkey_patch_rake = false\n    context = Airbrussh::Rake::Context.new(@config)\n    define_and_execute_rake_task(\"one\") do\n      assert_nil(context.current_task_name)\n    end\n  end\n\n  def test_current_task_name\n    @config.monkey_patch_rake = true\n    context = Airbrussh::Rake::Context.new(@config)\n\n    assert_nil(context.current_task_name)\n\n    define_and_execute_rake_task(\"one\") do\n      assert_equal(\"one\", context.current_task_name)\n    end\n\n    define_and_execute_rake_task(\"two\") do\n      assert_equal(\"two\", context.current_task_name)\n    end\n  end\n\n  def test_register_new_command_is_true_for_first_execution_per_rake_task\n    @config.monkey_patch_rake = true\n    context = Airbrussh::Rake::Context.new(@config)\n    define_and_execute_rake_task(\"one\") do\n      assert context.register_new_command(:command_one)\n      refute context.register_new_command(:command_one)\n      assert context.register_new_command(:command_two)\n      refute context.register_new_command(:command_two)\n    end\n\n    define_and_execute_rake_task(\"two\") do\n      assert context.register_new_command(:command_one)\n      refute context.register_new_command(:command_one)\n      assert context.register_new_command(:command_two)\n      refute context.register_new_command(:command_two)\n    end\n  end\n\n  def test_position\n    @config.monkey_patch_rake = true\n    context = Airbrussh::Rake::Context.new(@config)\n\n    define_and_execute_rake_task(\"one\") do\n      context.register_new_command(:command_one)\n      context.register_new_command(:command_two)\n\n      assert_equal(0, context.position(:command_one))\n      assert_equal(1, context.position(:command_two))\n    end\n\n    define_and_execute_rake_task(\"two\") do\n      context.register_new_command(:command_three)\n      context.register_new_command(:command_four)\n\n      assert_equal(0, context.position(:command_three))\n      assert_equal(1, context.position(:command_four))\n    end\n  end\nend\n"
  },
  {
    "path": "test/airbrussh/version_test.rb",
    "content": "require \"minitest_helper\"\n\nclass Airbrussh::VersionTest < Minitest::Test\n  def test_that_it_has_a_version_number\n    refute_nil ::Airbrussh::VERSION\n  end\nend\n"
  },
  {
    "path": "test/airbrussh_test.rb",
    "content": "require \"minitest_helper\"\n\nclass AirbrusshTest < Minitest::Test\n  def test_configure_yields_config_object\n    config = Airbrussh.configuration\n    assert_equal(config, Airbrussh.configure { |c| c })\n  end\n\n  def test_configuration_returns_passed_config\n    config = Airbrussh::Configuration.new\n    assert_equal(config, Airbrussh.configuration(config))\n  end\n\n  def test_configuration_applies_options\n    config = Airbrussh.configuration(:banner => \"test_success\")\n    assert_equal(\"test_success\", config.banner)\n    assert_equal(\"test_success\", Airbrussh.configuration.banner)\n  end\nend\n"
  },
  {
    "path": "test/minitest_helper.rb",
    "content": "$LOAD_PATH.unshift(File.expand_path(\"../../lib\", __FILE__))\n\n# Coveralls has to be loaded first\nrequire_relative(\"./support/coveralls\")\n\n# Load minitest before mocha\nrequire \"minitest/autorun\"\n\n# Load everything else from test/support\nDir[File.expand_path(\"../support/**/*.rb\", __FILE__)].each { |rb| require(rb) }\n\nrequire \"airbrussh\"\n"
  },
  {
    "path": "test/sshkit/formatter/airbrussh_test.rb",
    "content": "require \"minitest_helper\"\nrequire \"sshkit/formatter/airbrussh\"\nrequire_relative \"../../airbrussh/formatter_test.rb\"\n\n# SSHKit::Formatter::Airbrussh should behave identically to\n# Airbrussh::Formatter, so just reuse the Airbrussh::FormatterTest.\nclass SSHKit::Formatter::AirbrusshTest < Airbrussh::FormatterTest\n  private\n\n  def formatter_class\n    SSHKit::Formatter::Airbrussh\n  end\nend\n"
  },
  {
    "path": "test/support/coveralls.rb",
    "content": "if ENV[\"CI\"]\n  begin\n    require \"simplecov\"\n    require \"coveralls\"\n\n    SimpleCov.formatter = Coveralls::SimpleCov::Formatter\n    SimpleCov.start do\n      # No need to report coverage metrics for the test code\n      add_filter \"test\"\n    end\n  rescue LoadError\n    # ignore\n  end\nend\n"
  },
  {
    "path": "test/support/minitest_reporters.rb",
    "content": "require \"minitest/reporters\"\n\nMinitest::Reporters.use!(\n  Minitest::Reporters::ProgressReporter.new,\n  ENV,\n  Minitest.backtrace_filter\n)\n"
  },
  {
    "path": "test/support/mocha.rb",
    "content": "require \"mocha/minitest\"\n\nMocha.configure do |c|\n  c.stubbing_non_existent_method = :warn\n  c.stubbing_non_public_method = :warn\nend\n"
  },
  {
    "path": "test/support/rake_task_definition.rb",
    "content": "require \"rake\"\n\nmodule RakeTaskDefinition\n  def define_and_execute_rake_task(task_name, &block)\n    task_name ||= RakeTaskDefinition.unique_task_name(name)\n    Rake::Task[task_name].clear if Rake::Task.task_defined?(task_name)\n    Rake::Task.define_task(task_name, &block).execute\n  end\n\n  def self.unique_task_name(test_name)\n    @task_index ||= 0\n    \"#{test_name}_#{@task_index += 1}\"\n  end\nend\n"
  }
]