├── lib ├── rack-console.rb └── rack │ ├── console │ ├── session.rb │ ├── version.rb │ └── methods.rb │ └── console.rb ├── .rspec ├── Gemfile ├── spec ├── support │ └── config.ru ├── rack │ ├── console │ │ └── methods_spec.rb │ └── console_spec.rb └── spec_helper.rb ├── .travis.yml ├── .gitignore ├── .github └── workflows │ ├── linting.yml │ └── tests.yml ├── rack-console.gemspec ├── LICENSE.txt ├── bin └── rack-console └── README.md /lib/rack-console.rb: -------------------------------------------------------------------------------- 1 | require "rack/console" 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --warnings 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://gem.coop" 2 | 3 | # Specify your gem's dependencies in rack-console.gemspec 4 | gemspec 5 | 6 | gem "rspec", "~> 3.0" 7 | gem "standardrb" 8 | gem "pry" 9 | -------------------------------------------------------------------------------- /spec/support/config.ru: -------------------------------------------------------------------------------- 1 | require "rack" 2 | 3 | app = Rack::Builder.new do 4 | map("/") { [200, {"Content-Type" => "text/plain"}, ["Hello world."]] } 5 | end 6 | 7 | run app 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0.0 4 | - 2.1.2 5 | script: 6 | - bundle exec rspec 7 | notifications: 8 | email: 9 | recipients: 10 | - me@davidcel.is 11 | on_success: change 12 | on_failure: always 13 | 14 | -------------------------------------------------------------------------------- /lib/rack/console/session.rb: -------------------------------------------------------------------------------- 1 | require "rack/test" 2 | 3 | module Rack 4 | class Console 5 | class Session 6 | include Rack::Test::Methods 7 | 8 | attr_reader :app 9 | 10 | def initialize(app) 11 | @app = app 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | -------------------------------------------------------------------------------- /lib/rack/console/version.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Console 3 | class Version 4 | MAJOR = 2 5 | MINOR = 0 6 | PATCH = 0 7 | 8 | def self.to_s 9 | [MAJOR, MINOR, PATCH].join(".") 10 | end 11 | end 12 | 13 | VERSION = Version.to_s 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rack/console/methods.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Console 3 | module Methods 4 | def reload! 5 | ENV["IGNORE_RACK_CONSOLE_INTRO"] = "true" 6 | puts "Reloading..." 7 | Kernel.exec $0, *ARGV 8 | end 9 | 10 | def app 11 | @app 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | push: 7 | branches: [main] 8 | 9 | jobs: 10 | run-linters: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Ruby and install gems 18 | uses: ruby/setup-ruby@v1 19 | with: 20 | bundler-cache: true 21 | ruby-version: 3.4 22 | 23 | - name: Lint Ruby code 24 | run: | 25 | bundle exec standardrb 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | push: 7 | branches: [main] 8 | 9 | jobs: 10 | run-tests: 11 | runs-on: ubuntu-latest 12 | 13 | env: 14 | RACK_ENV: test 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Ruby and install gems 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | bundler-cache: true 24 | ruby-version: 3.4 25 | 26 | - name: Run tests 27 | run: | 28 | bundle exec rspec 29 | -------------------------------------------------------------------------------- /spec/rack/console/methods_spec.rb: -------------------------------------------------------------------------------- 1 | require "rack/console/methods" 2 | 3 | describe Rack::Console::Methods do 4 | let(:session) { Object.new } 5 | 6 | before { session.send :extend, Rack::Console::Methods } 7 | 8 | describe "reload!" do 9 | it "re-executes the running process" do 10 | expect(Kernel).to receive(:exec).with(/bin\/rspec$/) 11 | 12 | session.reload! 13 | end 14 | end 15 | 16 | describe "app" do 17 | before { session.instance_variable_set(:@app, "app") } 18 | subject { session.app } 19 | 20 | it { is_expected.to eq("app") } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /rack-console.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) 2 | require "rack/console/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "rack-console" 6 | s.version = Rack::Console::VERSION 7 | s.authors = ["David Celis"] 8 | s.email = ["me@davidcel.is"] 9 | 10 | s.summary = "`rails console` for your Rack applications" 11 | s.description = <<-DESCRIPTION.gsub(/^\s{4}/, "") 12 | Find yourself missing a `rails console` analogue in your other Ruby web 13 | applications? This lightweight gem provides a Rack::Console that will load 14 | your Rack application's code and environment into an IRB or Pry session. 15 | Either use `Rack::Console.start` directly, or run the provided `rack-console` 16 | executable. 17 | DESCRIPTION 18 | 19 | s.homepage = "https://github.com/davidcelis/rack-console" 20 | s.license = "MIT" 21 | 22 | s.executables = ["rack-console"] 23 | s.files = Dir["lib/**/*.rb"] 24 | s.require_paths = ["lib"] 25 | 26 | s.add_dependency "rack", "~> 3.0" 27 | s.add_dependency "rack-test" 28 | s.add_dependency "irb" 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 David Celis 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /bin/rack-console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "rack/console" 4 | require "optparse" 5 | 6 | options = {} 7 | 8 | OptionParser.new do |opts| 9 | opts.banner = "USAGE: rack-console [options]" 10 | 11 | opts.on("-c", "--config [RACKUP_FILE]", "Specify a rackup config file (default: ./config.ru)") do |config| 12 | options[:config] = config 13 | end 14 | 15 | opts.on("-e", "--environment [ENVIRONMENT]", "Specify the Rack environment (default: development)") do |environment| 16 | options[:environment] = environment 17 | end 18 | 19 | opts.on("-I", "--include [PATHS]", "Add paths (colon-separated) to the $LOAD_PATH") do |paths| 20 | options[:include] = paths.split(":") 21 | end 22 | 23 | opts.on("-r", "--require [LIBRARY]", "Require a file or library before the Rack console loads") do |library| 24 | options[:require] = library 25 | end 26 | 27 | opts.on("-P", "--[no-]pry", "Use Pry if available or explicitly disable it") do |pry| 28 | options[:pry] = pry 29 | end 30 | 31 | opts.on("-v", "--version", "Print version and exit") do |v| 32 | puts Rack::Console::VERSION 33 | exit 0 34 | end 35 | end.parse! 36 | 37 | Rack::Console.new(options).start 38 | -------------------------------------------------------------------------------- /lib/rack/console.rb: -------------------------------------------------------------------------------- 1 | require "rack" 2 | require "rack/console/methods" 3 | require "rack/console/session" 4 | require "rack/console/version" 5 | 6 | module Rack 7 | class Console 8 | def self.start(options = {}) 9 | new(options).start 10 | end 11 | 12 | def initialize(options = {}) 13 | options = default_options.merge(options) 14 | 15 | @config = options[:config] 16 | @environment = options[:environment] 17 | @include = options[:include] 18 | @require = options[:require] 19 | @use_pry = !!options[:pry] 20 | end 21 | 22 | def start 23 | ENV["RACK_ENV"] = @environment 24 | $LOAD_PATH.unshift(*@include) if @include 25 | require @require if @require 26 | 27 | set_preamble 28 | 29 | puts ENV["RACK_CONSOLE_INTRO"] unless ENV["IGNORE_RACK_CONSOLE_INTRO"] 30 | 31 | # Add convenience methods to the top-level binding (main) 32 | app = Rack::Builder.parse_file(@config) 33 | main.instance_variable_set(:@app, Rack::Console::Session.new(app)) 34 | main.extend(Rack::Console::Methods) 35 | 36 | if Gem::Specification.find_all_by_name("pry").any? && @use_pry 37 | require "pry" 38 | Pry.start 39 | else 40 | require "irb" 41 | IRB.start 42 | end 43 | end 44 | 45 | private 46 | 47 | def main 48 | TOPLEVEL_BINDING.eval("self") 49 | end 50 | 51 | def set_preamble 52 | return if ENV["RACK_CONSOLE_INTRO"] 53 | 54 | loading = "Loading #{ENV["RACK_ENV"]} environment" 55 | version = "(Rack::Console #{Rack::Console::VERSION})" 56 | 57 | ENV["RACK_CONSOLE_INTRO"] = "#{loading} #{version}" 58 | end 59 | 60 | def default_options 61 | { 62 | config: "config.ru", 63 | environment: ENV.fetch("RACK_ENV", "development") 64 | } 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RACK_ENV"] = "test" 2 | ENV["IGNORE_RACK_CONSOLE_INTRO"] = "true" 3 | 4 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 5 | RSpec.configure do |config| 6 | # Allow more verbose output when running an individual spec file. 7 | if config.files_to_run.one? 8 | # Use the documentation formatter for detailed output, unless a formatter 9 | # has already been configured (e.g. via a command-line flag). 10 | config.default_formatter = "doc" 11 | end 12 | 13 | # Print the 10 slowest examples and example groups at the 14 | # end of the spec run, to help surface which specs are running 15 | # particularly slow. 16 | # config.profile_examples = 10 17 | 18 | # Run specs in random order to surface order dependencies. If you find an 19 | # order dependency and want to debug it, you can fix the order by providing 20 | # the seed, which is printed after each run. 21 | # --seed 1234 22 | config.order = :random 23 | 24 | # Seed global randomization in this process using the `--seed` CLI option. 25 | # Setting this allows you to use `--seed` to deterministically reproduce 26 | # test failures related to randomization by passing the same `--seed` value 27 | # as the one that triggered the failure. 28 | Kernel.srand config.seed 29 | 30 | # rspec-expectations config goes here. You can use an alternate 31 | # assertion/expectation library such as wrong or the stdlib/minitest 32 | # assertions if you prefer. 33 | config.expect_with :rspec do |expectations| 34 | # Enable only the newer, non-monkey-patching expect syntax. 35 | # For more details, see: 36 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 37 | expectations.syntax = :expect 38 | end 39 | 40 | # rspec-mocks config goes here. You can use an alternate test double 41 | # library (such as bogus or mocha) by changing the `mock_with` option here. 42 | config.mock_with :rspec do |mocks| 43 | # Enable only the newer, non-monkey-patching expect syntax. 44 | # For more details, see: 45 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 46 | mocks.syntax = :expect 47 | 48 | # Prevents you from mocking or stubbing a method that does not exist on 49 | # a real object. This is generally recommended. 50 | mocks.verify_partial_doubles = true 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/rack/console_spec.rb: -------------------------------------------------------------------------------- 1 | require "rack/console" 2 | require "irb" 3 | require "pry" 4 | 5 | describe Rack::Console do 6 | before do 7 | @old_pwd = Dir.pwd 8 | Dir.chdir File.expand_path("../../support", __FILE__) 9 | 10 | allow(IRB).to receive(:start) 11 | allow(Pry).to receive(:start) 12 | end 13 | 14 | context "when using IRB" do 15 | before do 16 | expect(IRB).to receive(:start) 17 | expect(Pry).not_to receive(:start) 18 | end 19 | 20 | it "defaults to a config.ru file in the current working directory" do 21 | expect(Rack::Builder).to receive(:parse_file) 22 | .with("config.ru") 23 | .and_call_original 24 | 25 | Rack::Console.new.start 26 | end 27 | 28 | it "accepts the --config option to override the location of config.ru" do 29 | Dir.chdir @old_pwd 30 | expect(Rack::Builder).to receive(:parse_file) 31 | .with("spec/support/config.ru") 32 | .and_call_original 33 | 34 | Rack::Console.new(config: "spec/support/config.ru").start 35 | end 36 | 37 | it "accepts the --require option to require a file or library" do 38 | Rack::Console.new(require: "json").start 39 | expect { JSON }.not_to raise_error 40 | end 41 | 42 | it "accepts the --require option to add paths to $LOAD_PATH" do 43 | Rack::Console.new(include: ["lib", "spec"]).start 44 | expect($LOAD_PATH).to include("lib") 45 | expect($LOAD_PATH).to include("spec") 46 | end 47 | 48 | it "accepts the --environment option to set the environment" do 49 | Rack::Console.new(environment: "production").start 50 | expect(ENV["RACK_ENV"]).to eq("production") 51 | end 52 | end 53 | 54 | context "when using Pry" do 55 | it "accepts the --pry option to enable Pry" do 56 | expect(IRB).not_to receive(:start) 57 | expect(Pry).to receive(:start) 58 | 59 | Rack::Console.new(pry: true).start 60 | end 61 | end 62 | 63 | describe "preamble message" do 64 | it "defaults to a loading message" do 65 | preamble = "Loading #{ENV["RACK_ENV"]} environment (Rack::Console #{Rack::Console::VERSION})" 66 | 67 | Rack::Console.new.start 68 | expect(ENV["RACK_CONSOLE_INTRO"]).to eq(preamble) 69 | end 70 | 71 | it "does not override a preamble if one has already been set" do 72 | ENV["RACK_CONSOLE_INTRO"] = "Hello, Rack::Console!" 73 | 74 | expect { Rack::Console.new.start }.not_to change { ENV["RACK_CONSOLE_INTRO"] } 75 | 76 | ENV["RACK_CONSOLE_INTRO"] = nil 77 | end 78 | end 79 | 80 | after do 81 | Dir.chdir @old_pwd 82 | 83 | ENV["RACK_ENV"] = "test" 84 | ENV["RACK_CONSOLE_INTRO"] = nil 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rack::Console 2 | 3 | Find yourself missing a `rails console` analogue in your other Ruby web applications? This lightweight gem provides a Rack::Console class that will load your Rack application's code and environment into an IRB or Pry session. Either use `Rack::Console.new(options).start` directly, or run the provided `rack-console` executable. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem "rack-console" 11 | ``` 12 | 13 | And then execute: 14 | 15 | ```bash 16 | $ bundle install 17 | ``` 18 | 19 | Or install it system-wide: 20 | 21 | ```bash 22 | $ gem install rack-console 23 | ``` 24 | 25 | ## Usage 26 | 27 | Rack::Console ships with a `rack-console` executable that will load your application in an IRB shell (or 28 | [Pry](http://pryrepl.org) if that's included in your Gemfile and you specify the `--pry` option). Assuming you have a `config.ru` file in the current directory, simply run: 29 | 30 | ``` 31 | $ bundle exec rack-console 32 | Loading development environment (Rack::Console 2.0.0) 33 | irb(main):001> 34 | ``` 35 | 36 | Rack::Console supports some of the same things that `rails console` provides, as well as some of the options used in `rackup`: 37 | 38 | * An `app` method that will return your underlying Rack application with [rack-test](https://github.com/brynary/rack-test) methods mixed in. You can perform fake requests to your app (e.g. `response = app.get('/')`) 39 | * A `reload!` method to discard new code or defined variables/constants 40 | * The `-c` option (or `--config`) to specify a non-standard `config.ru` file 41 | * The `-e` option (or `--environment`) to specify the Rack environment to load 42 | * The `-r` option (or `--require`) to require a file/library before Rack::Console loads 43 | * The `-I` option (or `--include`) to specify paths (colon-separated) to add to `$LOAD_PATH` before Rack::Console loads 44 | * The `-P` option (or `--[no-]pry`) to specify whether to use Pry instead of IRB (Pry must be installed in your project) 45 | 46 | ## Framework CLI Example 47 | 48 | Because Rack::Console is just a class, it's easy to provide a `console` subcommand to a CLI for your own Rack framework. For example, here's how you could hypothetically implement a `console` subcommand for a generic Rack CLI using [Thor](https://github.com/rails/thor): 49 | 50 | ```ruby 51 | require "rack/console" 52 | require "thor" 53 | 54 | module Rack 55 | class CLI < Thor 56 | desc "console [ENVIRONMENT]", "Start a Rack console" 57 | 58 | method_option :config, aliases: "-c", type: "string", desc: "Specify a Rackup file (default: config.ru)" 59 | method_option :require, aliases: "-r", type: "string", desc: "Require a file/library before console boots" 60 | method_option :include, aliases: "-I", type: "string", desc: "Add colon-separated paths to $LOAD_PATH" 61 | 62 | def console 63 | # Set a custom intro message: 64 | # ENV["RACK_CONSOLE_INTRO"] = "Loading Rack::Console..." 65 | # 66 | # Or, to prevent an intro message from being printed at all: 67 | # ENV["IGNORE_RACK_CONSOLE_INTRO"] = "true" 68 | Rack::Console.new(options).start 69 | end 70 | end 71 | end 72 | 73 | Rack::CLI.start(ARGV) 74 | ``` 75 | 76 | ## Contributing 77 | 78 | 1. [Fork it](https://github.com/davidcelis/rack-console/fork) 79 | 2. Create your feature branch (`git checkout -b my-new-feature`) 80 | 3. Commit your changes (`git commit -am 'Add some feature'`) 81 | 4. Push to the branch (`git push origin my-new-feature`) 82 | 5. [Create a new Pull Request](https://github.com/davidcelis/rack-console/compare) 83 | --------------------------------------------------------------------------------