Full Code of mattbrictson/airbrussh for AI

main f88150d500f6 cached
53 files
82.7 KB
24.2k tokens
234 symbols
1 requests
Download .txt
Repository: mattbrictson/airbrussh
Branch: main
Commit: f88150d500f6
Files: 53
Total size: 82.7 KB

Directory structure:
gitextract_lyzq9qvi/

├── .github/
│   ├── dependabot.yml
│   ├── release-drafter.yml
│   └── workflows/
│       ├── ci.yml
│       └── push.yml
├── .gitignore
├── .rubocop.yml
├── .rubocop_todo.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── UPGRADING-CAP-3.5.md
├── airbrussh.gemspec
├── appveyor.yml
├── bin/
│   ├── console
│   └── setup
├── gemfiles/
│   └── legacy.gemfile
├── lib/
│   ├── airbrussh/
│   │   ├── capistrano/
│   │   │   └── tasks.rb
│   │   ├── capistrano.rb
│   │   ├── colors.rb
│   │   ├── command_formatter.rb
│   │   ├── configuration.rb
│   │   ├── console.rb
│   │   ├── console_formatter.rb
│   │   ├── delegating_formatter.rb
│   │   ├── formatter.rb
│   │   ├── log_file_formatter.rb
│   │   ├── rake/
│   │   │   └── context.rb
│   │   └── version.rb
│   ├── airbrussh.rb
│   └── sshkit/
│       └── formatter/
│           └── airbrussh.rb
└── test/
    ├── airbrussh/
    │   ├── capistrano/
    │   │   └── tasks_test.rb
    │   ├── capistrano_test.rb
    │   ├── colors_test.rb
    │   ├── command_formatter_test.rb
    │   ├── configuration_test.rb
    │   ├── console_formatter_test.rb
    │   ├── console_test.rb
    │   ├── delegating_formatter_test.rb
    │   ├── formatter_test.rb
    │   ├── log_file_formatter_test.rb
    │   ├── rake/
    │   │   └── context_test.rb
    │   └── version_test.rb
    ├── airbrussh_test.rb
    ├── minitest_helper.rb
    ├── sshkit/
    │   └── formatter/
    │       └── airbrussh_test.rb
    └── support/
        ├── coveralls.rb
        ├── minitest_reporters.rb
        ├── mocha.rb
        └── rake_task_definition.rb

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

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: bundler
    directory: "/"
    schedule:
      interval: monthly
      time: "16:00"
      timezone: America/Los_Angeles
    open-pull-requests-limit: 10
    labels:
      - "🏠 Housekeeping"
  - package-ecosystem: github-actions
    directory: "/"
    schedule:
      interval: monthly
      time: "16:00"
      timezone: America/Los_Angeles
    open-pull-requests-limit: 10
    labels:
      - "🏠 Housekeeping"


================================================
FILE: .github/release-drafter.yml
================================================
name-template: "$RESOLVED_VERSION"
tag-template: "v$RESOLVED_VERSION"
categories:
  - title: "⚠️ Breaking Changes"
    label: "⚠️ Breaking"
  - title: "✨ New Features"
    label: "✨ Feature"
  - title: "🐛 Bug Fixes"
    label: "🐛 Bug Fix"
  - title: "📚 Documentation"
    label: "📚 Docs"
  - title: "🏠 Housekeeping"
    label: "🏠 Housekeeping"
version-resolver:
  minor:
    labels:
      - "⚠️ Breaking"
      - "✨ Feature"
  default: patch
change-template: "- $TITLE (#$NUMBER) @$AUTHOR"
no-changes-template: "- No changes"
template: |
  $CHANGES

  **Full Changelog:** https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
  pull_request:
  push:
    branches:
      - main
jobs:
  rubocop:
    name: "RuboCop"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: ruby
          bundler-cache: true
      - run: bundle exec rubocop
  test:
    name: "Test / Ruby ${{ matrix.ruby }}"
    runs-on: ubuntu-latest
    strategy:
      matrix:
        ruby: ["3.2", "3.3", "3.4", "4.0", "head"]
    steps:
      - uses: actions/checkout@v6
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby }}
          bundler-cache: true
      - run: bundle exec rake test
  test_legacy:
    name: "Test / Ruby ${{ matrix.ruby }}"
    runs-on: ubuntu-latest
    strategy:
      matrix:
        ruby: ["2.5", "2.6", "2.7", "3.0", "3.1"]
    env:
      BUNDLE_GEMFILE: gemfiles/legacy.gemfile
    steps:
      - uses: actions/checkout@v6
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby }}
          bundler-cache: true
      - run: bundle exec rake test
  test_all:
    name: "Test / Ruby (All)"
    runs-on: ubuntu-latest
    needs: ["test", "test_legacy"]
    if: always()
    steps:
      - name: All tests ok
        if: ${{ !(contains(needs.*.result, 'failure')) }}
        run: exit 0
      - name: Some tests failed
        if: ${{ contains(needs.*.result, 'failure') }}
        run: exit 1


================================================
FILE: .github/workflows/push.yml
================================================
name: Release Drafter
on:
  push:
    branches:
      - main
jobs:
  update_release_draft:
    runs-on: ubuntu-latest
    steps:
      - uses: release-drafter/release-drafter@v7
        with:
          token: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/log/
/pkg/
/spec/reports/
/tmp/
*.bundle
*.so
*.o
*.a
mkmf.log


================================================
FILE: .rubocop.yml
================================================
inherit_from: .rubocop_todo.yml

AllCops:
  DisplayCopNames: true
  DisplayStyleGuide: true
  NewCops: disable
  SuggestExtensions: false
  TargetRubyVersion: 2.5
  Exclude:
    - "*.gemspec"
    - "vendor/**/*"

Layout/EndOfLine:
  Enabled: false

Layout/LineLength:
  Exclude:
    - "test/airbrussh/formatter_test.rb"

Layout/SpaceAroundEqualsInParameterDefault:
  EnforcedStyle: no_space

Metrics/AbcSize:
  Exclude:
    - "test/**/*"

Metrics/MethodLength:
  Exclude:
    - "test/**/*"

Metrics/ClassLength:
  Exclude:
    - "test/**/*"

Style/BarePercentLiterals:
  EnforcedStyle: percent_q

Style/ClassAndModuleChildren:
  Enabled: false

Style/Documentation:
  Enabled: false

Style/DoubleNegation:
  Enabled: false

Style/Encoding:
  Enabled: false

Style/FrozenStringLiteralComment:
  Enabled: false

Style/HashSyntax:
  Exclude:
    - "Gemfile"
    - "gemfiles/*.gemfile"
  EnforcedStyle: hash_rockets

Style/StringLiterals:
  EnforcedStyle: double_quotes

Style/TrivialAccessors:
  AllowPredicates: true


================================================
FILE: .rubocop_todo.yml
================================================
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2025-11-28 18:11:46 UTC using RuboCop version 1.81.7.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 7
# This cop supports safe autocorrection (--autocorrect).
Layout/EmptyLineAfterGuardClause:
  Exclude:
    - "lib/airbrussh.rb"
    - "lib/airbrussh/configuration.rb"
    - "lib/airbrussh/console_formatter.rb"
    - "lib/airbrussh/rake/context.rb"

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowAliasSyntax, AllowedMethods.
# AllowedMethods: alias_method, public, protected, private
Layout/EmptyLinesAroundAttributeAccessor:
  Exclude:
    - "lib/airbrussh/console_formatter.rb"

# Offense count: 4
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
# SupportedHashRocketStyles: key, separator, table
# SupportedColonStyles: key, separator, table
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
Layout/HashAlignment:
  Exclude:
    - "lib/airbrussh/colors.rb"

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Lint/NonDeterministicRequireOrder:
  Exclude:
    - "test/minitest_helper.rb"

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
Naming/MemoizedInstanceVariableName:
  Exclude:
    - "lib/airbrussh/log_file_formatter.rb"

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: PreferredName.
Naming/RescuedExceptionsVariableName:
  Exclude:
    - "test/airbrussh/formatter_test.rb"

# Offense count: 1
Security/Open:
  Exclude:
    - "lib/airbrussh/capistrano/tasks.rb"

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols, AllowModifiersOnAttrs, AllowModifiersOnAliasMethod.
# SupportedStyles: inline, group
Style/AccessModifierDeclarations:
  Exclude:
    - "lib/airbrussh/colors.rb"

# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
Style/ExpandPathArguments:
  Exclude:
    - "test/minitest_helper.rb"

# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns.
# SupportedStyles: annotated, template, unannotated
Style/FormatStringToken:
  EnforcedStyle: unannotated

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
  Exclude:
    - "spec/**/*"
    - "lib/airbrussh/console.rb"

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantBegin:
  Exclude:
    - "test/airbrussh/formatter_test.rb"

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantFileExtensionInRequire:
  Exclude:
    - "test/sshkit/formatter/airbrussh_test.rb"

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantFreeze:
  Exclude:
    - "lib/airbrussh/version.rb"

# Offense count: 4
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantRegexpEscape:
  Exclude:
    - "test/airbrussh/formatter_test.rb"

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Style/StderrPuts:
  Exclude:
    - "lib/airbrussh/configuration.rb"

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Mode.
Style/StringConcatenation:
  Exclude:
    - "lib/airbrussh/console.rb"

# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
  Exclude:
    - "test/airbrussh/formatter_test.rb"

# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: MinSize.
# SupportedStyles: percent, brackets
Style/SymbolArray:
  EnforcedStyle: brackets


================================================
FILE: CHANGELOG.md
================================================
Release notes for this project are kept here: https://github.com/mattbrictson/airbrussh/releases


================================================
FILE: CODE_OF_CONDUCT.md
================================================
Contributor Code of Conduct

As contributors and maintainers of this project, we pledge to respect all
people who contribute through reporting issues, posting feature requests,
updating documentation, submitting pull requests or patches, and other
activities.

We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, or religion.

Examples of unacceptable behavior by participants include the use of sexual
language or imagery, derogatory comments or personal attacks, trolling, public
or private harassment, insults, or other unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct. Project maintainers who do not
follow the Code of Conduct may be removed from the project team.

This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by opening an issue or contacting one or more of the project
maintainers.

This Code of Conduct is adapted from the Contributor Covenant
(http://contributor-covenant.org), version 1.1.0, available at
http://contributor-covenant.org/version/1/1/0/


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to airbrussh

Have a feature idea, bug fix, or refactoring suggestion? Contributions are welcome!

## Pull requests

1. Check [Issues][] to see if your contribution has already been discussed and/or implemented.
2. If not, open an issue to discuss your contribution. I won't accept all changes and do not want to waste your time.
3. Once you have the :thumbsup:, fork the repo, make your changes, and open a PR.

## Coding guidelines

* 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].
* Writing tests is strongly encouraged! This project uses Minitest.

## Getting started

After checking out the airbrussh repo, run `bin/setup` to install dependencies. Run `rake` to execute airbrussh's tests and RuboCop checks.

Airbrussh 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).

[Issues]: https://github.com/mattbrictson/airbrussh/issues
[RuboCop]: https://github.com/rubocop/rubocop
[style]: https://github.com/rubocop/ruby-style-guide


================================================
FILE: Gemfile
================================================
source "https://rubygems.org"

gemspec

gem "coveralls_reborn", "~> 0.29.0"
gem "irb"
gem "minitest", "~> 6.0"
gem "minitest-reporters", "~> 1.1"
gem "mocha", "~> 3.0"
gem "rake", "~> 13.0"
gem "rubocop", "1.86.0"


================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)

Copyright (c) 2026 Matt Brictson

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

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

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


================================================
FILE: README.md
================================================
# Airbrussh

[![Gem Version](https://badge.fury.io/rb/airbrussh.svg)](http://badge.fury.io/rb/airbrussh)
[![Build Status](https://github.com/mattbrictson/airbrussh/actions/workflows/ci.yml/badge.svg)](https://github.com/mattbrictson/airbrussh/actions/workflows/ci.yml)
[![Build status](https://ci.appveyor.com/api/projects/status/h052rlq54sne3md6/branch/main?svg=true)](https://ci.appveyor.com/project/mattbrictson/airbrussh/branch/main)
[![Coverage Status](https://coveralls.io/repos/mattbrictson/airbrussh/badge.svg?branch=main)](https://coveralls.io/r/mattbrictson/airbrussh?branch=main)


Airbrussh 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.

**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.

If 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.

![Sample output](https://raw.github.com/mattbrictson/airbrussh/HEAD/demo.gif)

For 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).

-----

* [Usage](#usage)
* [Configuration](#configuration)
* [FAQ](#faq)
* [Advanced/legacy usage](#advancedlegacy-usage)

## Usage

Airbrussh 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:

```ruby
# In deploy.rb
set :format, :airbrussh
```

### What's displayed

When you run a Capistrano command, Airbrussh provides the following information in its output:

![Sample output](https://raw.github.com/mattbrictson/airbrussh/HEAD/formatting.png)

* Name of Capistrano task being executed
* When each task started (minutes:seconds elapsed since the deploy began)
* 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`
* Stdout and stderr output from each command
* The duration of each command execution, per server

### What's *not* displayed

For 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`.

## Configuration

You can customize many aspects of Airbrussh's output. In Capistrano 3.5 and newer, this is done via the `:format_options` variable, like this:

```ruby
# Pass options to Airbrussh
set :format_options, color: false, truncate: 80
```

Here 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):

|Option|Default|Usage|
|---|---|---|
|`banner`|`nil`|Provide a string (e.g. "Capistrano started!") that will be printed when Capistrano starts up.|
|`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.|
|`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.|
|`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`.|
|`log_file`|`log/capistrano.log`|Capistrano's verbose output is saved to this file to facilitate debugging. Set to `nil` to disable completely.|
|`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.|
|`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|

## FAQ

**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?**

Make sure Airbrussh is configured to show SSH output.

```ruby
set :format_options, command_output: true
```

**I haven't upgraded to Capistrano 3.5 yet. Can I still use Airbrussh?**

Yes! Capistrano 3.4.x is also supported. Refer to the [advanced/legacy usage](#advancedlegacy-usage) section for installation instructions.

**Does Airbrussh work with Capistrano 2?**

No, 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.

**Does Airbrussh work with JRuby?**

JRuby 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.

```ruby
set :format_options, truncate: false
```

**I have a question that’s not answered here or elsewhere in the README.**

Please [open a GitHub issue](https://github.com/mattbrictson/airbrussh/issues/new) and we’ll be happy to help!

## Advanced/legacy usage

Although 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+.

### Capistrano 3.4.x

Add this line to your application's Gemfile:

```ruby
gem "airbrussh", require: false
```

And then execute:

    $ bundle

Finally, add this line to your application's Capfile:

```ruby
require "airbrussh/capistrano"
```

**Important:** explicitly setting Capistrano's `:format` option in your deploy.rb will override airbrussh. Remove this line if you have it:

```ruby
# Remove this
set :format, :pretty
```

Capistrano 3.4.x doesn't have the `:format_options` configuration system, so you will need to configure Airbrussh using this technique:

```ruby
Airbrussh.configure do |config|
  config.color = false
  config.command_output = true
  # etc.
end
```

Refer to the [configuration](#configuration) section above for the list of supported options.

### SSHKit

If you are using SSHKit directly (i.e. without Capistrano), you can use Airbrussh like this:

```ruby
require "airbrussh"
SSHKit.config.output = Airbrussh::Formatter.new($stdout)

# You can also pass configuration options like this
SSHKit.config.output = Airbrussh::Formatter.new($stdout, color: false)
```

## History

Airbrussh 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.

## Roadmap

Airbrussh 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).

## Contributing

Contributions are welcome! Read [CONTRIBUTING.md](CONTRIBUTING.md) to get started.

[capistrano-mb]: https://github.com/mattbrictson/capistrano-mb


================================================
FILE: Rakefile
================================================
require "bundler/gem_tasks"

require "rake/testtask"
Rake::TestTask.new(:test) do |t|
  t.libs << "test"
  t.libs << "lib"
  t.test_files = FileList["test/**/*_test.rb"]
end

begin
  require "rubocop/rake_task"
  RuboCop::RakeTask.new
  task :default => [:test, :rubocop]
rescue LoadError
  task :default => :test
end

# == "rake release" enhancements ==============================================

Rake::Task["release"].enhance do
  puts "Don't forget to publish the release on GitHub!"
  system "open https://github.com/mattbrictson/airbrussh/releases"
end

task :disable_overcommit do
  ENV["OVERCOMMIT_DISABLE"] = "1"
end

Rake::Task[:build].enhance [:disable_overcommit]

task :verify_gemspec_files do
  git_files = `git ls-files -z`.split("\x0")
  gemspec_files = Gem::Specification.load("airbrussh.gemspec").files.sort
  ignored_by_git = gemspec_files - git_files
  next if ignored_by_git.empty?

  raise <<-ERROR.gsub(/^\s+/, "")

    The `spec.files` specified in airbrussh.gemspec include the following files
    that are being ignored by git. Did you forget to add them to the repo? If
    not, you may need to delete these files or modify the gemspec to ensure
    that they are not included in the gem by mistake:

    #{ignored_by_git.join("\n").gsub(/^/, '  ')}

  ERROR
end

Rake::Task[:build].enhance [:verify_gemspec_files]


================================================
FILE: UPGRADING-CAP-3.5.md
================================================
# Capistrano 3.5 upgrade guide for existing Airbrussh users

If you have been using Airbrussh with Capistrano, and are upgrading to Capistrano 3.5, then this guide is for you.

## What changed?

* Airbrussh is built into Capistrano starting with Capistrano 3.5.0, and is enabled by default.
* Capistrano 3.5 initializes Airbrussh with a default configuration that is different than Airbrussh+Capistrano 3.4.0.
* In Capistrano 3.5, `set :format_options, ...` is now the preferred mechanism for configuring Airbrussh.

## How to upgrade

1. Remove `gem "airbrussh"` from your Gemfile. Airbrussh is now included automatically by Capistrano.
2. Likewise, remove `require "capistrano/airbrussh"` from your Capfile. Capistrano now does this internally.
3. Remove `Airbrussh.configure do ... end` from your deploy.rb and replace with `set :format_options, ...` (see below).

## New configuration system

In 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:

```ruby
# Old syntax
Airbrussh.configure do |config|
  config.color = false
end

# New syntax
set :format_options, color: false
```

Although the syntax is different, the names of the configuration keys have not changed.

## New defaults

Capistrano 3.5.0 changes Airbrussh defaults as follows:

* `banner: false`
* `command_output: true`

Therefore, 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:

```ruby
set :format_options, banner: :auto, command_output: false
```

## Trouble?

If you have any Airbrussh-related trouble with the upgrade, please [open an issue](https://github.com/mattbrictson/airbrussh/issues).


================================================
FILE: airbrussh.gemspec
================================================
# coding: utf-8
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "airbrussh/version"

Gem::Specification.new do |spec|
  spec.name          = "airbrussh"
  spec.version       = Airbrussh::VERSION
  spec.license       = "MIT"
  spec.authors       = ["Matt Brictson"]
  spec.email         = ["airbrussh@mattbrictson.com"]
  spec.summary       = "Airbrussh pretties up your SSHKit and Capistrano output"
  spec.description   = "A replacement log formatter for SSHKit that makes "\
                       "Capistrano output much easier on the eyes. Just add "\
                       "Airbrussh to your Capfile and enjoy concise, useful "\
                       "log output that is easy to read."
  spec.homepage      = "https://github.com/mattbrictson/airbrussh"
  spec.metadata      = {
    "bug_tracker_uri" => "https://github.com/mattbrictson/airbrussh/issues",
    "changelog_uri" => "https://github.com/mattbrictson/airbrussh/releases",
    "source_code_uri" => "https://github.com/mattbrictson/airbrussh",
    "homepage_uri" => "https://github.com/mattbrictson/airbrussh"
  }

  spec.files         = Dir.glob(%w[LICENSE.txt README.md {exe,lib}/**/*]).reject { |f| File.directory?(f) }
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.required_ruby_version = ">= 2.5"

  spec.add_dependency "sshkit", [">= 1.6.1", "!= 1.7.0"]
end


================================================
FILE: appveyor.yml
================================================
version: "{build}"
skip_tags: true
skip_branch_with_pr: true
environment:
  BUNDLE_GEMFILE: "gemfiles/legacy.gemfile"
  MT_COMPAT: "1" # Allows old versions of mocha gem to work with minitest
install:
  - set PATH=C:\Ruby26-x64\bin;%PATH%
  - bundle install --retry=3
test_script:
  - bundle exec rake
build: off


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

require "bundler/setup"
require "airbrussh"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require "irb"
IRB.start


================================================
FILE: bin/setup
================================================
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

bundle install

# Do any other automated setup that you need to do here


================================================
FILE: gemfiles/legacy.gemfile
================================================
source "https://rubygems.org"

gemspec path: ".."

gem "minitest", "~> 5.10"
gem "minitest-reporters", "~> 1.1"
gem "mocha", "~> 2.1"
gem "rake", "~> 13.0"


================================================
FILE: lib/airbrussh/capistrano/tasks.rb
================================================
require "airbrussh"
require "airbrussh/colors"
require "airbrussh/console"
require "forwardable"
require "shellwords"

module Airbrussh
  module Capistrano
    # Encapsulates the rake behavior that integrates Airbrussh into Capistrano.
    # This class allows us to easily test the behavior using a mock to stand in
    # for the Capistrano DSL.
    #
    # See airbrussh/capistrano.rb to see how this class is used.
    #
    class Tasks
      extend Forwardable
      def_delegators :dsl, :set, :env
      def_delegators :config, :log_file

      include Airbrussh::Colors

      def initialize(dsl, stderr=$stderr, config=Airbrussh.configuration)
        @dsl = dsl
        @stderr = stderr
        @config = config

        configure
        warn_if_missing_dsl
      end

      # Behavior for the rake load:defaults task.
      def load_defaults
        require "sshkit/formatter/airbrussh"
        set :format, :airbrussh
      end

      # Behavior for the rake deploy:failed task.
      def deploy_failed
        return unless airbrussh_is_being_used?
        return if log_file.nil?

        error_line
        error_line(red("** DEPLOY FAILED"))
        error_line(yellow("** Refer to #{log_file} for details. "\
                          "Here are the last 20 lines:"))
        error_line
        error_line(tail_log_file)
      end

      private

      attr_reader :dsl, :stderr, :config

      # Change airbrussh's default configuration to be more appropriate for
      # capistrano.
      def configure
        config.log_file = "log/capistrano.log"
        config.monkey_patch_rake = true
      end

      # Verify that capistrano and rake DSLs are present
      def warn_if_missing_dsl
        return if %w[set namespace task].all? { |m| dsl.respond_to?(m, true) }

        error_line(
          red("WARNING: airbrussh/capistrano must be loaded by Capistrano in "\
              "order to work.\nRequire this gem within your application's "\
              "Capfile, as described here:\n"\
              "https://github.com/mattbrictson/airbrussh#installation")
        )
      end

      def err_console
        @err_console ||= begin
          # Ensure we do not truncate the error output
          err_config = config.dup
          err_config.truncate = false
          Airbrussh::Console.new(stderr, err_config)
        end
      end

      def error_line(line="\n")
        line.each_line(&err_console.method(:print_line))
      end

      # Returns a String containing the last 20 lines of the log file. Since
      # this method uses a fixed-size buffer, fewer than 20 lines may be
      # returned in cases where the lines are extremely long.
      def tail_log_file
        open(log_file) do |file|
          file.seek(-[8192, file.size].min, IO::SEEK_END)
          file.readlines.last(20).join
        end
      end

      def airbrussh_is_being_used?
        # Obtain the current formatter from the SSHKit backend
        output = env.backend.config.output
        output.is_a?(Airbrussh::Formatter)
      end
    end
  end
end


================================================
FILE: lib/airbrussh/capistrano.rb
================================================
require "airbrussh/capistrano/tasks"

tasks = Airbrussh::Capistrano::Tasks.new(self)

# Hook into Capistrano's init process to set the formatter
namespace :load do
  task :defaults do
    tasks.load_defaults
  end
end

# Capistrano failure hook
namespace :deploy do
  task :failed do
    tasks.deploy_failed
  end
end


================================================
FILE: lib/airbrussh/colors.rb
================================================
module Airbrussh
  # Very basic support for ANSI color, so that we don't have to rely on
  # any external dependencies.
  module Colors
    ANSI_CODES = {
      :red    => 31,
      :green  => 32,
      :yellow => 33,
      :blue   => 34,
      :gray   => 90
    }.freeze

    # Define red, green, blue, etc. methods that return a copy of the
    # String that is wrapped in the corresponding ANSI color escape
    # sequence.
    ANSI_CODES.each do |name, code|
      define_method(name) do |string|
        "\e[0;#{code};49m#{string}\e[0m"
      end
      module_function(name)
    end
  end
end


================================================
FILE: lib/airbrussh/command_formatter.rb
================================================
# encoding: UTF-8

require "airbrussh/colors"
require "delegate"

module Airbrussh
  # Decorates an SSHKit Command to add string output helpers and the
  # command's position within currently executing rake task:
  #
  # * position - zero-based position of this command in the list of
  #              all commands that have been run in the current rake task; in
  #              some cases this could be nil
  class CommandFormatter < SimpleDelegator
    include Airbrussh::Colors

    def initialize(command, position)
      super(command)
      @position = position
    end

    # Prefixes the line with the command number and removes the newline.
    #
    # format_output("hello\n") # => "01 hello"
    #
    def format_output(line)
      "#{number} #{line.chomp}"
    end

    # Returns the abbreviated command (in yellow) with the number prefix.
    #
    # start_message # => "01 echo hello"
    #
    def start_message
      "#{number} #{yellow(abbreviated)}"
    end

    # Returns a green (success) or red (failure) message depending on the
    # exit status.
    #
    # exit_message # => "✔ 01 user@host 0.084s"
    # exit_message # => "✘ 01 user@host 0.084s"
    #
    def exit_message
      message = if failure?
                  red(failure_message)
                else
                  green(success_message)
                end
      message << " #{runtime}"
    end

    private

    def user_at_host
      user_str = host.user || (host.ssh_options || {})[:user]
      host_str = host.hostname
      [user_str, host_str].compact.join("@")
    end

    def runtime
      format("%5.3fs", super)
    end

    def abbreviated
      to_s.sub(%r{^/usr/bin/env }, "")
    end

    def number
      format("%02d", @position.to_i + 1)
    end

    def success_message
      "✔ #{number} #{user_at_host}"
    end

    def failure_message
      "✘ #{number} #{user_at_host}"
    end
  end
end


================================================
FILE: lib/airbrussh/configuration.rb
================================================
require "airbrussh/colors"
require "airbrussh/console_formatter"
require "airbrussh/log_file_formatter"

module Airbrussh
  class Configuration
    attr_accessor :log_file, :monkey_patch_rake, :color, :truncate, :banner,
                  :command_output, :task_prefix, :context

    def initialize
      self.log_file = nil
      self.monkey_patch_rake = false
      self.color = :auto
      self.truncate = :auto
      self.banner = :auto
      self.command_output = false
      self.task_prefix = nil
      self.context = Airbrussh::Rake::Context
    end

    def apply_options(options)
      return self if options.nil?

      options.each do |key, value|
        if respond_to?(writer = "#{key}=")
          public_send(writer, value)
        else
          warn_unrecognized_key(key)
        end
      end
      self
    end

    def banner_message
      return nil unless banner
      return banner unless banner == :auto
      msg = +"Using airbrussh format."
      if log_file
        msg << "\n"
        msg << "Verbose output is being written to #{Colors.blue(log_file)}."
      end
      msg
    end

    # This returns an array of formatters appropriate for the configuration.
    # Depending on whether a log file is configured, this could be just the
    # Airbrussh:ConsoleFormatter, or that plus the LogFileFormatter.
    def formatters(io)
      fmts = [Airbrussh::ConsoleFormatter.new(io, self)]
      fmts.unshift(Airbrussh::LogFileFormatter.new(log_file)) if log_file
      fmts
    end

    def show_command_output?(sym)
      command_output == true || Array(command_output).include?(sym)
    end

    private

    def warn_unrecognized_key(key)
      $stderr.puts("Ignoring unrecognized Airbrussh option: #{key}")
    end
  end
end


================================================
FILE: lib/airbrussh/console.rb
================================================
# encoding: UTF-8

require "io/console"

module Airbrussh
  # Helper class that wraps an IO object and provides methods for truncating
  # output, assuming the IO object represents a console window.
  #
  # This is useful for writing log messages that will typically show up on
  # an ANSI color-capable console. When a console is not present (e.g. when
  # running on a CI server) the output will gracefully degrade.
  class Console
    attr_reader :output, :config

    def initialize(output, config=Airbrussh.configuration)
      @output = output
      @config = config
    end

    # Writes to the IO after first truncating the output to fit the console
    # width. If the underlying IO is not a TTY, ANSI colors are removed from
    # the output. A newline is always added. Color output can be forced by
    # setting the SSHKIT_COLOR environment variable.
    def print_line(obj="")
      string = obj.to_s

      string = truncate_to_console_width(string) if console_width
      string = strip_ascii_color(string) unless color_enabled?

      write(string + "\n")
      output.flush
    end

    # Writes directly through to the IO with no truncation or color logic.
    # No newline is added.
    def write(string)
      output.write(string || "")
    end
    alias << write

    def truncate_to_console_width(string)
      string = (string || "").rstrip
      ellipsis = utf8_supported?(string) ? "…" : "..."
      width = console_width

      if strip_ascii_color(string).length > width
        width -= ellipsis.length
        string.chop! while strip_ascii_color(string).length > width
        string << "#{ellipsis}\e[0m"
      else
        string
      end
    end

    def strip_ascii_color(string)
      string ||= ""
      string = to_utf8(string) unless string.valid_encoding?

      string.gsub(/\033\[[0-9;]*m/, "")
    end

    def console_width
      width = case (truncate = config.truncate)
              when :auto
                IO.console.winsize.last if @output.tty? && IO.console
              when Integer
                truncate
              end

      width if width.to_i > 0
    end

    private

    def color_enabled?
      case config.color
      when true
        true
      when :auto
        ENV["SSHKIT_COLOR"] || @output.tty?
      else
        false
      end
    end

    def utf8_supported?(string)
      string.encode("UTF-8").valid_encoding?
    rescue Encoding::UndefinedConversionError
      false
    end

    def to_utf8(string)
      string.force_encoding("ASCII-8BIT")
            .encode("UTF-8", :invalid => :replace, :undef => :replace)
    end
  end
end


================================================
FILE: lib/airbrussh/console_formatter.rb
================================================
require "airbrussh/colors"
require "airbrussh/command_formatter"
require "airbrussh/console"
require "airbrussh/rake/context"
require "sshkit"

module Airbrussh
  class ConsoleFormatter < SSHKit::Formatter::Abstract
    include Airbrussh::Colors
    extend Forwardable

    attr_reader :config, :context
    def_delegators :context, :current_task_name, :register_new_command

    def initialize(io, config=Airbrussh.configuration)
      super(io)

      @config = config
      @context = config.context.new(config)
      @console = Airbrussh::Console.new(original_output, config)

      write_banner
    end

    def write_banner
      print_line(config.banner_message) if config.banner_message
    end

    def log_command_start(command)
      return if debug?(command)
      first_execution = register_new_command(command)
      command = decorate(command)
      print_task_if_changed
      print_indented_line(command.start_message) if first_execution
    end

    def log_command_data(command, stream_type, string)
      return if debug?(command)
      return unless config.show_command_output?(stream_type)
      command = decorate(command)
      string.each_line do |line|
        print_indented_line(command.format_output(line))
      end
    end

    def log_command_exit(command)
      return if debug?(command)
      command = decorate(command)
      print_indented_line(command.exit_message, -2)
    end

    def write(obj)
      case obj
      when SSHKit::Command
        log_command_start(obj)
        log_and_clear_command_output(obj, :stderr)
        log_and_clear_command_output(obj, :stdout)
        log_command_exit(obj) if obj.finished?
      when SSHKit::LogMessage
        write_log_message(obj)
      end
    end
    alias << write

    private

    attr_accessor :last_printed_task

    def write_log_message(log_message)
      return if debug?(log_message)
      print_task_if_changed
      print_indented_line(format_log_message(log_message))
    end

    def format_log_message(log_message)
      case log_message.verbosity
      when SSHKit::Logger::WARN
        "#{yellow('WARN')}  #{log_message}"
      when SSHKit::Logger::ERROR
        "#{red('ERROR')} #{log_message}"
      when SSHKit::Logger::FATAL
        "#{red('FATAL')} #{log_message}"
      else
        log_message.to_s
      end
    end

    # For SSHKit versions up to and including 1.7.1, the stdout and stderr
    # output was available as attributes on the Command. Print the data for
    # the specified command and stream if enabled and clear the stream.
    # (see Airbrussh::Configuration#command_output).
    def log_and_clear_command_output(command, stream)
      output = command.public_send(stream)
      log_command_data(command, stream, output)
      command.public_send("#{stream}=", "")
    end

    def print_task_if_changed
      return if current_task_name.nil?
      return if current_task_name == last_printed_task

      self.last_printed_task = current_task_name
      print_line("#{config.task_prefix}#{clock} #{blue(current_task_name)}")
    end

    def clock
      @start_at ||= Time.now
      duration = Time.now - @start_at

      minutes = (duration / 60).to_i
      seconds = (duration - minutes * 60).to_i

      format("%02d:%02d", minutes, seconds)
    end

    def debug?(obj)
      obj.verbosity <= SSHKit::Logger::DEBUG
    end

    def decorate(command)
      Airbrussh::CommandFormatter.new(command, @context.position(command))
    end

    def print_line(string)
      @console.print_line(string)
    end

    def print_indented_line(string, offset=0)
      indent = " " * (6 + offset)
      print_line([indent, string].join)
    end
  end
end


================================================
FILE: lib/airbrussh/delegating_formatter.rb
================================================
require "sshkit"

module Airbrussh
  # This class quacks like an SSHKit::Formatter, but when any formatting
  # methods are called, it simply forwards them to one more more concrete
  # formatters. This allows us to split out the responsibilities of
  # ConsoleFormatter and LogFileFormatter into two separate classes, with
  # DelegatingFormatter forwarding the logging messages to both at once.
  #
  class DelegatingFormatter
    FORWARD_METHODS = %w[
      fatal error warn info debug log
      log_command_start log_command_data log_command_exit
    ].freeze
    DUP_AND_FORWARD_METHODS = %w[<< write].freeze

    attr_reader :formatters

    def initialize(formatters)
      @formatters = formatters
    end

    FORWARD_METHODS.each do |method|
      define_method(method) do |*args|
        formatters.map { |f| f.public_send(method, *args) }.last
      end
    end

    # For versions of SSHKit up to and including 1.7.1, the LogfileFormatter
    # and ConsoleFormatter (and all of SSHKit's built in formatters) clear
    # the stdout and stderr data in the command obj. Therefore, ensure only
    # one of the formatters (the last one) gets the original command. This is
    # also the formatter whose return value is passed to the caller.
    #
    DUP_AND_FORWARD_METHODS.each do |method|
      define_method(method) do |command_or_log_message|
        formatters[0...-1].each do |f|
          f.public_send(method, command_or_log_message.dup)
        end
        formatters.last.public_send(method, command_or_log_message)
      end
    end
  end
end


================================================
FILE: lib/airbrussh/formatter.rb
================================================
require "airbrussh"
require "airbrussh/delegating_formatter"

# This is the formatter class that conforms to the SSHKit Formatter API and
# provides the airbrussh functionality to SSHKit. Note however that this class
# doesn't do much by itself; instead, it delegates to the ConsoleFormatter
# and (optionally) the LogFileFormatter, which handle the bulk of the logic.
#
module Airbrussh
  class Formatter < Airbrussh::DelegatingFormatter
    def initialize(io, options_or_config_object={})
      config = ::Airbrussh.configuration(options_or_config_object)
      # Delegate to ConsoleFormatter and (optionally) LogFileFormatter,
      # based on the configuration.
      super(config.formatters(io))
    end
  end
end


================================================
FILE: lib/airbrussh/log_file_formatter.rb
================================================
require "delegate"
require "fileutils"
require "logger"
require "sshkit"

module Airbrussh
  # A Pretty formatter that sends its output to a specified log file path.
  # LogFileFormatter takes care of creating the file (and its parent
  # directory) if it does not already exist, opens it for appending, and writes
  # a delimiter message. The file is automatically rotated if it reaches 20 MB.
  #
  class LogFileFormatter < SimpleDelegator
    attr_reader :path

    def initialize(path, formatter_class=SSHKit::Formatter::Pretty)
      @path = path
      ensure_directory_exists if path.is_a?(String)
      super(formatter_class.new(log_file_io))
      write_delimiter
    end

    private

    def write_delimiter
      delimiter = []
      delimiter << "-" * 75
      delimiter << "START #{Time.now} cap #{ARGV.join(' ')}"
      delimiter << "-" * 75
      delimiter.each do |line|
        write(SSHKit::LogMessage.new(SSHKit::Logger::INFO, line))
      end
    end

    def ensure_directory_exists
      FileUtils.mkdir_p(File.dirname(path))
    end

    def log_file_io
      @io ||= ::Logger.new(path, 1, 20_971_520)
    end
  end
end


================================================
FILE: lib/airbrussh/rake/context.rb
================================================
module Airbrussh
  module Rake
    # Maintains information about what Rake task is currently being invoked,
    # in order to be able to decorate SSHKit commands with additional
    # context-sensitive information. Works via a monkey patch to Rake::Task,
    # which can be disabled via by setting
    # Airbrussh.configuration.monkey_patch_rake = false.
    #
    # Note that this class is not thread-safe. Normally this is not a problem,
    # but some Capistrano users are known to use `invoke` to switch the Rake
    # task in the middle of an SSHKit thread, which causes Context to get very
    # confused. It such scenarios Context is not reliable and may return `nil`
    # for the `position` of a command.
    #
    class Context
      class << self
        attr_accessor :current_task_name
      end

      def initialize(config=Airbrussh.configuration)
        @history = []
        @enabled = config.monkey_patch_rake
        self.class.install_monkey_patch if enabled?
      end

      # Returns the name of the currently-executing rake task, if it can be
      # determined. If monkey patching is disabled, this will be nil.
      def current_task_name
        return nil unless enabled?
        self.class.current_task_name
      end

      # Update the context when a new command starts by:
      # * Clearing the command history if the rake task has changed
      # * Recording the command in the history
      #
      # Returns whether or not this command was the first execution
      # of this command in the current rake task
      def register_new_command(command)
        reset_history_if_task_changed

        first_execution = !history.include?(command.to_s)
        history << command.to_s
        history.uniq!
        first_execution
      end

      # The zero-based position of the specified command in the current rake
      # task. May be `nil` in certain multi-threaded scenarios, so be careful!
      def position(command)
        history.index(command.to_s)
      end

      if Object.respond_to?(:prepend)
        module Patch
          def execute(args=nil)
            ::Airbrussh::Rake::Context.current_task_name = name.to_s
            super
          end
        end

        def self.install_monkey_patch
          require "rake"
          return if ::Rake::Task.include?(::Airbrussh::Rake::Context::Patch)

          ::Rake::Task.prepend(::Airbrussh::Rake::Context::Patch)
        end
      else
        def self.install_monkey_patch
          require "rake"
          return if ::Rake::Task.instance_methods.include?(:_airbrussh_execute)

          ::Rake::Task.class_exec do
            alias_method :_airbrussh_execute, :execute
            def execute(args=nil)
              ::Airbrussh::Rake::Context.current_task_name = name.to_s
              _airbrussh_execute(args)
            end
          end
        end
      end

      private

      attr_reader :history
      attr_accessor :last_task_name

      def reset_history_if_task_changed
        history.clear if last_task_name != current_task_name
        self.last_task_name = current_task_name
      end

      def enabled?
        @enabled
      end
    end
  end
end


================================================
FILE: lib/airbrussh/version.rb
================================================
# frozen_string_literal: true

module Airbrussh
  VERSION = "1.6.1".freeze
end


================================================
FILE: lib/airbrussh.rb
================================================
require "airbrussh/configuration"
require "airbrussh/formatter"
require "airbrussh/version"

module Airbrussh
  def self.configuration(options={})
    return options if options.is_a?(::Airbrussh::Configuration)
    @configuration ||= Configuration.new
    @configuration.apply_options(options)
  end

  def self.configure
    yield(configuration)
  end
end


================================================
FILE: lib/sshkit/formatter/airbrussh.rb
================================================
require "airbrussh/formatter"

# Capistrano's formatter configuration requires that the formatter class
# be in the SSHKit::Formatter namespace. So we declare
# SSHKit::Formatter::Airbrussh that simply functions as an alias for
# Airbrussh::Formatter.
module SSHKit
  module Formatter
    class Airbrussh < Airbrussh::Formatter
    end
  end
end


================================================
FILE: test/airbrussh/capistrano/tasks_test.rb
================================================
require "minitest_helper"
require "airbrussh/capistrano/tasks"
require "airbrussh/configuration"
require "ostruct"
require "stringio"
require "tempfile"

class Airbrussh::Capistrano::TasksTest < Minitest::Test
  DSL = Struct.new(:formatter) do
    def set(*); end

    # The Tasks object needs to reach into the backend provided by `env` in
    # order to obtain the current Formatter, so here we build the complex mock
    # needed for that.
    def env
      OpenStruct.new(
        :backend => OpenStruct.new(
          :config => OpenStruct.new(
            :output => formatter
          )
        )
      )
    end

    private

    def namespace(*); end

    def task(*); end
  end

  def setup
    @config = Airbrussh::Configuration.new
    @dsl = DSL.new(Airbrussh::Formatter.new(StringIO.new, @config))
    @stderr = StringIO.new
    @tasks = Airbrussh::Capistrano::Tasks.new(@dsl, @stderr, @config)
  end

  def test_no_warning_is_printed_when_proper_dsl_is_present
    assert_empty(stderr)
  end

  def test_prints_warning_if_dsl_is_missing
    bad_dsl = Object.new
    Airbrussh::Capistrano::Tasks.new(bad_dsl, @stderr, @config)
    assert_match(/WARNING.*must be loaded by Capistrano/, stderr)
  end

  def test_configures_for_capistrano
    assert_equal("log/capistrano.log", @config.log_file)
    assert(@config.monkey_patch_rake)
    assert_equal(:auto, @config.color)
    assert_equal(:auto, @config.truncate)
    assert_equal(:auto, @config.banner)
    refute(@config.command_output)
  end

  def test_sets_airbrussh_formatter_on_load_defaults
    @dsl.expects(:set).with(:format, :airbrussh)
    @tasks.load_defaults
    assert(defined?(SSHKit::Formatter::Airbrussh))
  end

  def test_prints_last_20_logfile_lines_on_deploy_failure
    with_log_file do |log_file|
      log_file.write((11..31).map { |i| "line #{i}\n" }.join)
      log_file.close

      @tasks.deploy_failed

      assert_match("DEPLOY FAILED", stderr)
      refute_match("line 11", stderr)
      (12..31).each { |i| assert_match("line #{i}", stderr) }
    end
  end

  def test_does_not_truncate_log_file_lines
    @config.truncate = 80

    with_log_file do |log_file|
      long_line = "a" * 100
      log_file.puts(long_line)
      log_file.close

      @tasks.deploy_failed

      assert_match(long_line, stderr)
    end
  end

  def test_does_not_print_anything_on_deploy_failure_if_nil_logfile
    @config.log_file = nil
    @tasks.deploy_failed
    assert_empty(stderr)
  end

  def test_does_not_print_anything_on_deploy_failure_if_airbrussh_is_not_used
    pretty_dsl = DSL.new(SSHKit::Formatter::Pretty.new(StringIO.new))
    tasks = Airbrussh::Capistrano::Tasks.new(pretty_dsl, @stderr, @config)
    tasks.deploy_failed
    assert_empty(stderr)
  end

  private

  def stderr
    @stderr.string
  end

  def with_log_file
    log_file = Tempfile.new("airbrussh-test-")
    begin
      @config.log_file = log_file.path
      yield(log_file)
    ensure
      log_file.unlink
    end
  end
end


================================================
FILE: test/airbrussh/capistrano_test.rb
================================================
require "minitest_helper"

class Airbrussh::CapistranoTest < Minitest::Test
  def setup
    # Mute the warning that is normally printed to $stderr when
    # airbrussh/capistrano is required outside a capistrano runtime.
    $stderr.stubs(:write)
    require "airbrussh/capistrano"
  end

  def teardown
    Airbrussh::Rake::Context.current_task_name = nil
  end

  def test_defines_tasks
    assert_instance_of(Rake::Task, Rake.application["load:defaults"])
    assert_instance_of(Rake::Task, Rake.application["deploy:failed"])
  end

  def test_load_defaults_rake_task_delegates_to_tasks_instance
    Airbrussh::Capistrano::Tasks.any_instance.expects(:load_defaults)
    Rake.application["load:defaults"].execute
  end

  def test_deploy_failed_rake_task_delegates_to_tasks_instance
    Airbrussh::Capistrano::Tasks.any_instance.expects(:deploy_failed)
    Rake.application["deploy:failed"].execute
  end
end


================================================
FILE: test/airbrussh/colors_test.rb
================================================
require "minitest_helper"
require "airbrussh/colors"

class Airbrussh::ColorsTest < Minitest::Test
  include Airbrussh::Colors

  def test_red
    assert_equal("\e[0;31;49mhello\e[0m", red("hello"))
  end

  def test_green
    assert_equal("\e[0;32;49mhello\e[0m", green("hello"))
  end

  def test_yellow
    assert_equal("\e[0;33;49mhello\e[0m", yellow("hello"))
  end

  def test_blue
    assert_equal("\e[0;34;49mhello\e[0m", blue("hello"))
  end

  def test_gray
    assert_equal("\e[0;90;49mhello\e[0m", gray("hello"))
  end
end


================================================
FILE: test/airbrussh/command_formatter_test.rb
================================================
# encoding: UTF-8

require "minitest_helper"
require "ostruct"
require "airbrussh/command_formatter"

class Airbrussh::CommandFormatterTest < Minitest::Test
  def setup
    @sshkit_command = OpenStruct.new(
      :host => host("deployer", "12.34.56.78"),
      :options => { :user => "override" },
      :runtime => 1.23456,
      :failure? => false
    )
    @sshkit_command.define_singleton_method(:to_s) do
      "/usr/bin/env echo hello"
    end
    @command = Airbrussh::CommandFormatter.new(@sshkit_command, 0)
  end

  def test_format_output
    assert_equal("01 hello", @command.format_output("hello\n"))
  end

  def test_start_message
    assert_equal("01 \e[0;33;49mecho hello\e[0m", @command.start_message)
  end

  def test_exit_message_success
    assert_equal(
      "\e[0;32;49m✔ 01 deployer@12.34.56.78\e[0m 1.235s",
      @command.exit_message
    )
  end

  def test_exit_message_failure
    @command.stubs(:failure? => true)

    assert_equal(
      "\e[0;31;49m✘ 01 deployer@12.34.56.78\e[0m "\
      "1.235s",
      @command.exit_message
    )
  end

  def test_uses_ssh_options_if_host_user_is_absent
    @sshkit_command.host = host(nil, "12.34.56.78", :user => "sshuser")
    assert_equal(
      "\e[0;32;49m✔ 01 sshuser@12.34.56.78\e[0m 1.235s",
      @command.exit_message
    )
  end

  def test_shows_hostname_only_if_no_user
    @sshkit_command.host = host(nil, "12.34.56.78")
    assert_equal(
      "\e[0;32;49m✔ 01 12.34.56.78\e[0m 1.235s",
      @command.exit_message
    )
  end

  def test_handles_nil_position_gracefully
    command = Airbrussh::CommandFormatter.new(@sshkit_command, nil)
    assert_equal("01 hello", command.format_output("hello\n"))
  end

  private

  def host(user, hostname, ssh_options={})
    SSHKit::Host.new(
      :user => user,
      :hostname => hostname,
      :ssh_options => ssh_options
    )
  end
end


================================================
FILE: test/airbrussh/configuration_test.rb
================================================
require "minitest_helper"
require "airbrussh/console_formatter"
require "airbrussh/log_file_formatter"
require "tempfile"

class Airbrussh::ConfigurationTest < Minitest::Test
  def setup
    @config = Airbrussh::Configuration.new
  end

  def test_defaults
    assert_nil(@config.log_file)
    assert_equal(:auto, @config.color)
    assert_equal(:auto, @config.truncate)
    assert_equal(:auto, @config.banner)
    assert_nil(@config.task_prefix)
    refute(@config.monkey_patch_rake)
    refute(@config.command_output)
    assert_equal(Airbrussh::Rake::Context, @config.context)
  end

  def test_apply_options
    @config.apply_options(
      :log_file => "test",
      :color => true,
      :truncate => false,
      :banner => "hi",
      :monkey_patch_rake => true,
      :command_output => true,
      :context => Class
    )

    assert_equal("test", @config.log_file)
    assert_equal(true, @config.color)
    assert_equal(false, @config.truncate)
    assert_equal("hi", @config.banner)
    assert(@config.monkey_patch_rake)
    assert(@config.command_output)
    assert_equal(Class, @config.context)
  end

  def test_apply_options_warns_on_stderr_of_bad_key
    _out, err = capture_io { @config.apply_options(:bad => "test") }
    assert_match(/unrecognized/i, err)
    assert_match(/bad/, err)
  end

  def test_auto_banner_message_without_log
    @config.log_file = nil
    @config.banner = :auto
    assert_equal("Using airbrussh format.", @config.banner_message)
  end

  def test_auto_banner_message_with_log
    @config.log_file = "log/test.log"
    @config.banner = :auto
    assert_equal(
      "Using airbrussh format.\n"\
      "Verbose output is being written to \e[0;34;49mlog/test.log\e[0m.",
      @config.banner_message
    )
  end

  def test_nil_or_false_banner_message
    @config.banner = nil
    assert_nil(@config.banner_message)
    @config.banner = false
    assert_nil(@config.banner_message)
  end

  def test_custom_banner_message
    @config.banner = "Hello!"
    assert_equal("Hello!", @config.banner_message)
  end

  def test_formatters_without_log_file
    io = StringIO.new
    formatters = @config.formatters(io)
    assert_equal(1, formatters.length)
    assert_instance_of(Airbrussh::ConsoleFormatter, formatters.first)
    assert_equal(io, formatters.first.original_output)
    assert_equal(@config, formatters.first.config)
  end

  def test_formatters_with_log_file
    @config.log_file = Tempfile.new("airbrussh-test").path
    io = StringIO.new
    formatters = @config.formatters(io)
    assert_equal(2, formatters.length)
    assert_instance_of(Airbrussh::LogFileFormatter, formatters.first)
    assert_instance_of(Airbrussh::ConsoleFormatter, formatters.last)
    assert_equal(@config.log_file, formatters.first.path)
    assert_equal(io, formatters.last.original_output)
    assert_equal(@config, formatters.last.config)
  end

  def test_effects_of_command_output_true
    @config.command_output = true
    assert(@config.show_command_output?(:stdout))
    assert(@config.show_command_output?(:stderr))
  end

  def test_effects_of_command_output_false
    @config.command_output = false
    refute(@config.show_command_output?(:stdout))
    refute(@config.show_command_output?(:stderr))
  end

  def test_effects_of_command_output_stdout
    @config.command_output = :stdout
    assert(@config.show_command_output?(:stdout))
    refute(@config.show_command_output?(:stderr))
  end

  def test_effects_of_command_output_stderr
    @config.command_output = :stderr
    refute(@config.show_command_output?(:stdout))
    assert(@config.show_command_output?(:stderr))
  end

  def test_effects_of_command_output_stdout_stderr
    @config.command_output = [:stdout, :stderr]
    assert(@config.show_command_output?(:stdout))
    assert(@config.show_command_output?(:stderr))
  end
end


================================================
FILE: test/airbrussh/console_formatter_test.rb
================================================
require "minitest_helper"
require "stringio"
require "airbrussh/configuration"
require "airbrussh/console_formatter"

# ConsoleFormatter is currently tested via a comprehensive acceptance-style
# test in Airbrussh::FormatterTest. Hence the lack of unit tests here, which
# would be redundant.
class Airbrussh::ConsoleFormatterTest < Minitest::Test
  def setup
    @config = Airbrussh::Configuration.new
    @config.banner = false
    @config.command_output = true
    @output = StringIO.new
    @formatter = Airbrussh::ConsoleFormatter.new(@output, @config)
  end

  # Make sure that command data containing two lines is formatted as two
  # indented lines.
  def test_log_command_data_with_multiline_string
    command = stub(:verbosity => SSHKit::Logger::INFO, :to_s => "greet")
    data = "hello\nworld\n"
    @formatter.log_command_start(command)
    @formatter.log_command_data(command, :stdout, data)
    assert_equal(
      "      01 greet\n"\
      "      01 hello\n"\
      "      01 world\n",
      output
    )
  end

  private

  def output
    @output.string
  end
end


================================================
FILE: test/airbrussh/console_test.rb
================================================
# encoding: UTF-8

require "minitest_helper"
require "stringio"
require "airbrussh/configuration"
require "airbrussh/console"

class Airbrussh::ConsoleTest < Minitest::Test
  def setup
    @output = StringIO.new
  end

  def test_color_is_allowed_for_tty
    console = configured_console(:tty => true) do |config|
      config.color = :auto
    end
    console.print_line("The \e[0;32;49mgreen\e[0m text")
    assert_equal("The \e[0;32;49mgreen\e[0m text\n", output)
  end

  def test_color_is_can_be_forced
    console = configured_console(:tty => false) do |config|
      config.color = true
    end
    console.print_line("The \e[0;32;49mgreen\e[0m text")
    assert_equal("The \e[0;32;49mgreen\e[0m text\n", output)
  end

  def test_color_is_can_be_forced_via_env
    console = configured_console(:tty => false) do |config|
      config.color = :auto
    end
    ENV.stubs(:[]).with("SSHKIT_COLOR").returns("1")
    console.print_line("The \e[0;32;49mgreen\e[0m text")
    assert_equal("The \e[0;32;49mgreen\e[0m text\n", output)
  end

  def test_color_is_stripped_for_non_tty
    console = configured_console(:tty => false) do |config|
      config.color = :auto
    end
    console.print_line("The \e[0;32;49mgreen\e[0m text")
    assert_equal("The green text\n", output)
  end

  def test_color_can_be_disabled_for_tty
    console = configured_console(:tty => true) do |config|
      config.color = false
    end
    console.print_line("The \e[0;32;49mgreen\e[0m text")
    assert_equal("The green text\n", output)
  end

  def test_truncates_to_winsize
    console = configured_console(:tty => true) do |config|
      config.color = false
      config.truncate = :auto
    end
    IO.stubs(:console => stub(:winsize => [100, 20]))
    console.print_line("The quick brown fox jumps over the lazy dog.")
    assert_equal("The quick brown fox…\n", output)
  end

  def test_ignores_ascii_color_codes_when_determing_truncation_amount
    console = configured_console(:tty => true) do |config|
      config.color = true
      config.truncate = :auto
    end
    IO.stubs(:console => stub(:winsize => [100, 20]))
    twenty_chars_plus_color = "\e[0;31;49m#{'a' * 20}\e[0m"
    console.print_line(twenty_chars_plus_color)
    assert_equal("#{twenty_chars_plus_color}\n", output)
  end

  def test_truncates_to_explicit_width
    console = configured_console(:tty => true) do |config|
      config.color = false
      config.truncate = 25
    end
    console.print_line("The quick brown fox jumps over the lazy dog.")
    assert_equal("The quick brown fox jump…\n", output)
  end

  def test_truncation_can_be_disabled
    console = configured_console(:tty => true) do |config|
      config.truncate = false
    end
    IO.expects(:console).never
    console.print_line("The quick brown fox jumps over the lazy dog.")
    assert_equal("The quick brown fox jumps over the lazy dog.\n", output)
  end

  # SSHKit sometimes returns raw ASCII-8BIT data that cannot be converted to
  # UTF-8, which could frustrate the truncation logic. Make sure that Console
  # recovers gracefully in this scenario.
  def test_truncates_improperly_encoded_ascii_string
    console = configured_console(:tty => true) do |config|
      config.color = false
      config.truncate = 10
    end

    console.print_line(ascii_8bit("The ‘quick’ brown fox"))

    # Note that the left-apostrophe character is actually 3 bytes as raw
    # ASCII-8BIT, which accounts for the short truncated value.
    assert_equal(ascii_8bit("The ‘...\n"), ascii_8bit(output))
  end

  def test_print_line_handles_invalid_utf8
    console = configured_console(:tty => false)

    invalid_utf8 = "The ‘quick’ brown fox"
                   .encode("Windows-1255")
                   .force_encoding("UTF-8")

    console.print_line(invalid_utf8)
    assert_equal("The �quick� brown fox\n", output)
  end

  def test_doesnt_truncates_to_zero_width
    console = configured_console(:tty => true) do |config|
      config.color = false
      config.truncate = 0
    end
    console.print_line("The quick brown fox jumps over the lazy dog.")
    assert_equal("The quick brown fox jumps over the lazy dog.\n", output)
  end

  private

  def ascii_8bit(string)
    string.dup.force_encoding("ASCII-8BIT")
  end

  def output
    @output.string
  end

  def configured_console(opts={})
    config = Airbrussh::Configuration.new
    yield(config) if block_given?
    @output.stubs(:tty? => opts.fetch(:tty, false))
    Airbrussh::Console.new(@output, config)
  end
end


================================================
FILE: test/airbrussh/delegating_formatter_test.rb
================================================
require "minitest_helper"
require "airbrussh/delegating_formatter"

class Airbrussh::DelegatingFormatterTest < Minitest::Test
  def setup
    @fmt1 = stub
    @fmt2 = stub
    @delegating = Airbrussh::DelegatingFormatter.new([@fmt1, @fmt2])
  end

  def test_forwards_logger_methods
    %w[fatal error warn info debug log].each do |method|
      @fmt1.expects(method).with("string").returns(6)
      @fmt2.expects(method).with("string").returns(6)
      result = @delegating.public_send(method, "string")
      assert_equal(6, result)
    end
  end

  def test_forwards_start_and_exit_methods
    %w[log_command_start log_command_exit].each do |method|
      @fmt1.expects(method).with(:command).returns(nil)
      @fmt2.expects(method).with(:command).returns(nil)
      result = @delegating.public_send(method, :command)
      assert_nil(result)
    end
  end

  def test_forwards_log_command_data
    @fmt1.expects(:log_command_data).with(:command, :stdout, "a").returns(nil)
    @fmt2.expects(:log_command_data).with(:command, :stdout, "a").returns(nil)
    result = @delegating.log_command_data(:command, :stdout, "a")
    assert_nil(result)
  end

  def test_forwards_io_methods_to_multiple_formatters
    # All formatters get duped commands except for the last one. This is
    # because in SSHKit versions up to and including 1.7.1, the formatters
    # clear the command output, so each must be given it's own copy.
    command = stub(:dup => "I've been duped!")
    %w[<< write].each do |method|
      @fmt1.expects(method).with("I've been duped!").returns(16)
      @fmt2.expects(method).with(command).returns(10)
      result = @delegating.public_send(method, command)
      assert_equal(10, result)
    end
  end

  def test_forwards_io_methods_to_a_single_formatter
    command = stub(:dup => "I've been duped!")
    %w[<< write].each do |method|
      delegating = Airbrussh::DelegatingFormatter.new([@fmt1])
      @fmt1.expects(method).with(command).returns(10)
      result = delegating.public_send(method, command)
      assert_equal(10, result)
    end
  end
end


================================================
FILE: test/airbrussh/formatter_test.rb
================================================
# encoding: utf-8

require "minitest_helper"
require "bundler"
require "etc"

class Airbrussh::FormatterTest < Minitest::Test
  include RakeTaskDefinition

  def setup
    @output = StringIO.new
    @log_file = StringIO.new
    # sshkit > 1.6.1 && <= 1.7.1 uses Etc.getpwuid.name for local user lookup
    # which causes a NoMethodError on Windows, so we need to stub it here.
    Etc.stubs(:getpwuid => stub(:name => "stubbed_user")) unless sshkit_after?("1.7.1")
    @user = SSHKit::Host.new(:local).username # nil on sshkit < 1.7.0
    @user_at_localhost = [@user, "localhost"].compact.join("@")
  end

  def teardown
    Airbrussh::Rake::Context.current_task_name = nil
    SSHKit.reset_configuration!
  end

  def configure
    airbrussh_config = Airbrussh::Configuration.new
    airbrussh_config.log_file = @log_file

    # Replace command map so it doesn't prefix every cmd with /usr/bin/env
    sshkit_config = SSHKit.config
    sshkit_config.command_map = Hash.new { |h, cmd| h[cmd] = cmd.to_s }

    yield(airbrussh_config, sshkit_config)

    sshkit_config.output = formatter_class.new(@output, airbrussh_config)
  end

  def test_can_be_configured_with_options_hash
    formatter = Airbrussh::Formatter.new(@output, :banner => "success!")
    config = formatter.formatters.last.config
    assert_equal("success!", config.banner)
  end

  def test_formats_execute_with_color
    configure do |airbrussh_config, sshkit_config|
      sshkit_config.output_verbosity = ::Logger::DEBUG
      airbrussh_config.command_output = true
      airbrussh_config.color = true
    end

    on_local do
      execute(:echo, "foo")
    end

    assert_output_lines(
      "      01 \e[0;33;49mecho foo\e[0m\n",
      "      01 foo\n",
      /    \e\[0;32;49m✔ 01 #{@user_at_localhost}\e\[0m \d.\d+s\n/
    )

    assert_log_file_lines(
      command_running("echo foo"),
      command_started_debug("echo foo"),
      command_std_stream(:stdout, "foo"),
      command_success
    )
  end

  def test_formats_execute_without_color
    configure do |airbrussh_config|
      airbrussh_config.command_output = true
    end

    on_local do
      execute(:echo, "foo")
    end

    assert_output_lines(
      "      01 echo foo\n",
      "      01 foo\n",
      /    ✔ 01 #{@user_at_localhost} \d.\d+s\n/
    )

    assert_log_file_lines(
      command_running("echo foo"), command_success
    )
  end

  def test_formats_without_command_output
    configure do |airbrussh_config|
      airbrussh_config.command_output = false
    end

    on_local do
      execute(:ls, "-l")
    end

    assert_output_lines(
      "      01 ls -l\n",
      /    ✔ 01 #{@user_at_localhost} \d.\d+s\n/
    )
  end

  def test_formats_failing_execute_with_color
    configure do |airbrussh_config, sshkit_config|
      sshkit_config.output_verbosity = ::Logger::DEBUG
      airbrussh_config.command_output = true
      airbrussh_config.color = true
    end

    error = nil
    on_local do
      begin
        execute(:echo, "hi")
        execute(:ls, "_file_does_not_exist")
      rescue SSHKit::Command::Failed => error # rubocop:disable Lint/SuppressedException
      end
    end

    refute_nil error

    expected_output = [
      "      01 \e[0;33;49mecho hi\e[0m\n",
      "      01 hi\n",
      /    \e\[0;32;49m✔ 01 #{@user_at_localhost}\e\[0m \d.\d+s\n/,
      "      02 \e[0;33;49mls _file_does_not_exist\e[0m\n"
    ]

    error_message = /.*_file_does_not_exist.*No such file or directory/

    # Don't know why this log line doesn't show up in SSHKit 1.6.1
    expected_output << /      02 #{error_message}\n/ if sshkit_after?("1.6.1")

    assert_output_lines(*expected_output)

    expected_log_output = [
      command_running("echo hi"),
      command_started_debug("echo hi"),
      command_std_stream(:stdout, "hi"),
      command_success,

      command_running("ls _file_does_not_exist"),
      command_started_debug("ls _file_does_not_exist")
    ]

    if sshkit_after?("1.6.1")
      expected_log_output << command_std_stream(:stderr, error_message)
      expected_log_output << "\e[0m" if color_output?
    end

    assert_log_file_lines(*expected_log_output)
  end

  def test_formats_capture_with_color
    configure do |airbrussh_config|
      airbrussh_config.command_output = true
      airbrussh_config.color = true
    end

    on_local do
      capture(:ls, "-1", "airbrussh.gemspec", :verbosity => SSHKit::Logger::INFO)
    end

    assert_output_lines(
      "      01 \e[0;33;49mls -1 airbrussh.gemspec\e[0m\n",
      "      01 airbrussh.gemspec\n",
      /    \e\[0;32;49m✔ 01 #{@user_at_localhost}\e\[0m \d.\d+s\n/
    )

    assert_log_file_lines(
      command_running("ls -1 airbrussh.gemspec"), command_success
    )
  end

  def test_formats_capture_without_color
    configure do |airbrussh_config|
      airbrussh_config.command_output = true
    end

    on_local do
      capture(:ls, "-1", "airbrussh.gemspec", :verbosity => SSHKit::Logger::INFO)
    end

    assert_output_lines(
      "      01 ls -1 airbrussh.gemspec\n",
      "      01 airbrussh.gemspec\n",
      /    ✔ 01 #{@user_at_localhost} \d.\d+s\n/
    )

    assert_log_file_lines(
      command_running("ls -1 airbrussh.gemspec"), command_success
    )
  end

  def test_does_not_output_test_commands
    configure do |airbrussh_config, sshkit_config|
      airbrussh_config.command_output = true
      sshkit_config.output_verbosity = Logger::DEBUG
    end

    on_local do
      test("echo hi")
    end

    assert_output_lines

    assert_log_file_lines(
      command_running("echo hi", "DEBUG"),
      command_started_debug("echo hi"),
      command_std_stream(:stdout, "hi"),
      command_success("DEBUG")
    )
  end

  def test_handles_rake_tasks
    configure do |airbrussh_config|
      airbrussh_config.monkey_patch_rake = true
      airbrussh_config.command_output = true
    end

    on_local("special_rake_task") do
      execute(:echo, "command 1")
      info("Starting command 2")
      execute(:echo, "command 2")
    end
    on_local("special_rake_task_2") do
      error("New task starting")
    end
    on_local("special_rake_task_3") do
      execute(:echo, "command 3")
      execute(:echo, "command 4")
      warn("All done")
    end

    assert_output_lines(
      "00:00 special_rake_task\n",
      "      01 echo command 1\n",
      "      01 command 1\n",
      /    ✔ 01 #{@user_at_localhost} \d.\d+s\n/,
      "      Starting command 2\n",
      "      02 echo command 2\n",
      "      02 command 2\n",
      /    ✔ 02 #{@user_at_localhost} \d.\d+s\n/,
      "00:00 special_rake_task_2\n",
      "      ERROR New task starting\n",
      "00:00 special_rake_task_3\n",
      "      01 echo command 3\n",
      "      01 command 3\n",
      /    ✔ 01 #{@user_at_localhost} \d.\d+s\n/,
      "      02 echo command 4\n",
      "      02 command 4\n",
      /    ✔ 02 #{@user_at_localhost} \d.\d+s\n/,
      "      WARN  All done\n"
    )

    assert_log_file_lines(
      command_running("echo command 1"), command_success,
      /#{blue('INFO')} Starting command 2\n/,
      command_running("echo command 2"), command_success,
      /#{red('ERROR')} New task starting\n/,
      command_running("echo command 3"), command_success,
      command_running("echo command 4"), command_success,
      /#{yellow('WARN')} All done\n/
    )
  end

  def test_log_message_levels
    configure do |airbrussh_config, sshkit_config|
      airbrussh_config.color = true
      sshkit_config.output_verbosity = Logger::DEBUG
    end

    on_local do
      %w[log fatal error warn info debug].each do |level|
        send(level, "Test")
      end
    end

    assert_output_lines(
      "      Test\n",
      "      \e[0;31;49mFATAL\e[0m Test\n",
      "      \e[0;31;49mERROR\e[0m Test\n",
      "      \e[0;33;49mWARN\e[0m  Test\n",
      "      Test\n"
    )

    assert_log_file_lines(
      /#{blue('INFO')} Test\n/,
      /#{red('FATAL')} Test\n/,
      /#{red('ERROR')} Test\n/,
      /#{yellow('WARN')} Test\n/,
      /#{blue('INFO')} Test\n/,
      /#{black('DEBUG')} Test\n/
    )
  end

  def test_interleaved_debug_and_info_commands
    configure do |airbrussh_config|
      airbrussh_config.monkey_patch_rake = true
      airbrussh_config.command_output = true
    end

    on_local("interleaving_test") do
      test("echo hi")
      # test methods are logged at debug level by default
      execute(:echo, "command 1")
      test("echo hello")
      debug("Debug line should not be output")
      info("Info line should be output")
      execute(:echo, "command 2")
      execute(:echo, "command 3", :verbosity => :debug)
      execute(:echo, "command 4")
    end

    assert_output_lines(
      "00:00 interleaving_test\n",
      "      01 echo command 1\n",
      "      01 command 1\n",
      /    ✔ 01 #{@user_at_localhost} \d.\d+s\n/,
      "      Info line should be output\n",
      "      02 echo command 2\n",
      "      02 command 2\n",
      /    ✔ 02 #{@user_at_localhost} \d.\d+s\n/,
      "      03 echo command 4\n",
      "      03 command 4\n",
      /    ✔ 03 #{@user_at_localhost} \d.\d+s\n/
    )
  end

  def test_task_prefix
    configure do |airbrussh_config|
      airbrussh_config.monkey_patch_rake = true
      airbrussh_config.task_prefix = "--- "
    end

    on_local("task_prefix_test") do
      execute(:echo, "command 1")
      execute(:echo, "command 2")
    end

    assert_output_lines(
      "--- 00:00 task_prefix_test\n",
      "      01 echo command 1\n",
      /    ✔ 01 #{@user_at_localhost} \d.\d+s\n/,
      "      02 echo command 2\n",
      /    ✔ 02 #{@user_at_localhost} \d.\d+s\n/
    )
  end

  private

  def on_local(task_name=nil, &block)
    define_and_execute_rake_task(task_name) do
      local_backend = SSHKit::Backend::Local.new(&block)
      local_backend.run
    end
  end

  def assert_output_lines(*expected_output)
    expected_output = [
      "Using airbrussh format.\n",
      /Verbose output is being written to .*\n/
    ] + expected_output
    assert_string_io_lines(expected_output, @output)
  end

  def assert_string_io_lines(expected_output, string_io)
    lines = string_io.string.lines.to_a
    assert_equal expected_output.size, lines.size, lines.map(&:inspect).join(",\n")
    lines.each.with_index do |line, i|
      assert_case_equal(expected_output[i], line)
    end
  end

  def assert_log_file_lines(*command_lines)
    preamble_lines = [
      /#{blue('INFO')} ---------------------------------------------------------------------------\n/,
      /#{blue('INFO')} START [\d\-]+ [\d\:]+ [\+\-]\d+ cap\n/,
      /#{blue('INFO')} ---------------------------------------------------------------------------\n/
    ]

    assert_string_io_lines(preamble_lines + command_lines, @log_file)
  end

  def command_running(command, level="INFO")
    level_tag_color = level == "INFO" ? :blue : :black
    /#{send(level_tag_color, level)} \[#{green('\w+')}\] Running #{bold_yellow(command.to_s)} #{@user ? "as #{blue(@user)}@" : "on "}#{blue('localhost')}\n/
  end

  def command_started_debug(command)
    /#{black('DEBUG')} \[#{green('\w+')}\] Command: #{blue(command)}/
  end

  def command_std_stream(stream, output)
    # Note ansii character end code is omitted due to newline
    # This is probably a bug in SSHKit
    color = stream == :stdout ? :green : :red
    formatted_output = send(color, "\\t#{output}\\n").chomp('\\e\\[0m')
    /#{black('DEBUG')} \[#{green('\w+')}\] #{formatted_output}/
  end

  def command_success(level="INFO")
    level_tag_color = level == "INFO" ? :blue : :black
    /#{send(level_tag_color, level)} \[#{green('\w+')}\] Finished in \d.\d+ seconds with exit status 0 \(#{bold_green("successful")}\).\n/
  end

  def command_failed(exit_status)
    /#{black('DEBUG')} \[#{green('\w+')}\] Finished in \d.\d+ seconds with exit status #{exit_status} \(#{bold_red("failed")}\)/
  end

  {
    :black => "0;30;49",
    :red => "0;31;49",
    :green => "0;32;49",
    :yellow => "0;33;49",
    :blue => "0;34;49",
    :bold_red => "1;31;49",
    :bold_green => "1;32;49",
    :bold_yellow => "1;33;49"
  }.each do |color, code|
    define_method(color) do |string|
      if color_output?
        "\\e\\[#{code}m#{string}\\e\\[0m"
      else
        string
      end
    end
  end

  # Whether or not SSHKit emits color depends on the test environment. SSHKit
  # versions up to 1.7.1 added colors to the log file, but only if
  # `$stdout.tty?` is true. Later versions never output color to the log file.
  def color_output?
    $stdout.tty? && !(sshkit_after?("1.7.1") || sshkit_master?)
  end

  def sshkit_after?(version)
    Gem.loaded_specs["sshkit"].version > Gem::Version.new(version)
  end

  def sshkit_master?
    gem_source = Gem.loaded_specs["sshkit"].source
    gem_source.is_a?(Bundler::Source::Git) && gem_source.branch == "master"
  end

  def formatter_class
    Airbrussh::Formatter
  end

  module Minitest::Assertions
    # rubocop:disable Style/CaseEquality
    def assert_case_equal(matcher, obj, msg=nil)
      message = message(msg) { "Expected #{mu_pp matcher} to === #{mu_pp obj}" }
      assert matcher === obj, message
    end
    # rubocop:enable Style/CaseEquality
  end
end


================================================
FILE: test/airbrussh/log_file_formatter_test.rb
================================================
require "minitest_helper"
require "airbrussh/log_file_formatter"
require "fileutils"
require "tempfile"

class Airbrussh::LogFileFormatterTest < Minitest::Test
  def setup
    @file = Tempfile.new("airbrussh-test-")
    @file.puts("Existing data")
    @file.close
    @formatter = Airbrussh::LogFileFormatter.new(@file.path)
  end

  def teardown
    @file.unlink
    @output = nil
  end

  def test_appends_to_existing_file
    assert_match("Existing data", output)
  end

  def test_writes_delimiter
    assert_match("----------", output)
    assert_match("START", output)
  end

  def test_writes_through_via_pretty_formatter
    @formatter << SSHKit::LogMessage.new(SSHKit::Logger::INFO, "hello")
    assert_match(/INFO.*hello/, output)
  end

  def test_creates_log_directory_and_file
    with_tempdir do |dir|
      log_file = File.join(dir, "log", "capistrano.log")
      Airbrussh::LogFileFormatter.new(log_file)
      assert(File.exist?(log_file))
    end
  end

  private

  def output
    @output ||= IO.read(@file.path)
  end

  def with_tempdir
    dir = Dir.mktmpdir("airbrussh-test-")
    yield(dir)
  ensure
    FileUtils.rm_rf(dir)
  end
end


================================================
FILE: test/airbrussh/rake/context_test.rb
================================================
require "minitest_helper"
require "airbrussh/rake/context"

class Airbrussh::Rake::ContextTest < Minitest::Test
  include RakeTaskDefinition

  def setup
    @config = Airbrussh::Configuration.new
  end

  def teardown
    Airbrussh::Rake::Context.current_task_name = nil
  end

  def test_current_task_name_is_nil_when_disabled
    @config.monkey_patch_rake = false
    context = Airbrussh::Rake::Context.new(@config)
    define_and_execute_rake_task("one") do
      assert_nil(context.current_task_name)
    end
  end

  def test_current_task_name
    @config.monkey_patch_rake = true
    context = Airbrussh::Rake::Context.new(@config)

    assert_nil(context.current_task_name)

    define_and_execute_rake_task("one") do
      assert_equal("one", context.current_task_name)
    end

    define_and_execute_rake_task("two") do
      assert_equal("two", context.current_task_name)
    end
  end

  def test_register_new_command_is_true_for_first_execution_per_rake_task
    @config.monkey_patch_rake = true
    context = Airbrussh::Rake::Context.new(@config)
    define_and_execute_rake_task("one") do
      assert context.register_new_command(:command_one)
      refute context.register_new_command(:command_one)
      assert context.register_new_command(:command_two)
      refute context.register_new_command(:command_two)
    end

    define_and_execute_rake_task("two") do
      assert context.register_new_command(:command_one)
      refute context.register_new_command(:command_one)
      assert context.register_new_command(:command_two)
      refute context.register_new_command(:command_two)
    end
  end

  def test_position
    @config.monkey_patch_rake = true
    context = Airbrussh::Rake::Context.new(@config)

    define_and_execute_rake_task("one") do
      context.register_new_command(:command_one)
      context.register_new_command(:command_two)

      assert_equal(0, context.position(:command_one))
      assert_equal(1, context.position(:command_two))
    end

    define_and_execute_rake_task("two") do
      context.register_new_command(:command_three)
      context.register_new_command(:command_four)

      assert_equal(0, context.position(:command_three))
      assert_equal(1, context.position(:command_four))
    end
  end
end


================================================
FILE: test/airbrussh/version_test.rb
================================================
require "minitest_helper"

class Airbrussh::VersionTest < Minitest::Test
  def test_that_it_has_a_version_number
    refute_nil ::Airbrussh::VERSION
  end
end


================================================
FILE: test/airbrussh_test.rb
================================================
require "minitest_helper"

class AirbrusshTest < Minitest::Test
  def test_configure_yields_config_object
    config = Airbrussh.configuration
    assert_equal(config, Airbrussh.configure { |c| c })
  end

  def test_configuration_returns_passed_config
    config = Airbrussh::Configuration.new
    assert_equal(config, Airbrussh.configuration(config))
  end

  def test_configuration_applies_options
    config = Airbrussh.configuration(:banner => "test_success")
    assert_equal("test_success", config.banner)
    assert_equal("test_success", Airbrussh.configuration.banner)
  end
end


================================================
FILE: test/minitest_helper.rb
================================================
$LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))

# Coveralls has to be loaded first
require_relative("./support/coveralls")

# Load minitest before mocha
require "minitest/autorun"

# Load everything else from test/support
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each { |rb| require(rb) }

require "airbrussh"


================================================
FILE: test/sshkit/formatter/airbrussh_test.rb
================================================
require "minitest_helper"
require "sshkit/formatter/airbrussh"
require_relative "../../airbrussh/formatter_test.rb"

# SSHKit::Formatter::Airbrussh should behave identically to
# Airbrussh::Formatter, so just reuse the Airbrussh::FormatterTest.
class SSHKit::Formatter::AirbrusshTest < Airbrussh::FormatterTest
  private

  def formatter_class
    SSHKit::Formatter::Airbrussh
  end
end


================================================
FILE: test/support/coveralls.rb
================================================
if ENV["CI"]
  begin
    require "simplecov"
    require "coveralls"

    SimpleCov.formatter = Coveralls::SimpleCov::Formatter
    SimpleCov.start do
      # No need to report coverage metrics for the test code
      add_filter "test"
    end
  rescue LoadError
    # ignore
  end
end


================================================
FILE: test/support/minitest_reporters.rb
================================================
require "minitest/reporters"

Minitest::Reporters.use!(
  Minitest::Reporters::ProgressReporter.new,
  ENV,
  Minitest.backtrace_filter
)


================================================
FILE: test/support/mocha.rb
================================================
require "mocha/minitest"

Mocha.configure do |c|
  c.stubbing_non_existent_method = :warn
  c.stubbing_non_public_method = :warn
end


================================================
FILE: test/support/rake_task_definition.rb
================================================
require "rake"

module RakeTaskDefinition
  def define_and_execute_rake_task(task_name, &block)
    task_name ||= RakeTaskDefinition.unique_task_name(name)
    Rake::Task[task_name].clear if Rake::Task.task_defined?(task_name)
    Rake::Task.define_task(task_name, &block).execute
  end

  def self.unique_task_name(test_name)
    @task_index ||= 0
    "#{test_name}_#{@task_index += 1}"
  end
end
Download .txt
gitextract_lyzq9qvi/

├── .github/
│   ├── dependabot.yml
│   ├── release-drafter.yml
│   └── workflows/
│       ├── ci.yml
│       └── push.yml
├── .gitignore
├── .rubocop.yml
├── .rubocop_todo.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── UPGRADING-CAP-3.5.md
├── airbrussh.gemspec
├── appveyor.yml
├── bin/
│   ├── console
│   └── setup
├── gemfiles/
│   └── legacy.gemfile
├── lib/
│   ├── airbrussh/
│   │   ├── capistrano/
│   │   │   └── tasks.rb
│   │   ├── capistrano.rb
│   │   ├── colors.rb
│   │   ├── command_formatter.rb
│   │   ├── configuration.rb
│   │   ├── console.rb
│   │   ├── console_formatter.rb
│   │   ├── delegating_formatter.rb
│   │   ├── formatter.rb
│   │   ├── log_file_formatter.rb
│   │   ├── rake/
│   │   │   └── context.rb
│   │   └── version.rb
│   ├── airbrussh.rb
│   └── sshkit/
│       └── formatter/
│           └── airbrussh.rb
└── test/
    ├── airbrussh/
    │   ├── capistrano/
    │   │   └── tasks_test.rb
    │   ├── capistrano_test.rb
    │   ├── colors_test.rb
    │   ├── command_formatter_test.rb
    │   ├── configuration_test.rb
    │   ├── console_formatter_test.rb
    │   ├── console_test.rb
    │   ├── delegating_formatter_test.rb
    │   ├── formatter_test.rb
    │   ├── log_file_formatter_test.rb
    │   ├── rake/
    │   │   └── context_test.rb
    │   └── version_test.rb
    ├── airbrussh_test.rb
    ├── minitest_helper.rb
    ├── sshkit/
    │   └── formatter/
    │       └── airbrussh_test.rb
    └── support/
        ├── coveralls.rb
        ├── minitest_reporters.rb
        ├── mocha.rb
        └── rake_task_definition.rb
Download .txt
SYMBOL INDEX (234 symbols across 28 files)

FILE: lib/airbrussh.rb
  type Airbrussh (line 5) | module Airbrussh
    function configuration (line 6) | def self.configuration(options={})
    function configure (line 12) | def self.configure

FILE: lib/airbrussh/capistrano/tasks.rb
  type Airbrussh (line 7) | module Airbrussh
    type Capistrano (line 8) | module Capistrano
      class Tasks (line 15) | class Tasks
        method initialize (line 22) | def initialize(dsl, stderr=$stderr, config=Airbrussh.configuration)
        method load_defaults (line 32) | def load_defaults
        method deploy_failed (line 38) | def deploy_failed
        method configure (line 56) | def configure
        method warn_if_missing_dsl (line 62) | def warn_if_missing_dsl
        method err_console (line 73) | def err_console
        method error_line (line 82) | def error_line(line="\n")
        method tail_log_file (line 89) | def tail_log_file
        method airbrussh_is_being_used? (line 96) | def airbrussh_is_being_used?

FILE: lib/airbrussh/colors.rb
  type Airbrussh (line 1) | module Airbrussh
    type Colors (line 4) | module Colors

FILE: lib/airbrussh/command_formatter.rb
  type Airbrussh (line 6) | module Airbrussh
    class CommandFormatter (line 13) | class CommandFormatter < SimpleDelegator
      method initialize (line 16) | def initialize(command, position)
      method format_output (line 25) | def format_output(line)
      method start_message (line 33) | def start_message
      method exit_message (line 43) | def exit_message
      method user_at_host (line 54) | def user_at_host
      method runtime (line 60) | def runtime
      method abbreviated (line 64) | def abbreviated
      method number (line 68) | def number
      method success_message (line 72) | def success_message
      method failure_message (line 76) | def failure_message

FILE: lib/airbrussh/configuration.rb
  type Airbrussh (line 5) | module Airbrussh
    class Configuration (line 6) | class Configuration
      method initialize (line 10) | def initialize
      method apply_options (line 21) | def apply_options(options)
      method banner_message (line 34) | def banner_message
      method formatters (line 48) | def formatters(io)
      method show_command_output? (line 54) | def show_command_output?(sym)
      method warn_unrecognized_key (line 60) | def warn_unrecognized_key(key)

FILE: lib/airbrussh/console.rb
  type Airbrussh (line 5) | module Airbrussh
    class Console (line 12) | class Console
      method initialize (line 15) | def initialize(output, config=Airbrussh.configuration)
      method print_line (line 24) | def print_line(obj="")
      method write (line 36) | def write(string)
      method truncate_to_console_width (line 41) | def truncate_to_console_width(string)
      method strip_ascii_color (line 55) | def strip_ascii_color(string)
      method console_width (line 62) | def console_width
      method color_enabled? (line 75) | def color_enabled?
      method utf8_supported? (line 86) | def utf8_supported?(string)
      method to_utf8 (line 92) | def to_utf8(string)

FILE: lib/airbrussh/console_formatter.rb
  type Airbrussh (line 7) | module Airbrussh
    class ConsoleFormatter (line 8) | class ConsoleFormatter < SSHKit::Formatter::Abstract
      method initialize (line 15) | def initialize(io, config=Airbrussh.configuration)
      method write_banner (line 25) | def write_banner
      method log_command_start (line 29) | def log_command_start(command)
      method log_command_data (line 37) | def log_command_data(command, stream_type, string)
      method log_command_exit (line 46) | def log_command_exit(command)
      method write (line 52) | def write(obj)
      method write_log_message (line 69) | def write_log_message(log_message)
      method format_log_message (line 75) | def format_log_message(log_message)
      method log_and_clear_command_output (line 92) | def log_and_clear_command_output(command, stream)
      method print_task_if_changed (line 98) | def print_task_if_changed
      method clock (line 106) | def clock
      method debug? (line 116) | def debug?(obj)
      method decorate (line 120) | def decorate(command)
      method print_line (line 124) | def print_line(string)
      method print_indented_line (line 128) | def print_indented_line(string, offset=0)

FILE: lib/airbrussh/delegating_formatter.rb
  type Airbrussh (line 3) | module Airbrussh
    class DelegatingFormatter (line 10) | class DelegatingFormatter
      method initialize (line 19) | def initialize(formatters)

FILE: lib/airbrussh/formatter.rb
  type Airbrussh (line 9) | module Airbrussh
    class Formatter (line 10) | class Formatter < Airbrussh::DelegatingFormatter
      method initialize (line 11) | def initialize(io, options_or_config_object={})

FILE: lib/airbrussh/log_file_formatter.rb
  type Airbrussh (line 6) | module Airbrussh
    class LogFileFormatter (line 12) | class LogFileFormatter < SimpleDelegator
      method initialize (line 15) | def initialize(path, formatter_class=SSHKit::Formatter::Pretty)
      method write_delimiter (line 24) | def write_delimiter
      method ensure_directory_exists (line 34) | def ensure_directory_exists
      method log_file_io (line 38) | def log_file_io

FILE: lib/airbrussh/rake/context.rb
  type Airbrussh (line 1) | module Airbrussh
    type Rake (line 2) | module Rake
      class Context (line 15) | class Context
        method initialize (line 20) | def initialize(config=Airbrussh.configuration)
        method current_task_name (line 28) | def current_task_name
        method register_new_command (line 39) | def register_new_command(command)
        method position (line 50) | def position(command)
        type Patch (line 55) | module Patch
          function execute (line 56) | def execute(args=nil)
        method install_monkey_patch (line 62) | def self.install_monkey_patch
        method install_monkey_patch (line 69) | def self.install_monkey_patch
        method reset_history_if_task_changed (line 88) | def reset_history_if_task_changed
        method enabled? (line 93) | def enabled?

FILE: lib/airbrussh/version.rb
  type Airbrussh (line 3) | module Airbrussh

FILE: lib/sshkit/formatter/airbrussh.rb
  type SSHKit (line 7) | module SSHKit
    type Formatter (line 8) | module Formatter
      class Airbrussh (line 9) | class Airbrussh < Airbrussh::Formatter

FILE: test/airbrussh/capistrano/tasks_test.rb
  class Airbrussh::Capistrano::TasksTest (line 8) | class Airbrussh::Capistrano::TasksTest < Minitest::Test
    method set (line 10) | def set(*); end
    method env (line 15) | def env
    method namespace (line 27) | def namespace(*); end
    method task (line 29) | def task(*); end
    method setup (line 32) | def setup
    method test_no_warning_is_printed_when_proper_dsl_is_present (line 39) | def test_no_warning_is_printed_when_proper_dsl_is_present
    method test_prints_warning_if_dsl_is_missing (line 43) | def test_prints_warning_if_dsl_is_missing
    method test_configures_for_capistrano (line 49) | def test_configures_for_capistrano
    method test_sets_airbrussh_formatter_on_load_defaults (line 58) | def test_sets_airbrussh_formatter_on_load_defaults
    method test_prints_last_20_logfile_lines_on_deploy_failure (line 64) | def test_prints_last_20_logfile_lines_on_deploy_failure
    method test_does_not_truncate_log_file_lines (line 77) | def test_does_not_truncate_log_file_lines
    method test_does_not_print_anything_on_deploy_failure_if_nil_logfile (line 91) | def test_does_not_print_anything_on_deploy_failure_if_nil_logfile
    method test_does_not_print_anything_on_deploy_failure_if_airbrussh_is_not_used (line 97) | def test_does_not_print_anything_on_deploy_failure_if_airbrussh_is_not...
    method stderr (line 106) | def stderr
    method with_log_file (line 110) | def with_log_file

FILE: test/airbrussh/capistrano_test.rb
  class Airbrussh::CapistranoTest (line 3) | class Airbrussh::CapistranoTest < Minitest::Test
    method setup (line 4) | def setup
    method teardown (line 11) | def teardown
    method test_defines_tasks (line 15) | def test_defines_tasks
    method test_load_defaults_rake_task_delegates_to_tasks_instance (line 20) | def test_load_defaults_rake_task_delegates_to_tasks_instance
    method test_deploy_failed_rake_task_delegates_to_tasks_instance (line 25) | def test_deploy_failed_rake_task_delegates_to_tasks_instance

FILE: test/airbrussh/colors_test.rb
  class Airbrussh::ColorsTest (line 4) | class Airbrussh::ColorsTest < Minitest::Test
    method test_red (line 7) | def test_red
    method test_green (line 11) | def test_green
    method test_yellow (line 15) | def test_yellow
    method test_blue (line 19) | def test_blue
    method test_gray (line 23) | def test_gray

FILE: test/airbrussh/command_formatter_test.rb
  class Airbrussh::CommandFormatterTest (line 7) | class Airbrussh::CommandFormatterTest < Minitest::Test
    method setup (line 8) | def setup
    method test_format_output (line 21) | def test_format_output
    method test_start_message (line 25) | def test_start_message
    method test_exit_message_success (line 29) | def test_exit_message_success
    method test_exit_message_failure (line 36) | def test_exit_message_failure
    method test_uses_ssh_options_if_host_user_is_absent (line 46) | def test_uses_ssh_options_if_host_user_is_absent
    method test_shows_hostname_only_if_no_user (line 54) | def test_shows_hostname_only_if_no_user
    method test_handles_nil_position_gracefully (line 62) | def test_handles_nil_position_gracefully
    method host (line 69) | def host(user, hostname, ssh_options={})

FILE: test/airbrussh/configuration_test.rb
  class Airbrussh::ConfigurationTest (line 6) | class Airbrussh::ConfigurationTest < Minitest::Test
    method setup (line 7) | def setup
    method test_defaults (line 11) | def test_defaults
    method test_apply_options (line 22) | def test_apply_options
    method test_apply_options_warns_on_stderr_of_bad_key (line 42) | def test_apply_options_warns_on_stderr_of_bad_key
    method test_auto_banner_message_without_log (line 48) | def test_auto_banner_message_without_log
    method test_auto_banner_message_with_log (line 54) | def test_auto_banner_message_with_log
    method test_nil_or_false_banner_message (line 64) | def test_nil_or_false_banner_message
    method test_custom_banner_message (line 71) | def test_custom_banner_message
    method test_formatters_without_log_file (line 76) | def test_formatters_without_log_file
    method test_formatters_with_log_file (line 85) | def test_formatters_with_log_file
    method test_effects_of_command_output_true (line 97) | def test_effects_of_command_output_true
    method test_effects_of_command_output_false (line 103) | def test_effects_of_command_output_false
    method test_effects_of_command_output_stdout (line 109) | def test_effects_of_command_output_stdout
    method test_effects_of_command_output_stderr (line 115) | def test_effects_of_command_output_stderr
    method test_effects_of_command_output_stdout_stderr (line 121) | def test_effects_of_command_output_stdout_stderr

FILE: test/airbrussh/console_formatter_test.rb
  class Airbrussh::ConsoleFormatterTest (line 9) | class Airbrussh::ConsoleFormatterTest < Minitest::Test
    method setup (line 10) | def setup
    method test_log_command_data_with_multiline_string (line 20) | def test_log_command_data_with_multiline_string
    method output (line 35) | def output

FILE: test/airbrussh/console_test.rb
  class Airbrussh::ConsoleTest (line 8) | class Airbrussh::ConsoleTest < Minitest::Test
    method setup (line 9) | def setup
    method test_color_is_allowed_for_tty (line 13) | def test_color_is_allowed_for_tty
    method test_color_is_can_be_forced (line 21) | def test_color_is_can_be_forced
    method test_color_is_can_be_forced_via_env (line 29) | def test_color_is_can_be_forced_via_env
    method test_color_is_stripped_for_non_tty (line 38) | def test_color_is_stripped_for_non_tty
    method test_color_can_be_disabled_for_tty (line 46) | def test_color_can_be_disabled_for_tty
    method test_truncates_to_winsize (line 54) | def test_truncates_to_winsize
    method test_ignores_ascii_color_codes_when_determing_truncation_amount (line 64) | def test_ignores_ascii_color_codes_when_determing_truncation_amount
    method test_truncates_to_explicit_width (line 75) | def test_truncates_to_explicit_width
    method test_truncation_can_be_disabled (line 84) | def test_truncation_can_be_disabled
    method test_truncates_improperly_encoded_ascii_string (line 96) | def test_truncates_improperly_encoded_ascii_string
    method test_print_line_handles_invalid_utf8 (line 109) | def test_print_line_handles_invalid_utf8
    method test_doesnt_truncates_to_zero_width (line 120) | def test_doesnt_truncates_to_zero_width
    method ascii_8bit (line 131) | def ascii_8bit(string)
    method output (line 135) | def output
    method configured_console (line 139) | def configured_console(opts={})

FILE: test/airbrussh/delegating_formatter_test.rb
  class Airbrussh::DelegatingFormatterTest (line 4) | class Airbrussh::DelegatingFormatterTest < Minitest::Test
    method setup (line 5) | def setup
    method test_forwards_logger_methods (line 11) | def test_forwards_logger_methods
    method test_forwards_start_and_exit_methods (line 20) | def test_forwards_start_and_exit_methods
    method test_forwards_log_command_data (line 29) | def test_forwards_log_command_data
    method test_forwards_io_methods_to_multiple_formatters (line 36) | def test_forwards_io_methods_to_multiple_formatters
    method test_forwards_io_methods_to_a_single_formatter (line 49) | def test_forwards_io_methods_to_a_single_formatter

FILE: test/airbrussh/formatter_test.rb
  class Airbrussh::FormatterTest (line 7) | class Airbrussh::FormatterTest < Minitest::Test
    method setup (line 10) | def setup
    method teardown (line 20) | def teardown
    method configure (line 25) | def configure
    method test_can_be_configured_with_options_hash (line 38) | def test_can_be_configured_with_options_hash
    method test_formats_execute_with_color (line 44) | def test_formats_execute_with_color
    method test_formats_execute_without_color (line 69) | def test_formats_execute_without_color
    method test_formats_without_command_output (line 89) | def test_formats_without_command_output
    method test_formats_failing_execute_with_color (line 104) | def test_formats_failing_execute_with_color
    method test_formats_capture_with_color (line 154) | def test_formats_capture_with_color
    method test_formats_capture_without_color (line 175) | def test_formats_capture_without_color
    method test_does_not_output_test_commands (line 195) | def test_does_not_output_test_commands
    method test_handles_rake_tasks (line 215) | def test_handles_rake_tasks
    method test_log_message_levels (line 267) | def test_log_message_levels
    method test_interleaved_debug_and_info_commands (line 297) | def test_interleaved_debug_and_info_commands
    method test_task_prefix (line 330) | def test_task_prefix
    method on_local (line 352) | def on_local(task_name=nil, &block)
    method assert_output_lines (line 359) | def assert_output_lines(*expected_output)
    method assert_string_io_lines (line 367) | def assert_string_io_lines(expected_output, string_io)
    method assert_log_file_lines (line 375) | def assert_log_file_lines(*command_lines)
    method command_running (line 385) | def command_running(command, level="INFO")
    method command_started_debug (line 390) | def command_started_debug(command)
    method command_std_stream (line 394) | def command_std_stream(stream, output)
    method command_success (line 402) | def command_success(level="INFO")
    method command_failed (line 407) | def command_failed(exit_status)
    method color_output? (line 433) | def color_output?
    method sshkit_after? (line 437) | def sshkit_after?(version)
    method sshkit_master? (line 441) | def sshkit_master?
    method formatter_class (line 446) | def formatter_class
    type Minitest::Assertions (line 450) | module Minitest::Assertions
      function assert_case_equal (line 452) | def assert_case_equal(matcher, obj, msg=nil)

FILE: test/airbrussh/log_file_formatter_test.rb
  class Airbrussh::LogFileFormatterTest (line 6) | class Airbrussh::LogFileFormatterTest < Minitest::Test
    method setup (line 7) | def setup
    method teardown (line 14) | def teardown
    method test_appends_to_existing_file (line 19) | def test_appends_to_existing_file
    method test_writes_delimiter (line 23) | def test_writes_delimiter
    method test_writes_through_via_pretty_formatter (line 28) | def test_writes_through_via_pretty_formatter
    method test_creates_log_directory_and_file (line 33) | def test_creates_log_directory_and_file
    method output (line 43) | def output
    method with_tempdir (line 47) | def with_tempdir

FILE: test/airbrussh/rake/context_test.rb
  class Airbrussh::Rake::ContextTest (line 4) | class Airbrussh::Rake::ContextTest < Minitest::Test
    method setup (line 7) | def setup
    method teardown (line 11) | def teardown
    method test_current_task_name_is_nil_when_disabled (line 15) | def test_current_task_name_is_nil_when_disabled
    method test_current_task_name (line 23) | def test_current_task_name
    method test_register_new_command_is_true_for_first_execution_per_rake_task (line 38) | def test_register_new_command_is_true_for_first_execution_per_rake_task
    method test_position (line 56) | def test_position

FILE: test/airbrussh/version_test.rb
  class Airbrussh::VersionTest (line 3) | class Airbrussh::VersionTest < Minitest::Test
    method test_that_it_has_a_version_number (line 4) | def test_that_it_has_a_version_number

FILE: test/airbrussh_test.rb
  class AirbrusshTest (line 3) | class AirbrusshTest < Minitest::Test
    method test_configure_yields_config_object (line 4) | def test_configure_yields_config_object
    method test_configuration_returns_passed_config (line 9) | def test_configuration_returns_passed_config
    method test_configuration_applies_options (line 14) | def test_configuration_applies_options

FILE: test/sshkit/formatter/airbrussh_test.rb
  class SSHKit::Formatter::AirbrusshTest (line 7) | class SSHKit::Formatter::AirbrusshTest < Airbrussh::FormatterTest
    method formatter_class (line 10) | def formatter_class

FILE: test/support/rake_task_definition.rb
  type RakeTaskDefinition (line 3) | module RakeTaskDefinition
    function define_and_execute_rake_task (line 4) | def define_and_execute_rake_task(task_name, &block)
    function unique_task_name (line 10) | def self.unique_task_name(test_name)
Condensed preview — 53 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (92K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 455,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: bundler\n    directory: \"/\"\n    schedule:\n      interval: monthly\n      time: "
  },
  {
    "path": ".github/release-drafter.yml",
    "chars": 653,
    "preview": "name-template: \"$RESOLVED_VERSION\"\ntag-template: \"v$RESOLVED_VERSION\"\ncategories:\n  - title: \"⚠️ Breaking Changes\"\n    l"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1431,
    "preview": "name: CI\non:\n  pull_request:\n  push:\n    branches:\n      - main\njobs:\n  rubocop:\n    name: \"RuboCop\"\n    runs-on: ubuntu"
  },
  {
    "path": ".github/workflows/push.yml",
    "chars": 237,
    "preview": "name: Release Drafter\non:\n  push:\n    branches:\n      - main\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n  "
  },
  {
    "path": ".gitignore",
    "chars": 124,
    "preview": "/.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."
  },
  {
    "path": ".rubocop.yml",
    "chars": 1015,
    "preview": "inherit_from: .rubocop_todo.yml\n\nAllCops:\n  DisplayCopNames: true\n  DisplayStyleGuide: true\n  NewCops: disable\n  Suggest"
  },
  {
    "path": ".rubocop_todo.yml",
    "chars": 4626,
    "preview": "# This configuration was generated by\n# `rubocop --auto-gen-config`\n# on 2025-11-28 18:11:46 UTC using RuboCop version 1"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 97,
    "preview": "Release notes for this project are kept here: https://github.com/mattbrictson/airbrussh/releases\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 1524,
    "preview": "Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect all\npeople who contri"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1313,
    "preview": "# Contributing to airbrussh\n\nHave a feature idea, bug fix, or refactoring suggestion? Contributions are welcome!\n\n## Pul"
  },
  {
    "path": "Gemfile",
    "chars": 214,
    "preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"coveralls_reborn\", \"~> 0.29.0\"\ngem \"irb\"\ngem \"minitest\", \"~> 6.0\"\ngem \"mini"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1080,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2026 Matt Brictson\n\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "README.md",
    "chars": 8121,
    "preview": "# Airbrussh\n\n[![Gem Version](https://badge.fury.io/rb/airbrussh.svg)](http://badge.fury.io/rb/airbrussh)\n[![Build Status"
  },
  {
    "path": "Rakefile",
    "chars": 1343,
    "preview": "require \"bundler/gem_tasks\"\n\nrequire \"rake/testtask\"\nRake::TestTask.new(:test) do |t|\n  t.libs << \"test\"\n  t.libs << \"li"
  },
  {
    "path": "UPGRADING-CAP-3.5.md",
    "chars": 1815,
    "preview": "# Capistrano 3.5 upgrade guide for existing Airbrussh users\n\nIf you have been using Airbrussh with Capistrano, and are u"
  },
  {
    "path": "airbrussh.gemspec",
    "chars": 1485,
    "preview": "# coding: utf-8\nlib = File.expand_path(\"../lib\", __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequi"
  },
  {
    "path": "appveyor.yml",
    "chars": 313,
    "preview": "version: \"{build}\"\nskip_tags: true\nskip_branch_with_pr: true\nenvironment:\n  BUNDLE_GEMFILE: \"gemfiles/legacy.gemfile\"\n  "
  },
  {
    "path": "bin/console",
    "chars": 334,
    "preview": "#!/usr/bin/env ruby\n\nrequire \"bundler/setup\"\nrequire \"airbrussh\"\n\n# You can add fixtures and/or initialization code here"
  },
  {
    "path": "bin/setup",
    "chars": 115,
    "preview": "#!/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",
    "chars": 156,
    "preview": "source \"https://rubygems.org\"\n\ngemspec path: \"..\"\n\ngem \"minitest\", \"~> 5.10\"\ngem \"minitest-reporters\", \"~> 1.1\"\ngem \"moc"
  },
  {
    "path": "lib/airbrussh/capistrano/tasks.rb",
    "chars": 3057,
    "preview": "require \"airbrussh\"\nrequire \"airbrussh/colors\"\nrequire \"airbrussh/console\"\nrequire \"forwardable\"\nrequire \"shellwords\"\n\nm"
  },
  {
    "path": "lib/airbrussh/capistrano.rb",
    "chars": 318,
    "preview": "require \"airbrussh/capistrano/tasks\"\n\ntasks = Airbrussh::Capistrano::Tasks.new(self)\n\n# Hook into Capistrano's init proc"
  },
  {
    "path": "lib/airbrussh/colors.rb",
    "chars": 598,
    "preview": "module Airbrussh\n  # Very basic support for ANSI color, so that we don't have to rely on\n  # any external dependencies.\n"
  },
  {
    "path": "lib/airbrussh/command_formatter.rb",
    "chars": 1905,
    "preview": "# encoding: UTF-8\n\nrequire \"airbrussh/colors\"\nrequire \"delegate\"\n\nmodule Airbrussh\n  # Decorates an SSHKit Command to ad"
  },
  {
    "path": "lib/airbrussh/configuration.rb",
    "chars": 1755,
    "preview": "require \"airbrussh/colors\"\nrequire \"airbrussh/console_formatter\"\nrequire \"airbrussh/log_file_formatter\"\n\nmodule Airbruss"
  },
  {
    "path": "lib/airbrussh/console.rb",
    "chars": 2614,
    "preview": "# encoding: UTF-8\n\nrequire \"io/console\"\n\nmodule Airbrussh\n  # Helper class that wraps an IO object and provides methods "
  },
  {
    "path": "lib/airbrussh/console_formatter.rb",
    "chars": 3676,
    "preview": "require \"airbrussh/colors\"\nrequire \"airbrussh/command_formatter\"\nrequire \"airbrussh/console\"\nrequire \"airbrussh/rake/con"
  },
  {
    "path": "lib/airbrussh/delegating_formatter.rb",
    "chars": 1564,
    "preview": "require \"sshkit\"\n\nmodule Airbrussh\n  # This class quacks like an SSHKit::Formatter, but when any formatting\n  # methods "
  },
  {
    "path": "lib/airbrussh/formatter.rb",
    "chars": 719,
    "preview": "require \"airbrussh\"\nrequire \"airbrussh/delegating_formatter\"\n\n# This is the formatter class that conforms to the SSHKit "
  },
  {
    "path": "lib/airbrussh/log_file_formatter.rb",
    "chars": 1143,
    "preview": "require \"delegate\"\nrequire \"fileutils\"\nrequire \"logger\"\nrequire \"sshkit\"\n\nmodule Airbrussh\n  # A Pretty formatter that s"
  },
  {
    "path": "lib/airbrussh/rake/context.rb",
    "chars": 3174,
    "preview": "module Airbrussh\n  module Rake\n    # Maintains information about what Rake task is currently being invoked,\n    # in ord"
  },
  {
    "path": "lib/airbrussh/version.rb",
    "chars": 79,
    "preview": "# frozen_string_literal: true\n\nmodule Airbrussh\n  VERSION = \"1.6.1\".freeze\nend\n"
  },
  {
    "path": "lib/airbrussh.rb",
    "chars": 357,
    "preview": "require \"airbrussh/configuration\"\nrequire \"airbrussh/formatter\"\nrequire \"airbrussh/version\"\n\nmodule Airbrussh\n  def self"
  },
  {
    "path": "lib/sshkit/formatter/airbrussh.rb",
    "chars": 346,
    "preview": "require \"airbrussh/formatter\"\n\n# Capistrano's formatter configuration requires that the formatter class\n# be in the SSHK"
  },
  {
    "path": "test/airbrussh/capistrano/tasks_test.rb",
    "chars": 2991,
    "preview": "require \"minitest_helper\"\nrequire \"airbrussh/capistrano/tasks\"\nrequire \"airbrussh/configuration\"\nrequire \"ostruct\"\nrequi"
  },
  {
    "path": "test/airbrussh/capistrano_test.rb",
    "chars": 911,
    "preview": "require \"minitest_helper\"\n\nclass Airbrussh::CapistranoTest < Minitest::Test\n  def setup\n    # Mute the warning that is n"
  },
  {
    "path": "test/airbrussh/colors_test.rb",
    "chars": 535,
    "preview": "require \"minitest_helper\"\nrequire \"airbrussh/colors\"\n\nclass Airbrussh::ColorsTest < Minitest::Test\n  include Airbrussh::"
  },
  {
    "path": "test/airbrussh/command_formatter_test.rb",
    "chars": 1871,
    "preview": "# encoding: UTF-8\n\nrequire \"minitest_helper\"\nrequire \"ostruct\"\nrequire \"airbrussh/command_formatter\"\n\nclass Airbrussh::C"
  },
  {
    "path": "test/airbrussh/configuration_test.rb",
    "chars": 3835,
    "preview": "require \"minitest_helper\"\nrequire \"airbrussh/console_formatter\"\nrequire \"airbrussh/log_file_formatter\"\nrequire \"tempfile"
  },
  {
    "path": "test/airbrussh/console_formatter_test.rb",
    "chars": 1082,
    "preview": "require \"minitest_helper\"\nrequire \"stringio\"\nrequire \"airbrussh/configuration\"\nrequire \"airbrussh/console_formatter\"\n\n# "
  },
  {
    "path": "test/airbrussh/console_test.rb",
    "chars": 4518,
    "preview": "# encoding: UTF-8\n\nrequire \"minitest_helper\"\nrequire \"stringio\"\nrequire \"airbrussh/configuration\"\nrequire \"airbrussh/con"
  },
  {
    "path": "test/airbrussh/delegating_formatter_test.rb",
    "chars": 2081,
    "preview": "require \"minitest_helper\"\nrequire \"airbrussh/delegating_formatter\"\n\nclass Airbrussh::DelegatingFormatterTest < Minitest:"
  },
  {
    "path": "test/airbrussh/formatter_test.rb",
    "chars": 13222,
    "preview": "# encoding: utf-8\n\nrequire \"minitest_helper\"\nrequire \"bundler\"\nrequire \"etc\"\n\nclass Airbrussh::FormatterTest < Minitest:"
  },
  {
    "path": "test/airbrussh/log_file_formatter_test.rb",
    "chars": 1159,
    "preview": "require \"minitest_helper\"\nrequire \"airbrussh/log_file_formatter\"\nrequire \"fileutils\"\nrequire \"tempfile\"\n\nclass Airbrussh"
  },
  {
    "path": "test/airbrussh/rake/context_test.rb",
    "chars": 2263,
    "preview": "require \"minitest_helper\"\nrequire \"airbrussh/rake/context\"\n\nclass Airbrussh::Rake::ContextTest < Minitest::Test\n  includ"
  },
  {
    "path": "test/airbrussh/version_test.rb",
    "chars": 159,
    "preview": "require \"minitest_helper\"\n\nclass Airbrussh::VersionTest < Minitest::Test\n  def test_that_it_has_a_version_number\n    ref"
  },
  {
    "path": "test/airbrussh_test.rb",
    "chars": 588,
    "preview": "require \"minitest_helper\"\n\nclass AirbrusshTest < Minitest::Test\n  def test_configure_yields_config_object\n    config = A"
  },
  {
    "path": "test/minitest_helper.rb",
    "chars": 336,
    "preview": "$LOAD_PATH.unshift(File.expand_path(\"../../lib\", __FILE__))\n\n# Coveralls has to be loaded first\nrequire_relative(\"./supp"
  },
  {
    "path": "test/sshkit/formatter/airbrussh_test.rb",
    "chars": 387,
    "preview": "require \"minitest_helper\"\nrequire \"sshkit/formatter/airbrussh\"\nrequire_relative \"../../airbrussh/formatter_test.rb\"\n\n# S"
  },
  {
    "path": "test/support/coveralls.rb",
    "chars": 286,
    "preview": "if ENV[\"CI\"]\n  begin\n    require \"simplecov\"\n    require \"coveralls\"\n\n    SimpleCov.formatter = Coveralls::SimpleCov::Fo"
  },
  {
    "path": "test/support/minitest_reporters.rb",
    "chars": 138,
    "preview": "require \"minitest/reporters\"\n\nMinitest::Reporters.use!(\n  Minitest::Reporters::ProgressReporter.new,\n  ENV,\n  Minitest.b"
  },
  {
    "path": "test/support/mocha.rb",
    "chars": 133,
    "preview": "require \"mocha/minitest\"\n\nMocha.configure do |c|\n  c.stubbing_non_existent_method = :warn\n  c.stubbing_non_public_method"
  },
  {
    "path": "test/support/rake_task_definition.rb",
    "chars": 398,
    "preview": "require \"rake\"\n\nmodule RakeTaskDefinition\n  def define_and_execute_rake_task(task_name, &block)\n    task_name ||= RakeTa"
  }
]

About this extraction

This page contains the full source code of the mattbrictson/airbrussh GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 53 files (82.7 KB), approximately 24.2k tokens, and a symbol index with 234 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!