Repository: godfat/rib Branch: master Commit: 54d1f88d9c85 Files: 71 Total size: 132.8 KB Directory structure: gitextract_3eo0hgho/ ├── .gitignore ├── .gitlab-ci.yml ├── .gitmodules ├── CHANGES.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── TODO.md ├── asciicast.json ├── bin/ │ ├── rib │ ├── rib-all │ ├── rib-auto │ ├── rib-min │ ├── rib-rack │ └── rib-rails ├── lib/ │ ├── rib/ │ │ ├── all.rb │ │ ├── api.rb │ │ ├── app/ │ │ │ ├── auto.rb │ │ │ ├── rack.rb │ │ │ └── rails.rb │ │ ├── config.rb │ │ ├── core/ │ │ │ ├── completion.rb │ │ │ ├── history.rb │ │ │ ├── last_value.rb │ │ │ ├── multiline.rb │ │ │ ├── readline.rb │ │ │ ├── squeeze_history.rb │ │ │ └── strip_backtrace.rb │ │ ├── core.rb │ │ ├── debug.rb │ │ ├── extra/ │ │ │ ├── autoindent.rb │ │ │ ├── byebug.rb │ │ │ ├── hirb.rb │ │ │ ├── paging.rb │ │ │ └── spring.rb │ │ ├── more/ │ │ │ ├── anchor.rb │ │ │ ├── beep.rb │ │ │ ├── bottomup_backtrace.rb │ │ │ ├── caller.rb │ │ │ ├── color.rb │ │ │ ├── edit.rb │ │ │ ├── multiline_history.rb │ │ │ └── multiline_history_file.rb │ │ ├── more.rb │ │ ├── plugin.rb │ │ ├── runner.rb │ │ ├── shell.rb │ │ ├── test/ │ │ │ ├── history.rb │ │ │ └── multiline.rb │ │ ├── test.rb │ │ └── version.rb │ └── rib.rb ├── rib.gemspec └── test/ ├── core/ │ ├── test_completion.rb │ ├── test_history.rb │ ├── test_last_value.rb │ ├── test_multiline.rb │ ├── test_readline.rb │ ├── test_squeeze_history.rb │ └── test_strip_backtrace.rb ├── extra/ │ └── test_autoindent.rb ├── more/ │ ├── test_anchor.rb │ ├── test_beep.rb │ ├── test_caller.rb │ ├── test_color.rb │ └── test_multiline_history.rb ├── test_api.rb ├── test_plugin.rb ├── test_runner.rb └── test_shell.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /pkg/ /coverage/ ================================================ FILE: .gitlab-ci.yml ================================================ stages: - test .test: stage: test image: ruby:${RUBY_VERSION}-bullseye variables: GIT_DEPTH: "1" GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_PATHS: task RUBYOPT: --enable-frozen-string-literal before_script: - bundle install --retry=3 - unset CI # Coverage doesn't work well with frozen literal script: - ruby -vr bundler/setup -S rake test ruby:3.2: extends: - .test variables: RUBY_VERSION: '3.2' ruby:3.3: extends: - .test variables: RUBY_VERSION: '3.3' ruby:3.4: extends: - .test variables: RUBY_VERSION: '3.4' jruby:latest: extends: - .test image: jruby:latest ================================================ FILE: .gitmodules ================================================ [submodule "task"] path = task url = https://github.com/godfat/gemgem.git ================================================ FILE: CHANGES.md ================================================ # CHANGES ## Rib 1.6.2 -- 2026-01-20 ### Bugs fixed * [core/strip_backtrace] Now it works with backtrace quotes change from `` ` `` to `'` ## Rib 1.6.2 -- 2026-01-19 ### Bugs fixed * [core/multiline] Now it works with prism parser * [more/color] Now it works with backtrace quotes change from `` ` `` to `'` ## Rib 1.6.1 -- 2022-12-30 ### Bugs fixed * Fixed `rib rack` for constant resolution. ### Enhancement * [more/color] Instead of using `$1` and `$2`, use `Regexp.last_match`. It should be the same for MRI, but seems to be working better for JRuby. Perhaps `$1` and `$2` is not thread local on JRuby? ## Rib 1.6.0 -- 2018-06-20 * [core/multiline] Support for JRuby 9.2.0.0 is fixed * [more/beep] It would now beep every single time when any evaluation took longer than the threshold. * [more/color] Fix whenever the input is just `next` or `break` * [more/bottomup_backtrace] This would no longer format backtrace twice * [more/caller] It would now properly use the enabled plugins * [extra/autoindent] It would now indent with `ensure` for `begin` and `def`. Thanks @MITSUBOSHI https://github.com/godfat/rib/pull/21 and @alpaca-tc https://github.com/godfat/rib/pull/17 * [extra/autoindent] It would now indent with `for`. Thanks @MITSUBOSHI https://github.com/godfat/rib/pull/18 * [extra/autoindent] It would now indent with `()` and `[]`. Thanks @MITSUBOSHI https://github.com/godfat/rib/pull/20 and https://github.com/godfat/rib/pull/19 * [extra/byebug] This is now work-in-progress. ## Rib 1.5.4 -- 2017-10-23 * Removed unnecessary method definition for `puts` ## Rib 1.5.3 -- 2017-08-05 * Now we set trap only when the shell starts, and restore the old trap after the shell stops. This should fix some problems using anchor inside RSpec or Unicorn. They would no longer interfere with them. * [core/multiline] Now we support backslash. See: [Using backslash line continuation character results in syntax error](https://github.com/godfat/rib/issues/15) ## Rib 1.5.2 -- 2017-05-01 * We now `require 'rib/version'` from the beginning, avoid loading error under bundler. * Introduced `API#started_at` which is `config[:started_at]` for accessing when Rib started. * Introduced `API#inspect_result` which would inspect the result. The default behaviour would just inspect the result, but if the result is not a string, emit a warning. https://github.com/godfat/rib/issues/14 * Now all warnings would be printed after the result was printed. This would make it easier to spot warnings. * [more/color] It would now paint warnings. * [more/anchor] Introduced `Rib.stop_anchors` to stop all nested anchors. https://github.com/godfat/rib/issues/13 * Fixed Rib app detection for newer RubyGems ### Breaking changes * Extending `Rib::Plugin` would no longer automatically also include `Rib`. For compatibility concern, a `const_missing` is now defined in `Rib::Plugin` so that using `Shell` would still refer to `Rib::Shell` for you, considering that this might be a common pattern: extend Rib::Plugin Shell.use(self) Note that this would be removed in the future, so please take this chance to change it to: extend Rib::Plugin Rib::Shell.use(self) ## Rib 1.5.1 -- 2017-03-09 * [more/beep] This plugin would beep via `print "\a"` when the application was loaded and it's been too long. Configure the time via `Rib.config[:beep_threshold]`, default is 5 seconds. ## Rib 1.5.0 -- 2017-02-27 * Removed Ramaze direct support. Use Rack instead. * Introduced API#format_backtrace * Introduced API#puts * [more/anchor] Fixed multiline support when anchoring on an object * [more/caller] Added for showing formatted call stack * [core/underscore] This is changed to `core/last_value`, and the API is changed to `Rib.last_value` and `Rib.last_exception`. Surely this is less intrusive and more compatible for things like `Rib.anchor 42` because now literal integers are frozen and we cannot define methods on them. Also, using `Rib.last_value` is surely more descriptive. Of course, being able to type `_` is so much easier, but you could just add a plugin which defines `def _; Rib.last_value; end` and keep using `_` for your own taste! ## Rib 1.4.0 -- 2016-11-11 * Search Rib home by directories rather than config/history file. * Respect prefix option for detecting Rib home. * Update help message for `-n`, which won't load any config. * Change `Rib.config[:config]` to `Rib.config_path`. * [app/rails] Fix loading boot file when using prefix option. * [app/auto] Fix two variable shadowing warnings. ## Rib 1.3.1 -- 2016-11-03 * [core/strip_backtrace], [more/color] No longer show `./` for current path. This would be more friendly to some editors like Sublime. ## Rib 1.3.0 -- 2016-06-28 * Now `rib` would accept `-p` or `--prefix` to allow setting where we want to load the application. This is useful when we don't want to `cd` into a directory and `rib auto` or `rib rails` from there. ## Rib 1.2.91 -- 2016-01-06 * [core/multiline] Fixed a case for JRuby 9.0.4.0. * [extra/paging] Disable paging if `tput` is not found. * [extra/spring] Require `rib/extra/spring` in your `~/.spring` to enable Rails Spring support. #11 (thanks @swrobel) ## Rib 1.2.9 -- 2015-09-23 * [extra/paging] Disable paging if your terminal is nil. (e.g. on Heroku) ## Rib 1.2.8 -- 2015-09-23 * [extra/paging] Disable paging if your terminal is dumb. (e.g. on Heroku) ## Rib 1.2.7 -- 2015-01-28 * [more/bottomup_backtrace] A new plugin which prints the backtrace bottom-up. Ref: ## Rib 1.2.6 -- 2014-11-07 * [extra/autoindent] Now `module_function` would indent correctly. * [extra/autoindent] Now `else` in `case` would indent correctly. * [extra/autoindent] Fixed printing end clause twice. (performance fix) * [extra/multiline] Fixed in some cases it's not recognized as multiline in Rubinius (rbx). ## Rib 1.2.5 -- 2014-03-13 * Fixed binding. Sorry my bad. ## Rib 1.2.4 -- 2014-03-13 * Fixed a regression introduced in 1.2.0. Now `rib all -e 'p 123'` should work properly. Previously -e is ignored after rib apps. * Fixed a long standing Windows issue, where `rib all` did not work because there's no `which` on Windows. Now we fall back to `where`. * Warn on removing main method on `TOPLEVEL_BINDING`. Not sure if this would happen, but at least we could have a warning. ## Rib 1.2.3 -- 2014-02-08 * [extra/paging] Properly disable paging if less or ENV['PAGER'] is not available. ## Rib 1.2.2 -- 2014-01-23 * From now on, we support project specific config file located on "./.rib/config.rb". Note that if there's a project specific config file, your home config would not be loaded at all. The history file would also be put into "./.rib/history.rb". You might want to ignore it from your repository to avoid leaking confidential information. * [extra/paging] Now we strip ANSI sequences before calculating if we need a pager. This should make it much more accurate. ## Rib 1.2.1 -- 2014-01-18 * Fixed a bug where it cannot properly elegantly handle errors when loading the config file. * [extra/paging] This plugin would try to use $PAGER and by default less to paginate for output which cannot fit in a screen. * [more/edit] Now by default it would pick vim if $EDITOR is not set. ## Rib 1.2.0 -- 2014-01-17 * We no longer really eval on TOPLEVEL_BINDING, but a private binding derived from TOPLEVEL_BINDING. This is due to RubyGems bin stub would actually pollute TOPLEVEL_BINDING, leaving `str` and `version` as local variables. I would like to avoid them, thus introducing this change. Please let me know if this breaks anything, thanks! * [core/multiline] Fixed a multiline detection for Rubinius. ## Rib 1.1.6 -- 2013-08-14 * [more/color] Fixed inspecting recursive array and hash. ## Rib 1.1.5 -- 2013-07-11 * Fixed `rib -h` with commands don't have a description. * Added a description for `rib rack` ## Rib 1.1.4 -- 2013-07-11 * Further fixed a bug for displaying a BasicObject. Rib should never crash. * Added `rib-rack` executable which could load the app in config.ru, accessible from calling `app` in the console. Also works for `rib-auto` ## Rib 1.1.3 -- 2013-05-08 * Fixed a bug where if user input doesn't respond to `==` would crash rib. ## Rib 1.1.2 -- 2013-04-02 * [core/multiline] Ruby 2.0 compatibility. ## Rib 1.1.1 -- 2013-01-25 * Fixed some multiline issue with Rubinius and JRuby. * Properly indent for multiline prompt. * Removed ripl compatibility layer. * Only retry 5 times upon failures. This prevents from infinite retries. * Don't retry on quiting. * Added a half-baked debugger support. Try it with: `require 'rib/extra/debugger'; Rib.debug` ## Rib 1.1.0 -- 2012-07-18 * Support for Ruby 1.8 is dropped. * Now `Rib::Plugin` should be extended to the module, instead of included. This fits more naturally with Ruby, but not really compatible with Ruby 1.8? * [more/anchor] Fixed a bug where you run rib in top level while anchor in the other source, exit from the inner shell would break from the original call. Now you can safely exit from the inner shell and keep doing the original work. ## Rib 1.0.5 -- 2012-05-15 * [app/rails] Fixed SystemStackError issue. It's because ConsoleMethods should not pollute the Object, redefining `app` method. ## Rib 1.0.4 -- 2012-03-20 * [core/multiline] Fixed a corner case: ``` ruby 1/1.to_i + 1 ``` * [rib] Do not crash because of a loop error. Try to relaunch the shell. ## Rib 1.0.3 -- 2012-01-21 ### Bugs fixes * [rib-rails] Fixed sandbox mode. * [rib-rails] Bring back `reload!`, `new_session`, and `app` for Rails 3.2.0 ## Rib 1.0.2 -- 2011-12-24 ### Bugs fixes * [more/multiline_history] Make sure values are initialized even if before_loop is called later. This helps us enable plugins on the fly. ## Rib 1.0.1 -- 2011-12-15 ### Incompatible changes * [rib] Keyword `quit` to exit rib is removed, preferring `exit`. ### Bugs fixes * [rib] Now you exit rib with ` exit`. Thanks @ayamomiji * [rib] Fixed -e, --eval binding. It should be TOPLEVEL_BINDING ### Enhancement * [core/history, more/color, more/multiline_history_file, extra/autoindent] Make sure values are initialized even if before_loop is called later. This helps us enable plugins on the fly. * [extra/autoindent] Now it depends on history plugin as well. This is not really needed, but would help to reduce plugins ordering issue. ## Rib 1.0.0 -- 2011-11-05 ### Bugs fixes * [more/color] Fixed a bug for displaying `1/0`. Fixed #4, thanks @bootleq ## Rib 0.9.9 -- 2011-10-26 ### Bugs fixes * [more/color] Fixed Windows coloring support. * [more/color] Properly reset ANSI sequence. ### Enhancement * [commands] Extract commands description under `__END__` in the commands. Please read [rib-rest-core][] as an example. * [rib] Always show original errors if anything is wrong. [rib-rest-core]: https://github.com/cardinalblue/rest-core/blob/rest-core-0.7.0/bin/rib-rest-core#L21-22 ## Rib 0.9.5 -- 2011-09-03 * [rib-rails] Fixed Rails3 (sandbox) and Rails2 (env) console. Thanks bootleq * [rib-min] Fixed not being really minimum * [rib] Now you can run it with `rib -wdIlib`, isn't it convenient? It's equivalent to `rib -w -d -I lib` or `rib -w -d -I=lib` ## Rib 0.9.4 -- 2011-09-01 * [rib-rails] So now we replicated what Rails did for its console, both for Rails 2 and Rails 3. You can now fully use `rib rails` or `rib auto` as `rails console` or `./script/console` in respect to Rails 2 or 3. For example, it works for: rib auto production rib rails production rib auto test --debugger # remember to add ruby-debug(19)? to Gemfile rib auto test --sandbox rib rails test --debugger --sandbox It should also make Rails 3 print SQL log to stdout. Thanks tka. ## Rib 0.9.3 -- 2011-08-28 * [rib] Calling `Rib.shell` would no longer automatically `require 'rib/core'` anymore. This is too messy. We should only do this in `bin/rib`. See: commit #7a97441afeecae80f5493f4e8a4a6ba3044e2c33 require 'rib/more/anchor' Rib.anchor 123 Should no longer crashed... Thanks Andrew. ## Rib 0.9.2 -- 2011-08-25 * [extra/autoindent] It has been greatly improved. A lot more accurate. * [extra/autoindent] Fixed a bug when you're typing too fast upon rib launching, it might eat your input. Thanks bootleq. ## Rib 0.9.1 -- 2011-08-19 * [extra/autoindent] Autoindent plugin help you indent multiline editing. Note: This plugin is depending on [readline_buffer][], thus GNU Readline. * [ripl] After `require 'rib/ripl'`, ripl plugins should be usable for rib. * [rib] Introduce `ENV['RIB_HOME']` to set where to store config and history. By default, it's `~/.rib` now, but it would first search for existing config or history, which would first try to see `~/.rib/config.rb`, and then `~/.rib/history.rb`. If Rib can find anything there, then `RIB_HOME` would be set to `~/.rib`, the same goes to `~/.config/rib`. In short, by default `RIB_HOME` is `~/.rib`, but the old `~/.config/rib` still works. [readline_buffer]: https://github.com/godfat/readline_buffer ## Rib 0.9.0 -- 2011-08-14 * First serious release! * So much enhancement over ripl-rc! ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' gemspec gem 'rake' gem 'pork' gem 'muack' gem 'bond' gem 'hirb' gem 'simplecov', :require => false if ENV['COV'] gem 'coveralls', :require => false if ENV['CI'] platforms :ruby do gem 'readline_buffer' end ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Rib [![Pipeline status](https://gitlab.com/godfat/rib/badges/master/pipeline.svg)](https://gitlab.com/godfat/rib/-/pipelines) by Lin Jen-Shin ([godfat](https://godfat.org)) ## LINKS: * [github](https://github.com/godfat/rib) * [rubygems](https://rubygems.org/gems/rib) * [rdoc](https://rubydoc.info/github/godfat/rib) * [issues](https://github.com/godfat/rib/issues) (feel free to ask for support) ## DESCRIPTION: Ruby-Interactive-ruBy -- Yet another interactive Ruby shell Rib is based on the design of [ripl][] and the work of [ripl-rc][], some of the features are also inspired by [pry][]. The aim of Rib is to be fully featured and yet very easy to opt-out or opt-in other features. It shall be simple, lightweight and modular so that everyone could customize Rib. [ripl]: https://github.com/cldwalker/ripl [ripl-rc]: https://github.com/godfat/ripl-rc [pry]: https://github.com/pry/pry ## REQUIREMENTS: * Tested with MRI (official CRuby) and JRuby. * All gem dependencies are optional, but it's highly recommended to use Rib with [bond][] for tab completion. [bond]: https://github.com/cldwalker/bond ## INSTALLATION: gem install rib ## SYNOPSIS: ![Screenshot](https://github.com/godfat/rib/raw/master/screenshot.png) ### As an interactive shell As IRB (reads `~/.rib/config.rb` writes `~/.rib/history.rb`) rib As Rails console rib rails You could also run in production and pass arguments normally as you'd do in `rails console` or `./script/console` rib rails production --sandbox --debugger Note: You might need to add ruby-debug or ruby-debug19 to your Gemfile if you're passing --debugger and using bundler together. For [Rails Spring](https://github.com/rails/spring) support, put this line in your `~/.spring.rb`: require 'rib/extra/spring' As Rack console rib rack As a console for whichever the app in the current path it should be (for now, it's either Rails or Rack) rib auto If you're trying to use `rib auto` for a Rails app, you could also pass arguments as if you were using `rib rails`. `rib auto` is merely passing arguments. rib auto production --sandbox --debugger As a fully featured interactive Ruby shell (as ripl-rc) rib all As a fully featured app console (yes, some commands could be used together) rib all auto # or `rib auto all`, the order doesn't really matter #### Customization You can customize Rib's behaviour by setting a config file located at `$RIB_HOME/config.rb`, or `./.rib/config.rb`, or `~/.rib/config.rb`, or `~/.config/rib/config.rb`, searched by respected order. The default would be `~/.rib/config.rb`. Since it's merely a Ruby script which would be loaded into memory before launching Rib shell session, You can put any customization or monkey patch there. Personally, I use all plugins provided by Rib. My Personal [~/.config/rib/config](https://github.com/godfat/dev-tool/blob/master/.config/rib/config.rb) As you can see, putting `require 'rib/all'` into config file is exactly the same as running `rib all` without a config file. What `rib all` would do is merely require the file, and that file is also merely requiring all plugins, but without **extra plugins**, which you should enable them one by one. This is because most extra plugins are depending on other gems, or hard to work with other plugins, or having strong personal tastes, so you won't want to enable them all. Suppose you only want to use the core plugins and color plugin, you'll put this into your config file: ``` ruby require 'rib/core' require 'rib/more/color' ``` You can also write your plugins there. Here's another example: ``` ruby require 'rib/core' require 'pp' Rib.config[:prompt] = '$ ' module RibPP Rib::Shell.send(:include, self) def format_result result result_prompt + result.pretty_inspect end end ``` So that we override the original format_result to pretty_inspect the result. You can also build your own gem and then simply require it in your config file. To see a list of overridable API, please read [api.rb][] [api.rb]: https://github.com/godfat/rib/blob/master/lib/rib/api.rb #### Disable enabled plugins While it's convenient to just `require 'rib/all'`, you might not want to use all the plugins. No worries, you don't have to list everything in order to not use something. For example, you might not get used to `bottomup_backtrace` and don't want to use it. You could put this in your config: ``` ruby require 'rib/all' Rib::BottomupBacktrace.disable ``` This could disable `bottomup_backtrace` so you get regular top-down backtrace with all other plugins. This is particularly useful whenever there's a bug in one of the plugins, and you might need to disable some plugins in order to debug. You could always enable it again with: ``` ruby Rib::BottomupBacktrace.enable ``` You could do this any time, in the config, or in the shell session. No need to restart anything, because it takes effect immediately. #### Rib home and history file Rib home is used to store a config file and a history file, which is searched in this order: * $RIB_HOME * ./.rib * ~/.rib * ~/.config/rib Rib would stop searching whenever the directory is found. If none could be found, the default would be: * ~/.rib So the default history file would be located at `~/.rib/history.rb`. #### Project config and history Since `./.rib` would be searched before `~/.rib`, you could create project level config at the project directory, and the history would also be separated from each other, located at the respected `./.rib/history.rb`. To do this, you don't really have to create a project config. Creating an empty directory for Rib home at the project directory would also work. #### Project directory and command line options You could set the project directory by using `-p, --prefix` command line option. So consider this: cd ~/project rib auto Would work the same as: cd /tmp rib -p ~/project auto And the project config and history would be located at `~/project/.rib`. To check for more command line options, run `rib -h`: ``` Usage: rib [ruby OPTIONS] [rib OPTIONS] [rib COMMANDS] ruby options: -e, --eval LINE Evaluate a LINE of code -d, --debug Set debugging flags (set $DEBUG to true) -w, --warn Turn warnings on (set $-w and $VERBOSE to true) -I, --include PATH Specify $LOAD_PATH (may be used more than once) -r, --require LIBRARY Require the library, before executing your script rib options: -c, --config FILE Load config from FILE -p, --prefix PATH Prefix to locate the app. Default to . -n, --no-config Suppress loading ~/.config/rib/config.rb -h, --help Print this message -v, --version Print the version rib commands: all Load all recommended plugins auto Run as Rails or Rack console (auto-detect) min Run the minimum essence rack Run as Rack console rails Run as Rails console ``` #### Basic configuration Rib.config | Functionality -------------------------- | ------------------------------------------------- ENV['RIB_HOME'] | Specify where Rib should store config and history Rib.config[:name] | The name of this shell Rib.config[:result_prompt] | Default is "=>" Rib.config[:prompt] | Default is ">>" Rib.config[:binding] | Context, default: TOPLEVEL_BINDING Rib.config[:exit] | Commands to exit, default [nil] # control+d #### Plugin specific configuration Rib.config | Functionality ------------------------------ | --------------------------------------------- Rib.config[:completion] | Completion: Bond config Rib.config[:history_file] | Default is "~/.rib/history.rb" Rib.config[:history_size] | Default is 500 Rib.config[:color] | A hash of Class => :color mapping Rib.config[:autoindent_spaces] | How to indent? Default is two spaces: ' ' Rib.config[:beep_threshold] | When it should beep? Default is 5 seconds #### List of core plugins ``` ruby require 'rib/core' # You get all of the followings: ``` * `require 'rib/core/completion'` Completion from [bond][]. * `require 'rib/core/history'` Remember history in a history file. * `require 'rib/core/strip_backtrace'` Strip backtrace before Rib. * `require 'rib/core/readline'` Readline support. * `require 'rib/core/multiline'` You can interpret multiple lines. * `require 'rib/core/squeeze_history'` Remove duplicated input from history. * `require 'rib/core/last_value'` Save the last result in `Rib.last_value` and the last exception in `Rib.last_exception`. #### List of more plugins ``` ruby require 'rib/more' # You get all of the followings: ``` * `require 'rib/more/multiline_history_file'` Not only readline could have multiline history, but also the history file. * `require 'rib/more/bottomup_backtrace'` Show backtrace bottom-up instead of the regular top-down. * `require 'rib/more/color'` Class based colorizing. * `require 'rib/more/multiline_history'` Make readline aware of multiline history. * `require 'rib/more/beep'` Print "\a" when the application was loaded and it's been too long. Configure the threshold via `Rib.config[:beep_threshold]`. * `require 'rib/more/anchor'` See _As a debugging/interacting tool_. * `require 'rib/more/caller'` See _Current call stack (backtrace, caller)_. * `require 'rib/more/edit'` See _In place editing_. ### List of extra plugins There's no `require 'rib/extra'` for extra plugins because they might not be doing what you would expect or want, or having an external dependency, or having conflicted semantics. * `require 'rib/extra/autoindent'` This plugin is depending on: 1. [readline_buffer][] 2. readline plugin 3. multiline plugin Which would autoindent your input. * `require 'rib/extra/hirb'` This plugin is depending on: 1. [hirb][] Which would print the result with hirb. * `require 'rib/extra/paging'` This plugin is depending on `less` and `tput`. Which would pass the result to `less` (or `$PAGER` if set) if the result string is longer than the screen. * `require 'rib/extra/spring'` in your `~/.spring.rb` for [Rails Spring](https://github.com/rails/spring) support. [readline_buffer]: https://github.com/godfat/readline_buffer [hirb]: https://github.com/cldwalker/hirb ### As a debugging/interacting tool Rib could be used as a kind of debugging tool which you can set break point in the source program. ``` ruby require 'rib/config' # This would load your Rib config require 'rib/more/anchor' # If you enabled anchor in config, then needed not Rib.anchor binding # This would give you an interactive shell # when your program has been executed here. Rib.anchor 123 # You can also anchor on an object. ``` But this might be called in a loop, you might only want to enter the shell under certain circumstance, then you'll do: ``` ruby require 'rib/debug' Rib.enable_anchor do # Only `Rib.anchor` called in the block would launch a shell end Rib.anchor binding # No effect (no-op) outside the block ``` Anchor could also be nested. The level would be shown on the prompt, starting from 1. ### Current call stack (backtrace, caller) Often time we would want to see current call stack whenever we're using `Rib.anchor`. We could do that by simply using `caller` but it's barely readable because it's just returning an array without any format and it also contains backtrace from Rib itself. You could use pretty formatting with Rib: ``` ruby require 'rib/more/caller' Rib.caller ``` It would use the same format for exception backtrace to show current call stack for you. Colors, bottom up order, etc, if you're also using the corresponding plugins. Sometimes there are also too many stack frames which we don't care about. In this case, we could pass arguments to `Rib.caller` in order to filter against them. You could either pass: * A `String` represents the name of the gem you don't care * A `Regexp` which would be used to match against paths/methods you don't care Examples: ``` ruby require 'rib/more/caller' Rib.caller 'activesupport', /rspec/ ``` To remove backtrace from gem _activesupport_ and paths or methods containing rspec as part of the name, like things for _rspec_ or _rspec-core_ and so on. Note that if a method name also contains rspec then it would also be filtered. Just keep that in mind when using regular expression. Or if you don't care about any gems, only want to see application related calls, then try to match against `%r{/gems/}` because gems are often stored in a path containing `/gems/`: ``` Rib.caller %r{/gem/} ``` Happy debugging. ### In place editing Whenever you called: ``` ruby require 'rib/more/edit' Rib.edit ``` Rib would open an editor according to `$EDITOR` (`ENV['EDITOR']`) for you. By default it would pick vim if no `$EDITOR` was set. After save and leave the editor, Rib would evaluate what you had input. This also works inside an anchor. To use it, require either rib/more/edit or rib/more or rib/all. ### As a shell framework The essence is: ``` ruby require 'rib' ``` All others are optional. The core plugins are lying in `rib/core/*.rb`, and more plugins are lying in `rib/more/*.rb`. You can read `rib/app/rack.rb` and `bin/rib-rack` as a Rib App reference implementation, because it's very simple, simpler than rib-rails. ## Other plugins and apps * [rest-more][] `rib rest-core` Run as interactive rest-core client * [rib-heroku][] `rib heroku` Run console on Heroku Cedar with your config [rest-more]: https://github.com/cardinalblue/rest-more [rib-heroku]: https://github.com/godfat/rib-heroku ## CONTRIBUTORS: * Andrew Liu (@eggegg) * ayaya (@ayamomiji) * Lin Jen-Shin (@godfat) * Mr. Big Cat (@miaout17) * @alpaca-tc * @bootleq * @lulalala * @MITSUBOSH * @tka ## LICENSE: Apache License 2.0 (Apache-2.0) Copyright (c) 2011-2026, Lin Jen-Shin (godfat) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Rakefile ================================================ begin require "#{__dir__}/task/gemgem" rescue LoadError sh 'git submodule update --init --recursive' exec Gem.ruby, '-S', $PROGRAM_NAME, *ARGV end Gemgem.init(__dir__) do |s| require 'rib/version' s.name = 'rib' s.version = Rib::VERSION %w[screenshot.png asciicast.json].each do |file| s.files.delete(file) end end ================================================ FILE: TODO.md ================================================ # TODO * cleanup the tests. it's so messy right now * Runner tests * Documentation * Implement exception_spy So here we have a problem. Some plugins are expecting to override some other plugins. The order can't really be arbitrary..... Actually, users won't care about order either. We just want to enable/disable plugins, not really composability. So it really should be a bucket of plugins. At least for built-in plugins, so that we would be aware of the order. Users shouldn't pay attention on the order of built-in plugins. ================================================ FILE: asciicast.json ================================================ { "version": 1, "width": 80, "height": 24, "duration": 31.5, "command": null, "title": "rib", "env": { "TERM": "xterm-256color", "SHELL": "/usr/local/bin/fish" }, "stdout": [ [ 0.05, ">> " ], [ 0.05, "{" ], [ 0.05, "a" ], [ 0.05, ":" ], [ 0.05, "0" ], [ 0.05, "," ], [ 0.05, "'" ], [ 0.05, "b" ], [ 0.05, "'" ], [ 0.05, "=" ], [ 0.05, ">" ], [ 0.05, "[" ], [ 0.05, "O" ], [ 0.05, "b" ], [ 0.05, "j" ], [ 0.05, "e" ], [ 0.05, "c" ], [ 0.05, "t" ], [ 0.05, "]" ], [ 0.05, "," ], [ 0.05, "n" ], [ 0.05, "i" ], [ 0.05, "l" ], [ 0.05, "=" ], [ 0.05, ">" ], [ 0.05, "t" ], [ 0.05, "r" ], [ 0.05, "u" ], [ 0.05, "e" ], [ 0.05, "}" ], [ 0.05, "\r\n" ], [ 0.05, "=> \u001b[34m{\u001b[0m\u001b[36m:a\u001b[0m\u001b[34m=>\u001b[0m\u001b[31m0\u001b[0m\u001b[34m, \u001b[0m\u001b[32m\"b\"\u001b[0m\u001b[34m=>\u001b[0m\u001b[34m[\u001b[0m\u001b[33mObject\u001b[0m\u001b[34m]\u001b[0m\u001b[34m, \u001b[0m\u001b[35mnil\u001b[0m\u001b[34m=>\u001b[0m\u001b[35mtrue\u001b[0m\u001b[34m}\u001b[0m\r\n" ], [ 0.05, ">> " ], [ 1.5, "R" ], [ 0.05, "i" ], [ 0.05, "b" ], [ 0.05, "." ], [ 0.05, "a" ], [ 0.05, "n" ], [ 0.05, "c" ], [ 0.05, "h" ], [ 0.05, "o" ], [ 0.05, "r" ], [ 0.05, " " ], [ 0.05, "'" ], [ 0.05, "r" ], [ 0.05, "o" ], [ 0.05, "c" ], [ 0.05, "k" ], [ 0.05, "'" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1)>> " ], [ 1.5, "u" ], [ 0.05, "p" ], [ 0.05, "c" ], [ 0.05, "a" ], [ 0.05, "s" ], [ 0.05, "e" ], [ 0.05, "\r\n" ], [ 0.05, "=> \u001b[32m\"ROCK\"\u001b[0m\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1)>> " ], [ 1.5, "R" ], [ 0.05, "i" ], [ 0.05, "b" ], [ 0.05, "." ], [ 0.05, "a" ], [ 0.05, "n" ], [ 0.05, "c" ], [ 0.05, "h" ], [ 0.05, "o" ], [ 0.05, "r" ], [ 0.05, " " ], [ 0.05, "s" ], [ 0.05, "i" ], [ 0.05, "z" ], [ 0.05, "e" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[31m4\u001b[0m(2)>> " ], [ 1.5, "s" ], [ 0.05, "e" ], [ 0.05, "l" ], [ 0.05, "f" ], [ 0.05, " " ], [ 0.05, "+" ], [ 0.05, " " ], [ 0.05, "1" ], [ 0.05, "\r\n" ], [ 0.05, "=> \u001b[31m5\u001b[0m\r\n" ], [ 0.05, "\u001b[31m4\u001b[0m(2)>> " ], [ 1.5, "R" ], [ 0.05, "i" ], [ 0.05, "b" ], [ 0.05, "." ], [ 0.05, "a" ], [ 0.05, "n" ], [ 0.05, "c" ], [ 0.05, "h" ], [ 0.05, "o" ], [ 0.05, "r" ], [ 0.05, " " ], [ 0.05, "s" ], [ 0.05, "e" ], [ 0.05, "l" ], [ 0.05, "f" ], [ 0.05, "." ], [ 0.05, "c" ], [ 0.05, "l" ], [ 0.05, "a" ], [ 0.05, "s" ], [ 0.05, "s" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[33mInteger\u001b[0m(3)>> " ], [ 1.5, "e" ], [ 0.05, "x" ], [ 0.05, "i" ], [ 0.05, "t" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[31m4\u001b[0m(2)>> " ], [ 1.5, "e" ], [ 0.05, "x" ], [ 0.05, "i" ], [ 0.05, "t" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1)>> " ], [ 1.5, "d" ], [ 0.05, "e" ], [ 0.05, "f" ], [ 0.05, " " ], [ 0.05, "g" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1) | " ], [ 0.05, " " ], [ 0.5, "a" ], [ 0.05, " " ], [ 0.05, "=" ], [ 0.05, " " ], [ 0.05, "1" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1) | " ], [ 0.05, " " ], [ 0.5, "R" ], [ 0.05, "i" ], [ 0.05, "b" ], [ 0.05, "." ], [ 0.05, "a" ], [ 0.05, "n" ], [ 0.05, "c" ], [ 0.05, "h" ], [ 0.05, "o" ], [ 0.05, "r" ], [ 0.05, " " ], [ 0.05, "b" ], [ 0.05, "i" ], [ 0.05, "n" ], [ 0.05, "d" ], [ 0.05, "i" ], [ 0.05, "n" ], [ 0.05, "g" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1) | " ], [ 0.05, " " ], [ 0.5, "e" ], [ 0.05, "n" ], [ 0.05, "d" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[1A\u001b[K\u001b[32m\"rock\"\u001b[0m(1) | end\r\n" ], [ 0.05, "=> \u001b[36m:g\u001b[0m\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1)>> " ], [ 1.5, "a" ], [ 0.05, "\r\n" ], [ 0.05, " \u001b[33m\u001b[33m\u001b[33m(rib)\u001b[0m\u001b[0m\u001b[0m:\u001b[31m2\u001b[0m:in \u001b[32m\u001b[32m\u001b[32m`main'\u001b[0m\u001b[0m\u001b[0m\r\n" ], [ 0.05, "\u001b[35mNameError: undefined local variable or method `a' for \"rock\":String\u001b[0m\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1)>> " ], [ 1.5, "g" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[33m\"rock\"\u001b[0m(2)>> " ], [ 1.5, "a" ], [ 0.05, "\r\n" ], [ 0.05, "=> \u001b[31m1\u001b[0m\r\n" ], [ 0.05, "\u001b[33m\"rock\"\u001b[0m(2)>> " ], [ 1.5, "e" ], [ 0.05, "x" ], [ 0.05, "i" ], [ 0.05, "t" ], [ 0.05, "\r\n" ], [ 0.05, "\u001b[32m\"rock\"\u001b[0m(1)>> " ], [ 1.5, "e" ], [ 0.05, "x" ], [ 0.05, "i" ], [ 0.05, "t" ], [ 0.05, "\r\n" ], [ 0.05, ">> " ], [ 1.5, "e" ], [ 0.05, "x" ], [ 0.05, "i" ], [ 0.05, "t" ], [ 0.05, "\r\n" ] ] } ================================================ FILE: bin/rib ================================================ #!/usr/bin/env ruby require 'rib/runner' Rib.config[:mimic_irb] = true Rib::Runner.run(ARGV) ================================================ FILE: bin/rib-all ================================================ #!/usr/bin/env ruby require 'rib/runner' require 'rib/all' Rib::Runner.run(ARGV) ================================================ FILE: bin/rib-auto ================================================ #!/usr/bin/env ruby require 'rib/runner' # create the shell before app to prvent your bundler (if any) kicks in Rib.shell # we need to require anything before loading the app, # and both `rib auto` (true) and `rib-auto` (nil) should work require 'rib/core' if Rib.config.delete(:mimic_irb) != false require 'rib/app/auto' # load the app Rib::Auto.load Rib::Runner.run(ARGV) ================================================ FILE: bin/rib-min ================================================ #!/usr/bin/env ruby require 'rib/runner' Rib.config[:mimic_irb] = false Rib::Runner.run(ARGV) ================================================ FILE: bin/rib-rack ================================================ #!/usr/bin/env ruby require 'rib/runner' # create the shell before app to prvent your bundler (if any) kicks in Rib.shell # we need to require anything before loading the app, # and both `rib auto` (true) and `rib-auto` (nil) should work require 'rib/core' if Rib.config.delete(:mimic_irb) != false require 'rib/app/rack' # load the app Rib::Rack.load Rib::Runner.run(ARGV) ================================================ FILE: bin/rib-rails ================================================ #!/usr/bin/env ruby require 'rib/runner' # create the shell before app to prvent your bundler (if any) kicks in Rib.shell # we need to require anything before loading the app, # and both `rib auto` (true) and `rib-auto` (nil) should work require 'rib/core' if Rib.config.delete(:mimic_irb) != false require 'rib/app/rails' # load the app Rib::Rails.load Rib::Runner.run(ARGV) ================================================ FILE: lib/rib/all.rb ================================================ # frozen_string_literal: true require 'rib/core' require 'rib/more' ================================================ FILE: lib/rib/api.rb ================================================ # frozen_string_literal: true module Rib; module API # Called before shell starts looping def before_loop self end # Called after shell finishes looping def after_loop self end # Handle interrupt (control-c) def handle_interrupt; puts ; end # The prompt string of this shell def prompt ; config[:prompt] ; end # The result prompt string of this shell def result_prompt; config[:result_prompt] ; end # The name of this shell def name ; config[:name] ; end # The binding for evaluation def eval_binding ; config[:binding] ; end # The line number for next evaluation def line ; config[:line] ; end # When the application loaded def started_at ; config[:started_at] ; end # Main loop def in_loop input = catch(:rib_exit){ loop_once while running? } puts if input == nil && running? end # Loop iteration: REPL def loop_once input, result, err = get_input, nil, nil throw(:rib_exit, input) if config[:exit].include?(input) result, err = eval_input(input) if err print_eval_error(err) elsif input.strip != '' && !equal_rib_skip(result) print_result(result) else # print nothing for blank input or Rib::Skip end flush_warnings [result, err] rescue Interrupt handle_interrupt end # Get user input. This is most likely overrided in Readline plugin def get_input print(prompt) if input = $stdin.gets input.chomp else nil end end # Evaluate the input using #loop_eval and handle it def eval_input input [loop_eval(input), nil] rescue SystemExit throw(:rib_exit, input) rescue Exception => e [nil, e] ensure config[:line] += 1 end # Evaluate user input with #eval_binding, name and line def loop_eval input if eval_binding.kind_of?(Binding) eval_binding.eval(input, "(#{name})", line) else eval_binding.instance_eval(input, "(#{name})", line) end end def puts str='' super end # Print result using #format_result def print_result result puts(format_result(result)) rescue StandardError, SyntaxError => e warn("Error while printing result:\n #{format_error(e)}") end # Print evaluated error using #format_error def print_eval_error err puts(format_error(err)) rescue StandardError, SyntaxError => e warn("Error while printing error:\n #{format_error(e)}") end def warn message warnings << message end # Format result using #result_prompt def format_result result "#{result_prompt}#{inspect_result(result)}" end def inspect_result result string = result.inspect warn("#{result.class}#inspect is not returning a string") unless string.kind_of?(String) string end # Format error raised in #loop_eval with #get_error def format_error err message, backtrace = get_error(err) "#{message}\n #{backtrace.join("\n ")}" end module_function :format_error # Get error message and backtrace from a particular error def get_error err ["#{err.class}: #{err.message}", format_backtrace(err.backtrace)] end module_function :get_error def format_backtrace backtrace backtrace end private def equal_rib_skip result result == Rib::Skip rescue # do nothing, it cannot respond to == correctly, it can't be Rib::Skip end def flush_warnings Rib.warn(warnings.shift) until warnings.empty? end end; end ================================================ FILE: lib/rib/app/auto.rb ================================================ # frozen_string_literal: true module Rib; module Auto module_function def load app, name = %w[rails rack].find{ |n| require "rib/app/#{n}" a = Rib.const_get(n.capitalize) break a, n if a.public_send("#{n}?") } if app Rib.say("Found #{name.capitalize}, loading it...") begin app.load rescue LoadError => e Rib.warn("Error: #{e}", "Is this a #{app} app?") end else Rib.warn("No app found") end end end; end ================================================ FILE: lib/rib/app/rack.rb ================================================ # frozen_string_literal: true module Rib; module Rack singleton_class.module_eval{ attr_accessor :app } module_function def load load_rack rescue LoadError => e Rib.abort("Error: #{e}", "Is this a Rack app?") end def load_rack require 'rack' Rib.abort("Error: Cannot find config.ru") unless rack? app, _ = ::Rack::Builder.parse_file(configru_path) self.app = app Rib.shell.eval_binding.eval('def app; Rib::Rack.app; end') Rib.say("Access your app via :app method") end def rack? File.exist?(configru_path) end def configru_path "#{Rib.config[:prefix]}/config.ru" end end; end ================================================ FILE: lib/rib/app/rails.rb ================================================ # frozen_string_literal: true module Rib; module Rails module_function def load load_rails rescue LoadError => e Rib.abort("Error: #{e}", "Is this a Rails app?") end def load_rails require path_for('boot') if File.exist?(path_for('application.rb')) Rib::Rails.load_rails3 else Rib::Rails.load_rails2 end end def load_rails2 optparse_env Rib.silence{ # rails 2 is so badly written require 'stringio' stderr = $stderr $stderr = StringIO.new Object.const_set('RAILS_ENV', ENV['RAILS_ENV'] || 'development') $stderr = stderr } # copied from commands/console [path_for('environment'), 'console_app', 'console_with_helpers'].each{ |f| require f } optparse_rails end def load_rails3 optparse_env # copied from rails/commands require path_for('application') ::Rails.application.require_environment! optparse_rails end # copied from rails/commands/console def optparse_env # Has to set the RAILS_ENV before config/application is required if ARGV.first && !ARGV.first.index("-") && env = ARGV.shift # has to shift the env ARGV so IRB doesn't freak ENV['RAILS_ENV'] = %w(production development test).detect {|e| e =~ /^#{env}/} || env end end # copied from rails/commands/console def optparse_rails require 'optparse' options = {} OptionParser.new do |opt| opt.banner = "Usage: rib rails [environment] [options]" opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v } opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v } opt.parse!(ARGV) end # rails 3 if ::Rails.respond_to?(:application) && (app = ::Rails.application) # rails 3.1 if app.respond_to?(:sandbox) app.sandbox = options[:sandbox] app.load_console # rails 3.0 else app.load_console(options[:sandbox]) end else # rails 2 require 'console_sandbox' if options[:sandbox] end if options[:debugger] begin require 'ruby-debug' puts "=> Debugger enabled" rescue Exception puts "You need to install ruby-debug to run the console in debugging mode. With gems, use 'gem install ruby-debug'" exit end end if options[:sandbox] puts "Loading #{::Rails.env} environment in sandbox (Rails #{::Rails.version})" puts "Any modifications you make will be rolled back on exit" else puts "Loading #{::Rails.env} environment (Rails #{::Rails.version})" end # rails 3.2 if ::Rails.const_defined?(:ConsoleMethods) Rib.shell.eval_binding.eval('extend ::Rails::ConsoleMethods') end end def rails? File.exist?(path_for('boot.rb')) && File.exist?(path_for('environment.rb')) end def path_for file File.expand_path("#{Rib.config[:prefix]}/config/#{file}") end end; end ================================================ FILE: lib/rib/config.rb ================================================ # frozen_string_literal: true require 'rib' Rib.require_config ================================================ FILE: lib/rib/core/completion.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module Completion extend Plugin Shell.use(self) # --------------- Rib API --------------- def before_loop return super if Completion.disabled? config[:completion] ||= {} config[:completion][:gems] ||= [] config[:completion][:eval_binding] ||= method(:eval_binding).to_proc Rib.silence{Bond.start(config[:completion]) unless Bond.started?} super end end; end begin Rib.silence{require 'bond'} rescue LoadError => e Rib.warn("Error: #{e}" , "Please install bond to use completion plugin:\n", " gem install bond\n" , "Or add bond to Gemfile if that's the case" ) Rib::Completion.disable end ================================================ FILE: lib/rib/core/history.rb ================================================ # frozen_string_literal: true require 'rib' require 'fileutils' module Rib; module History extend Plugin Shell.use(self) # --------------- Rib API --------------- def before_loop return super if History.disabled? history_file history_size FileUtils.mkdir_p(File.dirname(history_file_path)) read_history Rib.say("History read from: #{history_file_path}") if $VERBOSE super end def after_loop return super if History.disabled? write_history Rib.say("History wrote to: #{history_file_path}") if $VERBOSE super end def get_input return super if History.disabled? (history << super).last end # --------------- Plugin API --------------- # The history data def history; config[:history] ||= []; end # Read config[:history_file] into #history, handled in history_file plugin def read_history return super if History.disabled? File.exist?(history_file_path) && history.empty? && File.readlines(history_file_path).each{ |e| history << e.chomp } end # Write #history into config[:history_file], handled in history_file plugin def write_history return super if History.disabled? history_text = "#{history.to_a.last(history_size).join("\n")}\n" File.write(history_file_path, history_text) end private def history_file config[:history_file] ||= File.join(Rib.home, 'history.rb') end def history_file_path config[:history_file_path] ||= File.expand_path(history_file) end def history_size config[:history_size] ||= 500 end end; end ================================================ FILE: lib/rib/core/last_value.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module LastValue extend Plugin Shell.use(self) attr_reader :last_value, :last_exception def print_result result return super if LastValue.disabled? @last_value = result super end def print_eval_error err return super if LastValue.disabled? @last_exception = err super end module Imp def last_value Rib.shell.last_value end def last_exception Rib.shell.last_exception end end Rib.extend(Imp) end; end ================================================ FILE: lib/rib/core/multiline.rb ================================================ # frozen_string_literal: true require 'rib' # from https://github.com/janlelis/ripl-multi_line module Rib; module Multiline extend Plugin Shell.use(self) engine = if Object.const_defined?(:RUBY_ENGINE) RUBY_ENGINE else 'ruby' end # test those: # ruby -e '"' # ruby -e '{' # ruby -e '[' # ruby -e '(' # ruby -e '/' # ruby -e 'class C' # ruby -e 'def f' # ruby -e 'begin' # ruby -e 'case 1' # ruby -e 'eval "1+1.to_i +"' # ruby -e 'eval "1+1.to_i -"' # ruby -e 'eval "1+1.to_i *"' # ruby -e 'eval "1+1.to_i /"' # ruby -e 'eval "1+1.to_i &"' # ruby -e 'eval "1+1.to_i |"' # ruby -e 'eval "1+1.to_i ^"' BINARY_OP = %w[tUPLUS tUMINUS tSTAR tREGEXP_BEG tAMPER] RUBY20_IO = %w[unary+ unary- * tREGEXP_BEG &]. map(&Regexp.method(:escape)) ERROR_REGEXP = case engine when 'ruby' ; Regexp.new( [ # string or regexp "unterminated \\w+ meets end of file", # mri and rubinius "unexpected (#{BINARY_OP.join('|')}), expecting \\$end", "syntax error, unexpected \\$end" , # prism "expected an? `.+?` to close the", "expected a matching `\\)`", # ruby 2.0 and prism "unexpected end-of-input", # ruby 2.0 "syntax error, unexpected (#{RUBY20_IO.join('|')})," ].join('|')) when 'rbx' ; Regexp.new( [ # string or regexp "unterminated \\w+ meets end of file", # mri and rubinius "syntax error, unexpected \\$end" , # rubinius "expecting keyword_end" , "expecting keyword_then" , "expecting keyword_when" , "expecting keyword_do_cond" , "expecting \\$end" , "expecting '.+'( or '.+')*" , "missing '.+' for '.+' started on line \\d+"].join('|')) when 'jruby'; Regexp.new( [ # string or regexp "unterminated \\w+ meets end of file", # jruby "syntax error, unexpected end\\-of\\-file", # jruby 9.0.4.0 "formal argument must be local variable", # jruby 9.2.0.0 "syntax error, unexpected (#{RUBY20_IO.join('|')})", "syntax error, unexpected '\\W'" ].join('|')) end # --------------- Rib API --------------- def loop_once return super if Multiline.disabled? result = nil catch(:rib_multiline) do result = super multiline_buffer.clear end result end def loop_eval input return super if Multiline.disabled? multiline_buffer << input if input =~ /\\\z/ throw :rib_multiline else super(multiline_buffer.join("\n")) end end def print_eval_error err return super if Multiline.disabled? if multiline?(err) throw :rib_multiline else super end end def prompt return super if Multiline.disabled? if multiline_buffer.empty? super else mprompt = multiline_prompt[0, config[:prompt].size] "#{' '*(config[:prompt].size-mprompt.size)}#{mprompt}" end end def handle_interrupt return super if Multiline.disabled? if multiline_buffer.empty? super else print "[removed this line: #{multiline_buffer.pop}]" super throw :rib_multiline end end # --------------- Plugin API --------------- def multiline? err err.is_a?(SyntaxError) && err.message =~ ERROR_REGEXP end def multiline_prompt config[:multiline_prompt] ||= '| ' end private def multiline_buffer @multiline_buffer ||= [] end end; end ================================================ FILE: lib/rib/core/readline.rb ================================================ # frozen_string_literal: true require 'rib' require 'readline' module Rib; module Readline extend Plugin Shell.use(self) # --------------- Rib API --------------- def before_loop return super if Readline.disabled? config[:history] = ::Readline::HISTORY super end def get_input return super if Readline.disabled? ::Readline.readline(prompt, true) end end unless ::Readline::HISTORY.respond_to?(:last) def (::Readline::HISTORY).last self[-1] end end; end ================================================ FILE: lib/rib/core/squeeze_history.rb ================================================ # frozen_string_literal: true require 'rib/core/history' # dependency module Rib; module SqueezeHistory extend Plugin Shell.use(self) # --------------- Rib API --------------- # squeeze history in memory too def loop_once return super if SqueezeHistory.disabled? begin input, last_input = history[-1], history[-2] rescue IndexError # EditLine is really broken, to_a is needed for it array = history.to_a input, last_input = array[-1], array[-2] end history.pop if input.to_s.strip == '' || (history.size > 1 && input == last_input) super end # --------------- Plugin API --------------- # write squeezed history def write_history return super if SqueezeHistory.disabled? config[:history] = squeezed_history super end private def squeezed_history history.to_a.inject([]){ |result, item| if result.last == item || item.strip == '' result else result << item end } end end; end ================================================ FILE: lib/rib/core/strip_backtrace.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module StripBacktrace extend Plugin Shell.use(self) # --------------- Rib API --------------- def format_error err return super if StripBacktrace.disabled? message, backtrace = get_error(err) "#{message}\n #{backtrace.join("\n ")}" end def get_error err return super if StripBacktrace.disabled? ["#{err.class}: #{err.message}", format_backtrace(err.backtrace)] end module_function def format_backtrace backtrace return super if StripBacktrace.disabled? strip_home_backtrace( strip_cwd_backtrace( strip_rib_backtrace(super(backtrace)))) end def strip_home_backtrace backtrace backtrace.map(&method(:replace_path_prefix).curry[ENV['HOME'], '~/']) end def strip_cwd_backtrace backtrace backtrace.map(&method(:replace_path_prefix).curry[Dir.pwd, '']) end def strip_rib_backtrace backtrace backtrace[ 0..backtrace.rindex{ |l| l =~ /\(#{name}\):\d+:in (?:`|').+?'/ } || -1] end def replace_path_prefix prefix, substitute, path path.sub(/\A#{Regexp.escape(prefix)}\//, substitute) end end; end ================================================ FILE: lib/rib/core.rb ================================================ # frozen_string_literal: true # before session starts require 'rib/core/completion' # upon session ends require 'rib/core/history' # upon formatting output require 'rib/core/strip_backtrace' # upon input require 'rib/core/readline' require 'rib/core/multiline' require 'rib/core/squeeze_history' # special tools require 'rib/core/last_value' ================================================ FILE: lib/rib/debug.rb ================================================ # frozen_string_literal: true require 'rib/config' require 'rib/more/anchor' Rib::Anchor.disable Rib::Debugger.disable if Rib.const_defined?(:Debugger) ================================================ FILE: lib/rib/extra/autoindent.rb ================================================ # frozen_string_literal: true require 'rib/core/history' # otherwise the order might be wrong require 'rib/core/readline' # dependency require 'rib/core/multiline' # dependency module Rib; module Autoindent extend Plugin Shell.use(self) # begin block could be simpler, because it should also trigger # SyntaxError, otherwise indention would be wiped out. # but end block should be exactly match, because we don't have # SyntaxError information, also triggering SyntaxError doesn't # mean it's not an end block, thinking about nested multiline! BLOCK_REGEXP = { # rescue Expression? (=> VariableName)? # consider cases: # rescue # rescue=>e # rescue => e # rescue =>e # rescue E=>e # rescue E # rescue E => e # rescue E=> e # rescue E =>e # ensure /^begin$/ => /^(end)\b|^else$|^rescue *((\w+)? *(=> *\w+)?)?$|^ensure$/, /^def\b/ => /^(end)\b|^else$|^rescue *((\w+)? *(=> *\w+)?)?$|^ensure$/, # elsif Expression # consider cases: # elsif(true) # elsif true # elsif true == true # elsif (a = true) && false /^if\b/ => /^(end)\b|^else$|^elsif\b/, /^unless\b/ => /^(end)\b|^else$|^elsif\b/, /^case\b/ => /^(end)\b|^else$|when\b/ , /^class\b/ => /^(end)\b/ , /^module\b/ => /^(end)\b/ , /^while\b/ => /^(end)\b/ , /^for\b/ => /^(end)\b/ , /^until\b/ => /^(end)\b/ , # consider cases: # 'do # ' do # "' do # /do # '{ # %q{ # %q| do # hey, two lines are even harder! # " # begin /do( *\|.*\|)?$/ => /^(end)\b/ , /\{( *\|.*\|)?$/ => /^(\})\B/ , /\($/ => /^(\))\B/ , /\[$/ => /^(\])\B/ , # those are too hard to deal with, so we use syntax error to double check # what about this then? # v = if true # else # end } # --------------- Rib API --------------- def before_loop return super if Autoindent.disabled? autoindent_spaces super end def get_input return super if Autoindent.disabled? # this is only a fix in case we don't autoindent correctly # if we're autoindenting 100% correct, then this is a useless check autoindent_stack.clear if multiline_buffer.empty? # this should be called after ::Readline.readline, but it's blocking, # and i don't know if there's any hook to do this, so here we use thread Thread.new do sleep(0.01) ::Readline.line_buffer = current_autoindent if ::Readline.line_buffer && ::Readline.line_buffer.empty? end super end def eval_input raw_input return super if Autoindent.disabled? input = raw_input.strip indent = detect_autoindent(input) result, err = super handle_autoindent(input, indent, err) [result, err] end # --------------- Plugin API --------------- def detect_autoindent input _, backmark = BLOCK_REGEXP.find{ |key, _| input =~ key } if backmark # e.g. begin [:right, backmark] elsif input =~ autoindent_stack.last if $1 # e.g. end, }, etc [:left_end] else # e.g. elsif, rescue, etc [:left_tmp] end else [:stay] end end def handle_autoindent input, indent, err case indent.first when :right # we need to go deeper if multiline?(err) if err.message =~ /unterminated \w+ meets end of file/ # skip if we're in the middle of a string or regexp else # indent.last is the way (input regexp matches) to go back autoindent_stack << indent.last end end when :left_end # we need to go back # could happen in either multiline or not handle_last_line(input) autoindent_stack.pop when :left_tmp # temporarily go back handle_last_line(input) if multiline?(err) end end def handle_last_line input, indent=current_autoindent(autoindent_stack.size-1) new_input = "#{indent}#{input}" puts("\e[1A\e[K#{prompt}#{new_input}") new_input end private def current_autoindent size=autoindent_stack.size autoindent_spaces * size end def autoindent_spaces config[:autoindent_spaces] ||= ' ' end def autoindent_stack @autoindent_stack ||= [] end end; end begin require 'readline_buffer' rescue LoadError => e Rib.warn("Error: #{e}" , "Please install readline_buffer to use autoindent plugin:\n", " gem install readline_buffer\n" , "Or add readline_buffer to Gemfile if that's the case" ) Rib::Autoindent.disable end ================================================ FILE: lib/rib/extra/byebug.rb ================================================ # frozen_string_literal: true require 'rib/more/anchor' require 'byebug/core' # This is based on lib/byebug/processors/pry_processor.rb module Rib; module Byebug extend Plugin Shell.use(self) def before_loop return super if Rib::Byebug.disabled? super # ::Byebug::RibProcessor.start end module Imp def byebug return if Rib::Byebug.disabled? ::Byebug::RibProcessor.start end def location Rib.shell.config[:byebug].location end def step times=1 throw :rib_byebug, [:step, times] end def next lines=1 throw :rib_byebug, [:next, lines] end def finish throw :rib_byebug, [:finish] end end Rib.extend(Imp) end; end module Byebug class RibProcessor < CommandProcessor def self.start Byebug.start Setting[:autolist] = false Context.processor = self steps = caller.index{ |path| !path.start_with?(__FILE__) } Byebug.current_context.step_out(steps + 2, true) end def at_line resume_rib end def at_return(_return_value) resume_rib end def at_end resume_rib end def at_breakpoint(breakpoint) raise NotImplementedError end def location context.location end private def print_location shell = Rib.shell shell.puts(shell.format_backtrace([location]).first) end def resume_rib return if Rib.shell.running? byebug_binding = frame._binding print_location action, *args = catch(:rib_byebug) do allowing_other_threads do Rib.anchor byebug_binding, :byebug => self end end perform(action, args) end def perform action, args case action when :step context.step_into(*args, frame.pos) when :next context.step_over(*args, frame.pos) when :finish context.step_out(1) end end end end ================================================ FILE: lib/rib/extra/hirb.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module Hirb extend Plugin Shell.use(self) # --------------- Rib API --------------- def format_result result return super if Hirb.disabled? ::Hirb::View.view_or_page_output(result) || super end end begin Rib.silence{ require 'hirb' ::Hirb.enable } rescue LoadError => e Rib.warn("Error: #{e}" , "Please install hirb to use hirb plugin:\n", " gem install hirb\n" , "Or add hirb to Gemfile if that's the case") Rib::Hirb.disable end; end ================================================ FILE: lib/rib/extra/paging.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module Paging extend Plugin Shell.use(self) # --------------- Rib API --------------- # Print if the it fits one screen, paging it through a pager otherwise. def puts str='' return super if Paging.disabled? if one_screen?(str) super else page_result(str) end end # `less -F` can't cat the output, so we need to detect by ourselves. # `less -X` would mess up the buffers, so it's not desired, either. def one_screen? str cols, lines = `tput cols`.to_i, `tput lines`.to_i (str.count("\n") + 2) <= lines && # count last line and prompt str.gsub(/\e\[[^m]*m/, '').size <= cols * lines end def page_result str less = IO.popen(pager, 'w') less.write(str) less.close_write rescue Errno::EPIPE # less quit without consuming all the input end def pager ENV['PAGER'] || 'less -R' end end; end pager = ENV['PAGER'] || 'less' if `which #{pager}`.empty? Rib.warn("#{pager} is not available, disabling Rib::Paging") Rib::Paging.disable elsif `which tput`.empty? Rib.warn("tput is not available, disabling Rib::Paging") Rib::Paging.disable elsif ENV['TERM'] == 'dumb' || ENV['TERM'].nil? Rib.warn("Your terminal is dumb, disabling Rib::Paging") Rib::Paging.disable end ================================================ FILE: lib/rib/extra/spring.rb ================================================ # frozen_string_literal: true module Spring module Commands class Rib def call load `which rib`.chomp end end Spring.register_command 'rib', Rib.new end end ================================================ FILE: lib/rib/more/anchor.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module Anchor extend Plugin Shell.use(self) # --------------- Rib API --------------- def prompt return super if Rib::Anchor.disabled? return super unless anchor? level = "(#{Rib.shells.size - 1})" if Rib.const_defined?(:Color) && kind_of?(Rib::Color) && Rib::Color.enabled? "#{format_color(eval_binding, prompt_anchor)}#{level}#{super}" else "#{prompt_anchor}#{level}#{super}" end end def anchor? !!config[:prompt_anchor] end private def prompt_anchor @prompt_anchor ||= if eval_binding.kind_of?(Binding) eval_binding.eval('self', __FILE__, __LINE__) else eval_binding end.inspect[0..9] end module Imp # Enter an interactive Rib shell based on a particular context. # # @api public # @param obj_or_binding [Object, Binding] The context of the shell. # @param opts [Hash] The config hash passed to the newly created shell. # See {Rib::Shell#initialize} for all possible options. # @return [Rib::Skip] This is the mark telling Rib do not print anything. # It's only used internally in Rib. # @see Rib::Shell#initialize # @example # Rib.anchor binding # Rib.anchor 123 def anchor obj_or_binding, opts={} return if Rib::Anchor.disabled? if Rib.shell.running? Rib.shells << Rib::Shell.new( Rib.shell.config.merge( :binding => obj_or_binding, :prompt_anchor => true ). merge(opts)) else Rib.shell.config.merge!(:binding => obj_or_binding, :prompt_anchor => true ). merge!(opts) end Rib.shell.loop Rib::Skip ensure Rib.shells.pop end def stop_anchors Rib.shells.select(&:anchor?).each(&:stop) Rib::Skip end end Rib.extend(Imp) end; end ================================================ FILE: lib/rib/more/beep.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module Beep extend Plugin Shell.use(self) # --------------- Rib API --------------- def loop_once return super if Beep.disabled? beep if started_at && (Time.now - started_at) > beep_threshold config[:started_at] = Time.now super end private def beep print "\a" end def beep_threshold config[:beep_threshold] ||= 5 end end; end ================================================ FILE: lib/rib/more/bottomup_backtrace.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module BottomupBacktrace extend Plugin Shell.use(self) # --------------- Rib API --------------- def format_error err return super if BottomupBacktrace.disabled? message, backtrace = get_error(err) " #{backtrace.join("\n ")}\n#{message}" end def format_backtrace backtrace super(backtrace).reverse end end; end ================================================ FILE: lib/rib/more/caller.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module Caller extend Plugin Shell.use(self) module Imp def caller *filters return if Rib::Caller.disabled? display_backtrace(super().drop(1), *filters) end def display_backtrace raw_backtrace, *filters backtrace = Rib.shell.format_backtrace(raw_backtrace) lib = %r{\brib-#{Rib::VERSION}/lib/rib/} if backtrace.first =~ lib backtrace.shift while backtrace.first =~ lib elsif backtrace.last =~ lib backtrace.pop while backtrace.last =~ lib end result = filters.map do |f| case f when Regexp f when String %r{\bgems/#{Regexp.escape(f)}\-[\d\.]+/lib/} end end.inject(backtrace, &:grep_v) Rib.shell.puts result.map{ |l| " #{l}" }.join("\n") Rib::Skip end end Rib.extend(Imp) end; end ================================================ FILE: lib/rib/more/color.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module Color extend Plugin Shell.use(self) # --------------- Rib API --------------- def before_loop colors super end def format_result result return super if Color.disabled? "#{result_prompt}#{format_color(result)}" end def get_error err return super if Color.disabled? message, backtrace = super [format_color(err, message), backtrace] end def warn message return super if Color.disabled? super(red{message}) end # --------------- Plugin API --------------- def colors config[:color] ||= { Numeric => :red , String => :green , Symbol => :cyan , Array => :blue , Hash => :blue , NilClass => :magenta, TrueClass => :magenta, FalseClass => :magenta, Exception => :magenta, Object => :yellow } end def format_color result, display=inspect_result(result) case result when String ; send(colors[String ]){ display } when Numeric; send(colors[Numeric]){ display } when Symbol ; send(colors[Symbol ]){ display } when Array ; send(colors[Array ]){ '[' } + result.map{ |e | if e.object_id == result.object_id send(colors[Array]){'[...]'} else format_color(e); end }. join(send(colors[Array ]){ ', ' }) + send(colors[Array ]){ ']' } when Hash ; send(colors[Hash ]){ '{' } + result.map{ |k, v| format_color(k) + send(colors[Hash ]){ '=>' } + if v.object_id == result.object_id send(colors[Hash]){'{...}'} else format_color(v); end }. join(send(colors[Hash ]){ ', ' }) + send(colors[Hash ]){ '}' } else ; if color = find_color(colors, result) send(color){ display } else send(colors[Object]){ display } end end end def format_backtrace backtrace colorize_backtrace(super(backtrace)) end module_function def colorize_backtrace backtrace backtrace.map{ |b| path, msgs = b.split(':', 2) dir, sep, file = path.rpartition('/') msgs ||= (line - 1).to_s # msgs would be nil when input is next/break msg = msgs.sub(/(\d+)(:?)/) do m = Regexp.last_match "#{red{m[1]}}#{m[2]}" end.sub(/(?:`|').+?'/){green{Regexp.last_match[0]}} "#{dir+sep}#{yellow{file}}:#{msg}" } end def find_color colors, value (colors.sort{ |(k1, _), (k2, _)| # Class <=> Class if k1 < k2 then -1 elsif k1 > k2 then 1 else 0 end}.find{ |(klass, _)| value.kind_of?(klass) } || []).last end def color rgb "\e[#{rgb}m" + if block_given? then "#{yield}#{reset}" else '' end end def black █ color(30, &block); end def red █ color(31, &block); end def green █ color(32, &block); end def yellow █ color(33, &block); end def blue █ color(34, &block); end def magenta █ color(35, &block); end def cyan █ color(36, &block); end def white █ color(37, &block); end def reset █ color( 0, &block); end end; end begin require 'win32console' if defined?(Gem) && Gem.win_platform? rescue LoadError => e Rib.warn("Error: #{e}" , "Please install win32console to use color plugin on Windows:\n", " gem install win32console\n" , "Or add win32console to Gemfile if that's the case" ) Rib::Color.disable end ================================================ FILE: lib/rib/more/edit.rb ================================================ # frozen_string_literal: true require 'rib' require 'tempfile' module Rib; module Edit extend Plugin Shell.use(self) module Imp def edit return if Rib::Edit.disabled? file = Tempfile.new(['rib.edit', '.rb']) file.puts(Rib.vars[:edit]) file.close shell = Rib.shell system("#{shell.editor} #{file.path}") if shell.running? shell.send(:multiline_buffer).pop else shell.before_loop end shell.loop_eval(Rib.vars[:edit] = File.read(file.path)) ensure file.close file.unlink end end def editor ENV['EDITOR'] || 'vim' end Rib.extend(Imp) end; end ================================================ FILE: lib/rib/more/multiline_history.rb ================================================ # frozen_string_literal: true require 'rib/core/history' # dependency require 'rib/core/multiline' # dependency module Rib; module MultilineHistory extend Plugin Shell.use(self) # --------------- Rib API --------------- def before_loop @multiline_trash = 0 super end def loop_eval input return super if MultilineHistory.disabled? super ensure # SyntaxError might mean we're multiline editing handle_multiline unless multiline?($!) end def handle_interrupt return super if MultilineHistory.disabled? if multiline_buffer.size > 1 multiline_trash @multiline_trash += 1 end super end private def handle_multiline if multiline_buffer.size > 1 # so multiline editing is considering done here # TODO: there's no history.pop(size) (multiline_buffer.size + multiline_trash).times{ history.pop } history << "\n" + multiline_buffer.join("\n") end end def multiline_trash @multiline_trash ||= 0 end end; end ================================================ FILE: lib/rib/more/multiline_history_file.rb ================================================ # frozen_string_literal: true require 'rib/more/multiline_history' module Rib; module MultilineHistoryFile extend Plugin Shell.use(self) # --------------- Rib API --------------- def before_loop return super if MultilineHistoryFile.disabled? multiline_history_file_token super end # --------------- Plugin API --------------- def read_history return super if MultilineHistoryFile.disabled? buffer = [] File.exist?(history_file_path) && history.empty? && IO.readlines(history_file_path).each{ |line| if line.end_with?( "#{config[:multiline_history_file_token]}\n") buffer << line[0... -multiline_history_file_token.size-1] + "\n" else history << (buffer.join + line).chomp buffer = [] end } end def write_history return super if MultilineHistoryFile.disabled? # TODO: hisotroy.map is MRI 1.9+ config[:history] = history.to_a.map{ |line| line.gsub("\n", "#{config[:multiline_history_file_token]}\n") } super end private def multiline_history_file_token config[:multiline_history_file_token] ||= ' ' end end; end ================================================ FILE: lib/rib/more.rb ================================================ # frozen_string_literal: true # upon session ends require 'rib/more/multiline_history_file' # upon formatting output require 'rib/more/bottomup_backtrace' require 'rib/more/color' # upon input require 'rib/more/multiline_history' # special tools require 'rib/more/anchor' require 'rib/more/caller' require 'rib/more/edit' # upon application loads require 'rib/more/beep' ================================================ FILE: lib/rib/plugin.rb ================================================ # frozen_string_literal: true module Rib; module Plugin attr_accessor :disabled def enable self.disabled = false if block_given? then yield else enabled? end ensure self.disabled = true if block_given? end def disable self.disabled = true if block_given? then yield else enabled? end ensure self.disabled = false if block_given? end def enabled? !disabled end def disabled? !!disabled end # Backward compatibility def const_missing mod if Rib.const_defined?(mod) Rib.warn("Using #{mod} is deprecated, please change to Rib::#{mod}", "This compatibility layer would be removed in Rib 1.6+", "Called: #{caller.first}") Rib.const_get(mod) else super end end def self.extended mod return unless mod.name snake_name = mod.name.sub(/(\w+::)+?(\w+)$/, '\2'). gsub(/([A-Z][a-z]*)/, '\\1_').downcase[0..-2] code = (%w[enable disable].map{ |meth| <<-RUBY def #{meth}_#{snake_name} &block #{mod.name}.#{meth}(&block) end RUBY } + %w[enabled? disabled?].map{ |meth| <<-RUBY def #{snake_name}_#{meth} &block #{mod.name}.#{meth}(&block) end RUBY }).join("\n") Rib.singleton_class.module_eval(code, __FILE__, __LINE__) end end; end ================================================ FILE: lib/rib/runner.rb ================================================ # frozen_string_literal: true require 'rib' module Rib; module Runner module_function def options @options ||= [['ruby options:' , '' ], ['-e, --eval LINE' , 'Evaluate a LINE of code' ], ['-d, --debug' , 'Set debugging flags (set $DEBUG to true)' ], ['-w, --warn' , 'Turn warnings on (set $-w and $VERBOSE to true)' ], ['-I, --include PATH' , 'Specify $LOAD_PATH (may be used more than once)' ], ['-r, --require LIBRARY' , 'Require the library, before executing your script' ], ['rib options:' , '' ], ['-c, --config FILE', 'Load config from FILE' ], ['-p, --prefix PATH', 'Prefix to locate the app. Default to .'], ['-n, --no-config' , 'Suppress loading any config' ], ['-h, --help' , 'Print this message' ], ['-v, --version' , 'Print the version' ]] + [['rib commands:' , '']] + commands end def commands @commands ||= command_paths.map{ |path| name = File.basename(path)[/^rib\-(.+)$/, 1] [name, command_descriptions[name] || command_descriptions_find(path) || ' '] } end def command_paths @command_paths ||= Gem.path.map{ |path| Dir["#{path}/bin/*"].map{ |f| (File.executable?(f) && File.basename(f) =~ /^rib\-.+$/ && f) || nil # a trick to make false to be nil and then }.compact # this compact could eliminate them }.flatten end def command_descriptions @command_descriptions ||= {'all' => 'Load all recommended plugins' , 'min' => 'Run the minimum essence' , 'auto' => 'Run as Rails or Rack console (auto-detect)', 'rails' => 'Run as Rails console' , 'rack' => 'Run as Rack console' } end # Extract the text below __END__ in the bin file as the description def command_descriptions_find path # FIXME: Can we do better? This is not reliable File.read(path) =~ /Gem\.activate_bin_path\(['"](.+)['"], ['"](.+)['"],/ (File.read(Gem.bin_path($1, $2))[/\n__END__\n(.+)$/m, 1] || '').strip end def run argv=ARGV (@running_commands ||= []) << Rib.config[:name] unused = parse(argv) # we only want to run the loop if we're running the rib command, # otherwise, it must be a rib app, which we only want to parse # the arguments and proceed (this is recursive!) if @running_commands.pop == 'rib' Rib.warn("Unused arguments: #{unused.inspect}") unless unused.empty? require 'rib/core' if Rib.config.delete(:mimic_irb) loop end end def loop retry_times=5 Rib.shell.loop rescue => e if retry_times <= 0 Rib.warn("Error: #{e}. Too many retries, give up.") elsif Rib.shells.last.running? Rib.warn("Error: #{e}. Relaunching a new shell... ##{retry_times}") Rib.warn("Backtrace: #{e.backtrace}") if $VERBOSE Rib.shells.pop Rib.shells << Rib::Shell.new(Rib.config) retry_times -= 1 retry else Rib.warn("Error: #{e}. Closing.") Rib.warn("Backtrace: #{e.backtrace}") if $VERBOSE end end def parse argv unused = [] until argv.empty? case arg = argv.shift when /^-e=?(.+)?/, /^--eval=?(.+)?/ Rib.shell.eval_binding.eval( $1 || argv.shift || '', __FILE__, __LINE__) when /^-d/, '--debug' $DEBUG = true parse_next(argv, arg) when /^-w/, '--warn' $-w, $VERBOSE = true, true parse_next(argv, arg) when /^-I=?(.+)?/, /^--include=?(.+)?/ paths = ($1 || argv.shift).split(':') $LOAD_PATH.unshift(*paths) when /^-r=?(.+)?/, /^--require=?(.+)?/ require($1 || argv.shift) when /^-c=?(.+)?/, /^--config=?(.+)?/ Rib.config_path = $1 || argv.shift when /^-p=?(.+)?/, /^--prefix=?(.+)?/ Rib.config[:prefix] = $1 || argv.shift when /^-n/, '--no-config' Rib.config_path = Rib::Skip parse_next(argv, arg) when /^-h/, '--help' puts(help) exit when /^-v/, '--version' require 'rib/version' puts(Rib::VERSION) exit when /^[^-]/ load_command(arg) else unused << arg end end unused end def parse_next argv, arg argv.unshift("-#{arg[2..-1]}") if arg.size > 2 end def help optt = options.transpose maxn = optt.first.map(&:size).max maxd = optt.last .map(&:size).max "Usage: #{Rib.config[:name]}" \ " [ruby OPTIONS] [rib OPTIONS] [rib COMMANDS]\n" + options.map{ |(name, desc)| if name.end_with?(':') name else sprintf(" %-*s %-*s", maxn, name, maxd, desc) end }.join("\n") end def load_command command bin = "rib-#{command}" path = which_bin(bin) if path == '' Rib.warn( "Can't find #{bin} in $PATH. Please make sure it is installed,", "or is there any typo? You can try this to install it:\n" , " gem install #{bin}") else Rib.config[:name] = bin load(path) end end def which_bin bin # handle windows here `which #{bin}`.strip rescue Errno::ENOENT # probably a windows platform, try where `where #{bin}`.lines.first.strip end end; end ================================================ FILE: lib/rib/shell.rb ================================================ # frozen_string_literal: true require 'rib/plugin' require 'rib/api' module Rib; class Shell include API def self.use mod include mod end attr_reader :config # Create a new shell. # # @api public # @param config [Hash] The config of the shell. # @option config [String] :config ('~/.rib/config.rb') # The path to Rib config file. # @option config [String] :name ('rib') # The name of the shell. Used for Rib application. # @option config [String] :result_prompt ('=> ') # @option config [String] :prompt ('>> ') # @option config [Binding, Object] :binding (new_private_binding) # The context of the shell. Could be an Object. # @option config [Array] :exit ([nil]) # The keywords to exit the shell. `nil` means EOF (ctrl+d). # @option config [Fixnum] :line (1) The beginning of line number. # @option config [String] :history_file ('~/.rib/config/history.rb') # (Only if {Rib::History} plugin is used) The path to history file. # @option config [Fixnum] :history_size (500) # (Only if {Rib::History} plugin is used) Maximum numbers of history. # @option config [Hash] :color (...) # (Only if {Rib::Color} plugin is used) Data type colors mapping. # @option config [String] :autoindent_spaces (' ') # (Only if {Rib::Autoindent} plugin is used) The indented string. def initialize(config={}) config[:binding] ||= new_private_binding self.config = {:result_prompt => '=> ', :prompt => '>> ', :exit => [nil], :line => 1 }.merge(config) stop end # Loops shell until user exits def loop before_loop set_trap start in_loop stop self rescue Exception => e Rib.warn("Error while running loop:\n #{format_error(e)}") raise ensure release_trap after_loop end def start @running = true end def stop @running = false end def running? !!@running end def warnings @warnings ||= [] end protected attr_writer :config def set_trap @trap_proc = trap('INT'){ raise Interrupt } end def release_trap trap('INT', &@trap_proc) if @trap_proc.kind_of?(Proc) end private # Avoid namespace pollution from rubygems bin stub. # To be specific, version and str. def new_private_binding TOPLEVEL_BINDING.eval <<-RUBY singleton_class.module_eval do Rib.warn("Removing existing main...") if method_defined?(:main) def main; binding; end # any way to define
method? end ret = main singleton_class.send(:remove_method, 'main') # never pollute anything ret RUBY end end; end ================================================ FILE: lib/rib/test/history.rb ================================================ # frozen_string_literal: true require 'tempfile' copy :setup_history do before do if readline? ::Readline::HISTORY.clear stub_readline end shell(:history_file => history_file) end after do tempfile.unlink if @tempfile end def tempfile @tempfile ||= Tempfile.new('rib') end def history_file tempfile.path end end ================================================ FILE: lib/rib/test/multiline.rb ================================================ # frozen_string_literal: true copy :setup_multiline do def setup_input str if readline? stub_readline(:mock) else mock($stdin).gets{ str.chomp } end end def input str setup_input(str) mock(shell).throw(:rib_multiline) end def input_done str, err=nil setup_input(str) if err mock(shell).print_eval_error(is_a(err)){} else mock(shell).print_result(is_a(Object)){} end shell.loop_once ok end def check str, err=nil yield if block_given? lines = str.split("\n") lines[0...-1].each{ |line| input(line) shell.loop_once } input_done(lines.last, err) end before do stub_output end end copy :multiline do would 'work with no prompt' do shell.config[:prompt] = '' check <<~RUBY def f 0 end RUBY end would 'def f' do check <<~RUBY def f 1 end RUBY end would 'class C' do check <<~RUBY class C end RUBY end would 'begin' do check <<~RUBY begin end RUBY end would 'begin with RuntimeError' do check <<~RUBY, RuntimeError begin raise 'multiline raised an error' end RUBY end would 'do end' do check <<~RUBY [].each do end RUBY end would 'block brace' do check <<~RUBY [].each{ } RUBY end would 'hash' do check <<~RUBY { } RUBY end would 'hash value' do check <<~RUBY {1 => 2} RUBY end would 'array' do check <<~RUBY [ ] RUBY end would 'group' do check <<~RUBY ( ) RUBY end would 'string double quote' do check <<~RUBY " " RUBY end would 'string single quote' do check <<~RUBY ' ' RUBY end would 'be hash treated as a block SyntaxError' do code = <<~RUBY puts { :x => 10 }.class RUBY if RUBY_VERSION >= '3.0.0' check code do stub(shell.config[:binding_object]).puts{} end else check code, SyntaxError end end would 'SyntaxError' do check <<~RUBY, SyntaxError s-y n RUBY end would 'binary operator +' do check <<~RUBY 1/1.to_i + 1 RUBY end would 'binary operator -' do check <<~RUBY 1*1.to_i - 1 RUBY end would 'binary operator *' do check <<~RUBY 1-1.to_i * 1 RUBY end would 'binary operator /' do code = <<~RUBY 1+1.to_i / 1 RUBY if RUBY_VERSION >= '3.0.0' check code.lines.first, SyntaxError else check code end end would 'binary operator |' do check <<~RUBY 1+1.to_i | 1 RUBY end would 'binary operator &' do check <<~RUBY 1+1.to_i & 1 RUBY end would 'binary operator ^' do check <<~RUBY 1+1.to_i ^ 1 RUBY end would 'backslash at the end' do check <<~RUBY 'nice ' \\ 'shell' RUBY end end ================================================ FILE: lib/rib/test.rb ================================================ # frozen_string_literal: true require 'pork/auto' require 'muack' Pork::Suite.include(Muack::API) require 'rib' copy :rib do before do Rib.disable_plugins end after do Muack.verify end def shell opts={} @shell ||= new_shell(opts) end def new_shell opts={} binding_object = Object.new result = Rib::Shell.new( {:binding => binding_object.instance_eval{binding}, :binding_object => binding_object}. merge(opts)) yield(result) if block_given? result.before_loop end def stub_output stub(shell).print(is_a(String)){} stub(shell).puts(is_a(String)){} stub(shell).puts{} end def readline? Rib.constants.map(&:to_s).include?('Readline') && Rib::Readline.enabled? end def stub_readline meth=:stub send(meth, ::Readline).readline(is_a(String), true) do (::Readline::HISTORY << str.chomp).last end end singleton_class.module_eval do def test_for *plugins, &block require 'rib/all' # exhaustive tests rest = Rib.plugins - plugins before do Rib.enable_plugins(plugins) Rib.disable_plugins(rest) end describe "enabling #{plugins}" do block.call case ENV['TEST_LEVEL'] when '0' when '1' test_level1(rest, block) when '2' test_level2(rest, block) when '3' test_level3(rest, block) else # test_level3 is too slow because of rr (i guess) test_level2(rest, block) end end end def test_level1 rest, block rest.each{ |target| target.enable describe "also enabling #{target}" do block.call end target.disable } end def test_level2 rest, block rest.combination(2).each{ |targets| Rib.enable_plugins(targets) describe "also enabling #{targets.join(', ')}" do block.call end Rib.disable_plugins(targets) } end def test_level3 rest, block if rest.empty? block.call else rest[0].enable describe "also enabling #{rest[0]}" do test_level3(rest[1..-1], block) end rest[0].disable describe "disabling #{rest[0]}" do test_level3(rest[1..-1], block) end end end end end def main 'rib' end Rib::Blackhole = Object.new b = Rib::Blackhole.singleton_class b.instance_methods(true).each{ |m| b.send(:undef_method, m) unless [:object_id, :__send__, :__id__].include?(m) } ================================================ FILE: lib/rib/version.rb ================================================ # frozen_string_literal: true module Rib VERSION = '1.6.3' end ================================================ FILE: lib/rib.rb ================================================ # frozen_string_literal: true require 'rib/shell' require 'rib/version' module Rib Skip = Object.new module_function # All default Rib configs, would be passed to Shell.new in Rib.shell, # but calling Shell.new directly won't bring this in. # # @api public def config @config ||= {:name => 'rib', :prefix => '.', :started_at => Time.now} end # All shells in the memory def shells @shells ||= [] end # All shared variables for all shells def vars @vars ||= {} end # Rib.home is where Rib storing things. By default, it goes to '~/.rib', # or somewhere containing a 'config.rb' or 'history.rb' in the order of # './.rib' (project specific config), or '~/.rib' (home config), or # '~/.config/rib' (home config, residing in ~/.config) # # @api public def home ENV['RIB_HOME'] ||= File.expand_path( ["#{config[:prefix]}/.rib", '~/.rib', '~/.config/rib'].find{ |path| File.exist?(File.expand_path(path)) } || '~/.rib' ) end # Convenient shell accessor, which would just give you current last shell # or create one and load the config file. If you need a clean shell which # does not load config file, use Shell.new instead. # # @api public def shell shells.last || begin require_config if config_path && config_path != Skip (shells << Shell.new(config)).last end end # All plugins which have been loaded into the memory regardless # it's enabled or not. # # @api public def plugins Shell.ancestors.drop(1).select{ |a| a.singleton_class < Plugin } end # Convenient way to disable all plugins in the memory. # This could also take a list of plugins and disable them. # # @api public # @param plugs [Array] (Rib.plugins) Plugins which would be disabled. def disable_plugins plugs=plugins plugs.each(&:disable) end # Convenient way to enable all plugins in the memory. # This could also take a list of plugins and enable them. # # @api public # @param plugs [Array] (Rib.plugins) Plugins which would be enabled. def enable_plugins plugs=plugins plugs.each(&:enable) end # Load (actually require) the config file if it exists. # This might emit warnings if there's some error while loading it. # # @api public def require_config result = require(config_path) if File.exist?(config_path) Rib.say("Config loaded from: #{config_path}") if $VERBOSE && result result rescue StandardError, LoadError, SyntaxError => e Rib.warn("Error loading #{config_path}\n" \ " #{Rib::API.format_error(e)}") end # The config path where Rib tries to load upon Rib.shell. # It is depending on where Rib.home was discovered if # no specific config path was specified via -c or --config command # # @api public def config_path @config_path ||= File.join(home, 'config.rb') end def config_path= new_path @config_path = new_path end # Say (print to $stdout, with colors in the future, maybe) # something by the name of Rib. # # @api public # @param words [Array[String]] Words you want to say. def say *words $stdout.puts(Rib.prepare(words)) end # Warn (print to $stderr, with colors in the future, maybe) # something by the name of Rib. # # @api public # @param words [Array[String]] Words you want to warn. def warn *words $stderr.puts(Rib.prepare(words)) end # Warn (print to $stderr, with colors in the future, maybe) # something by the name of Rib and then exit(1). # # @api public # @param words [Array[String]] Words you want to warn before aborting. def abort *words warn(words) exit(1) end def silence w, v = $-w, $VERBOSE $-w, $VERBOSE = false, false yield ensure $-w, $VERBOSE = w, v end private def self.prepare words name = config[:name] "#{name}: #{words.join("\n#{' '*(name.size+2)}")}" end end ================================================ FILE: rib.gemspec ================================================ # -*- encoding: utf-8 -*- # stub: rib 1.6.3 ruby lib Gem::Specification.new do |s| s.name = "rib".freeze s.version = "1.6.3".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Lin Jen-Shin (godfat)".freeze] s.date = "2026-01-20" s.description = "Ruby-Interactive-ruBy -- Yet another interactive Ruby shell\n\nRib is based on the design of [ripl][] and the work of [ripl-rc][], some of\nthe features are also inspired by [pry][]. The aim of Rib is to be fully\nfeatured and yet very easy to opt-out or opt-in other features. It shall\nbe simple, lightweight and modular so that everyone could customize Rib.\n\n[ripl]: https://github.com/cldwalker/ripl\n[ripl-rc]: https://github.com/godfat/ripl-rc\n[pry]: https://github.com/pry/pry".freeze s.email = ["godfat (XD) godfat.org".freeze] s.executables = [ "rib".freeze, "rib-all".freeze, "rib-auto".freeze, "rib-min".freeze, "rib-rack".freeze, "rib-rails".freeze] s.files = [ ".gitignore".freeze, ".gitlab-ci.yml".freeze, ".gitmodules".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "TODO.md".freeze, "bin/rib".freeze, "bin/rib-all".freeze, "bin/rib-auto".freeze, "bin/rib-min".freeze, "bin/rib-rack".freeze, "bin/rib-rails".freeze, "lib/rib.rb".freeze, "lib/rib/all.rb".freeze, "lib/rib/api.rb".freeze, "lib/rib/app/auto.rb".freeze, "lib/rib/app/rack.rb".freeze, "lib/rib/app/rails.rb".freeze, "lib/rib/config.rb".freeze, "lib/rib/core.rb".freeze, "lib/rib/core/completion.rb".freeze, "lib/rib/core/history.rb".freeze, "lib/rib/core/last_value.rb".freeze, "lib/rib/core/multiline.rb".freeze, "lib/rib/core/readline.rb".freeze, "lib/rib/core/squeeze_history.rb".freeze, "lib/rib/core/strip_backtrace.rb".freeze, "lib/rib/debug.rb".freeze, "lib/rib/extra/autoindent.rb".freeze, "lib/rib/extra/byebug.rb".freeze, "lib/rib/extra/hirb.rb".freeze, "lib/rib/extra/paging.rb".freeze, "lib/rib/extra/spring.rb".freeze, "lib/rib/more.rb".freeze, "lib/rib/more/anchor.rb".freeze, "lib/rib/more/beep.rb".freeze, "lib/rib/more/bottomup_backtrace.rb".freeze, "lib/rib/more/caller.rb".freeze, "lib/rib/more/color.rb".freeze, "lib/rib/more/edit.rb".freeze, "lib/rib/more/multiline_history.rb".freeze, "lib/rib/more/multiline_history_file.rb".freeze, "lib/rib/plugin.rb".freeze, "lib/rib/runner.rb".freeze, "lib/rib/shell.rb".freeze, "lib/rib/test.rb".freeze, "lib/rib/test/history.rb".freeze, "lib/rib/test/multiline.rb".freeze, "lib/rib/version.rb".freeze, "rib.gemspec".freeze, "task/README.md".freeze, "task/gemgem.rb".freeze, "test/core/test_completion.rb".freeze, "test/core/test_history.rb".freeze, "test/core/test_last_value.rb".freeze, "test/core/test_multiline.rb".freeze, "test/core/test_readline.rb".freeze, "test/core/test_squeeze_history.rb".freeze, "test/core/test_strip_backtrace.rb".freeze, "test/extra/test_autoindent.rb".freeze, "test/more/test_anchor.rb".freeze, "test/more/test_beep.rb".freeze, "test/more/test_caller.rb".freeze, "test/more/test_color.rb".freeze, "test/more/test_multiline_history.rb".freeze, "test/test_api.rb".freeze, "test/test_plugin.rb".freeze, "test/test_runner.rb".freeze, "test/test_shell.rb".freeze] s.homepage = "https://github.com/godfat/rib".freeze s.licenses = ["Apache-2.0".freeze] s.rubygems_version = "4.0.4".freeze s.summary = "Ruby-Interactive-ruBy -- Yet another interactive Ruby shell".freeze s.test_files = [ "test/core/test_completion.rb".freeze, "test/core/test_history.rb".freeze, "test/core/test_last_value.rb".freeze, "test/core/test_multiline.rb".freeze, "test/core/test_readline.rb".freeze, "test/core/test_squeeze_history.rb".freeze, "test/core/test_strip_backtrace.rb".freeze, "test/extra/test_autoindent.rb".freeze, "test/more/test_anchor.rb".freeze, "test/more/test_beep.rb".freeze, "test/more/test_caller.rb".freeze, "test/more/test_color.rb".freeze, "test/more/test_multiline_history.rb".freeze, "test/test_api.rb".freeze, "test/test_plugin.rb".freeze, "test/test_runner.rb".freeze, "test/test_shell.rb".freeze] end ================================================ FILE: test/core/test_completion.rb ================================================ require 'rib/test' require 'rib/core/completion' describe Rib::Completion do paste :rib before do Rib::Completion.enable end would 'start bond' do new_shell do |sh| eval_binding = sh.method(:eval_binding).source_location mock(Bond).start(having(eval_binding: is_a(Proc))).peek_args do |*args| expect(args.first[:eval_binding].source_location).eq eval_binding args end end expect(Bond).started? end end ================================================ FILE: test/core/test_history.rb ================================================ require 'rib/test' require 'rib/test/history' require 'rib/core/history' describe Rib::History do paste :rib paste :setup_history test_for Rib::History do would '#after_loop save history' do inputs = %w[blih blah] shell.history.push(*inputs) shell.after_loop expect(File.read(history_file)).eq "#{inputs.join("\n")}\n" end would '#before_loop load previous history' do File.write(history_file, "check\nthe\nmike") shell.before_loop expect(shell.history.to_a).eq %w[check the mike] end would '#before_loop have empty history if no history file exists' do expect(shell.history.to_a).eq [] end would '#read_history be accessible to plugins in #before_loop' do mod = Module.new do def read_history config[:history] = ['pong_read_history'] end end klass = Rib::Shell.dup klass.use(mod) shell = klass.new.before_loop expect(shell.history).eq ['pong_read_history'] end would '#write_history be accessible to plugins in #after_loop' do mod = Module.new do def write_history config[:history] = ['pong_write_history'] end end klass = Rib::Shell.dup klass.use(mod) shell = klass.new.before_loop.after_loop expect(shell.history).eq ['pong_write_history'] end end end ================================================ FILE: test/core/test_last_value.rb ================================================ require 'rib/test' require 'rib/core/last_value' describe Rib::LastValue do paste :rib before do stub_output stub(Rib).shell{shell} end test_for Rib::LastValue do would 'set last_value' do mock(shell).get_input{'Rib.last_value'} mock(shell).get_input{'10**2'} mock(shell).get_input{'Rib.last_value'} expect(shell.loop_once).eq [nil, nil] shell.loop_once expect(shell.loop_once).eq [100, nil] end would 'set last_exception' do mock(shell).get_input{'XD'} mock(shell).get_input{'Rib.last_exception'} shell.loop_once expect(shell.loop_once.first).kind_of?(NameError) end end end ================================================ FILE: test/core/test_multiline.rb ================================================ require 'rib/test' require 'rib/test/multiline' require 'rib/core/multiline' describe Rib::Multiline do paste :rib paste :setup_multiline test_for Rib::Multiline do paste :multiline end end ================================================ FILE: test/core/test_readline.rb ================================================ require 'rib/test' require 'rib/core/readline' describe Rib::Readline do paste :rib test_for Rib::Readline do would '#before_loop set @history' do expect(shell.history).eq Readline::HISTORY end would '#get_input calling Readline.readline' do mock(Readline).readline(shell.prompt, true){'ok'} expect(shell.get_input).eq 'ok' end end end ================================================ FILE: test/core/test_squeeze_history.rb ================================================ require 'rib/test' require 'rib/test/history' require 'rib/core/squeeze_history' describe Rib::SqueezeHistory do paste :rib paste :setup_history test_for Rib::History, Rib::SqueezeHistory do before do @input = %w[foo bar bar foo bar] end would 'after_loop saves squeezed history' do shell.history.push(*@input) shell.after_loop expect(File.read(history_file)).eq %w[foo bar foo bar].join("\n") + "\n" end would 'loop_once squeeze history' do stub_output stub(shell).get_input{ (shell.history << "'#{@input.shift}'").last } @input.size.times{ shell.loop_once } expect(shell.history.to_a).eq %w[foo bar foo bar].map{ |i| "'#{i}'" } end would 'be disabled if disabled' do Rib::SqueezeHistory.disable do stub_output stub(shell).get_input{ (shell.history << "'#{@input.shift}'").last } input = @input.dup @input.size.times{ shell.loop_once } expect(shell.history.to_a).eq input.map{ |i| "'#{i}'" } end end end end ================================================ FILE: test/core/test_strip_backtrace.rb ================================================ require 'rib/test' require 'rib/core/strip_backtrace' describe Rib::StripBacktrace do would 'strip home' do backtrace = ["#{ENV['HOME']}/test", "/prefix/#{ENV['HOME']}/test"] expect(Rib::StripBacktrace.strip_home_backtrace(backtrace)). eq ['~/test', "/prefix/#{ENV['HOME']}/test"] end would 'strip current working directory' do backtrace = [File.expand_path(__FILE__)] expect(Rib::StripBacktrace.strip_cwd_backtrace(backtrace)). eq ['test/core/test_strip_backtrace.rb'] end end ================================================ FILE: test/extra/test_autoindent.rb ================================================ require 'rib/test' require 'rib/extra/autoindent' describe Rib::Autoindent do paste :rib autoindent = Class.new do include Rib::Autoindent, Rib::Multiline, Rib::API def config {:line => 0, :binding => TOPLEVEL_BINDING, :prompt => '>> '} end def stack_size autoindent_stack.size end end before do Rib::Multiline.enable Rib::Autoindent.enable @indent = autoindent.new mock(@indent).puts(matching(/^\e/)).times(0) expect(@indent.stack_size).eq 0 end def ri input, size @indent.eval_input(input) expect(@indent.stack_size).eq size end def le input, size mock(@indent).puts(matching(/^\e/)){} @indent.eval_input(input) expect(@indent.stack_size).eq size end would 'begin rescue else ensure end' do ri('begin' , 1) ri( '1' , 1) le('rescue' , 1) ri( '1' , 1) le('rescue=>e' , 1) le('rescue => e' , 1) le('rescue =>e' , 1) le('rescue E=>e ' , 1) le('rescue E' , 1) le('rescue E => e ', 1) le('rescue E=> e' , 1) le('rescue E =>e ' , 1) le('else' , 1) ri( '1' , 1) le('ensure' , 1) ri( '1' , 1) le('end while nil' , 0) end would 'def rescue else ensure end' do ri('def f a' , 1) ri( 'if a' , 2) le( 'end' , 1) le('rescue' , 1) ri( '1' , 1) le('rescue=>e' , 1) le('rescue => e' , 1) le('rescue =>e' , 1) le('rescue E=>e ' , 1) le('rescue E' , 1) le('rescue E => e ', 1) le('rescue E=> e' , 1) le('rescue E =>e ' , 1) le('else' , 1) ri( '1' , 1) le('ensure' , 1) ri( '1' , 1) le('end while nil' , 0) end would 'if elsif else end' do ri('if true' , 1) ri( 'if false' , 2) ri( '1' , 2) le( 'end' , 1) ri( 'if true' , 2) le( 'elsif true' , 2) ri( '1' , 2) le( 'else' , 2) ri( '1' , 2) le( 'end' , 1) ri( 'if 1' , 2) ri( 'if 2' , 3) le( 'end' , 2) le( 'end' , 1) le('end' , 0) end would 'unless else end' do ri('unless 1' , 1) ri( 'unless 1' , 2) ri( '1' , 2) le( 'end ' , 1) le('else' , 1) ri( '1' , 1) le('end' , 0) end would 'case when else end' do ri('case 1' , 1) le('when 1' , 1) ri( '1' , 1) le('when 2' , 1) ri(' if 1' , 2) le(' end' , 1) le('else' , 1) ri( '1' , 1) le('end' , 0) end would 'class Object end' do ri('class Object' , 1) ri( 'if true' , 2) le( 'end' , 1) le('end' , 0) end would 'module Rib end' do ri('module Rib' , 1) ri( 'module_function', 1) ri( 'if true' , 2) le( 'end' , 1) le('end' , 0) end would 'while end' do ri('while false' , 1) ri( 'if true' , 2) le( 'end' , 1) le('end' , 0) end would 'for end' do ri('for x in 1..2' , 1) ri( 'if true' , 2) le( 'end' , 1) le('end' , 0) end would 'until end' do ri('until true' , 1) ri( 'if true' , 2) le( 'end' , 1) le('end' , 0) end would 'do end' do ri("to_s''do" , 1) ri( "to_s '' do" , 2) le( 'end' , 1) ri( 'to_s "" do' , 2) le( 'end' , 1) ri( 'to_s // do' , 2) le( 'end' , 1) le('end' , 0) end would '{}' do ri('{' , 1) ri( ':a => :b' , 1) le('}' , 0) end would '[].each{}' do ri('[].each{' , 1) ri( '0' , 1) ri( '[].each {' , 2) le( '}' , 1) le('}' , 0) end would '()' do ri('(' , 1) ri( '0' , 1) le(')' , 0) end would '{}.dig()' do ri('{}.dig(' , 1) ri( '0,' , 1) ri( '1' , 1) le(')' , 0) end would '[]' do ri('[' , 1) ri( '0,' , 1) ri( '1,' , 1) ri( '[' , 2) ri( '2' , 2) le( ']' , 1) le(']' , 0) end end ================================================ FILE: test/more/test_anchor.rb ================================================ require 'rib/test' require 'rib/more/anchor' require 'rib/core/multiline' require 'rib/test/multiline' describe Rib::Anchor do paste :rib paste :setup_multiline before do Rib::Anchor.enable end describe '#anchor?' do would 'give true when anchoring' do stub(Rib).shell{shell} mock(shell).get_input do expect(shell).anchor? mock(shell).puts{} nil end Rib.anchor 'test' end would 'give false when not anchoring' do expect(new_shell).not.anchor? end end describe '.stop_anchors' do def anchor_deeper shell, index mock(shell).get_input do mock(shell).puts.times(0) expect(shell).anchor? expect(shell.loop_eval('self')).eq index mock_deeper(index + 1) 'Rib.anchor self + 1' end end def escape shell mock(shell).get_input do 'Rib.stop_anchors' end end def mock_deeper index mock(Rib).shell.times(2) # ignore first 2 calls, see Rib.anchor mock(Rib).shell.peek_return do |deeper_shell| if index < 5 anchor_deeper(deeper_shell, index) else escape(deeper_shell) end deeper_shell end end would 'exit all anchors' do shell = Rib.shell mock(shell).get_input do mock_deeper(0) 'Rib.anchor 0' end mock(shell).get_input{} mock(shell).puts{} shell.loop end end test_for Rib::Anchor, Rib::Multiline do paste :multiline end end ================================================ FILE: test/more/test_beep.rb ================================================ require 'rib/test' require 'rib/more/beep' describe Rib::Beep do paste :rib before do Rib::Beep.enable end def verify delay, threshold=nil, &block shell(:started_at => Time.now - delay, :beep_threshold => threshold, &block) stub_output mock(shell).get_input{} shell.loop ok end def expect_beep sh mock(sh).print("\a"){} end def unexpect_beep sh stub(sh).print.with_any_args{ flunk } end describe 'beep' do would 'beep if loading too long' do verify(10, &method(:expect_beep)) end would 'be configurable via beep_threshold' do verify(2, 1, &method(:expect_beep)) end end describe 'not beep' do would 'not beep if not loading long' do verify(2, &method(:unexpect_beep)) end would 'be configurable via beep_threshold' do verify(10, 15, &method(:unexpect_beep)) end end end ================================================ FILE: test/more/test_caller.rb ================================================ require 'rib/test' require 'rib/more/caller' describe Rib::Caller do paste :rib test_for Rib::Caller do would 'puts some backtrace' do mock(Rib.shell).puts(is_a(String)){ ok } Rib.caller end end end ================================================ FILE: test/more/test_color.rb ================================================ require 'rib/test' require 'rib/more/color' describe Rib::Color do paste :rib before do Rib::Color.enable end color = Class.new do include Rib::Color def colors @colors ||= Rib::Shell.new.before_loop.config[:color] end def inspect_result result result.inspect end end.new would 'give correct color' do color.send(:format_color, [{0 => :a}, 'b', [nil, {false => Object}], {true => Exception.new}]). should.eq \ "\e[34m[\e[0m\e[34m{\e[0m\e[31m0\e[0m\e[34m=>\e[0m\e[36m:a\e[0m\e" \ "[34m}\e[0m\e[34m, \e[0m\e[32m\"b\"\e[0m\e[34m, \e[0m\e[34m[\e[0m" \ "\e[35mnil\e[0m\e[34m, \e[0m\e[34m{\e[0m\e[35mfalse\e[0m\e[34m=>" \ "\e[0m\e[33mObject\e[0m\e[34m}\e[0m\e[34m]\e[0m\e[34m, \e[0m\e[34m"\ "{\e[0m\e[35mtrue\e[0m\e[34m=>\e[0m\e[35m#" \ "\e[0m\e[34m}\e[0m\e[34m]\e[0m" end would 'inspect recursive array and hash just like built-in inspect' do a = [] a << a h = {} h[0] = h color.send(:format_color, [a, h]).should.eq \ "\e[34m[\e[0m\e[34m[\e[0m\e[34m[...]\e[0m\e[34m]\e[0m\e[34m, \e[0m" \ "\e[34m{\e[0m\e[31m0\e[0m\e[34m=>\e[0m\e[34m{...}\e[0m\e[34m}\e[0m" \ "\e[34m]\e[0m" end # regression test would "colorize errors with `/' inside" do error = Class.new(Exception) begin line = __LINE__; raise error rescue error => e msg = "test/more/#{Rib::Color.yellow{'test_color.rb'}}:" \ "#{Rib::Color.red{line}}:in #{Rib::Color.green}" Rib::Color.colorize_backtrace(e.backtrace).first.should =~ \ Regexp.new( "#{Regexp.escape(msg)}(?:`|').+'#{Regexp.escape(Rib::Color.reset)}") end end would 'colorize warnings' do shell = new_shell shell.warn('test') expect(shell.warnings).eq ["\e[31mtest\e[0m"] end end ================================================ FILE: test/more/test_multiline_history.rb ================================================ require 'rib/test' require 'rib/test/multiline' require 'rib/more/multiline_history' describe Rib::MultilineHistory do paste :rib paste :setup_multiline def check str, err=nil shell.history.clear yield if block_given? with_history(str, err) @shell = nil stub_output shell.history.clear shell.history << 'old history' yield if block_given? with_history(str, err, 'old history') end def with_history str, err, *prefix lines = str.split("\n") lines[0...-1].inject([]){ |result, line| input(line) shell.loop_once result << line expect(shell.history.to_a).eq prefix + result result } input_done(lines.last, err) history = if lines.size == 1 lines.first else "\n#{lines.join("\n")}" end expect(shell.history.to_a).eq prefix + [history] end test_for Rib::History, Rib::Multiline, Rib::MultilineHistory do paste :multiline end end ================================================ FILE: test/test_api.rb ================================================ require 'rib/test' require 'rib/shell' describe Rib::API do paste :rib Rib::API.instance_methods.delete_if{ |e| e[/=$/] }.each do |meth| would "##{meth} be accessible to plugins" do mod = Module.new do define_method meth do "pong_#{meth}" end end klass = Rib::Shell.dup klass.use(mod) expect(klass.new.send(meth)).eq "pong_#{meth}" end end would 'emit a warning whenever result is not a string' do object = Class.new{ alias_method :inspect, :object_id }.new mock(shell).get_input{'object'} mock(shell).loop_eval('object'){object} mock(shell).puts("=> #{object.object_id}"){} mock($stderr).puts(including("#{object.class}#inspect")){} shell.loop_once ok end describe '#warn' do would 'append a warning message to warnings' do shell.warn('test') expect(shell.warnings).eq ['test'] end end describe '#flush_warnings' do before do shell.warn('test') mock($stderr).puts('rib: test'){} end would 'warn to $stderr from #warnings' do shell.send(:flush_warnings) ok end end end ================================================ FILE: test/test_plugin.rb ================================================ require 'rib/test' require 'rib/all' describe Rib::Plugin do paste :rib before do @names = Dir[File.expand_path( "#{File.dirname(__FILE__)}/../lib/rib/{core,more,zore}/*.rb")]. map {|path| File.basename(path)[0..-4] } @mods = Rib.plugins end would 'have shortcut methods' do @names.each do |name| %w[enable disable].each do |meth| expect(Rib).respond_to?("#{meth}_#{name}") end %w[enabled? disabled?].each do |meth| expect(Rib).respond_to?("#{name}_#{meth}") end end end would 'be the same as mod methods' do @mods.shuffle.take(@mods.size/2).each(&:disable) @names.each do |name| %w[enabled? disabled?].each do |meth| expect(Rib.send("#{name}_#{meth}")).eq \ @mods.find{ |mod| mod.name[/::\w+$/].tr(':', '') == name.gsub(/([^_]+)/){$1.capitalize}.tr('_', '') }. send(meth) end end end would 'have backward compatibility for accessing Shell' do mock(Rib).warn( is_a(String), is_a(String), including("#{__FILE__}:47")){ok} Module.new.module_eval <<-RUBY, __FILE__, __LINE__ + 1 extend Rib::Plugin Shell RUBY end end ================================================ FILE: test/test_runner.rb ================================================ require 'rib/test' require 'rib/runner' describe Rib::Runner do paste :rib before do mock(Rib).shell{ shell }.times(2) end def input *args args.each{ |item| mock(shell).get_input{ item } } mock(shell).get_input{} end def output *args args.each{ |item| mock(shell).puts("=> #{item}"){} } mock(shell).puts{} end would '-e' do input('a') output('1') expect(Rib::Runner.run(%w[-ea=1])).eq shell end would '-e nothing' do input output expect(Rib::Runner.run(%w[-e])).eq shell end def verify_app_e argv input('a') output('1') conf = {:name => 'rib'} min = 'rib-min' mock(Rib::Runner).which_bin(min){ min } mock(Rib::Runner).load(min){ Rib::Runner.run(argv) } stub(Rib).config{ conf } expect(Rib::Runner.run(argv)).eq shell end would 'min -e' do verify_app_e(%w[min -ea=1]) end would '-e min' do verify_app_e(%w[-ea=1 min]) end end ================================================ FILE: test/test_shell.rb ================================================ require 'rib/test' require 'rib/shell' describe Rib::Shell do paste :rib describe '#loop' do def input str=Rib::Skip mock(shell).get_input{if block_given? then yield else str end} shell.loop ok end would 'exit' do input('exit' ) end would 'also exit' do input(' exit') end would 'ctrl+d' do mock(shell).puts{} ; input(nil) end would ':q' do shell.config[:exit] << ':q'; input(':q') end would '\q' do shell.config[:exit] << '\q'; input('\q') end would 'not puts anything if it is not running' do mock(shell).puts.times(0) shell.eval_binding.eval('self').instance_variable_set(:@shell, shell) input('@shell.stop; throw :rib_exit') end describe 'trap' do before do @token = Class.new(Exception) @old_trap = trap('INT'){ raise @token } mock(shell).handle_interrupt{ mock(shell).get_input{'exit'} } end after do trap('INT', &@old_trap) end def interrupt Process.kill('SIGINT', Process.pid) sleep end would 'fence and restore ctrl+c interruption' do input{ interrupt } expect.raise(@token){ interrupt } end end end describe '#loop_once' do def input str=nil if block_given? mock(shell).get_input{ yield } else mock(shell).get_input{ str } end shell.loop_once ok end would 'handles ctrl+c' do mock(shell).handle_interrupt{} input{ raise Interrupt } end would 'prints result' do mock(shell).puts('=> "mm"'){} input('"m" * 2') end %w[next break].each do |keyword| would "handle #{keyword}" do mock(shell).puts(matching(/^SyntaxError:/)){} input(keyword) end end would 'error in print_result' do mock(Rib).warn(matching(/Error while printing result.*BOOM/m)){} input('obj = Object.new; def obj.inspect; raise "BOOM"; end; obj') end would 'not crash if user input is a blackhole' do mock(Rib).warn(matching(/Error while printing result/)){} input('Rib::Blackhole') end would 'print error from eval' do mock(shell).puts(matching(/RuntimeError/)){} input('raise "blah"') end end describe '#prompt' do would 'be changeable' do shell.config[:prompt] = '> ' expect(shell.prompt).eq '> ' end end describe '#eval_input' do before do @line = shell.config[:line] end would 'line' do shell.eval_input('10 ** 2') expect(shell.config[:line]).eq @line + 1 end would 'print error and increments line' do result, err = shell.eval_input('{') expect(result).eq nil expect(err).kind_of?(SyntaxError) expect(shell.config[:line]).eq @line + 1 end end describe '#running?' do would 'have complete flow' do expect(shell).not.running? mock(shell).get_input do expect(shell).running? mock(shell).puts{} nil end shell.loop expect(shell).not.running? end end describe '#stop' do would 'stop the loop if it is stopped' do mock(shell).get_input do expect(shell).running? shell.stop expect(shell).not.running? 'Rib::Skip' end shell.loop end end would 'call after_loop even if in_loop raises' do mock(shell).loop_once{ raise 'boom' } mock(Rib).warn(is_a(String)){} mock(shell).after_loop{} expect.raise(RuntimeError) do shell.loop end end would 'have empty binding' do expect(shell(:binding => nil).eval_input('local_variables').first).empty? end would 'not pollute main' do expect(shell(:binding => nil).eval_input('main').first).eq 'rib' end would 'be main' do expect(shell(:binding => nil).eval_input('self.inspect').first).eq 'main' end would 'warn on removing main' do mock(TOPLEVEL_BINDING.eval('singleton_class')).method_defined?(:main) do true end mock(Rib).warn(is_a(String)){} expect(shell(:binding => nil).eval_input('main').first).eq 'rib' end end