├── .coveralls.yml ├── .document ├── .gitignore ├── .rspec ├── .ruby-gemset ├── .ruby-version ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── TODO.md ├── VERSION ├── lib ├── pd.rb ├── puts_debuggerer.rb └── puts_debuggerer │ ├── core_ext │ ├── kernel.rb │ ├── logger.rb │ └── logging │ │ └── logger.rb │ ├── run_determiner.rb │ └── source_file.rb ├── puts_debuggerer.gemspec └── spec ├── lib ├── puts_debuggerer │ └── core_ext │ │ └── kernel__opal__spec.rb ├── puts_debuggerer__exception_cases__spec.rb ├── puts_debuggerer__with_app_path_provided__spec.rb ├── puts_debuggerer__with_caller_backtrace__spec.rb ├── puts_debuggerer__with_custom_announcer__spec.rb ├── puts_debuggerer__with_custom_formatter__spec.rb ├── puts_debuggerer__with_custom_print_engine__spec.rb ├── puts_debuggerer__with_custom_printer__spec.rb ├── puts_debuggerer__with_footer_support_enabled__spec.rb ├── puts_debuggerer__with_header_support_enabled__spec.rb ├── puts_debuggerer__with_irb_support__spec.rb ├── puts_debuggerer__with_piecemeal_options__spec.rb ├── puts_debuggerer__with_rails__spec.rb ├── puts_debuggerer__with_run_at_support__spec.rb ├── puts_debuggerer__with_wrapper_support_enabled__spec.rb └── puts_debuggerer_spec.rb ├── spec_helper.rb └── support └── puts_debuggerer_invoker.rb /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: mggqeJz1Jdj1KxTJ3Y8fuCfXnM0Mrbf4v 2 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | coverage.data 4 | 5 | # rdoc generated 6 | rdoc 7 | 8 | # yard generated 9 | doc 10 | .yardoc 11 | 12 | # bundler 13 | .bundle 14 | 15 | # jeweler generated 16 | pkg 17 | 18 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 19 | # 20 | # * Create a file at ~/.gitignore 21 | # * Include files you want ignored 22 | # * Run: git config --global core.excludesfile ~/.gitignore 23 | # 24 | # After doing this, these files will be ignored in all your git projects, 25 | # saving you from having to 'pollute' every project you touch with them 26 | # 27 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 28 | # 29 | # For MacOS: 30 | # 31 | #.DS_Store 32 | 33 | # For TextMate 34 | #*.tmproj 35 | #tmtags 36 | 37 | # For emacs: 38 | #*~ 39 | #\#* 40 | #.\#* 41 | 42 | # For vim: 43 | #*.swp 44 | 45 | # For redcar: 46 | #.redcar 47 | 48 | # For rubinius: 49 | #*.rbc 50 | 51 | # Gladiator (Glimmer Editor) 52 | .gladiator 53 | 54 | tmp/ 55 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | puts_debuggerer 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.2.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 3.0.0 4 | - 2.7.1 5 | - 2.6.6 6 | - 2.5.3 7 | after_script: 8 | ruby -e "$(curl -s https://undercover-ci.com/uploader.rb)" -- --repo AndyObtiva/puts_debuggerer --commit $TRAVIS_COMMIT --lcov coverage/lcov/puts_debuggerer.lcov 9 | 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.0.1 4 | 5 | - Fix issue with deleting `pd` options from a `Hash` if it was the only argument breaking the guarantee that `pd` never modifies the printed object, which could cause bugs. 6 | - Fix issue with attempting to modify a frozen `Hash` when passing a frozen `Hash` as the only argument for `pd` method 7 | 8 | ## 1.0.0 9 | 10 | - Support including class/method after file/line in every `pd` printout 11 | 12 | ## 0.13.5 13 | 14 | - Fix not printing source line in Rails app w/ Pry 15 | - Note that Pry's compatibility (inside Pry) is experimental and fragile because Pry's API is not reliable 16 | 17 | ## 0.13.4 18 | 19 | - Reverted change to default `printer` behavior from 0.13.3 to avoid causing a double-print to stdout as it turns out `puts` is not always needed since Rails redirects to standard out by default in `Rails.logger.debug` calls 20 | 21 | ## 0.13.3 22 | 23 | - Update default `printer` behavior for Rails to always output via `puts` (not just in tests) in addition to `Rails.logger.debug` 24 | - Update custom implementation of `caller` for Opal to accept args (optional `start` and `length` or alternatively `range`) just like the Ruby API 25 | 26 | ## 0.13.2 27 | 28 | - Fix issue caused by MiniTest Rails having `IRB` constant declared despite being outside of IRB 29 | 30 | ## 0.13.1 31 | 32 | - Support `a: '[PD]'` shortcut to passing `announcer: '[PD]'` 33 | - Support `c: :t` shortcut to passing `caller: true` 34 | 35 | ## 0.13.0 36 | 37 | - Support `h: :t` shortcut to passing `header: true` 38 | - Support `f: :t` shortcut to passing `footer: true` 39 | - Support `w: :t` shortcut to passing `wrapper: true` 40 | 41 | ## 0.12.0 42 | 43 | - Upgrade `awesome_print` to `~> 1.9.2` 44 | - Support passing pd options as part of a printed hash instead of requiring a separate hash (e.g. `pd(path: path, header: true)` instead of `pd({path: path}, header: true)` ) 45 | - Support empty use of pd statement + options (e.g. `pd` or `pd header: true`) 46 | 47 | ## 0.11.0 48 | 49 | - Pry support 50 | - In Opal, print exceptions as errors in the web console using an alternative to full_message since it's not implemented in Opal yet 51 | - Fix `pd_inspect` and `pdi` in IRB 52 | 53 | ## 0.10.2 54 | 55 | - Improve Opal Ruby compatibility by displaying source file/line 56 | 57 | ## 0.10.1 58 | 59 | - Remove the need for specifying `require 'ap'` before `require 'pd'` 60 | 61 | ## 0.10.0 62 | 63 | - Support `require 'pd`' as a shorter alternative to `require 'puts_debuggerer'` 64 | - Support `printer` as a Logger object or Logging::Logger (from "logging" gem). Basically any object that responds to :debug method. 65 | - Support `printer: false` option to return rendered String instead of printing and returning object 66 | - Set logger formatter to PutsDebuggerer::LOGGER_FORMATTER_DECORATOR when passing as printer (keeping format the same, but decorating msg with pd) 67 | - Add pd_inspect (and pdi alias) Kernel core extension methods 68 | - Made awesome_print gem require happen only if printer is set to :ap or :awesome_print 69 | - Support logging gem logger and Decorate logger layout with PutsDebuggerer::LOGGING_LAYOUT_DECORATOR for logging gem 70 | 71 | ## 0.9.0 72 | 73 | - Provide partial support for Opal Ruby (missing display of file name, line number, and source code) 74 | - `source_line_count` option 75 | - `wraper` option for including both `header` and `footer` 76 | - Special handling of exceptions (prints using full_message) 77 | - Change :ap printer default to :p when unavailable 78 | - Support varargs printing (example: `pd arg1, arg2, arg3`) 79 | - Display `run_at` run number in printout 80 | 81 | ## 0.8.2 82 | 83 | - require 'stringio' for projects that don't require automatically via other gems 84 | 85 | ## 0.8.1 86 | 87 | - `printer` option support for Rails test environment 88 | 89 | ## 0.8.0 90 | 91 | - `printer` option support 92 | 93 | ## 0.7.1 94 | 95 | - default print engine to :ap (AwesomePrint) 96 | 97 | ## 0.7.0 98 | 99 | - `run_at` option, global and piecemeal. 100 | 101 | ## 0.6.1 102 | 103 | - updated README and broke apart specs 104 | 105 | ## 0.6.0 106 | 107 | - unofficial erb support, returning evaluated object/expression, removed static syntax support (replaced with header support) 108 | 109 | ## 0.5.1 110 | 111 | - support for print engine lambdas and smart defaults for leveraging Rails and AwesomePrint debuggers in Rails 112 | 113 | ## 0.5.0 114 | 115 | - custom formatter, caller backtrace, per-puts piecemeal options, and multi-line support 116 | 117 | ## 0.4.0 118 | 119 | - custom print engine (e.g. ap), custom announcer, and IRB support 120 | 121 | ## 0.3.0 122 | 123 | - header/footer support, multi-line printout, improved format 124 | 125 | ## 0.2.0 126 | 127 | - App path exclusion support, Rails root support, improved format 128 | 129 | ## 0.1.0 130 | 131 | - File/line/expression print out 132 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | # Add dependencies required to use your gem here. 3 | # Example: 4 | # gem 'activesupport', '>= 2.3.5' 5 | 6 | gem 'awesome_print', '~> 1.9.2', require: false 7 | 8 | # Add dependencies to develop your gem here. 9 | # Include everything needed to run rake, tests, features, etc. 10 | group :development do 11 | gem 'rspec', '~> 3.5' 12 | gem 'rspec-mocks', '~> 3.5' 13 | gem 'rdoc', '~> 3.12' 14 | gem 'jeweler', '~> 2.3.9' 15 | gem 'bundler', '>= 2.1.4' 16 | # gem 'coveralls', '= 0.8.23', require: false 17 | # gem 'simplecov', '~> 0.16.1', require: nil 18 | # gem 'simplecov-lcov', '~> 0.7.0', require: nil 19 | gem 'logging', '>= 2.3.0' 20 | gem 'rake-tui' 21 | end 22 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.4.0) 5 | awesome_print (1.9.2) 6 | builder (3.2.4) 7 | descendants_tracker (0.0.4) 8 | thread_safe (~> 0.3, >= 0.3.1) 9 | diff-lcs (1.5.0) 10 | faraday (0.9.2) 11 | multipart-post (>= 1.2, < 3) 12 | git (1.11.0) 13 | rchardet (~> 1.8) 14 | github_api (0.16.0) 15 | addressable (~> 2.4.0) 16 | descendants_tracker (~> 0.0.4) 17 | faraday (~> 0.8, < 0.10) 18 | hashie (>= 3.4) 19 | mime-types (>= 1.16, < 3.0) 20 | oauth2 (~> 1.0) 21 | hashie (5.0.0) 22 | highline (2.1.0) 23 | jeweler (2.3.9) 24 | builder 25 | bundler 26 | git (>= 1.2.5) 27 | github_api (~> 0.16.0) 28 | highline (>= 1.6.15) 29 | nokogiri (>= 1.5.10) 30 | psych 31 | rake 32 | rdoc 33 | semver2 34 | json (1.8.6) 35 | jwt (2.7.0) 36 | little-plugger (1.1.4) 37 | logging (2.3.1) 38 | little-plugger (~> 1.1) 39 | multi_json (~> 1.14) 40 | mime-types (2.99.3) 41 | mini_portile2 (2.8.2) 42 | multi_json (1.15.0) 43 | multi_xml (0.6.0) 44 | multipart-post (2.3.0) 45 | nokogiri (1.14.3) 46 | mini_portile2 (~> 2.8.0) 47 | racc (~> 1.4) 48 | oauth2 (1.4.8) 49 | faraday (>= 0.8, < 3.0) 50 | jwt (>= 1.0, < 3.0) 51 | multi_json (~> 1.3) 52 | multi_xml (~> 0.5) 53 | rack (>= 1.2, < 3) 54 | pastel (0.8.0) 55 | tty-color (~> 0.5) 56 | psych (5.1.0) 57 | stringio 58 | racc (1.6.2) 59 | rack (2.2.7) 60 | rake (13.0.6) 61 | rake-tui (0.2.3) 62 | tty-prompt 63 | rchardet (1.8.0) 64 | rdoc (3.12.2) 65 | json (~> 1.4) 66 | rspec (3.12.0) 67 | rspec-core (~> 3.12.0) 68 | rspec-expectations (~> 3.12.0) 69 | rspec-mocks (~> 3.12.0) 70 | rspec-core (3.12.2) 71 | rspec-support (~> 3.12.0) 72 | rspec-expectations (3.12.3) 73 | diff-lcs (>= 1.2.0, < 2.0) 74 | rspec-support (~> 3.12.0) 75 | rspec-mocks (3.12.5) 76 | diff-lcs (>= 1.2.0, < 2.0) 77 | rspec-support (~> 3.12.0) 78 | rspec-support (3.12.0) 79 | semver2 (3.4.2) 80 | stringio (3.0.6) 81 | thread_safe (0.3.6) 82 | tty-color (0.6.0) 83 | tty-cursor (0.7.1) 84 | tty-prompt (0.23.1) 85 | pastel (~> 0.8) 86 | tty-reader (~> 0.8) 87 | tty-reader (0.9.0) 88 | tty-cursor (~> 0.7) 89 | tty-screen (~> 0.8) 90 | wisper (~> 2.0) 91 | tty-screen (0.8.1) 92 | wisper (2.0.1) 93 | 94 | PLATFORMS 95 | ruby 96 | 97 | DEPENDENCIES 98 | awesome_print (~> 1.9.2) 99 | bundler (>= 2.1.4) 100 | jeweler (~> 2.3.9) 101 | logging (>= 2.3.0) 102 | rake-tui 103 | rdoc (~> 3.12) 104 | rspec (~> 3.5) 105 | rspec-mocks (~> 3.5) 106 | 107 | BUNDLED WITH 108 | 2.4.13 109 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2024 - Andy Maleh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puts Debuggerer 1.0.1 2 | ## Debugger-less Debugging FTW 3 | ## [Featured in State of the Art Rails 2023 Edition](https://github.com/DanielVartanov/state-of-the-art-rails/tree/bd7a509f5f0ab07cebfeada779b5c73e1eaf22ed) 4 | [![Gem Version](https://badge.fury.io/rb/puts_debuggerer.svg)](http://badge.fury.io/rb/puts_debuggerer) 5 | [![Coverage Status](https://coveralls.io/repos/github/AndyObtiva/puts_debuggerer/badge.svg?branch=master)](https://coveralls.io/github/AndyObtiva/puts_debuggerer?branch=master) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/81d8f6e046eb1b4a36f4/maintainability)](https://codeclimate.com/github/AndyObtiva/puts_debuggerer/maintainability) 7 | 8 | (credit to Aaron Patterson for partial inspiration: https://tenderlovemaking.com/2016/02/05/i-am-a-puts-debuggerer.html) 9 | 10 | If you like [Awesome_Print](https://rubygems.org/gems/awesome_print) (or [Amazing_Print](https://github.com/amazing-print/amazing_print)), you will love Puts Debuggerer (which builds upon them)! 11 | 12 | Debuggers are great! They help us troubleshoot complicated programming problems by inspecting values produced by code, line by line. They are invaluable when trying to understand what is going on in a large application composed of thousands or millions of lines of code. 13 | 14 | In day-to-day test-driven development and simple app debugging though, a puts statement can be a lot quicker in revealing what is going on than halting execution completely just to inspect a single value or a few. This is certainly true when writing the simplest possible code that could possibly work, and running a test every few seconds or minutes. Still, there are a number of problems with puts debugging, like difficulty in locating puts statements in a large output log, knowing which files, line numbers, and methods the puts statements were invoked from, identifying which variables were printed, and seeing the content of structured hashes and arrays in an understandable format. 15 | 16 | Enter [puts_debuggerer](https://rubygems.org/gems/puts_debuggerer)! A guilt-free puts debugging Ruby gem FTW that prints file names, line numbers, class names, method names, code statements, headers, footers, and stack traces; and formats output nicely courtesy of [awesome_print](https://rubygems.org/gems/awesome_print) (or [amazing_print](https://github.com/amazing-print/amazing_print) if you prefer). 17 | 18 | [puts_debuggerer](https://rubygems.org/gems/puts_debuggerer) automates tips mentioned in [this blog post](https://tenderlovemaking.com/2016/02/05/i-am-a-puts-debuggerer.html) by Aaron Patterson using the `pd` method available everywhere after requiring the [gem](https://rubygems.org/gems/puts_debuggerer). 19 | 20 | Basic Example: 21 | 22 | ```ruby 23 | # /Users/User/trivia_app.rb # line 1 24 | require 'pd' # line 2 25 | class TriviaApp # line 3 26 | def question # line 4 27 | bug_or_band = 'Beatles' # line 5 28 | pd bug_or_band # line 6 29 | end # line 7 30 | end # line 8 31 | TriviaApp.new.question # line 9 32 | ``` 33 | 34 | Output: 35 | 36 | ```bash 37 | [PD] /Users/User/trivia_app.rb:6 in TriviaApp.question 38 | > pd bug_or_band # line 6 39 | => "Beatles" 40 | ``` 41 | 42 | `pd` revealed that the variable contains the band name "Beatles" not the bug "Beetle", in addition to revealing the printed code statement `pd bug_or_band`, the file name `/Users/User/trivia_app.rb`, the line number `6`, the class name `TriviaApp`, and the method name `question`. 43 | 44 | ## Background 45 | 46 | It can be quite frustrating to lose puts statements in a large output or log file. One way to help find them is add an announcer (e.g. `puts "The Order Total"`) or a header (e.g. `puts '>'*80`) before every puts statement. Unfortunately, that leads to repetitive wasteful effort that adds up quickly over many work sessions and interrupts thinking flow while solving problems. 47 | 48 | puts_debuggerer automates that work via the short and simple `pd` command, automatically printing meaningful headers for output and accelerating problem solving work due to ease of typing. 49 | 50 | Example without pd: 51 | 52 | ```ruby 53 | puts order_total 54 | ``` 55 | 56 | Output: 57 | ``` 58 | 195.50 59 | ``` 60 | 61 | Which gets lost in a logging stream such as: 62 | 63 | ``` 64 | (2.7ms) CREATE TABLE "ar_internal_metadata" ("key" character varying PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) 65 | ActiveRecord::InternalMetadata Load (0.4ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 66 | (0.2ms) BEGIN 67 | SQL (0.3ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "development"], ["created_at", 2017-08-24 22:56:52 UTC], ["updated_at", 2017-08-24 22:56:52 UTC]] 68 | (0.3ms) COMMIT 69 | 195.50 70 | ActiveRecord::InternalMetadata Load (0.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 71 | (0.2ms) BEGIN 72 | (0.2ms) COMMIT 73 | ``` 74 | 75 | Here is a simple example using `pd` instead, which provides everything the puts statements above provide in addition to deducing the file name, line number, class name, and method name automatically for dead-easy debugging: 76 | 77 | ```ruby 78 | pd order_total 79 | ``` 80 | 81 | Output: 82 | 83 | ``` 84 | (2.7ms) CREATE TABLE "ar_internal_metadata" ("key" character varying PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) 85 | ActiveRecord::InternalMetadata Load (0.4ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 86 | (0.2ms) BEGIN 87 | SQL (0.3ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "development"], ["created_at", 2017-08-24 22:56:52 UTC], ["updated_at", 2017-08-24 22:56:52 UTC]] 88 | (0.3ms) COMMIT 89 | [PD] /Users/User/ordering/order.rb:39 in Order.calculate_order_total 90 | > pd order_total 91 | => 195.50 92 | ActiveRecord::InternalMetadata Load (0.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 93 | (0.2ms) BEGIN 94 | (0.2ms) COMMIT 95 | ``` 96 | 97 | This is not only easy to locate in a logging stream such as the one below, but also announces the `order_total` variable with `[PD]` for easy findability among other pd statements (you may always enter `[PD]` or variable name `order_total` using the CMD+F Quick Find to instantly jump to that line in the log): 98 | 99 | ```ruby 100 | pd order_total 101 | pd order_summary 102 | pd order_details 103 | ``` 104 | 105 | Output: 106 | 107 | ``` 108 | (2.7ms) CREATE TABLE "ar_internal_metadata" ("key" character varying PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) 109 | ActiveRecord::InternalMetadata Load (0.4ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 110 | [PD] /Users/User/ordering/order.rb:39 in Order.calculate_order_total 111 | > pd order_total 112 | => 195.50 113 | (0.2ms) BEGIN 114 | SQL (0.3ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "development"], ["created_at", 2017-08-24 22:56:52 UTC], ["updated_at", 2017-08-24 22:56:52 UTC]] 115 | (0.3ms) COMMIT 116 | [PD] /Users/User/ordering/order.rb:40 in Order.calculate_order_total 117 | > pd order_summary 118 | => "Pragmatic Ruby Book" 119 | ActiveRecord::InternalMetadata Load (0.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 120 | (0.2ms) BEGIN 121 | (0.2ms) COMMIT 122 | [PD] /Users/User/ordering/order.rb:41 in Order.calculate_order_total 123 | > pd order_details 124 | => "[Hard Cover] Pragmatic Ruby Book - English Version" 125 | ``` 126 | 127 | What if you would like to add a header for faster findability of groups of related pd statements? Just use the `header` option: 128 | 129 | ```ruby 130 | pd order_total, header: true 131 | pd order_summary 132 | pd order_details 133 | ``` 134 | 135 | Or the `h` shortcut: 136 | 137 | ```ruby 138 | pd order_total, h: :t 139 | pd order_summary 140 | pd order_details 141 | ``` 142 | 143 | Output: 144 | 145 | ``` 146 | (2.7ms) CREATE TABLE "ar_internal_metadata" ("key" character varying PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) 147 | ActiveRecord::InternalMetadata Load (0.4ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 148 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 149 | [PD] /Users/User/ordering/order.rb:39 in Order.calculate_order_total 150 | > pd order_total, header: true 151 | => 195.50 152 | (0.2ms) BEGIN 153 | SQL (0.3ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "development"], ["created_at", 2017-08-24 22:56:52 UTC], ["updated_at", 2017-08-24 22:56:52 UTC]] 154 | (0.3ms) COMMIT 155 | [PD] /Users/User/ordering/order.rb:40 in Order.calculate_order_total 156 | > pd order_summary 157 | => "Pragmatic Ruby Book" 158 | ActiveRecord::InternalMetadata Load (0.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 159 | (0.2ms) BEGIN 160 | (0.2ms) COMMIT 161 | [PD] /Users/User/ordering/order.rb:41 in Order.calculate_order_total 162 | > pd order_details 163 | => "[Hard Cover] Pragmatic Ruby Book - English Version" 164 | ``` 165 | 166 | Wanna add a footer too? No problem! 167 | 168 | ```ruby 169 | pd order_total, header: true 170 | pd order_summary 171 | pd order_details, footer: true 172 | ``` 173 | 174 | Or use the `f` shortcut: 175 | 176 | ```ruby 177 | pd order_total, h: :t 178 | pd order_summary 179 | pd order_details, f: :t 180 | ``` 181 | 182 | Output: 183 | 184 | ``` 185 | (2.7ms) CREATE TABLE "ar_internal_metadata" ("key" character varying PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) 186 | ActiveRecord::InternalMetadata Load (0.4ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 187 | ################################################################################ 188 | [PD] /Users/User/ordering/order.rb:39 in Order.calculate_order_total 189 | > pd order_total, header: '>'*80 190 | => 195.50 191 | (0.2ms) BEGIN 192 | SQL (0.3ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "development"], ["created_at", 2017-08-24 22:56:52 UTC], ["updated_at", 2017-08-24 22:56:52 UTC]] 193 | (0.3ms) COMMIT 194 | [PD] /Users/User/ordering/order.rb:40 in Order.calculate_order_total 195 | > pd order_summary 196 | => "Pragmatic Ruby Book" 197 | ActiveRecord::InternalMetadata Load (0.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 198 | (0.2ms) BEGIN 199 | (0.2ms) COMMIT 200 | [PD] /Users/User/ordering/order.rb:41 in Order.calculate_order_total 201 | > pd order_details, footer: '<'*80 202 | => "[Hard Cover] Pragmatic Ruby Book - English Version" 203 | ################################################################################ 204 | ``` 205 | 206 | Need a quick stack trace? Just use the `caller` option (you may surround with header and footer too via `wrapper`). 207 | 208 | ```ruby 209 | pd order_total, caller: true, wrapper: true 210 | pd order_summary 211 | pd order_details 212 | ``` 213 | 214 | Or use the `c` and `w` shortcuts: 215 | 216 | ```ruby 217 | pd order_total, c: :t, w: :t 218 | pd order_summary 219 | pd order_details 220 | ``` 221 | 222 | Output: 223 | 224 | ``` 225 | (2.7ms) CREATE TABLE "ar_internal_metadata" ("key" character varying PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) 226 | ActiveRecord::InternalMetadata Load (0.4ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 227 | ******************************************************************************** 228 | [PD] /Users/User/ordering/order.rb:39 in Order.calculate_order_total 229 | > pd order_total, caller: true, wrapper: true 230 | => 195.50 231 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require' 232 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi' 233 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register' 234 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi' 235 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require' 236 | /Users/User/.rvm/gems/ruby-2.7.1/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `block in require' 237 | /Users/User/.rvm/gems/ruby-2.7.1/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in `load_dependency' 238 | /Users/User/.rvm/gems/ruby-2.7.1/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `require' 239 | /Users/User/.rvm/gems/ruby-2.7.1/gems/railties-5.2.4.3/lib/rails/commands/server/server_command.rb:145:in `block in perform' 240 | /Users/User/.rvm/gems/ruby-2.7.1/gems/railties-5.2.4.3/lib/rails/commands/server/server_command.rb:142:in `tap' 241 | /Users/User/.rvm/gems/ruby-2.7.1/gems/railties-5.2.4.3/lib/rails/commands/server/server_command.rb:142:in `perform' 242 | /Users/User/.rvm/gems/ruby-2.7.1/gems/thor-1.0.1/lib/thor/command.rb:27:in `run' 243 | /Users/User/.rvm/gems/ruby-2.7.1/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command' 244 | /Users/User/.rvm/gems/ruby-2.7.1/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch' 245 | /Users/User/.rvm/gems/ruby-2.7.1/gems/railties-5.2.4.3/lib/rails/command/base.rb:69:in `perform' 246 | /Users/User/.rvm/gems/ruby-2.7.1/gems/railties-5.2.4.3/lib/rails/command.rb:46:in `invoke' 247 | /Users/User/.rvm/gems/ruby-2.7.1/gems/railties-5.2.4.3/lib/rails/commands.rb:18:in `
' 248 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require' 249 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi' 250 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register' 251 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi' 252 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require' 253 | /Users/User/.rvm/gems/ruby-2.7.1/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `block in require' 254 | /Users/User/.rvm/gems/ruby-2.7.1/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in `load_dependency' 255 | /Users/User/.rvm/gems/ruby-2.7.1/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `require' 256 | /Users/User/code/sample-glimmer-dsl-opal-rails5-app/bin/rails:9:in `' 257 | /Users/User/.rvm/gems/ruby-2.7.1/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `load' 258 | /Users/User/.rvm/gems/ruby-2.7.1/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call' 259 | /Users/User/.rvm/gems/ruby-2.7.1/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call' 260 | /Users/User/.rvm/gems/ruby-2.7.1/gems/spring-2.1.0/lib/spring/client.rb:30:in `run' 261 | /Users/User/.rvm/gems/ruby-2.7.1/gems/spring-2.1.0/bin/spring:49:in `' 262 | /Users/User/.rvm/gems/ruby-2.7.1/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load' 263 | /Users/User/.rvm/gems/ruby-2.7.1/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `' 264 | /Users/User/code/sample-glimmer-dsl-opal-rails5-app/bin/spring:15:in `require' 265 | /Users/User/code/sample-glimmer-dsl-opal-rails5-app/bin/spring:15:in `' 266 | bin/rails:3:in `load' 267 | bin/rails:3:in `
' 268 | ******************************************************************************** 269 | (0.2ms) BEGIN 270 | SQL (0.3ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "development"], ["created_at", 2017-08-24 22:56:52 UTC], ["updated_at", 2017-08-24 22:56:52 UTC]] 271 | (0.3ms) COMMIT 272 | [PD] /Users/User/ordering/order.rb:40 in Order.calculate_order_total 273 | > pd order_summary 274 | => "Pragmatic Ruby Book" 275 | ActiveRecord::InternalMetadata Load (0.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 276 | (0.2ms) BEGIN 277 | (0.2ms) COMMIT 278 | [PD] /Users/User/ordering/order.rb:41 in Order.calculate_order_total 279 | > pd order_details 280 | => "[Hard Cover] Pragmatic Ruby Book - English Version" 281 | ``` 282 | 283 | Is the stack trace too long? Shorten it by passing number of lines to display to `caller` option. 284 | 285 | ```ruby 286 | pd order_total, caller: 3, wrapper: true 287 | pd order_summary 288 | pd order_details 289 | ``` 290 | 291 | Or use shortcut syntax: 292 | 293 | ```ruby 294 | pd order_total, c: 3, w: :t 295 | pd order_summary 296 | pd order_details 297 | ``` 298 | 299 | ``` 300 | (2.7ms) CREATE TABLE "ar_internal_metadata" ("key" character varying PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) 301 | ActiveRecord::InternalMetadata Load (0.4ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 302 | ******************************************************************************** 303 | [PD] /Users/User/ordering/order.rb:39 in Order.calculate_order_total 304 | > pd order_total, caller: 3, wrapper: true 305 | => 195.50 306 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require' 307 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi' 308 | /Users/User/.rvm/gems/ruby-2.7.1/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register' 309 | ******************************************************************************** 310 | (0.2ms) BEGIN 311 | SQL (0.3ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "development"], ["created_at", 2017-08-24 22:56:52 UTC], ["updated_at", 2017-08-24 22:56:52 UTC]] 312 | (0.3ms) COMMIT 313 | [PD] /Users/User/ordering/order.rb:40 in Order.calculate_order_total 314 | > pd order_summary 315 | => "Pragmatic Ruby Book" 316 | ActiveRecord::InternalMetadata Load (0.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", :environment], ["LIMIT", 1]] 317 | (0.2ms) BEGIN 318 | (0.2ms) COMMIT 319 | [PD] /Users/User/ordering/order.rb:41 in Order.calculate_order_total 320 | > pd order_details 321 | => "[Hard Cover] Pragmatic Ruby Book - English Version" 322 | ``` 323 | 324 | There are many more options and features in [puts_debuggerer](https://rubygems.org/gems/puts_debuggerer) as detailed below. 325 | 326 | ## Instructions 327 | 328 | ### Option 1: Bundler 329 | 330 | This is the recommended way for installing in [Rails](rubyonrails.org) apps in addition to configuring the [`app_path` option](#putsdebuggererapp_path). 331 | 332 | Add the following to bundler's `Gemfile` (in Rails, you can optionally limit to the `:development` and `:test` groups). 333 | 334 | ```ruby 335 | gem 'puts_debuggerer', '~> 1.0.1' 336 | ``` 337 | 338 | Run: 339 | 340 | ``` 341 | bundle 342 | ``` 343 | 344 | Optionally, you may configure the [Rails](rubyonrails.org) initializer `config/initializers/puts_debuggerer_options.rb` with further customizations as per the [Options](#options) section below. 345 | 346 | Also, you may want to add the following to the initializer too if you limited the `puts_debuggerer` gem to the `:development` and `:test` groups: 347 | 348 | ```ruby 349 | unless Rails.env.development? || Rails.env.test? 350 | def pd(*args, &block) # `pd(...)` in Ruby 2.7+ 351 | # No Op (just a stub in case developers forget troubleshooting pd calls in the code and deploy to production) 352 | end 353 | end 354 | ``` 355 | 356 | The Rails `config.log_level` is assumed to be `:debug`. If you have it set to something else like `:info`, then you need to update `PutsDebuggerer.printer` to print at a different log level (e.g. `:info`) by adding the following code to the initializer above (this code is a modification of the default at `PutsDebuggerer::PRINTER_RAILS`): 357 | 358 | ```ruby 359 | PutsDebuggerer.printer = lambda do |output| 360 | puts output if Rails.env.test? 361 | Rails.logger.info(output) 362 | end 363 | ``` 364 | 365 | ### Option 2: Manual 366 | 367 | Or manually install and require library. 368 | 369 | ```bash 370 | gem install puts_debuggerer -v1.0.1 371 | ``` 372 | 373 | ```ruby 374 | require 'puts_debuggerer' 375 | ``` 376 | 377 | Or the shorter form (often helpful to quickly troubleshoot an app): 378 | 379 | ```ruby 380 | require 'pd' 381 | ``` 382 | 383 | ### Awesome Print 384 | 385 | [puts_debuggerer](https://rubygems.org/gems/puts_debuggerer) comes with [awesome_print](https://github.com/awesome-print/awesome_print). 386 | 387 | It is the default `PutsDebuggerer.print_engine` 388 | 389 | Still, if you do not need it, you may disable by setting `PutsDebuggerer.print_engine` to another value. Example: 390 | 391 | ```ruby 392 | PutsDebuggerer.print_engine = :puts 393 | ``` 394 | 395 | If you also avoid requiring 'awesome_print', PutsDebuggerer will NOT require it either if it sees that you have a different `print_engine`. In fact, you may switch to another print engine if you prefer like [amazing_print](https://github.com/amazing-print/amazing_print) as [explained here](https://github.com/AndyObtiva/puts_debuggerer#putsdebuggererprint_engine). 396 | 397 | You may also avoid requiring in Bundler `Gemfile` with `require: false`: 398 | 399 | ```ruby 400 | gem "awesome_print", require: false 401 | gem "puts_debuggerer" 402 | ``` 403 | 404 | ### Usage 405 | 406 | First, add `pd` method anywhere in your code to display details about an object or expression (if you're used to awesome_print, you're in luck! puts_debuggerer includes awesome_print (or amazing_print if preferred) as the default print engine for output). 407 | 408 | Example: 409 | 410 | ```ruby 411 | # /Users/User/trivia_app.rb # line 1 412 | require 'pd' # line 2 413 | class TriviaApp # line 3 414 | def question # line 4 415 | bug_or_band = 'Beatles' # line 5 416 | pd bug_or_band # line 6 417 | end # line 7 418 | end # line 8 419 | TriviaApp.new.question # line 9 420 | ``` 421 | 422 | Output: 423 | 424 | ```bash 425 | [PD] /Users/User/trivia_app.rb:6 in TriviaApp.question 426 | > pd bug_or_band # line 6 427 | => "Beatles" 428 | ``` 429 | 430 | In addition to the object/expression output, you get to see the source file name, line number, class name, method name, and source code to help you debug and troubleshoot problems quicker (it even works in IRB). 431 | 432 | You can use `pd` at the top-level main object too, and it prings `Object.
` for the class/method. 433 | 434 | Example: 435 | 436 | ```ruby 437 | # /Users/User/finance_calculator_app/pd_test.rb # line 1 438 | bug = 'Beetle' # line 2 439 | pd "Show me the source of the bug: #{bug}" # line 3 440 | pd "Show me the result of the calculation: #{(12.0/3.0)}" # line 4 441 | ``` 442 | 443 | Output: 444 | 445 | ```bash 446 | [PD] /Users/User/finance_calculator_app/pd_test.rb:3 in Object.
447 | > pd "Show me the source of the bug: #{bug}" 448 | => "Show me the source of the bug: Beetle" 449 | [PD] /Users/User/finance_calculator_app/pd_test.rb:4 in Object.
450 | > pd "Show me the result of the calculation: #{(12.0/3.0)}" 451 | => "Show me the result of the calculation: 4.0" 452 | ``` 453 | 454 | Second, quickly locate printed lines using the Find feature (e.g. CTRL+F) by looking for: 455 | * [PD] 456 | * file:line_number 457 | * class.method 458 | * known ruby expression. 459 | 460 | Third, easily remove your ` pd ` statements via the source code Find feature once done debugging. 461 | 462 | Note that `pd` returns the passed in object or expression argument unchanged, permitting debugging with shorter syntax than tap, and supporting chaining of extra method invocations afterward. 463 | 464 | Example: 465 | 466 | ```ruby 467 | # /Users/User/greeting_app/pd_test.rb # line 1 468 | name = 'Robert' # line 2 469 | greeting = "Hello #{pd(name)}" # line 3 470 | ``` 471 | 472 | Output: 473 | 474 | ```bash 475 | [PD] /Users/User/greeting_app/pd_test.rb:3 in Object.
476 | > greeting = "Hello #{pd(name)}" 477 | => "Hello Robert" 478 | ``` 479 | 480 | Happy puts_debuggerering! 481 | 482 | #### `pd_inspect` kernel method 483 | 484 | You may want to just return the string produced by the `pd` method without printing it. 485 | 486 | In that case, you may use the `pd` alternative to `object.inspect`: 487 | - `object.pd_inspect` 488 | - `obj.pdi` (shorter alias) 489 | 490 | This returns the `pd` formatted string without printing to the terminal or log files. 491 | 492 | #### Ruby Logger and Logging::Logger 493 | 494 | Ruby Logger and Logging::Logger (from [logging gem](https://github.com/TwP/logging)) are supported as [printers](#putsdebuggererprinter) (learn more under [PutsDebuggerer#printer](#putsdebuggererprinter)). 495 | 496 | ### Options 497 | 498 | Options enable more data to be displayed with puts_debuggerer, such as the caller 499 | backtrace, header, and footer. They also allow customization of output format. 500 | 501 | Options can be set as a global configuration or piecemeal per puts statement. 502 | 503 | Global configuration is done via `PutsDebuggerer` module attribute writers. 504 | On the other hand, piecemeal options can be passed to the `pd` global method as 505 | the second argument. 506 | 507 | Example 1: 508 | 509 | ```ruby 510 | # File Name: /Users/User/project/piecemeal.rb 511 | data = [1, [2, 3]] 512 | pd data, header: true 513 | ``` 514 | 515 | Prints out: 516 | 517 | ```bash 518 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 519 | [PD] /Users/User/project/piecemeal.rb:3 in Object.
520 | > pd data, header: true 521 | => [1, [2, 3]] 522 | ``` 523 | 524 | Example 2: 525 | 526 | ```ruby 527 | # File Name: /Users/User/project/piecemeal.rb 528 | data = [1, [2, 3]] 529 | pd data, header: '>'*80, footer: '<'*80, announcer: " -<[PD]>-\n " 530 | ``` 531 | 532 | Prints out: 533 | 534 | ```bash 535 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 536 | -<[PD]>- 537 | /Users/User/project/piecemeal.rb:3 in Object.
538 | > pd data, header: '>'*80, footer: '<'*80, announcer: " -<[PD]>-\n " 539 | => [1, [2, 3]] 540 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 541 | ``` 542 | 543 | Details about all the available options are included below. 544 | 545 | #### `PutsDebuggerer.app_path` 546 | (default = `nil`) 547 | 548 | Sets absolute application path. Makes `pd` file path output relative to it. 549 | 550 | In [Rails](rubyonrails.org), you can add the following code to a `config/initializers/puts_debuggerer_options.rb` file to make all output relative to [Rails](rubyonrails.org) application path: 551 | 552 | ```ruby 553 | PutsDebuggerer.app_path = Rails.root.to_s 554 | ``` 555 | 556 | Example: 557 | 558 | ```ruby 559 | # /Users/User/finance_calculator_app/pd_test.rb # line 1 560 | PutsDebuggerer.app_path = File.join('/Users', 'User', 'finance_calculator_app') # line 2 561 | bug = 'Beetle' # line 3 562 | pd "Show me the source of the bug: #{bug}" # line 4 563 | ``` 564 | 565 | Example Printout: 566 | 567 | ```bash 568 | [PD] /pd_test.rb:4 in Object.
569 | > pd "Show me the source of the bug: #{bug}" 570 | => "Show me the source of the bug: Beetle" 571 | ``` 572 | 573 | #### `PutsDebuggerer.header` 574 | (default = `'>'*80`) [shortcut: `h`] 575 | 576 | Header to include at the top of every print out. 577 | * Default value is `nil` 578 | * Value `true` enables header as `'>'*80` 579 | * Value `false`, `nil`, or empty string disables header 580 | * Any other string value gets set as a custom header 581 | 582 | Example: 583 | 584 | ```ruby 585 | pd (x=1), header: true 586 | ``` 587 | 588 | Prints out: 589 | 590 | ```bash 591 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 592 | [PD] /Users/User/example.rb:1 in Object.
593 | > pd (x=1), header: true 594 | => "1" 595 | ``` 596 | 597 | Shortcut Example: 598 | 599 | ```ruby 600 | pd (x=1), h: :t 601 | ``` 602 | 603 | Prints out: 604 | 605 | ```bash 606 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 607 | [PD] /Users/User/example.rb:1 in Object.
608 | > pd (x=1), h: :t 609 | => "1" 610 | ``` 611 | 612 | Global Option Example: 613 | 614 | ```ruby 615 | PutsDebuggerer.header = true 616 | pd (x=1) 617 | pd (x=2) 618 | ``` 619 | 620 | Prints out: 621 | 622 | ```bash 623 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 624 | [PD] /Users/User/example.rb:2 in Object.
625 | > pd (x=1) 626 | => "1" 627 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 628 | [PD] /Users/User/example.rb:3 in Object.
629 | > pd (x=2) 630 | => "2" 631 | ``` 632 | 633 | #### `PutsDebuggerer.footer` 634 | (default = `'<'*80`) [shortcut: `f`] 635 | 636 | Footer to include at the bottom of every print out. 637 | * Default value is `nil` 638 | * Value `true` enables footer as `'<'*80` 639 | * Value `false`, `nil`, or empty string disables footer 640 | * Any other string value gets set as a custom footer 641 | 642 | Example: 643 | 644 | ```ruby 645 | pd (x=1), footer: true 646 | ``` 647 | 648 | Prints out: 649 | 650 | ```bash 651 | [PD] /Users/User/example.rb:1 in Object.
652 | > pd (x=1), footer: true 653 | => "1" 654 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 655 | ``` 656 | 657 | Shortcut Example: 658 | 659 | ```ruby 660 | pd (x=1), f: :t 661 | ``` 662 | 663 | Prints out: 664 | 665 | ```bash 666 | [PD] /Users/User/example.rb:1 in Object.
667 | > pd (x=1), f: :t 668 | => "1" 669 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 670 | ``` 671 | 672 | Global Option Example: 673 | 674 | ```ruby 675 | PutsDebuggerer.footer = true 676 | pd (x=1) 677 | pd (x=2) 678 | ``` 679 | 680 | Prints out: 681 | 682 | ```bash 683 | [PD] /Users/User/example.rb:2 in Object.
684 | > pd (x=1) 685 | => "1" 686 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 687 | [PD] /Users/User/example.rb:3 in Object.
688 | > pd (x=2) 689 | => "2" 690 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 691 | ``` 692 | 693 | #### `PutsDebuggerer.wrapper` 694 | (default = `'*'*80`) [shortcut: `w`] 695 | 696 | Wrapper to include at the top and bottom of every print out (both header and footer). 697 | * Default value is `nil` 698 | * Value `true` enables wrapper as `'*'*80` 699 | * Value `false`, `nil`, or empty string disables wrapper 700 | * Any other string value gets set as a custom wrapper 701 | 702 | Example: 703 | 704 | ```ruby 705 | pd (x=1), wrapper: true 706 | ``` 707 | 708 | Prints out: 709 | 710 | ```bash 711 | ******************************************************************************** 712 | [PD] /Users/User/example.rb:1 in Object.
713 | > pd x=1, wrapper: true 714 | => "1" 715 | ******************************************************************************** 716 | ``` 717 | 718 | Shortcut Example: 719 | 720 | ```ruby 721 | pd (x=1), w: :t 722 | ``` 723 | 724 | Prints out: 725 | 726 | ```bash 727 | ******************************************************************************** 728 | [PD] /Users/User/example.rb:1 in Object.
729 | > pd x=1, w: :t 730 | => "1" 731 | ******************************************************************************** 732 | ``` 733 | 734 | Global Option Example: 735 | 736 | ```ruby 737 | PutsDebuggerer.wrapper = true 738 | pd (x=1) 739 | pd (x=2) 740 | ``` 741 | 742 | Prints out: 743 | 744 | ```bash 745 | ******************************************************************************** 746 | [PD] /Users/User/example.rb:2 in Object.
747 | > pd (x=1) 748 | => "1" 749 | ******************************************************************************** 750 | ******************************************************************************** 751 | [PD] /Users/User/example.rb:3 in Object.
752 | > pd (x=2) 753 | => "2" 754 | ******************************************************************************** 755 | ``` 756 | 757 | #### `PutsDebuggerer.source_line_count` 758 | (default = `1`) 759 | 760 | Prints multiple source code lines as per count specified. Useful when a statement is broken down on multiple lines or when there is a need to get more context around the line printed. 761 | 762 | Example: 763 | 764 | ```ruby 765 | pd (true || 766 | false), source_line_count: 2 767 | ``` 768 | 769 | Prints out: 770 | 771 | ``` 772 | [PD] /Users/User/example.rb:1 in Object.
773 | > pd (true || 774 | false), source_line_count: 2 775 | => "true" 776 | ``` 777 | 778 | Example: 779 | 780 | ```ruby 781 | PutsDebuggerer.source_line_count = 2 # setting via global option 782 | pd (true || 783 | false) 784 | ``` 785 | 786 | Prints out: 787 | 788 | ``` 789 | [PD] /Users/User/example.rb:2 in Object.
790 | > pd (true || 791 | false), source_line_count: 2 792 | => "true" 793 | ``` 794 | 795 | #### `PutsDebuggerer.printer` 796 | (default = `:puts`) 797 | 798 | Printer is a global method symbol, lambda expression, or logger to use in printing to the user. 799 | 800 | Examples of a global method are `:puts` and `:print`. 801 | An example of a lambda expression is `lambda {|output| Rails.logger.info(output)}` 802 | Examples of a logger are a Ruby `Logger` instance or `Logging::Logger` instance 803 | 804 | When a logger is supplied, it is automatically enhanced with a PutsDebuggerer formatter to use 805 | when calling logger methods outside of PutsDebuggerer (e.g. `logger.error('msg')` will use `pd`) 806 | 807 | Printer may be set to `false` to avoid printing and only return the formatted string. 808 | It is equivalent of just calling `.pd_inspect` (or alias `.pdi`) on the object 809 | 810 | Defaults to `:puts` 811 | In Rails, it defaults to: 812 | ```ruby 813 | lambda do |output| 814 | puts output if Rails.env.test? 815 | Rails.logger.debug(output) 816 | end 817 | ``` 818 | 819 | Example of adding the following code to `config/initializers/puts_debuggerer_options.rb`: 820 | 821 | ```ruby 822 | # File Name: /Users/user/railsapp/config/initializers/puts_debuggerer_options.rb 823 | PutsDebuggerer.printer = lambda do |output| 824 | puts output 825 | end 826 | str = "Hello" 827 | pd str 828 | ``` 829 | 830 | Prints out the following in standard out stream only (not in log files): 831 | 832 | ```bash 833 | [PD] /Users/user/railsapp/config/initializers/puts_debuggerer_options.rb:6 834 | > pd str 835 | => Hello 836 | ``` 837 | 838 | #### `PutsDebuggerer.print_engine` 839 | (default = `:ap`) 840 | 841 | Print engine is similar to `printer`, except it is focused on the scope of formatting 842 | the data object being printed (excluding metadata such as file name, line number, 843 | class name, method name, and expression, which are handled by the `printer`). 844 | As such, it is also a global method symbol or lambda expression. 845 | Examples of global methods are `:p`, `:ap`, and `:pp`. 846 | An example of a lambda expression is `lambda {|object| puts object.to_a.join(" | ")}` 847 | 848 | Defaults to [awesome_print](https://github.com/awesome-print/awesome_print). It does not load the library however until the first use of the `pd` command. 849 | 850 | If you want to avoid loading [awesome_print](https://github.com/awesome-print/awesome_print) to use an alternative instead like [amazing_print](https://github.com/amazing-print/amazing_print), make sure to load [amazing_print](https://github.com/amazing-print/amazing_print) and call `PutsDebuggerer.print_engine = :ap` before the first `pd` call ([amazing_print](https://github.com/amazing-print/amazing_print) works through `ap` just like [awesome_print](https://github.com/awesome-print/awesome_print)). 851 | 852 | Example: 853 | 854 | ```ruby 855 | # File Name: /Users/User/example.rb 856 | PutsDebuggerer.print_engine = :p 857 | array = [1, [2, 3]] 858 | pd array 859 | ``` 860 | 861 | Prints out: 862 | 863 | ```bash 864 | [PD] /Users/User/example.rb:4 in Object.
865 | > pd array 866 | => [1, [2, 3]] 867 | ``` 868 | 869 | #### `PutsDebuggerer.announcer` 870 | (default = `"[PD]"`) [shortcut: `a`] 871 | 872 | Announcer (e.g. `[PD]`) to announce every print out with (default: `"[PD]"`) 873 | 874 | Example: 875 | 876 | ```ruby 877 | PutsDebuggerer.announcer = "*** PD ***\n " 878 | pd (x=1) 879 | ``` 880 | 881 | Prints out: 882 | 883 | ```bash 884 | *** PD *** 885 | /Users/User/example.rb:2 in Object.
886 | > pd x=1 887 | => "1" 888 | ``` 889 | 890 | #### `PutsDebuggerer.formatter` 891 | (default = `PutsDebuggerer::FORMATTER_DEFAULT`) 892 | 893 | Formatter used in every print out 894 | Passed a data argument with the following keys: 895 | * `:announcer` (`String`) 896 | * `:caller` (`Array`) 897 | * `:class` (`String`) 898 | * `:file` (`String`) 899 | * `:footer` (`String`) 900 | * `:header` (`String`) 901 | * `:line_number` (`String`) 902 | * `:method` (`String`) 903 | * `:pd_expression` (`String`) 904 | * `:object` (`Object`) 905 | * `:object_printer` (`Proc`) 906 | 907 | NOTE: data for :object_printer is not a string, yet a proc that must 908 | be called to output value. It is a proc as it automatically handles usage 909 | of print_engine and encapsulates its details. In any case, data for :object 910 | is available should one want to avoid altogether. 911 | 912 | Example: 913 | 914 | ```ruby 915 | PutsDebuggerer.formatter = -> (data) { 916 | puts "-<#{data[:announcer]}>-" 917 | puts "HEADER: #{data[:header]}" 918 | puts "FILE: #{data[:file]}" 919 | puts "LINE: #{data[:line_number]}" 920 | puts "CLASS: #{data[:class]}" 921 | puts "METHOD: #{data[:method]}" 922 | puts "EXPRESSION: #{data[:pd_expression]}" 923 | print "PRINT OUT: " 924 | data[:object_printer].call 925 | puts "CALLER: #{data[:caller].to_a.first}" 926 | puts "FOOTER: #{data[:footer]}" 927 | } 928 | pd (x=1) 929 | ``` 930 | 931 | Prints out: 932 | 933 | ```bash 934 | -<[PD]>- 935 | HEADER: ******************************************************************************** 936 | FILE: /Users/User/example.rb 937 | LINE: 9 938 | CLASS: Example 939 | METHOD: test 940 | EXPRESSION: x=1 941 | PRINT OUT: 1 942 | CALLER: #/Users/User/master_examples.rb:83:in `block (3 levels) in ' 943 | FOOTER: ******************************************************************************** 944 | ``` 945 | 946 | #### `PutsDebuggerer.caller` 947 | (default = nil) [shortcut: `c`] 948 | 949 | Caller backtrace included at the end of every print out 950 | Passed an argument of true/false, nil, or depth as an integer. 951 | * true and -1 means include full caller backtrace 952 | * false and nil means do not include caller backtrace 953 | * depth (0-based) means include limited caller backtrace depth 954 | 955 | Example: 956 | 957 | ```ruby 958 | # File Name: /Users/User/sample_app/lib/sample.rb 959 | class Sample 960 | pd (x=1), caller: 3 961 | end 962 | ``` 963 | 964 | Prints out (fictional): 965 | 966 | ```bash 967 | [PD] /Users/User/sample_app/lib/sample.rb:3 in Sample. 968 | > pd x=1, caller: 3 969 | => 1 970 | /Users/User/sample_app/lib/master_samples.rb:368:in \`block (3 levels) in \' 971 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in \`eval\' 972 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in \`evaluate\' 973 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/context.rb:381:in \`evaluate\' 974 | ``` 975 | 976 | Shortcut Example: 977 | 978 | ```ruby 979 | # File Name: /Users/User/sample_app/lib/sample.rb 980 | class Sample 981 | pd (x=1), c: 3 982 | end 983 | ``` 984 | 985 | Prints out (fictional): 986 | 987 | ```bash 988 | [PD] /Users/User/sample_app/lib/sample.rb:3 in Sample. 989 | > pd x=1, caller: 3 990 | => 1 991 | /Users/User/sample_app/lib/master_samples.rb:368:in \`block (3 levels) in \' 992 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in \`eval\' 993 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in \`evaluate\' 994 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/context.rb:381:in \`evaluate\' 995 | ``` 996 | 997 | Global Option Example: 998 | 999 | ```ruby 1000 | # File Name: /Users/User/sample_app/lib/sample.rb 1001 | PutsDebuggerer.caller = 3 # always print 3 lines only of the stack trace 1002 | class Sample 1003 | class << self 1004 | def test 1005 | pd (x=1) 1006 | pd (x=2) 1007 | end 1008 | end 1009 | end 1010 | Sample.test 1011 | ``` 1012 | 1013 | Prints out: 1014 | 1015 | ```bash 1016 | [PD] /Users/User/sample_app/lib/sample.rb:6 in Sample.test 1017 | > pd (x=1) 1018 | => 1 1019 | /Users/User/sample_app/lib/master_samples.rb:368:in \`block (3 levels) in \' 1020 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in \`eval\' 1021 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in \`evaluate\' 1022 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/context.rb:381:in \`evaluate\' 1023 | [PD] /Users/User/sample_app/lib/sample.rb:7 in Sample.test 1024 | > pd (x=2) 1025 | => 2 1026 | /Users/User/sample_app/lib/master_samples.rb:368:in \`block (3 levels) in \' 1027 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in \`eval\' 1028 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in \`evaluate\' 1029 | /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/context.rb:381:in \`evaluate\' 1030 | ``` 1031 | 1032 | #### `PutsDebuggerer.run_at` 1033 | (default = nil) 1034 | 1035 | Set condition for when to run as specified by an index, array, or range. 1036 | * Default value is `nil` meaning always 1037 | * Value as an Integer index (1-based) specifies at which run to print once 1038 | * Value as an Array of indices specifies at which runs to print multiple times 1039 | * Value as a range specifies at which runs to print multiple times, 1040 | indefinitely if it ends with ..-1 or ...-1 1041 | 1042 | Can be set globally via `PutsDebuggerer.run_at` or piecemeal via `pd object, run_at: run_at_value` 1043 | 1044 | Global usage should be good enough for most cases. When there is a need to track 1045 | a single expression among several, you may add the option piecemeal, but it expects 1046 | the same exact `object` passed to `pd` for counting. 1047 | 1048 | Examples (global): 1049 | 1050 | ```ruby 1051 | PutsDebuggerer.run_at = 1 1052 | pd (x=1) # prints standard PD output 1053 | pd (x=1) # prints nothing 1054 | 1055 | PutsDebuggerer.run_at = 2 1056 | pd (x=1) # prints nothing 1057 | pd (x=1) # prints standard PD output 1058 | 1059 | PutsDebuggerer.run_at = [1, 3] 1060 | pd (x=1) # prints standard PD output 1061 | pd (x=1) # prints nothing 1062 | pd (x=1) # prints standard PD output 1063 | pd (x=1) # prints nothing 1064 | 1065 | PutsDebuggerer.run_at = 3..5 1066 | pd (x=1) # prints nothing 1067 | pd (x=1) # prints nothing 1068 | pd (x=1) # prints standard PD output 1069 | pd (x=1) # prints standard PD output 1070 | pd (x=1) # prints standard PD output 1071 | pd (x=1) # prints nothing 1072 | pd (x=1) # prints nothing 1073 | 1074 | PutsDebuggerer.run_at = 3...6 1075 | pd (x=1) # prints nothing 1076 | pd (x=1) # prints nothing 1077 | pd (x=1) # prints standard PD output 1078 | pd (x=1) # prints standard PD output 1079 | pd (x=1) # prints standard PD output 1080 | pd (x=1) # prints nothing 1081 | 1082 | PutsDebuggerer.run_at = 3..-1 1083 | pd (x=1) # prints nothing 1084 | pd (x=1) # prints nothing 1085 | pd (x=1) # prints standard PD output 1086 | pd (x=1) # ... continue printing indefinitely on all subsequent runs 1087 | 1088 | PutsDebuggerer.run_at = 3...-1 1089 | pd (x=1) # prints nothing 1090 | pd (x=1) # prints nothing 1091 | pd (x=1) # prints standard PD output 1092 | pd (x=1) # ... continue printing indefinitely on all subsequent runs 1093 | ``` 1094 | 1095 | You may reset the run_at number counter via: 1096 | `PutsDebuggerer.reset_run_at_global_number` for global usage. 1097 | 1098 | And: 1099 | `PutsDebuggerer.reset_run_at_number` or 1100 | `PutsDebuggerer.reset_run_at_numbers` 1101 | for piecemeal usage. 1102 | 1103 | ### Bonus API 1104 | 1105 | puts_debuggerer comes with the following bonus API methods: 1106 | 1107 | #### `__caller_line_number__(caller_depth=0)` 1108 | 1109 | Provides caller line number starting 1 level above caller of this method (with default `caller_depth=0`). 1110 | 1111 | Example: 1112 | 1113 | ```ruby 1114 | # File Name: lib/example.rb # line 1 1115 | # Print out __caller_line_number__ # line 2 1116 | puts __caller_line_number__ # line 3 1117 | ``` 1118 | 1119 | Prints out `3` 1120 | 1121 | 1122 | #### `__caller_file__(caller_depth=0)` 1123 | 1124 | Provides caller file starting 1 level above caller of this method (with default `caller_depth=0`). 1125 | 1126 | Example: 1127 | 1128 | ```ruby 1129 | # File Name: lib/example.rb 1130 | puts __caller_file__ 1131 | ``` 1132 | 1133 | Prints out `lib/example.rb` 1134 | 1135 | #### `__caller_source_line__(caller_depth=0)` 1136 | 1137 | Provides caller source line starting 1 level above caller of this method (with default `caller_depth=0`). 1138 | 1139 | Example: 1140 | 1141 | ```ruby 1142 | puts __caller_source_line__ 1143 | ``` 1144 | 1145 | Prints out `puts __caller_source_line__` 1146 | 1147 | ## Compatibility 1148 | 1149 | [puts_debuggerer](https://rubygems.org/gems/puts_debuggerer) is fully compatible with: 1150 | - [Ruby](https://www.ruby-lang.org/en/) 1151 | - [JRuby](https://www.jruby.org/) 1152 | - IRB (including Rails Console) 1153 | - Pry (experimental and fragile because Pry's API is not reliable) 1154 | 1155 | ### Opal Ruby 1156 | 1157 | [puts_debuggerer](https://rubygems.org/gems/puts_debuggerer) provides partial-compatibility in [Opal Ruby](https://opalrb.com/) with everything working except: 1158 | - AwesomePrint (using the `:p` printer instead) 1159 | - Source code display 1160 | 1161 | [puts_debuggerer](https://rubygems.org/gems/puts_debuggerer) renders clickable source file/line links in Opal Ruby that take you to the source code in the web browser. 1162 | 1163 | Here is an example of `pd` output in Opal: 1164 | 1165 | ``` 1166 | [PD] http://localhost:3000/assets/views/garderie_rainbow_daily_agenda/app_view.self-72626d75e0f68a619b1c8ad139535d799d45ab6c730d083820b790d71338e983.js?body=1:72:12 1167 | > 1168 | => "body" 1169 | ``` 1170 | 1171 | Note that it ignores the configured printer when printing exceptions as it relies on Opal's `$stderr.puts` instead to show the stack trace in the web console. 1172 | 1173 | ## Change Log 1174 | 1175 | [CHANGELOG.md](CHANGELOG.md) 1176 | 1177 | ## TODO 1178 | 1179 | [TODO.md](TODO) 1180 | 1181 | ## Contributing 1182 | 1183 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet. 1184 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it. 1185 | * Fork the project. 1186 | * Change directory into project 1187 | * Run `gem install bundler && bundle && rake` and make sure RSpec tests are passing 1188 | * Start a feature/bugfix branch. 1189 | * Write RSpec tests, Code, Commit and push until you are happy with your contribution. 1190 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 1191 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 1192 | 1193 | ## Copyright 1194 | 1195 | [MIT](LICENSE.txt) 1196 | 1197 | Copyright (c) 2017-2024 - Andy Maleh. 1198 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options 17 | gem.name = "puts_debuggerer" 18 | gem.homepage = "http://github.com/AndyObtiva/puts_debuggerer" 19 | gem.license = "MIT" 20 | gem.summary = %Q{Ruby library for improved puts debugging, automatically displaying bonus useful information such as source file name, line number, class name, method name, and source code.} 21 | gem.description = <<-MULTI 22 | Debuggers are great! They help us troubleshoot complicated programming problems by inspecting values produced by code, line by line. They are invaluable when trying to understand what is going on in a large application composed of thousands or millions of lines of code. 23 | In day-to-day test-driven development and simple debugging though, a puts statement can be a lot quicker in revealing what is going on than halting execution completely just to inspect a single value or a few. This is certainly true when writing the simplest possible code that could possibly work, and running a test every few seconds or minutes. Problem is you need to locate puts statements in large output logs, know which file names, line numbers, classes, and methods contained the puts statements, find out what variable names are being printed, and see nicely formatted output. Enter puts_debuggerer. A guilt-free puts debugging Ruby gem FTW that prints file names, line numbers, class names, method names, and code statements; and formats output nicely courtesy of awesome_print. 24 | Partially inspired by this blog post: https://tenderlovemaking.com/2016/02/05/i-am-a-puts-debuggerer.html (Credit to Tenderlove.) 25 | MULTI 26 | gem.email = "andy.am@gmail.com" 27 | gem.authors = ["Andy Maleh"] 28 | gem.files = Dir['VERSION', 'LICENSE.txt', 'README.md', 'lib/**/*.rb'] 29 | # dependencies defined in Gemfile 30 | end 31 | Jeweler::RubygemsDotOrgTasks.new 32 | 33 | require 'rspec/core' 34 | require 'rspec/core/rake_task' 35 | RSpec::Core::RakeTask.new(:spec) do |spec| 36 | spec.pattern = FileList['spec/**/*_spec.rb'] 37 | end 38 | 39 | desc "Code coverage detail" 40 | task :simplecov do 41 | ENV['COVERAGE'] = "true" 42 | Rake::Task['spec'].execute 43 | end 44 | 45 | task :default => :spec 46 | 47 | require 'rdoc/task' 48 | Rake::RDocTask.new do |rdoc| 49 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 50 | 51 | rdoc.rdoc_dir = 'rdoc' 52 | rdoc.title = "puts_debuggerer #{version}" 53 | rdoc.rdoc_files.include('README*') 54 | rdoc.rdoc_files.include('lib/**/*.rb') 55 | end 56 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | Here are tasks considered for future versions. Once done, they are moved to the [Change Log](CHANGELOG.md) 4 | 5 | ## Next 6 | 7 | - When using last arg as hash for options, leave out options that are not puts_debuggerer-specific for printing out 8 | - Support displaying the date/time of the pd printout via an option (local and global) 9 | - Display class#method for instance methods and class::method for class methods 10 | - Consider supporting `header: :method` to print the Class#method name as the header (consider supporting in footer/wrapper too) 11 | - Consider adding performance profiling to pd methods automatically, with some customization options too 12 | - Fix issue with no printing code in bigger rails apps filled with other gems (perhaps there is some conflict?) 13 | - Support passing options directly without an object (e.g. pd caller: true) 14 | - Add the ability to disable all pd statement with a switch for convenience when going back and forth with printing/not-printing (e.g. troubleshooting while testing performance) 15 | - Consider supporting `lines` and `line_count` as aliases for the `source_line_count` option 16 | - Have `caller` printing in Glimmer DSL for Opal print one statement per line 17 | - Support header: 30 to customize the length of the header (and do the same for footer and wrapper) 18 | - Support header: '#' to customize the character of the header (and do the same for footer and wrapper) 19 | - :h and :f alternatives for header and footer (as well as other ideas to shorten) 20 | - Support `methods: true` to print unique methods not on Object or `methods: :all`, automatically sorted 21 | - Consider making header use >>> and footer <<< instead of * for better findability. 22 | - Provide option to set a logger as printer without hooking formatter unto logger 23 | - Provide an option to control what log level is used when printing to logger 24 | - Look into [Maintainability](https://codeclimate.com/github/AndyObtiva/puts_debuggerer/issues) issues 25 | - Special treatment for string objects since AwesomePrint seems does not print multiline 26 | - Add a hyperlink to file showing up (linking to GitHub repo using something like tty-markdown) 27 | 28 | ## Version TBD 29 | 30 | - Add method_name to what shows up as part of pd call 31 | - Add performance monitoring support 32 | - Support auto puts_debuggering of all method names and arguments upon invokation 33 | - Refactor internals to avoid global method pollution 34 | - fix issue with printing in rspec inside a Rails project without having to do extra configuration 35 | - fix issue with erb support 36 | - Consider the idea of customizing print stream (e.g. stderr instead of stdout). Currently possible through setting `printer` 37 | - Highlight the pd being printed if multiple pds exist in the same line (perhaps by calling .red on its string reusing that from ap) 38 | - Have pd support running from JAR files in JRuby 39 | - Fix issue with using SimpleDelegator, which seems to break puts_debuggerer output for the class that inherits from it 40 | - Enable easier setting of the `pd` log level for a Rails application than requiring a `PutsDebuggerer.printer` lambda by offering the option `PutsDebuggerer.log_level`. 41 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.1 2 | -------------------------------------------------------------------------------- /lib/pd.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.expand_path(__dir__)) 2 | 3 | require 'puts_debuggerer' 4 | -------------------------------------------------------------------------------- /lib/puts_debuggerer.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.expand_path(__dir__)) unless $LOAD_PATH.include?(File.expand_path(__dir__)) 2 | 3 | require 'puts_debuggerer/core_ext/kernel' 4 | require 'puts_debuggerer/core_ext/logger' 5 | require 'puts_debuggerer/core_ext/logging/logger' 6 | require 'puts_debuggerer/run_determiner' 7 | require 'puts_debuggerer/source_file' 8 | 9 | module PutsDebuggerer 10 | SOURCE_LINE_COUNT_DEFAULT = 1 11 | HEADER_DEFAULT = '>'*80 12 | WRAPPER_DEFAULT = '*'*80 13 | FOOTER_DEFAULT = '<'*80 14 | LOGGER_FORMATTER_DECORATOR = proc { |original_formatter| 15 | proc { |severity, datetime, progname, msg| 16 | original_formatter.call(severity, datetime, progname, msg.pd_inspect) 17 | } 18 | } 19 | LOGGING_LAYOUT_DECORATOR = proc {|original_layout| 20 | original_layout.clone.tap do |layout| 21 | layout.singleton_class.class_eval do 22 | alias original_format_obj format_obj 23 | def format_obj(obj) 24 | obj.pdi # alias to pd_inspect 25 | end 26 | end 27 | end 28 | } 29 | RETURN_DEFAULT = true 30 | OBJECT_PRINTER_DEFAULT = lambda do |object, print_engine_options=nil, source_line_count=nil, run_number=nil| 31 | lambda do 32 | if object.is_a?(Exception) 33 | if RUBY_ENGINE == 'opal' 34 | object.backtrace.each { |line| puts line } 35 | else 36 | puts object.full_message 37 | end 38 | elsif PutsDebuggerer.print_engine.is_a?(Proc) 39 | PutsDebuggerer.print_engine.call(object) 40 | else 41 | send(PutsDebuggerer.print_engine, object) 42 | end 43 | end 44 | end 45 | PRINTER_DEFAULT = :puts 46 | PRINTER_RAILS = lambda do |output| 47 | puts output if Rails.env.test? 48 | Rails.logger.debug(output) 49 | end 50 | PRINT_ENGINE_DEFAULT = :ap 51 | PRINTER_MESSAGE_INVALID = 'printer must be a valid global method symbol (e.g. :puts), a logger, or a lambda/proc receiving a text arg' 52 | PRINT_ENGINE_MESSAGE_INVALID = 'print_engine must be a valid global method symbol (e.g. :p, :ap or :pp) or lambda/proc receiving an object arg' 53 | ANNOUNCER_DEFAULT = '[PD]' 54 | FORMATTER_DEFAULT = -> (data) { 55 | puts data[:wrapper] if data[:wrapper] 56 | puts data[:header] if data[:header] 57 | print "#{data[:announcer]} #{data[:file]}#{':' if data[:line_number]}#{data[:line_number]} in #{[data[:class], data[:method]].compact.join('.')}#{" (run:#{data[:run_number]})" if data[:run_number]}#{__format_pd_expression__(data[:pd_expression], data[:object])} " 58 | data[:object_printer].call 59 | puts data[:caller].map {|l| ' ' + l} unless data[:caller].to_a.empty? 60 | puts data[:footer] if data[:footer] 61 | puts data[:wrapper] if data[:wrapper] 62 | } 63 | CALLER_DEPTH_ZERO = 4 #depth includes pd + with_options method + nested block + build_pd_data method 64 | CALLER_DEPTH_ZERO_OPAL = -1 #depth includes pd + with_options method + nested block + build_pd_data method 65 | STACK_TRACE_CALL_LINE_NUMBER_REGEX = /\:(\d+)\:in / 66 | STACK_TRACE_CALL_SOURCE_FILE_REGEX = /[ ]*([^:]+)\:\d+\:in / 67 | STACK_TRACE_CALL_SOURCE_FILE_REGEX_OPAL = /(http[^\)]+)/ 68 | STACK_TRACE_CALL_METHOD_REGEX = /`([^']+)'$/ 69 | OPTIONS = [:app_path, :source_line_count, :header, :h, :wrapper, :w, :footer, :f, :printer, :print_engine, :announcer, :formatter, :caller, :run_at] 70 | OPTION_ALIASES = { 71 | a: :announcer, 72 | c: :caller, 73 | h: :header, 74 | f: :footer, 75 | w: :wrapper, 76 | } 77 | 78 | class << self 79 | # Application root path to exclude when printing out file path 80 | # 81 | # Example: 82 | # 83 | # # File Name: /Users/User/sample_app/lib/sample.rb 84 | # PutsDebuggerer.app_path = '/Users/User/sample_app' 85 | # pd (x=1) 86 | # 87 | # Prints out: 88 | # 89 | # [PD] lib/sample.rb:3 90 | # > pd x=1 91 | # => "1" 92 | attr_reader :app_path 93 | 94 | def app_path=(path) 95 | @app_path = (path || Rails.root.to_s) rescue nil 96 | end 97 | 98 | # Source Line Count. 99 | # * Default value is `1` 100 | # 101 | # Example: 102 | # 103 | # PutsDebuggerer.source_line_count = 2 104 | # pd (true || 105 | # false), source_line_count: 2 106 | # 107 | # Prints out: 108 | # 109 | # ******************************************************************************** 110 | # [PD] /Users/User/example.rb:2 111 | # > pd (true || 112 | # false), source_line_count: 2 113 | # => "true" 114 | attr_reader :source_line_count 115 | 116 | def source_line_count=(value) 117 | @source_line_count = value || SOURCE_LINE_COUNT_DEFAULT 118 | end 119 | 120 | # Header to include at the top of every print out. 121 | # * Default value is `nil` 122 | # * Value `true` enables header as `'*'*80` 123 | # * Value `false`, `nil`, or empty string disables header 124 | # * Any other string value gets set as a custom header 125 | # 126 | # Example: 127 | # 128 | # PutsDebuggerer.header = true 129 | # pd (x=1) 130 | # 131 | # Prints out: 132 | # 133 | # ******************************************************************************** 134 | # [PD] /Users/User/example.rb:2 135 | # > pd x=1 136 | # => "1" 137 | attr_reader :header 138 | 139 | # Wrapper to include at the top and bottom of every print out (both header and footer). 140 | # * Default value is `nil` 141 | # * Value `true` enables wrapper as `'*'*80` 142 | # * Value `false`, `nil`, or empty string disables wrapper 143 | # * Any other string value gets set as a custom wrapper 144 | # 145 | # Example: 146 | # 147 | # PutsDebuggerer.wrapper = true 148 | # pd (x=1) 149 | # 150 | # Prints out: 151 | # 152 | # [PD] /Users/User/example.rb:2 153 | # > pd x=1 154 | # => "1" 155 | # ******************************************************************************** 156 | attr_reader :wrapper 157 | 158 | # Footer to include at the bottom of every print out. 159 | # * Default value is `nil` 160 | # * Value `true` enables footer as `'*'*80` 161 | # * Value `false`, `nil`, or empty string disables footer 162 | # * Any other string value gets set as a custom footer 163 | # 164 | # Example: 165 | # 166 | # PutsDebuggerer.footer = true 167 | # pd (x=1) 168 | # 169 | # Prints out: 170 | # 171 | # [PD] /Users/User/example.rb:2 172 | # > pd x=1 173 | # => "1" 174 | # ******************************************************************************** 175 | attr_reader :footer 176 | 177 | ['header', 'footer', 'wrapper'].each do |boundary_option| 178 | define_method("#{boundary_option}=") do |value| 179 | if value.equal?(true) 180 | instance_variable_set(:"@#{boundary_option}", const_get(:"#{boundary_option.upcase}_DEFAULT")) 181 | elsif value == '' 182 | instance_variable_set(:"@#{boundary_option}", nil) 183 | else 184 | instance_variable_set(:"@#{boundary_option}", value) 185 | end 186 | end 187 | 188 | define_method("#{boundary_option}?") do 189 | !!instance_variable_get(:"@#{boundary_option}") 190 | end 191 | end 192 | 193 | # Printer is a global method symbol, lambda expression, or logger to use in printing to the user. 194 | # Examples of a global method are `:puts` and `:print`. 195 | # An example of a lambda expression is `lambda {|output| Rails.logger.ap(output)}` 196 | # Examples of a logger are a Ruby `Logger` instance or `Logging::Logger` instance 197 | # 198 | # Defaults to `:puts` 199 | # In Rails, it defaults to: `lambda {|output| Rails.logger.ap(output)}` 200 | # 201 | # Example: 202 | # 203 | # # File Name: /Users/User/example.rb 204 | # PutsDebuggerer.printer = lambda {|output| Rails.logger.error(output)} 205 | # str = "Hello" 206 | # pd str 207 | # 208 | # Prints out in the Rails app log as error lines: 209 | # 210 | # [PD] /Users/User/example.rb:5 211 | # > pd str 212 | # => Hello 213 | attr_reader :printer 214 | 215 | def printer=(printer) 216 | if printer.nil? 217 | @printer = printer_default 218 | elsif printer.is_a?(Logger) 219 | @printer = printer 220 | @logger_original_formatter = printer.formatter || Logger::Formatter.new 221 | printer.formatter = LOGGER_FORMATTER_DECORATOR.call(@logger_original_formatter) 222 | elsif printer.is_a?(Logging::Logger) 223 | @printer = printer 224 | @logging_original_layouts = printer.appenders.reduce({}) do |hash, appender| 225 | hash.merge(appender => appender.layout) 226 | end 227 | printer.appenders.each do |appender| 228 | appender.layout = LOGGING_LAYOUT_DECORATOR.call(appender.layout) 229 | end 230 | elsif printer == false || printer.is_a?(Proc) || printer.respond_to?(:log) # a logger 231 | @printer = printer 232 | else 233 | @printer = method(printer).name rescue raise(PRINTER_MESSAGE_INVALID) 234 | end 235 | end 236 | 237 | def printer_default 238 | Object.const_defined?(:Rails) ? PRINTER_RAILS : PRINTER_DEFAULT 239 | end 240 | 241 | # Logger original formatter before it was decorated with PutsDebuggerer::LOGGER_FORMATTER_DECORATOR 242 | # upon setting the logger as a printer. 243 | attr_reader :logger_original_formatter 244 | 245 | # Logging library original layouts before being decorated with PutsDebuggerer::LOGGING_LAYOUT_DECORATOR 246 | # upon setting the Logging library logger as a printer. 247 | attr_reader :logging_original_layouts 248 | 249 | # Print engine is similar to `printer`, except it is focused on the scope of formatting 250 | # the data object being printed (excluding metadata such as file name, line number, 251 | # and expression, which are handled by the `printer`). 252 | # As such, it is also a global method symbol or lambda expression. 253 | # Examples of global methods are `:p`, `:ap`, and `:pp`. 254 | # An example of a lambda expression is `lambda {|object| puts object.to_a.join(" | ")}` 255 | # 256 | # Defaults to [awesome_print](https://github.com/awesome-print/awesome_print). 257 | # 258 | # Example: 259 | # 260 | # # File Name: /Users/User/example.rb 261 | # require 'awesome_print' 262 | # PutsDebuggerer.print_engine = :p 263 | # array = [1, [2, 3]] 264 | # pd array 265 | # 266 | # Prints out: 267 | # 268 | # [PD] /Users/User/example.rb:5 269 | # > pd array 270 | # => [1, [2, 3]] 271 | # ] 272 | def print_engine 273 | if @print_engine.nil? 274 | require 'awesome_print' if RUBY_ENGINE != 'opal' 275 | @print_engine = print_engine_default 276 | end 277 | @print_engine 278 | end 279 | 280 | def print_engine=(engine) 281 | if engine.is_a?(Proc) || engine.nil? 282 | @print_engine = engine 283 | else 284 | @print_engine = method(engine).name rescue raise(PRINT_ENGINE_MESSAGE_INVALID) 285 | end 286 | end 287 | 288 | def print_engine_default 289 | Object.const_defined?(:AwesomePrint) ? PRINT_ENGINE_DEFAULT : :p 290 | end 291 | 292 | # Announcer (e.g. [PD]) to announce every print out with (default: "[PD]") 293 | # 294 | # Example: 295 | # 296 | # PutsDebuggerer.announcer = "*** PD ***\n " 297 | # pd (x=1) 298 | # 299 | # Prints out: 300 | # 301 | # *** PD *** 302 | # /Users/User/example.rb:2 303 | # > pd x=1 304 | # => 1 305 | attr_reader :announcer 306 | 307 | def announcer=(text) 308 | @announcer = text.nil? ? ANNOUNCER_DEFAULT : text 309 | end 310 | 311 | # Formatter used in every print out 312 | # Passed a data argument with the following keys: 313 | # * :announcer (string) 314 | # * :caller (array) 315 | # * :file (string) 316 | # * :wrapper (string) 317 | # * :footer (string) 318 | # * :header (string) 319 | # * :line_number (string) 320 | # * :pd_expression (string) 321 | # * :object (object) 322 | # * :object_printer (proc) 323 | # * :source_line_count (integer) 324 | # 325 | # NOTE: data for :object_printer is not a string, yet a proc that must 326 | # be called to output value. It is a proc as it automatically handles usage 327 | # of print_engine and encapsulates its details. In any case, data for :object 328 | # is available should one want to avoid altogether. 329 | # 330 | # Example: 331 | # 332 | # PutsDebuggerer.formatter = -> (data) { 333 | # puts "-<#{data[:announcer]}>-" 334 | # puts "HEADER: #{data[:header]}" 335 | # puts "FILE: #{data[:file]}" 336 | # puts "LINE: #{data[:line_number]}" 337 | # puts "EXPRESSION: #{data[:pd_expression]}" 338 | # print "PRINT OUT: " 339 | # data[:object_printer].call 340 | # puts "CALLER: #{data[:caller].to_a.first}" 341 | # puts "FOOTER: #{data[:footer]}" 342 | # } 343 | # pd (x=1) 344 | # 345 | # Prints out: 346 | # 347 | # -<[PD]>- 348 | # FILE: /Users/User/example.rb 349 | # HEADER: ******************************************************************************** 350 | # LINE: 9 351 | # EXPRESSION: x=1 352 | # PRINT OUT: 1 353 | # CALLER: #/Users/User/master_examples.rb:83:in `block (3 levels) in ' 354 | # FOOTER: ******************************************************************************** 355 | attr_reader :formatter 356 | 357 | def formatter=(formatter_proc) 358 | @formatter = formatter_proc.nil? ? FORMATTER_DEFAULT : formatter_proc 359 | end 360 | 361 | # Caller backtrace included at the end of every print out 362 | # Passed an argument of true/false, nil, or depth as an integer. 363 | # * true and -1 means include full caller backtrace 364 | # * false and nil means do not include caller backtrace 365 | # * depth (0-based) means include limited caller backtrace depth 366 | # 367 | # Example: 368 | # 369 | # # File Name: /Users/User/sample_app/lib/sample.rb 370 | # PutsDebuggerer.caller = 3 371 | # pd (x=1) 372 | # 373 | # Prints out: 374 | # 375 | # [PD] /Users/User/sample_app/lib/sample.rb:3 376 | # > pd x=1 377 | # => "1" 378 | # /Users/User/sample_app/lib/master_samples.rb:368:in `block (3 levels) in ' 379 | # /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in `eval' 380 | # /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/workspace.rb:87:in `evaluate' 381 | # /Users/User/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/irb/context.rb:381:in `evaluate' 382 | attr_reader :caller 383 | 384 | def caller=(value) 385 | if value.equal?(true) 386 | @caller = -1 #needed for upper bound in pd method 387 | else 388 | @caller = value 389 | end 390 | end 391 | 392 | def caller? 393 | !!caller 394 | end 395 | 396 | 397 | # Options as a hash. Useful for reading and backing up options 398 | def options 399 | { 400 | header: header, 401 | wrapper: wrapper, 402 | footer: footer, 403 | printer: printer, 404 | print_engine: print_engine, 405 | source_line_count: source_line_count, 406 | app_path: app_path, 407 | announcer: announcer, 408 | formatter: formatter, 409 | caller: caller, 410 | run_at: run_at 411 | } 412 | end 413 | 414 | # Sets options included in hash 415 | def options=(hash) 416 | hash.each do |option, value| 417 | send("#{option}=", value) 418 | end 419 | end 420 | 421 | # When to run as specified by an index, array, or range. 422 | # * Default value is `nil` meaning always 423 | # * Value as an Integer index (1-based) specifies at which run to print once 424 | # * Value as an Array of indices specifies at which runs to print multiple times 425 | # * Value as a range specifies at which runs to print multiple times, 426 | # indefinitely if it ends with ..-1 427 | # 428 | # Example: 429 | # 430 | # PutsDebuggerer.run_at = 1 431 | # pd (x=1) # prints standard PD output 432 | # pd (x=1) # prints nothing 433 | # 434 | # PutsDebuggerer.run_at = 2 435 | # pd (x=1) # prints nothing 436 | # pd (x=1) # prints standard PD output 437 | # 438 | # PutsDebuggerer.run_at = [1, 3] 439 | # pd (x=1) # prints standard PD output 440 | # pd (x=1) # prints nothing 441 | # pd (x=1) # prints standard PD output 442 | # pd (x=1) # prints nothing 443 | # 444 | # PutsDebuggerer.run_at = 3..5 445 | # pd (x=1) # prints nothing 446 | # pd (x=1) # prints nothing 447 | # pd (x=1) # prints standard PD output 448 | # pd (x=1) # prints standard PD output 449 | # pd (x=1) # prints standard PD output 450 | # pd (x=1) # prints nothing 451 | # pd (x=1) # prints nothing 452 | # 453 | # PutsDebuggerer.run_at = 3...6 454 | # pd (x=1) # prints nothing 455 | # pd (x=1) # prints nothing 456 | # pd (x=1) # prints standard PD output 457 | # pd (x=1) # prints standard PD output 458 | # pd (x=1) # prints standard PD output 459 | # pd (x=1) # prints nothing 460 | # 461 | # PutsDebuggerer.run_at = 3..-1 462 | # pd (x=1) # prints nothing 463 | # pd (x=1) # prints nothing 464 | # pd (x=1) # prints standard PD output 465 | # pd (x=1) ... continue printing indefinitely on all subsequent runs 466 | # 467 | # PutsDebuggerer.run_at = 3...-1 468 | # pd (x=1) # prints nothing 469 | # pd (x=1) # prints nothing 470 | # pd (x=1) # prints standard PD output 471 | # pd (x=1) ... continue printing indefinitely on all subsequent runs 472 | attr_reader :run_at 473 | 474 | def run_at=(value) 475 | @run_at = value 476 | end 477 | 478 | def run_at? 479 | !!@run_at 480 | end 481 | 482 | def determine_options(objects) 483 | if objects.size > 1 && objects.last.is_a?(Hash) 484 | convert_options(objects.delete_at(-1)) 485 | elsif objects.size == 1 && objects.first.is_a?(Hash) 486 | hash = objects.first 487 | convert_options(hash.slice(*OPTIONS)) 488 | end 489 | end 490 | 491 | def convert_options(hash) 492 | Hash[hash.map { |key, value| OPTION_ALIASES[key] ? ( value == :t ? [OPTION_ALIASES[key], true] : [OPTION_ALIASES[key], value] ) : [key, value]}] 493 | end 494 | 495 | def determine_object(objects) 496 | objects.compact.size > 1 ? objects : objects.first 497 | end 498 | 499 | def determine_run_at(options) 500 | ((options && options[:run_at]) || PutsDebuggerer.run_at) 501 | end 502 | 503 | def determine_printer(options) 504 | if options && options.has_key?(:printer) 505 | options[:printer] 506 | else 507 | PutsDebuggerer.printer 508 | end 509 | end 510 | end 511 | end 512 | 513 | # setting values to nil defaults them properly 514 | PutsDebuggerer.printer = nil 515 | PutsDebuggerer.print_engine = nil 516 | PutsDebuggerer.announcer = nil 517 | PutsDebuggerer.formatter = nil 518 | PutsDebuggerer.app_path = nil 519 | PutsDebuggerer.caller = nil 520 | PutsDebuggerer.run_at = nil 521 | PutsDebuggerer.source_line_count = nil 522 | -------------------------------------------------------------------------------- /lib/puts_debuggerer/core_ext/kernel.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | module Kernel 4 | # Prints object with bonus info such as file name, line number and source 5 | # expression. Optionally prints out header and footer. 6 | # Lookup PutsDebuggerer attributes for more details about configuration options. 7 | # 8 | # Simply invoke global `pd` method anywhere you'd like to see line number and source code with output. 9 | # If the argument is a pure string, the print out is simplified by not showing duplicate source. 10 | # 11 | # Quickly locate printed lines using Find feature (e.g. CTRL+F) by looking for: 12 | # * \[PD\] 13 | # * file:line_number 14 | # * ruby expression. 15 | # 16 | # This gives you the added benefit of easily removing your pd statements later on from the code. 17 | # 18 | # Happy puts_debuggerering! 19 | # 20 | # Example Code: 21 | # 22 | # # /Users/User/finance_calculator_app/pd_test.rb # line 1 23 | # bug = 'beattle' # line 2 24 | # pd "Show me the source of the bug: #{bug}" # line 3 25 | # pd 'What line number am I?' # line 4 26 | # 27 | # Example Printout: 28 | # 29 | # [PD] /Users/User/finance_calculator_app/pd_test.rb:3 30 | # > pd "Show me the source of the bug: #{bug}" 31 | # => "Show me the source of the bug: beattle" 32 | # [PD] /Users/User/finance_calculator_app/pd_test.rb:4 "What line number am I?" 33 | def pd(*objects) 34 | options = PutsDebuggerer.determine_options(objects) || {} 35 | object = PutsDebuggerer.determine_object(objects) 36 | run_at = PutsDebuggerer.determine_run_at(options) 37 | printer = PutsDebuggerer.determine_printer(options) 38 | pd_inspect = options.delete(:pd_inspect) 39 | logger_formatter_decorated = PutsDebuggerer.printer.is_a?(Logger) && PutsDebuggerer.printer.formatter != PutsDebuggerer.logger_original_formatter 40 | logging_layouts_decorated = PutsDebuggerer.printer.is_a?(Logging::Logger) && PutsDebuggerer.printer.appenders.map(&:layout) != (PutsDebuggerer.logging_original_layouts.values) 41 | 42 | string = nil 43 | if PutsDebuggerer::RunDeterminer.run_pd?(object, run_at) 44 | __with_pd_options__(options) do |print_engine_options| 45 | run_number = PutsDebuggerer::RunDeterminer.run_number(object, run_at) 46 | formatter_pd_data = __build_pd_data__(object, print_engine_options: print_engine_options, source_line_count: PutsDebuggerer.source_line_count, run_number: run_number, pd_inspect: pd_inspect, logger_formatter_decorated: logger_formatter_decorated, logging_layouts_decorated: logging_layouts_decorated) 47 | stdout = $stdout 48 | $stdout = sio = StringIO.new 49 | PutsDebuggerer.formatter.call(formatter_pd_data) 50 | $stdout = stdout 51 | string = sio.string 52 | if RUBY_ENGINE == 'opal' && object.is_a?(Exception) 53 | $stderr.puts(string) 54 | else 55 | if PutsDebuggerer.printer.is_a?(Proc) 56 | PutsDebuggerer.printer.call(string) 57 | elsif PutsDebuggerer.printer.is_a?(Logger) 58 | logger_formatter = PutsDebuggerer.printer.formatter 59 | begin 60 | PutsDebuggerer.printer.formatter = PutsDebuggerer.logger_original_formatter 61 | PutsDebuggerer.printer.debug(string) 62 | ensure 63 | PutsDebuggerer.printer.formatter = logger_formatter 64 | end 65 | elsif PutsDebuggerer.printer.is_a?(Logging::Logger) 66 | logging_layouts = PutsDebuggerer.printer.appenders.reduce({}) do |hash, appender| 67 | hash.merge(appender => appender.layout) 68 | end 69 | begin 70 | PutsDebuggerer.logging_original_layouts.each do |appender, original_layout| 71 | appender.layout = original_layout 72 | end 73 | PutsDebuggerer.printer.debug(string) 74 | ensure 75 | PutsDebuggerer.logging_original_layouts.each do |appender, original_layout| 76 | appender.layout = logging_layouts[appender] 77 | end 78 | end 79 | elsif PutsDebuggerer.printer != false 80 | send(PutsDebuggerer.send(:printer), string) 81 | end 82 | end 83 | end 84 | end 85 | 86 | printer ? object : string 87 | end 88 | 89 | # Implement caller backtrace method in Opal since it returns an empty array in Opal v1 90 | if RUBY_ENGINE == 'opal' 91 | def caller(*args) 92 | dup_args = args.dup 93 | start = args.shift if args.first.is_a?(Integer) 94 | length = args.shift if args.first.is_a?(Integer) 95 | range = args.shift if args.first.is_a?(Range) 96 | if range 97 | start = range.begin 98 | length = range.end - start 99 | end 100 | begin 101 | raise 'error' 102 | rescue => e 103 | the_backtrace = e.backtrace 104 | start ||= 0 105 | start = 2 + start 106 | length ||= the_backtrace.size - start 107 | the_backtrace[start, length] 108 | end 109 | end 110 | end 111 | 112 | def pd_inspect 113 | pd self, printer: false, pd_inspect: true 114 | end 115 | alias pdi pd_inspect 116 | 117 | # Provides caller line number starting 1 level above caller of 118 | # this method. 119 | # 120 | # Example: 121 | # 122 | # # lib/example.rb # line 1 123 | # puts "Print out __caller_line_number__" # line 2 124 | # puts __caller_line_number__ # line 3 125 | # 126 | # prints out `3` 127 | def __caller_line_number__(caller_depth=0) 128 | return if RUBY_ENGINE == 'opal' 129 | caller[caller_depth] && caller[caller_depth][PutsDebuggerer::STACK_TRACE_CALL_LINE_NUMBER_REGEX, 1].to_i 130 | end 131 | 132 | # Provides caller file starting 1 level above caller of 133 | # this method. 134 | # 135 | # Example: 136 | # 137 | # # File Name: lib/example.rb 138 | # puts __caller_file__ 139 | # 140 | # prints out `lib/example.rb` 141 | def __caller_file__(caller_depth=0) 142 | regex = RUBY_ENGINE == 'opal' ? PutsDebuggerer::STACK_TRACE_CALL_SOURCE_FILE_REGEX_OPAL : PutsDebuggerer::STACK_TRACE_CALL_SOURCE_FILE_REGEX 143 | caller[caller_depth] && caller[caller_depth][regex, 1] 144 | end 145 | 146 | # Provides caller method starting 1 level above caller of 147 | # this method. 148 | def __caller_method__(caller_depth=0) 149 | regex = PutsDebuggerer::STACK_TRACE_CALL_METHOD_REGEX 150 | caller[caller_depth] && caller[caller_depth][regex, 1] 151 | end 152 | 153 | # Provides caller source line starting 1 level above caller of 154 | # this method. 155 | # 156 | # Example: 157 | # 158 | # puts __caller_source_line__ 159 | # 160 | # prints out `puts __caller_source_line__` 161 | def __caller_source_line__(caller_depth=0, source_line_count=nil, source_file=nil, source_line_number=nil) 162 | source_line_number ||= __caller_line_number__(caller_depth+1) 163 | source_file ||= __caller_file__(caller_depth+1) 164 | source_line = '' 165 | if defined?(Pry) && source_file.include?('(pry)') 166 | @pry_instance ||= Pry.new 167 | source_line = Pry::Command::Hist.new(pry_instance: @pry_instance).call.instance_variable_get(:@buffer).split("\n")[source_line_number - 1] # TODO handle multi-lines in source_line_count 168 | elsif defined?(IRB) && TOPLEVEL_BINDING.receiver.respond_to?(:conf) 169 | source_line = TOPLEVEL_BINDING.receiver.conf.io.line(source_line_number) # TODO handle multi-lines in source_line_count 170 | else 171 | source_line = PutsDebuggerer::SourceFile.new(source_file).source(source_line_count, source_line_number) 172 | end 173 | source_line 174 | end 175 | 176 | private 177 | 178 | def __with_pd_options__(options=nil) 179 | options ||= {} 180 | permanent_options = PutsDebuggerer.options 181 | PutsDebuggerer.options = options.select {|option, _| PutsDebuggerer.options.keys.include?(option)} 182 | print_engine_options = options.delete_if {|option, _| PutsDebuggerer.options.keys.include?(option)} 183 | yield print_engine_options 184 | PutsDebuggerer.options = permanent_options 185 | end 186 | 187 | def __build_pd_data__(object, print_engine_options:nil, source_line_count:nil, run_number:nil, pd_inspect:false, logger_formatter_decorated:false, logging_layouts_decorated:false) 188 | depth = RUBY_ENGINE == 'opal' ? PutsDebuggerer::CALLER_DEPTH_ZERO_OPAL : PutsDebuggerer::CALLER_DEPTH_ZERO 189 | if pd_inspect 190 | depth += 1 191 | depth += 4 if logger_formatter_decorated 192 | depth += 8 if logging_layouts_decorated 193 | end 194 | 195 | pd_data = { 196 | announcer: PutsDebuggerer.announcer, 197 | file: __caller_file__(depth)&.sub(PutsDebuggerer.app_path.to_s, ''), 198 | class: self.is_a?(Module) ? self : self.class, 199 | method: __caller_method__(depth)&.sub(PutsDebuggerer.app_path.to_s, ''), 200 | line_number: __caller_line_number__(depth), 201 | pd_expression: __caller_pd_expression__(depth, source_line_count), 202 | run_number: run_number, 203 | object: object, 204 | object_printer: PutsDebuggerer::OBJECT_PRINTER_DEFAULT.call(object, print_engine_options, source_line_count, run_number) 205 | } 206 | pd_data[:caller] = __caller_caller__(depth) 207 | ['header', 'wrapper', 'footer'].each do |boundary_option| 208 | pd_data[boundary_option.to_sym] = PutsDebuggerer.send(boundary_option) if PutsDebuggerer.send("#{boundary_option}?") 209 | end 210 | pd_data 211 | end 212 | 213 | # Returns the caller stack trace of the caller of pd 214 | def __caller_caller__(depth) 215 | return unless PutsDebuggerer.caller? 216 | start_depth = depth.to_i + 1 217 | caller_depth = PutsDebuggerer.caller == -1 ? -1 : (start_depth + PutsDebuggerer.caller) 218 | caller[start_depth..caller_depth].to_a 219 | end 220 | 221 | def __format_pd_expression__(expression, object) 222 | "\n > #{expression}\n =>" 223 | end 224 | 225 | def __caller_pd_expression__(depth=0, source_line_count=nil) 226 | # Caller Source Line Depth 2 = 1 to pd method + 1 to caller 227 | source_line = __caller_source_line__(depth+1, source_line_count) 228 | source_line = __extract_pd_expression__(source_line) 229 | source_line = source_line.gsub(/(^'|'$)/, '"') if source_line.start_with?("'") && source_line.end_with?("'") 230 | source_line = source_line.gsub(/(^\(|\)$)/, '') if source_line.start_with?("(") && source_line.end_with?(")") 231 | source_line 232 | end 233 | 234 | # Extracts pd source line expression. 235 | # 236 | # Example: 237 | # 238 | # __extract_pd_expression__("pd (x=1)") 239 | # 240 | # outputs `(x=1)` 241 | def __extract_pd_expression__(source_line) 242 | source_line.to_s.strip 243 | end 244 | end 245 | -------------------------------------------------------------------------------- /lib/puts_debuggerer/core_ext/logger.rb: -------------------------------------------------------------------------------- 1 | # in case 'logger' is not required 2 | class Logger 3 | end 4 | -------------------------------------------------------------------------------- /lib/puts_debuggerer/core_ext/logging/logger.rb: -------------------------------------------------------------------------------- 1 | # in case 'logging' is not required 2 | module Logging 3 | class Logger 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/puts_debuggerer/run_determiner.rb: -------------------------------------------------------------------------------- 1 | module PutsDebuggerer 2 | module RunDeterminer 3 | OBJECT_RUN_AT = {} 4 | 5 | class << self 6 | attr_reader :run_at_global_number 7 | 8 | def run_at_global_number=(value) 9 | @run_at_global_number = value 10 | end 11 | 12 | def init_run_at_global_number 13 | @run_at_global_number = 1 14 | end 15 | 16 | def increment_run_at_global_number 17 | @run_at_global_number += 1 18 | end 19 | 20 | def reset_run_at_global_number 21 | @run_at_global_number = nil 22 | end 23 | 24 | def run_at_number(object, run_at) 25 | OBJECT_RUN_AT[[object,run_at]] 26 | end 27 | 28 | def init_run_at_number(object, run_at) 29 | OBJECT_RUN_AT[[object,run_at]] = 1 30 | end 31 | 32 | def increment_run_at_number(object, run_at) 33 | OBJECT_RUN_AT[[object,run_at]] += 1 34 | end 35 | 36 | def reset_run_at_number(object, run_at) 37 | OBJECT_RUN_AT.delete([object, run_at]) 38 | end 39 | 40 | def reset_run_at_numbers 41 | OBJECT_RUN_AT.clear 42 | end 43 | 44 | def run_number(object, run_at) 45 | run_at_global_number || run_at_number(object, run_at) 46 | end 47 | 48 | def run_pd?(object, run_at) 49 | run_pd = false 50 | if run_at.nil? 51 | run_pd = true 52 | else 53 | run_number = determine_run_number(object, run_at) 54 | run_pd = determine_run_pd(run_at, run_number) 55 | end 56 | run_pd 57 | end 58 | 59 | def determine_run_number(object, run_at) 60 | if PutsDebuggerer.run_at? # check if global option is set 61 | determine_global_run_number 62 | else 63 | determine_local_run_number(object, run_at) 64 | end 65 | end 66 | 67 | def determine_global_run_number 68 | if PutsDebuggerer::RunDeterminer.run_at_global_number.nil? 69 | PutsDebuggerer::RunDeterminer.init_run_at_global_number 70 | else 71 | PutsDebuggerer::RunDeterminer.increment_run_at_global_number 72 | end 73 | PutsDebuggerer::RunDeterminer.run_at_global_number 74 | end 75 | 76 | def determine_local_run_number(object, run_at) 77 | if PutsDebuggerer::RunDeterminer.run_at_number(object, run_at).nil? 78 | PutsDebuggerer::RunDeterminer.init_run_at_number(object, run_at) 79 | else 80 | PutsDebuggerer::RunDeterminer.increment_run_at_number(object, run_at) 81 | end 82 | PutsDebuggerer::RunDeterminer.run_at_number(object, run_at) 83 | end 84 | 85 | def determine_run_pd(run_at, run_number) 86 | if run_at.is_a?(Integer) 87 | determine_run_pd_integer(run_at, run_number) 88 | elsif run_at.is_a?(Array) 89 | determine_run_pd_array(run_at, run_number) 90 | elsif run_at.is_a?(Range) 91 | determine_run_pd_range(run_at, run_number) 92 | end 93 | end 94 | 95 | def determine_run_pd_integer(run_at, run_number) 96 | run_pd = true if run_at == run_number 97 | end 98 | 99 | def determine_run_pd_array(run_at, run_number) 100 | run_pd = true if run_at.include?(run_number) 101 | end 102 | 103 | def determine_run_pd_range(run_at, run_number) 104 | run_pd = true if run_at.cover?(run_number) || (run_at.end == -1 && run_number >= run_at.begin) 105 | end 106 | 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/puts_debuggerer/source_file.rb: -------------------------------------------------------------------------------- 1 | module PutsDebuggerer 2 | class SourceFile 3 | def initialize(file_path) 4 | @file = File.new(file_path) if file_path && File.exist?(file_path) 5 | end 6 | 7 | def source(source_line_count, source_line_number) 8 | @source = '' 9 | return @source if RUBY_ENGINE == 'opal' 10 | # For Opal Ruby compatibility, skip source lines if file does not respond to readline (as in Opal) 11 | lines = source_lines(source_line_count, source_line_number) 12 | @source = lines.join(' '*5) if @file.respond_to?(:readline) 13 | @source 14 | end 15 | 16 | def source_lines(source_line_count, source_line_number) 17 | lines = [] 18 | begin 19 | while @file && @file.lineno < source_line_number + source_line_count 20 | file_line_number = @file.lineno + 1 21 | file_line = @file.readline 22 | if file_line_number >= source_line_number && file_line_number < source_line_number + source_line_count 23 | lines << file_line 24 | end 25 | end 26 | rescue EOFError 27 | # Done 28 | end 29 | lines 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /puts_debuggerer.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | # stub: puts_debuggerer 1.0.1 ruby lib 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "puts_debuggerer".freeze 9 | s.version = "1.0.1".freeze 10 | 11 | s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= 12 | s.require_paths = ["lib".freeze] 13 | s.authors = ["Andy Maleh".freeze] 14 | s.date = "2024-06-28" 15 | s.description = "Debuggers are great! They help us troubleshoot complicated programming problems by inspecting values produced by code, line by line. They are invaluable when trying to understand what is going on in a large application composed of thousands or millions of lines of code.\nIn day-to-day test-driven development and simple debugging though, a puts statement can be a lot quicker in revealing what is going on than halting execution completely just to inspect a single value or a few. This is certainly true when writing the simplest possible code that could possibly work, and running a test every few seconds or minutes. Problem is you need to locate puts statements in large output logs, know which file names, line numbers, classes, and methods contained the puts statements, find out what variable names are being printed, and see nicely formatted output. Enter puts_debuggerer. A guilt-free puts debugging Ruby gem FTW that prints file names, line numbers, class names, method names, and code statements; and formats output nicely courtesy of awesome_print.\nPartially inspired by this blog post: https://tenderlovemaking.com/2016/02/05/i-am-a-puts-debuggerer.html (Credit to Tenderlove.)\n".freeze 16 | s.email = "andy.am@gmail.com".freeze 17 | s.extra_rdoc_files = [ 18 | "CHANGELOG.md", 19 | "LICENSE.txt", 20 | "README.md" 21 | ] 22 | s.files = [ 23 | "LICENSE.txt", 24 | "README.md", 25 | "VERSION", 26 | "lib/pd.rb", 27 | "lib/puts_debuggerer.rb", 28 | "lib/puts_debuggerer/core_ext/kernel.rb", 29 | "lib/puts_debuggerer/core_ext/logger.rb", 30 | "lib/puts_debuggerer/core_ext/logging/logger.rb", 31 | "lib/puts_debuggerer/run_determiner.rb", 32 | "lib/puts_debuggerer/source_file.rb" 33 | ] 34 | s.homepage = "http://github.com/AndyObtiva/puts_debuggerer".freeze 35 | s.licenses = ["MIT".freeze] 36 | s.rubygems_version = "3.5.3".freeze 37 | s.summary = "Ruby library for improved puts debugging, automatically displaying bonus useful information such as source file name, line number, class name, method name, and source code.".freeze 38 | 39 | s.specification_version = 4 40 | 41 | s.add_runtime_dependency(%q.freeze, ["~> 1.9.2".freeze]) 42 | s.add_development_dependency(%q.freeze, ["~> 3.5".freeze]) 43 | s.add_development_dependency(%q.freeze, ["~> 3.5".freeze]) 44 | s.add_development_dependency(%q.freeze, ["~> 3.12".freeze]) 45 | s.add_development_dependency(%q.freeze, ["~> 2.3.9".freeze]) 46 | s.add_development_dependency(%q.freeze, [">= 2.1.4".freeze]) 47 | s.add_development_dependency(%q.freeze, [">= 2.3.0".freeze]) 48 | s.add_development_dependency(%q.freeze, [">= 0".freeze]) 49 | end 50 | 51 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer/core_ext/kernel__opal__spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Kernel' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | before :all do 7 | @ruby_engine = RUBY_ENGINE 8 | end 9 | 10 | after :all do 11 | RUBY_ENGINE = @ruby_engine 12 | load 'puts_debuggerer/core_ext/kernel.rb' 13 | end 14 | 15 | describe 'caller' do 16 | it 'returns backtrace' do 17 | original_caller = caller 18 | RUBY_ENGINE = 'opal' 19 | load 'puts_debuggerer/core_ext/kernel.rb' 20 | expect(caller).to match_array(original_caller) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__exception_cases__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | before do 7 | $stdout = StringIO.new 8 | PutsDebuggerer.print_engine = :p 9 | end 10 | 11 | context 'exception cases' do 12 | it 'handles multi line ruby expressions correctly' do 13 | name = 'Robert' 14 | PutsDebuggererInvoker.multi_line_dynamic_greeting(name) 15 | output = $stdout.string 16 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:30 in PutsDebuggererInvoker.multi_line_dynamic_greeting\n > pd \"Hello \" +\n => \"Hello Robert\"\n") 17 | end 18 | 19 | it 'handles multi line ruby expressions correctly printing source line count of 2' do 20 | name = 'Robert' 21 | PutsDebuggererInvoker.multi_line_dynamic_greeting_source_line_count(name) 22 | output = $stdout.string 23 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:43 in PutsDebuggererInvoker.multi_line_dynamic_greeting_source_line_count\n > pd \"Hello \" +\n name.to_s, source_line_count: 2\n => \"Hello Robert\"\n") 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_app_path_provided__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | context 'with app path provided' do 7 | before do 8 | PutsDebuggerer.app_path = File.expand_path(File.join(__FILE__, '..', '..', '..')) 9 | end 10 | after do 11 | PutsDebuggerer.app_path = nil 12 | end 13 | it 'prints file relative to app path, line number, ruby expression, and evaluated string object' do 14 | name = 'Robert' 15 | PutsDebuggererInvoker.dynamic_greeting(name) 16 | output = $stdout.string 17 | expect(output).to eq("[PD] /spec/support/puts_debuggerer_invoker.rb:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_caller_backtrace__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | context 'with caller backtrace' do 7 | before do 8 | PutsDebuggerer.caller = true 9 | end 10 | after do 11 | PutsDebuggerer.caller = nil # defaults to false 12 | end 13 | it 'includes full caller backtrace when printing file, line number, ruby expression, and evaluated string object' do 14 | name = 'Robert' 15 | PutsDebuggererInvoker.dynamic_greeting(name) 16 | output = $stdout.string 17 | expected_caller = (["#{__FILE__}:#{__LINE__-2}:in `block (3 levels) in '"] + caller).map {|l| ' '*5 + l} 18 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n#{expected_caller.join("\n")}\n") 19 | end 20 | it 'includes depth-limited caller backtrace when printing file, line number, ruby expression, and evaluated string object' do 21 | PutsDebuggerer.caller = 0 # just give me one backtrace entry 22 | name = 'Robert' 23 | PutsDebuggererInvoker.dynamic_greeting(name) 24 | output = $stdout.string 25 | expected_caller = [" #{__FILE__}:#{__LINE__-2}:in `block (3 levels) in '"] 26 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n#{expected_caller.join("\n")}\n") 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_custom_announcer__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | context 'with custom announcer' do 7 | let(:custom_announcer) {''} 8 | before do 9 | PutsDebuggerer.announcer = custom_announcer 10 | end 11 | after do 12 | PutsDebuggerer.announcer = nil 13 | end 14 | it 'changes [PD] to ' do 15 | name = 'Robert' 16 | PutsDebuggererInvoker.dynamic_greeting(name) 17 | output = $stdout.string 18 | expect(output).to eq(" #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 19 | end 20 | it 'resets to default announcer when announcer is set to nil' do 21 | PutsDebuggerer.announcer = nil 22 | name = 'Robert' 23 | PutsDebuggererInvoker.dynamic_greeting(name) 24 | output = $stdout.string 25 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_custom_formatter__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | context 'with custom formatter' do 6 | before do 7 | PutsDebuggerer.header = true # keep the default 8 | PutsDebuggerer.footer = true # keep the default 9 | PutsDebuggerer.caller = 0 10 | PutsDebuggerer.formatter = -> (data) { 11 | puts "-<#{data[:announcer]}>-" 12 | puts "HEADER: #{data[:header]}" 13 | puts "FILE: #{data[:file]}" 14 | puts "LINE: #{data[:line_number]}" 15 | puts "EXPRESSION: #{data[:pd_expression]}" 16 | print "PRINT OUT: " 17 | data[:object_printer].call 18 | puts "CALLER: #{data[:caller].to_a.first}" 19 | puts "FOOTER: #{data[:footer]}" 20 | } 21 | end 22 | it 'prints custom format' do 23 | name = 'Robert' 24 | PutsDebuggererInvoker.dynamic_greeting(name) 25 | output = $stdout.string 26 | expected_output = <<-MULTI 27 | -<[PD]>- 28 | HEADER: #{'>'*80} 29 | FILE: #{puts_debuggerer_invoker_file} 30 | LINE: 10 31 | EXPRESSION: pd "Hello \#{name}" 32 | PRINT OUT: "Hello Robert" 33 | CALLER: #{__FILE__}:#{__LINE__-9}:in `block (3 levels) in ' 34 | FOOTER: #{'<'*80} 35 | MULTI 36 | expect(output).to eq(expected_output) 37 | end 38 | it 'resets format' do 39 | PutsDebuggerer.formatter = nil 40 | name = 'Robert' 41 | PutsDebuggererInvoker.dynamic_greeting(name) 42 | output = $stdout.string 43 | expected_caller = " #{__FILE__}:#{__LINE__-2}:in `block (3 levels) in '" 44 | expect(output).to eq("#{PutsDebuggerer::HEADER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n#{expected_caller}\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 45 | end 46 | #TODO support formatting header, footer, and caller backtrace too 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_custom_print_engine__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | context 'with custom print engine' do 7 | let(:expected_object_printout) { 8 | "[\n [0] 1,\n [1] [\n [0] 2,\n [1] 3\n ]\n]" 9 | } 10 | let(:expected_object_printout_indent2) { 11 | "[\n [0] 1,\n [1] [\n [0] 2,\n [1] 3\n ]\n]" 12 | } 13 | before do 14 | load "awesome_print/core_ext/kernel.rb" 15 | @awesome_print_defaults = AwesomePrint.defaults 16 | AwesomePrint.defaults = { 17 | plain: true 18 | } 19 | Kernel.class_eval do 20 | def print_meh(object) 21 | puts "Meh! #{object}" 22 | end 23 | end 24 | PutsDebuggerer.print_engine = nil #auto detect awesome_print 25 | end 26 | after do 27 | AwesomePrint.defaults = @awesome_print_defaults 28 | Kernel.send(:remove_method, :print_meh) rescue nil 29 | Kernel.send(:remove_method, :ap) rescue nil 30 | end 31 | it 'prints using passed in custom lambda print engine' do 32 | PutsDebuggerer.print_engine = lambda {|text| puts "**\"#{text}\"**"} #intentionally set as :p 33 | name = 'Robert' 34 | PutsDebuggererInvoker.dynamic_greeting(name) 35 | output = $stdout.string 36 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => **\"Hello Robert\"**\n") 37 | end 38 | it 'prints file relative to app path, line number, ruby expression, and evaluated string object' do 39 | PutsDebuggererInvoker.static_nested_array 40 | output = $stdout.string 41 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:22 in PutsDebuggererInvoker.static_nested_array\n > pd [1, [2, 3]]\n => #{expected_object_printout}\n") 42 | end 43 | it 'prints file relative to app path, line number, ruby expression, and evaluated string object' do 44 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 45 | output = $stdout.string 46 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => #{expected_object_printout}\n") 47 | end 48 | it 'raises informative error if print_engine was invalid' do 49 | expect {PutsDebuggerer.print_engine = :invalid}.to raise_error('print_engine must be a valid global method symbol (e.g. :p, :ap or :pp) or lambda/proc receiving an object arg') 50 | end 51 | it 'supports passing extra options to print_engines like awesome_print' do 52 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], indent: 2) 53 | output = $stdout.string 54 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => #{expected_object_printout_indent2}\n") 55 | end 56 | it 'ignores extra options with print_engines not supporting them' do 57 | PutsDebuggerer.print_engine = :print_meh 58 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], indent: 2) 59 | output = $stdout.string 60 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => Meh! [1, [2, 3]]\n") 61 | end 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_custom_printer__spec.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 3 | 4 | describe 'PutsDebuggerer' do 5 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 6 | 7 | before do 8 | now = Time.new(2000, 1, 1, 1, 1, 1, 1) 9 | Time.stub(:now).and_return(now) 10 | load "awesome_print/core_ext/kernel.rb" 11 | @awesome_print_defaults = AwesomePrint.defaults 12 | AwesomePrint.defaults = { 13 | plain: true 14 | } 15 | PutsDebuggerer.print_engine = :ap 16 | end 17 | 18 | context 'with custom printer' do 19 | let(:expected_object_printout) { 20 | "[\n [0] 1,\n [1] [\n [0] 2,\n [1] 3\n ]\n]" 21 | } 22 | 23 | let(:expected_object_printout_awesome_print) { 24 | "[\\n [0] 1,\\n [1] [\\n [0] 2,\\n [1] 3\\n ]\\n]" 25 | } 26 | 27 | before do 28 | Object.send(:remove_const, :Rails) rescue nil 29 | PutsDebuggerer.app_path = nil 30 | PutsDebuggerer.printer = nil 31 | end 32 | 33 | after do 34 | Object.send(:remove_const, :Rails) rescue nil 35 | end 36 | 37 | it 'prints using passed in custom lambda printer' do 38 | PutsDebuggerer.printer = lambda {|text| puts "\n#{text}\n"} #intentionally set as :p 39 | name = 'Robert' 40 | PutsDebuggererInvoker.dynamic_greeting(name) 41 | output = $stdout.string 42 | expect(output).to eq("\n[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n\n") 43 | end 44 | 45 | it 'prints with default :puts printer (file relative to app path, line number, ruby expression, and evaluated string object)' do 46 | expect(PutsDebuggerer.printer).to eq(:puts) 47 | PutsDebuggererInvoker.static_nested_array 48 | output = $stdout.string 49 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:22 in PutsDebuggererInvoker.static_nested_array\n > pd [1, [2, 3]]\n => #{expected_object_printout}\n") 50 | end 51 | 52 | it 'prints with specified :print printer (file relative to app path, line number, ruby expression, and evaluated string object)' do 53 | PutsDebuggerer.printer = :ap 54 | expect(PutsDebuggerer.printer).to eq(:ap) 55 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 56 | output = $stdout.string 57 | expect(output).to eq("\"[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\\n > pd *array_including_options\\n => #{expected_object_printout_awesome_print}\\n\"\n") 58 | end 59 | 60 | it 'prints with specified logger object (file relative to app path, line number, ruby expression, and evaluated string object)' do 61 | logger = Logger.new($stdout) 62 | PutsDebuggerer.printer = logger 63 | expect(PutsDebuggerer.printer).to eq(logger) 64 | PutsDebuggererInvoker.static_nested_array 65 | output = $stdout.string 66 | expect(output).to eq("D, [2000-01-01T01:01:01.000000 ##{Process.pid}] DEBUG -- : [PD] #{puts_debuggerer_invoker_file}:22 in PutsDebuggererInvoker.static_nested_array\n > pd [1, [2, 3]]\n => #{expected_object_printout}\n\n") 67 | 68 | $stdout = StringIO.new 69 | logger = Logger.new($stdout) 70 | PutsDebuggerer.printer = logger 71 | PutsDebuggererInvoker.logger_log logger, 'error', [1, [2, 3]] 72 | output = $stdout.string 73 | expect(output).to eq("E, [2000-01-01T01:01:01.000000 ##{Process.pid}] ERROR -- : [PD] #{puts_debuggerer_invoker_file}:64 in Array.logger_log\n > logger.send(severity, *args)\n => #{expected_object_printout}\n\n") 74 | end 75 | 76 | it 'prints with specified logging logger object (file relative to app path, line number, ruby expression, and evaluated string object)' do 77 | logger = Logging.logger['test'] 78 | logger.add_appenders(Logging.appenders.string_io(StringIO.new)) 79 | logger.level = 0 80 | PutsDebuggerer.printer = logger 81 | expect(PutsDebuggerer.printer).to eq(logger) 82 | PutsDebuggererInvoker.static_nested_array 83 | expected_output1 = "DEBUG test : [PD] #{puts_debuggerer_invoker_file}:22 in PutsDebuggererInvoker.static_nested_array\n > pd [1, [2, 3]]\n => #{expected_object_printout}\n\n" 84 | 85 | PutsDebuggererInvoker.logger_log logger, 'error', [1, [2, 3]] 86 | output = logger.appenders.first.sio.string 87 | expected_output2 = "ERROR test : [PD] #{puts_debuggerer_invoker_file}:64 in Array.logger_log\n > logger.send(severity, *args)\n => #{expected_object_printout}\n\n" 88 | expect(output).to eq("#{expected_output1}#{expected_output2}") 89 | end 90 | 91 | it 'does not print with printer globally as false, but returns rendered string instead of object' do 92 | PutsDebuggerer.printer = false 93 | expect(PutsDebuggerer.printer).to eq(false) 94 | return_value = PutsDebuggererInvoker.static_nested_array 95 | output = $stdout.string 96 | expect(output).to eq('') 97 | expect(return_value).to eq("[PD] #{puts_debuggerer_invoker_file}:22 in PutsDebuggererInvoker.static_nested_array\n > pd [1, [2, 3]]\n => #{expected_object_printout}\n") 98 | end 99 | 100 | it 'does not print with printer as false, but returns rendered string instead of object' do 101 | return_value = PutsDebuggererInvoker.call_pd [1, [2, 3]], printer: false 102 | output = $stdout.string 103 | expect(output).to eq('') 104 | expect(return_value).to eq("[PD] #{puts_debuggerer_invoker_file}:60 in PutsDebuggererInvoker.call_pd\n > pd *args\n => #{expected_object_printout}\n") 105 | end 106 | 107 | it 'raises informative error if print_engine was invalid' do 108 | expect {PutsDebuggerer.printer = :invalid}.to raise_error('printer must be a valid global method symbol (e.g. :puts), a logger, or a lambda/proc receiving a text arg') 109 | end 110 | 111 | it 'prints using Rails non-test env lambda printer' do 112 | Object.class_eval do 113 | module Rails 114 | class Logger 115 | def debug(object) 116 | # simulate a Rails logger. Adding extra prefix to ease testing. 117 | puts "Rails.logger.debug: #{object}" 118 | end 119 | end 120 | def self.root 121 | File.expand_path(File.join(__FILE__, '..', '..', '..')) 122 | end 123 | def self.logger 124 | @logger ||= Logger.new 125 | end 126 | def self.env 127 | OpenStruct.new(:test? => false) 128 | end 129 | end 130 | end 131 | PutsDebuggerer.app_path = nil #defaults to Rails logger debug printing 132 | PutsDebuggerer.printer = nil #defaults to Rails logger debug printing 133 | name = 'Robert' 134 | PutsDebuggererInvoker.dynamic_greeting(name) 135 | output = $stdout.string 136 | expect(output).to eq("Rails.logger.debug: [PD] /spec/support/puts_debuggerer_invoker.rb:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 137 | end 138 | 139 | it 'prints using Rails test env lambda printer' do 140 | Object.class_eval do 141 | module Rails 142 | class Logger 143 | def debug(object) 144 | # simulate a Rails logger. Adding extra prefix to ease testing. 145 | puts "Rails.logger.debug: #{object}" 146 | end 147 | end 148 | def self.root 149 | File.expand_path(File.join(__FILE__, '..', '..', '..')) 150 | end 151 | def self.logger 152 | @logger ||= Logger.new 153 | end 154 | def self.env 155 | OpenStruct.new(:test? => true) 156 | end 157 | end 158 | end 159 | PutsDebuggerer.app_path = nil #defaults to Rails logger debug printing 160 | PutsDebuggerer.printer = nil #defaults to Rails logger debug printing 161 | name = 'Robert' 162 | PutsDebuggererInvoker.dynamic_greeting(name) 163 | output = $stdout.string 164 | expect(output).to eq( 165 | "[PD] /spec/support/puts_debuggerer_invoker.rb:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n" + 166 | "Rails.logger.debug: [PD] /spec/support/puts_debuggerer_invoker.rb:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n" 167 | ) 168 | end 169 | end 170 | 171 | end 172 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_footer_support_enabled__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | context 'with footer support enabled' do 6 | context 'as default footer' do 7 | before do 8 | PutsDebuggerer.footer = true 9 | end 10 | it 'prints asterisk footer 80 times by default before dynamic PD print out' do 11 | name = 'Robert' 12 | PutsDebuggererInvoker.dynamic_greeting(name) 13 | output = $stdout.string 14 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 15 | end 16 | it 'disables footer with nil footer' do 17 | PutsDebuggerer.footer = nil 18 | name = 'Robert' 19 | PutsDebuggererInvoker.dynamic_greeting(name) 20 | output = $stdout.string 21 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 22 | end 23 | it 'disables footer with false footer' do 24 | PutsDebuggerer.footer = false 25 | name = 'Robert' 26 | PutsDebuggererInvoker.dynamic_greeting(name) 27 | output = $stdout.string 28 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 29 | end 30 | it 'disables footer with empty string footer' do 31 | PutsDebuggerer.footer = '' 32 | name = 'Robert' 33 | PutsDebuggererInvoker.dynamic_greeting(name) 34 | output = $stdout.string 35 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 36 | end 37 | end 38 | context 'as custom footer' do 39 | let(:custom_footer) {'>>>PRINT OUT<<<'} 40 | before do 41 | PutsDebuggerer.footer = custom_footer 42 | end 43 | it 'prints asterisk footer 80 times by default before dynamic PD print out' do 44 | name = 'Robert' 45 | PutsDebuggererInvoker.dynamic_greeting(name) 46 | output = $stdout.string 47 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n#{custom_footer}\n") 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_header_support_enabled__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | context 'with header support enabled' do 6 | context 'as default header' do 7 | before do 8 | PutsDebuggerer.header = true 9 | end 10 | it 'prints asterisk header 80 times by default before dynamic PD print out' do 11 | name = 'Robert' 12 | PutsDebuggererInvoker.dynamic_greeting(name) 13 | output = $stdout.string 14 | expect(output).to eq("#{PutsDebuggerer::HEADER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 15 | end 16 | it 'disables header with nil header' do 17 | PutsDebuggerer.header = nil 18 | name = 'Robert' 19 | PutsDebuggererInvoker.dynamic_greeting(name) 20 | output = $stdout.string 21 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 22 | end 23 | it 'disables header with false header' do 24 | PutsDebuggerer.header = false 25 | name = 'Robert' 26 | PutsDebuggererInvoker.dynamic_greeting(name) 27 | output = $stdout.string 28 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 29 | end 30 | it 'disables header with empty string header' do 31 | PutsDebuggerer.header = '' 32 | name = 'Robert' 33 | PutsDebuggererInvoker.dynamic_greeting(name) 34 | output = $stdout.string 35 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 36 | end 37 | end 38 | context 'as custom header' do 39 | let(:custom_header) {'>>>PRINT OUT<<<'} 40 | before do 41 | PutsDebuggerer.header = custom_header 42 | end 43 | it 'prints asterisk header 80 times by default before dynamic PD print out' do 44 | name = 'Robert' 45 | PutsDebuggererInvoker.dynamic_greeting(name) 46 | output = $stdout.string 47 | expect(output).to eq("#{custom_header}\n[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_irb_support__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | context 'irb support' do 7 | after do 8 | Object.send(:remove_const, :IRB) 9 | end 10 | it 'works' do 11 | # senseless faking to get irb support to work (tested in IRB as working) 12 | allow_any_instance_of(Kernel).to receive(:caller) {["(irb):285:in "]*7} 13 | io = double("io") 14 | allow(io).to receive(:line).with(285).and_return("pd 'whoami'") 15 | conf = double("conf", :io => io) 16 | IRB = Module.new {} 17 | allow_any_instance_of(Kernel).to receive(:conf) {conf} 18 | allow_any_instance_of(Kernel).to receive(:__LINE__) {'285'} 19 | pd 'whoami' 20 | output = $stdout.string 21 | expect(output.lines.size).to eq(3) 22 | expect(output.lines[0]).to match(/\[PD\] \(irb\):285 in RSpec::ExampleGroups::PutsDebuggerer[^:]*::IrbSupport/) 23 | expect(output.lines[1]).to match(/ > pd 'whoami'/) 24 | expect(output.lines[2]).to match(/ => \"whoami\"\n/) 25 | end 26 | it 'does not utilize IRB when not having conf io (like in MiniTest Rails)' do 27 | # senseless faking to get irb support to work (tested in IRB as working) 28 | allow_any_instance_of(Kernel).to receive(:caller) {["(irb):285:in "]*7} 29 | IRB = Module.new {} 30 | allow_any_instance_of(Kernel).to receive(:__LINE__) {'285'} 31 | pd 'whoami' 32 | output = $stdout.string 33 | expect(output.lines.size).to eq(3) 34 | expect(output.lines[0]).to match(/\[PD\] \(irb\):285 in RSpec::ExampleGroups::PutsDebuggerer[^:]*::IrbSupport/) 35 | expect(output.lines[1]).to match(/ > /) 36 | expect(output.lines[2]).to match(/ => \"whoami\"/) 37 | end 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_piecemeal_options__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | context 'with piecemeal options' do 6 | before do 7 | load "awesome_print/core_ext/kernel.rb" 8 | @awesome_print_defaults = AwesomePrint.defaults 9 | AwesomePrint.defaults = { 10 | plain: true 11 | } 12 | end 13 | after do 14 | AwesomePrint.defaults = @awesome_print_defaults 15 | Kernel.send(:remove_method, :ap) 16 | end 17 | it 'supports enabling header per single puts' do 18 | PutsDebuggererInvoker.dynamic_nested_array(header: true) # support options alone 19 | output = $stdout.string 20 | expect(output).to eq("#{PutsDebuggerer::HEADER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => {:header=>true}\n") 21 | $stdout = StringIO.new 22 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], header: true) 23 | output = $stdout.string 24 | expect(output).to eq("#{PutsDebuggerer::HEADER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 25 | $stdout = StringIO.new 26 | PutsDebuggererInvoker.dynamic_nested_array(name: 'Sean', header: true) # support hash including options 27 | output = $stdout.string 28 | expect(output).to eq("#{PutsDebuggerer::HEADER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => {:name=>\"Sean\", :header=>true}\n") 29 | $stdout = StringIO.new 30 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 31 | output = $stdout.string 32 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 33 | end 34 | it 'supports enabling header per single puts using shortcut syntax' do 35 | PutsDebuggererInvoker.dynamic_nested_array(h: :t) # support options alone 36 | output = $stdout.string 37 | expect(output).to eq("#{PutsDebuggerer::HEADER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => {:h=>:t}\n") 38 | $stdout = StringIO.new 39 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], h: :t) 40 | output = $stdout.string 41 | expect(output).to eq("#{PutsDebuggerer::HEADER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 42 | $stdout = StringIO.new 43 | PutsDebuggererInvoker.dynamic_nested_array(name: 'Sean', h: :t) # support hash including options 44 | output = $stdout.string 45 | expect(output).to eq("#{PutsDebuggerer::HEADER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => {:name=>\"Sean\", :h=>:t}\n") 46 | $stdout = StringIO.new 47 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 48 | output = $stdout.string 49 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 50 | end 51 | it 'supports enabling footer per single puts' do 52 | PutsDebuggererInvoker.dynamic_nested_array(footer: true) # support options alone 53 | output = $stdout.string 54 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => {:footer=>true}\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 55 | $stdout = StringIO.new 56 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], footer: true) 57 | output = $stdout.string 58 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 59 | $stdout = StringIO.new 60 | PutsDebuggererInvoker.dynamic_nested_array(name: 'Sean', footer: true) # support hash including options 61 | output = $stdout.string 62 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => {:name=>\"Sean\", :footer=>true}\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 63 | $stdout = StringIO.new 64 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 65 | output = $stdout.string 66 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 67 | end 68 | it 'supports enabling footer per single puts using shortcut syntax' do 69 | PutsDebuggererInvoker.dynamic_nested_array(f: :t) # support options alone 70 | output = $stdout.string 71 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => {:f=>:t}\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 72 | $stdout = StringIO.new 73 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], f: :t) 74 | output = $stdout.string 75 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 76 | $stdout = StringIO.new 77 | PutsDebuggererInvoker.dynamic_nested_array(name: 'Sean', f: :t) # support hash including options 78 | output = $stdout.string 79 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => {:name=>\"Sean\", :f=>:t}\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 80 | $stdout = StringIO.new 81 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 82 | output = $stdout.string 83 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 84 | end 85 | it 'supports enabling both header and footer per single puts' do 86 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], header: '#'*80, footer: true) 87 | output = $stdout.string 88 | expect(output).to eq("#{'#'*80}\n[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n#{PutsDebuggerer::FOOTER_DEFAULT}\n") 89 | $stdout = StringIO.new 90 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 91 | output = $stdout.string 92 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 93 | end 94 | it 'supports enabling wrapper per single puts using shortcut syntax' do 95 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], w: :t) 96 | output = $stdout.string 97 | expect(output).to eq("#{PutsDebuggerer::WRAPPER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n#{PutsDebuggerer::WRAPPER_DEFAULT}\n") 98 | $stdout = StringIO.new 99 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 100 | output = $stdout.string 101 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 102 | end 103 | it 'supports switching printer per single puts' do 104 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], printer: lambda {|output| puts output.upcase}) 105 | output = $stdout.string 106 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file.upcase}:26 IN PUTSDEBUGGERERINVOKER.DYNAMIC_NESTED_ARRAY\n > PD *ARRAY_INCLUDING_OPTIONS\n => [1, [2, 3]]\n") 107 | $stdout = StringIO.new 108 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 109 | output = $stdout.string 110 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 111 | end 112 | it 'supports switching print engine per single puts' do 113 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], print_engine: :ap) 114 | output = $stdout.string 115 | expected_object_printout = "[\n [0] 1,\n [1] [\n [0] 2,\n [1] 3\n ]\n]" 116 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => #{expected_object_printout}\n") 117 | $stdout = StringIO.new 118 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 119 | output = $stdout.string 120 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 121 | end 122 | it 'supports switching app path per single puts' do 123 | app_path = File.expand_path(File.join(__FILE__, '..')) 124 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], app_path: app_path) 125 | output = $stdout.string 126 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file.sub(app_path, '')}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 127 | $stdout = StringIO.new 128 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 129 | output = $stdout.string 130 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 131 | end 132 | it 'supports switching announcer per single puts' do 133 | app_path = File.expand_path(File.join(__FILE__, '..')) 134 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], announcer: "!!!!!HELLO!!!!!") 135 | output = $stdout.string 136 | expect(output).to eq("!!!!!HELLO!!!!! #{puts_debuggerer_invoker_file.sub(app_path, '')}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 137 | $stdout = StringIO.new 138 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 139 | output = $stdout.string 140 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 141 | end 142 | it 'supports switching announcer per single puts with shortcut syntax' do 143 | app_path = File.expand_path(File.join(__FILE__, '..')) 144 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], a: "!!!!!HELLO!!!!!") 145 | output = $stdout.string 146 | expect(output).to eq("!!!!!HELLO!!!!! #{puts_debuggerer_invoker_file.sub(app_path, '')}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 147 | $stdout = StringIO.new 148 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 149 | output = $stdout.string 150 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 151 | end 152 | it 'supports switching formatter per single puts' do 153 | app_path = File.expand_path(File.join(__FILE__, '..')) 154 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], formatter: -> (data) { 155 | print "#{data[:announcer]}:#{data[:line_number]}:" 156 | data[:object_printer].call 157 | }) 158 | output = $stdout.string 159 | expect(output).to eq("[PD]:26:[1, [2, 3]]\n") 160 | $stdout = StringIO.new 161 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 162 | output = $stdout.string 163 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 164 | end 165 | it 'supports enabling caller per single puts' do 166 | app_path = File.expand_path(File.join(__FILE__, '..')) 167 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], caller: 0) 168 | output = $stdout.string 169 | expected_caller = [" #{__FILE__}:#{__LINE__-2}:in `block (3 levels) in '"] 170 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n#{expected_caller.join("\n")}\n") 171 | $stdout = StringIO.new 172 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 173 | output = $stdout.string 174 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 175 | end 176 | it 'supports enabling caller per single puts with shortcut syntax' do 177 | app_path = File.expand_path(File.join(__FILE__, '..')) 178 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]], c: 0) 179 | output = $stdout.string 180 | expected_caller = [" #{__FILE__}:#{__LINE__-2}:in `block (3 levels) in '"] 181 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n#{expected_caller.join("\n")}\n") 182 | $stdout = StringIO.new 183 | PutsDebuggererInvoker.dynamic_nested_array([1, [2, 3]]) 184 | output = $stdout.string 185 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:26 in PutsDebuggererInvoker.dynamic_nested_array\n > pd *array_including_options\n => [1, [2, 3]]\n") 186 | end 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_rails__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | context 'with rails' do 6 | before do 7 | load "awesome_print/core_ext/kernel.rb" 8 | @awesome_print_defaults = AwesomePrint.defaults 9 | AwesomePrint.defaults = { 10 | plain: true 11 | } 12 | Kernel.send(:remove_method, :ap) rescue nil 13 | Object.class_eval do 14 | module Rails 15 | class Logger 16 | def debug(object) 17 | # simulate a Rails logger. Adding extra prefix to ease testing. 18 | puts "Rails.logger.debug: #{object.inspect}" 19 | end 20 | def ap(object) 21 | # simulate a Rails ap logger. Adding extra prefix to ease testing. 22 | print "Rails.logger.ap:" 23 | Object.send(:ap, object) 24 | end 25 | end 26 | def self.root 27 | File.expand_path(File.join(__FILE__, '..', '..', '..')) 28 | end 29 | def self.logger 30 | @logger ||= Logger.new 31 | end 32 | end 33 | end 34 | PutsDebuggerer.app_path = nil # defaults to Rails 35 | PutsDebuggerer.print_engine = :p 36 | end 37 | after do 38 | Object.send(:remove_const, :Rails) 39 | PutsDebuggerer.app_path = nil # default app_path 40 | Kernel.send(:remove_method, :ap) rescue nil 41 | AwesomePrint.defaults = @awesome_print_defaults 42 | end 43 | it 'prints file relative to app path, line number, ruby expression, and evaluated string object' do 44 | name = 'Robert' 45 | PutsDebuggererInvoker.dynamic_greeting(name) 46 | output = $stdout.string 47 | expect(output).to eq("[PD] /spec/support/puts_debuggerer_invoker.rb:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 48 | end 49 | it 'defaults to Rails awesome_print logger in Rails app with awesome_print' do 50 | load "awesome_print/core_ext/kernel.rb" 51 | PutsDebuggerer.print_engine = nil 52 | PutsDebuggererInvoker.static_nested_array 53 | output = $stdout.string 54 | expect(output).to eq(<<-MULTILINE 55 | [PD] /spec/support/puts_debuggerer_invoker.rb:22 in PutsDebuggererInvoker.static_nested_array 56 | > pd [1, [2, 3]] 57 | => [ 58 | [0] 1, 59 | [1] [ 60 | [0] 2, 61 | [1] 3 62 | ] 63 | ] 64 | MULTILINE 65 | ) 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_run_at_support__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | context 'with run_at specified' do 7 | context 'as an index' do 8 | it 'skips first 4 runs, prints on the 5th run, skips 6th and 7th runs' do 9 | name = 'Robert' 10 | output = $stdout.string 11 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 5) 12 | expect(output).to be_empty 13 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 5) 14 | expect(output).to be_empty 15 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 5) 16 | expect(output).to be_empty 17 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 5) 18 | expect(output).to be_empty 19 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 5) 20 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:5)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 21 | $stdout = StringIO.new 22 | output = $stdout.string 23 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 5) 24 | expect(output).to be_empty 25 | $stdout = StringIO.new 26 | output = $stdout.string 27 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 5) 28 | expect(output).to be_empty 29 | end 30 | 31 | it 'prints 7 times' do 32 | name = 'Robert' 33 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, nil) 34 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, nil) 35 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, nil) 36 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, nil) 37 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, nil) 38 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, nil) 39 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, nil) 40 | output = $stdout.string 41 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n" * 7) 42 | end 43 | end 44 | 45 | context 'as an array' do 46 | it 'prints on 1st run, skips 2nd run, prints on 3rd run, skips 6th the rest' do 47 | name = 'Robert' 48 | output = $stdout.string 49 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, [1, 3]) 50 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:1)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 51 | 52 | $stdout = StringIO.new 53 | output = $stdout.string 54 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, [1, 3]) 55 | expect(output).to be_empty 56 | 57 | $stdout = StringIO.new 58 | output = $stdout.string 59 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, [1, 3]) 60 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:3)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 61 | 62 | $stdout = StringIO.new 63 | output = $stdout.string 64 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, [1, 3]) 65 | expect(output).to be_empty 66 | 67 | $stdout = StringIO.new 68 | output = $stdout.string 69 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, [1, 3]) 70 | expect(output).to be_empty 71 | 72 | $stdout = StringIO.new 73 | output = $stdout.string 74 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, [1, 3]) 75 | expect(output).to be_empty 76 | 77 | $stdout = StringIO.new 78 | output = $stdout.string 79 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, [1, 3]) 80 | expect(output).to be_empty 81 | end 82 | end 83 | 84 | context 'as a range' do 85 | context 'that is finite' do 86 | it 'skips first 2 runs, prints on 3rd, 4th, 5th, and skips the rest' do 87 | name = 'Robert' 88 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..5) 89 | output = $stdout.string 90 | expect(output).to be_empty 91 | 92 | $stdout = StringIO.new 93 | output = $stdout.string 94 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..5) 95 | expect(output).to be_empty 96 | 97 | $stdout = StringIO.new 98 | output = $stdout.string 99 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..5) 100 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:3)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 101 | 102 | $stdout = StringIO.new 103 | output = $stdout.string 104 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..5) 105 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:4)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 106 | 107 | $stdout = StringIO.new 108 | output = $stdout.string 109 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..5) 110 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:5)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 111 | 112 | $stdout = StringIO.new 113 | output = $stdout.string 114 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..5) 115 | expect(output).to be_empty 116 | 117 | $stdout = StringIO.new 118 | output = $stdout.string 119 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..5) 120 | expect(output).to be_empty 121 | end 122 | end 123 | 124 | context 'that is infinite' do 125 | it 'skips first 2 runs, prints on the rest' do 126 | name = 'Robert' 127 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..-1) 128 | output = $stdout.string 129 | expect(output).to be_empty 130 | 131 | $stdout = StringIO.new 132 | output = $stdout.string 133 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..-1) 134 | expect(output).to be_empty 135 | 136 | $stdout = StringIO.new 137 | output = $stdout.string 138 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..-1) 139 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:3)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 140 | 141 | $stdout = StringIO.new 142 | output = $stdout.string 143 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..-1) 144 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:4)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 145 | 146 | $stdout = StringIO.new 147 | output = $stdout.string 148 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..-1) 149 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:5)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 150 | 151 | $stdout = StringIO.new 152 | output = $stdout.string 153 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..-1) 154 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:6)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 155 | 156 | $stdout = StringIO.new 157 | output = $stdout.string 158 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3..-1) 159 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:7)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 160 | end 161 | 162 | it 'skips first 2 runs, prints on the rest' do 163 | name = 'Robert' 164 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3...-1) 165 | output = $stdout.string 166 | expect(output).to be_empty 167 | 168 | $stdout = StringIO.new 169 | output = $stdout.string 170 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3...-1) 171 | expect(output).to be_empty 172 | 173 | $stdout = StringIO.new 174 | output = $stdout.string 175 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3...-1) 176 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:3)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 177 | 178 | $stdout = StringIO.new 179 | output = $stdout.string 180 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3...-1) 181 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:4)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 182 | 183 | $stdout = StringIO.new 184 | output = $stdout.string 185 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3...-1) 186 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:5)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 187 | 188 | $stdout = StringIO.new 189 | output = $stdout.string 190 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3...-1) 191 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:6)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 192 | 193 | $stdout = StringIO.new 194 | output = $stdout.string 195 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 3...-1) 196 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:7)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 197 | end 198 | end 199 | 200 | context 'set as a global option' do 201 | it 'skips first 2 runs, prints on 3rd, 4th, 5th, and skips the rest' do 202 | PutsDebuggerer.run_at = 3..5 203 | expect(PutsDebuggerer.run_at?).to be_truthy 204 | name = 'Robert' 205 | 206 | $stdout = StringIO.new 207 | output = $stdout.string 208 | PutsDebuggererInvoker.dynamic_greeting(name) 209 | output = $stdout.string 210 | expect(output).to be_empty 211 | 212 | $stdout = StringIO.new 213 | output = $stdout.string 214 | PutsDebuggererInvoker.dynamic_greeting(name) 215 | expect(output).to be_empty 216 | 217 | $stdout = StringIO.new 218 | output = $stdout.string 219 | PutsDebuggererInvoker.dynamic_greeting(name) 220 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting (run:3)\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 221 | 222 | $stdout = StringIO.new 223 | output = $stdout.string 224 | PutsDebuggererInvoker.dynamic_greeting(name) 225 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting (run:4)\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 226 | 227 | $stdout = StringIO.new 228 | output = $stdout.string 229 | PutsDebuggererInvoker.dynamic_greeting(name) 230 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting (run:5)\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 231 | 232 | $stdout = StringIO.new 233 | output = $stdout.string 234 | PutsDebuggererInvoker.dynamic_greeting(name) 235 | expect(output).to be_empty 236 | 237 | $stdout = StringIO.new 238 | output = $stdout.string 239 | PutsDebuggererInvoker.dynamic_greeting(name) 240 | expect(output).to be_empty 241 | end 242 | end 243 | 244 | describe 'reset' do 245 | it 'resets the object run at number (counter)' do 246 | name = 'Robert' 247 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 248 | output = $stdout.string 249 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:1)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 250 | 251 | $stdout = StringIO.new 252 | output = $stdout.string 253 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 254 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:2)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 255 | 256 | $stdout = StringIO.new 257 | output = $stdout.string 258 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 259 | expect(output).to be_empty 260 | 261 | PutsDebuggerer::RunDeterminer.reset_run_at_number("Hello Robert", 1..2) 262 | 263 | 264 | $stdout = StringIO.new 265 | output = $stdout.string 266 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 267 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:1)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 268 | 269 | $stdout = StringIO.new 270 | output = $stdout.string 271 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 272 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:2)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 273 | 274 | $stdout = StringIO.new 275 | output = $stdout.string 276 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 277 | expect(output).to be_empty 278 | end 279 | 280 | it 'resets all object run at numbers (counters)' do 281 | name = 'Robert' 282 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 283 | output = $stdout.string 284 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:1)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 285 | 286 | $stdout = StringIO.new 287 | output = $stdout.string 288 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 289 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:2)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 290 | 291 | $stdout = StringIO.new 292 | output = $stdout.string 293 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 294 | expect(output).to be_empty 295 | 296 | PutsDebuggerer::RunDeterminer.reset_run_at_numbers 297 | 298 | 299 | $stdout = StringIO.new 300 | output = $stdout.string 301 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 302 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:1)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 303 | 304 | $stdout = StringIO.new 305 | output = $stdout.string 306 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 307 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:39 in PutsDebuggererInvoker.dynamic_greeting_run_at (run:2)\n > pd \"Hello \#{name}\", run_at: run_at\n => \"Hello Robert\"\n") 308 | 309 | $stdout = StringIO.new 310 | output = $stdout.string 311 | PutsDebuggererInvoker.dynamic_greeting_run_at(name, 1..2) 312 | expect(output).to be_empty 313 | end 314 | 315 | it 'resets global run at counter' do 316 | PutsDebuggerer.run_at = 1..2 317 | name = 'Robert' 318 | PutsDebuggererInvoker.dynamic_greeting(name) 319 | output = $stdout.string 320 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting (run:1)\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 321 | 322 | $stdout = StringIO.new 323 | output = $stdout.string 324 | PutsDebuggererInvoker.dynamic_greeting(name) 325 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting (run:2)\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 326 | 327 | $stdout = StringIO.new 328 | output = $stdout.string 329 | PutsDebuggererInvoker.dynamic_greeting(name) 330 | expect(output).to be_empty 331 | 332 | PutsDebuggerer::RunDeterminer.reset_run_at_global_number 333 | 334 | 335 | $stdout = StringIO.new 336 | output = $stdout.string 337 | PutsDebuggererInvoker.dynamic_greeting(name) 338 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting (run:1)\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 339 | 340 | $stdout = StringIO.new 341 | output = $stdout.string 342 | PutsDebuggererInvoker.dynamic_greeting(name) 343 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting (run:2)\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 344 | 345 | $stdout = StringIO.new 346 | output = $stdout.string 347 | PutsDebuggererInvoker.dynamic_greeting(name) 348 | expect(output).to be_empty 349 | end 350 | end 351 | end 352 | end 353 | end 354 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer__with_wrapper_support_enabled__spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | context 'with wrapper support enabled' do 6 | context 'as default wrapper' do 7 | before do 8 | PutsDebuggerer.wrapper = true 9 | end 10 | it 'prints asterisk wrapper 80 times by default before dynamic PD print out' do 11 | name = 'Robert' 12 | PutsDebuggererInvoker.dynamic_greeting(name) 13 | output = $stdout.string 14 | expect(output).to eq("#{PutsDebuggerer::WRAPPER_DEFAULT}\n[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n#{PutsDebuggerer::WRAPPER_DEFAULT}\n") 15 | end 16 | it 'disables wrapper with nil wrapper' do 17 | PutsDebuggerer.wrapper = nil 18 | name = 'Robert' 19 | PutsDebuggererInvoker.dynamic_greeting(name) 20 | output = $stdout.string 21 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 22 | end 23 | it 'disables wrapper with false wrapper' do 24 | PutsDebuggerer.wrapper = false 25 | name = 'Robert' 26 | PutsDebuggererInvoker.dynamic_greeting(name) 27 | output = $stdout.string 28 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 29 | end 30 | it 'disables wrapper with empty string wrapper' do 31 | PutsDebuggerer.wrapper = '' 32 | name = 'Robert' 33 | PutsDebuggererInvoker.dynamic_greeting(name) 34 | output = $stdout.string 35 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 36 | end 37 | end 38 | context 'as custom wrapper' do 39 | let(:custom_wrapper) {'>>>PRINT OUT<<<'} 40 | before do 41 | PutsDebuggerer.wrapper = custom_wrapper 42 | end 43 | after do 44 | PutsDebuggerer.wrapper = nil 45 | end 46 | it 'prints asterisk wrapper 80 times by default before dynamic PD print out' do 47 | name = 'Robert' 48 | PutsDebuggererInvoker.dynamic_greeting(name) 49 | output = $stdout.string 50 | expect(output).to eq("#{custom_wrapper}\n[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n#{custom_wrapper}\n") 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/lib/puts_debuggerer_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe 'PutsDebuggerer' do 4 | let(:puts_debuggerer_invoker_file) {File.expand_path(File.join(__FILE__, '..', '..', 'support', 'puts_debuggerer_invoker.rb'))} 5 | 6 | it 'prints file, line number, ruby expression, and evaluated string object; returns evaluated object' do 7 | name = 'Robert' 8 | expect(PutsDebuggererInvoker.dynamic_greeting(name)).to eq('Hello Robert') 9 | output = $stdout.string 10 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:10 in PutsDebuggererInvoker.dynamic_greeting\n > pd \"Hello \#{name}\"\n => \"Hello Robert\"\n") 11 | end 12 | 13 | it 'prints file, line number, ruby expression, and evaluated string object without extra parentheses when already surrounded; returns evaluated object' do 14 | name = 'Robert' 15 | expect(PutsDebuggererInvoker.parentheses_dynamic_greeting(name)).to eq("Hello Robert") 16 | output = $stdout.string 17 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:14 in PutsDebuggererInvoker.parentheses_dynamic_greeting\n > pd (\"Hello \#{name}\")\n => \"Hello Robert\"\n") 18 | end 19 | 20 | it 'prints file, line number, ruby expression, and evaluated numeric object without quotes; returns evaluated integer' do 21 | expect(PutsDebuggererInvoker.numeric_squaring(3)).to eq(9) 22 | output = $stdout.string 23 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:18 in PutsDebuggererInvoker.numeric_squaring\n > pd n*n\n => 9\n") 24 | end 25 | 26 | it 'prints inside pd expression' do 27 | name = 'Robert' 28 | expect(PutsDebuggererInvoker.inside_dynamic_greeting(name)).to eq('Hello Robert') 29 | output = $stdout.string 30 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:35 in PutsDebuggererInvoker.inside_dynamic_greeting\n > greeting = \"Hello \#{pd(name)}\"\n => \"Robert\"\n") 31 | end 32 | 33 | it 'prints exception stack trace' do 34 | class FakeException < Exception 35 | def full_message 36 | <<~MULTI_LINE_STRING 37 | stack trace line 1 38 | stack trace line 2 39 | stack trace line 3 40 | MULTI_LINE_STRING 41 | end 42 | end 43 | e = FakeException.new 44 | PutsDebuggererInvoker.exception_stack_trace(e) 45 | output = $stdout.string 46 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:48 in PutsDebuggererInvoker.exception_stack_trace\n > pd error\n => stack trace line 1\nstack trace line 2\nstack trace line 3\n") 47 | end 48 | 49 | it 'prints *args array' do 50 | result = PutsDebuggererInvoker.vararg_array 51 | expect(result).to eq(['hello', 3, true]) 52 | output = $stdout.string 53 | expect(output).to eq("[PD] #{puts_debuggerer_invoker_file}:52 in PutsDebuggererInvoker.vararg_array\n > pd 'hello', 3, true\n => [\"hello\", 3, true]\n") 54 | end 55 | 56 | it 'prints *args array with options at the end' do 57 | result = PutsDebuggererInvoker.vararg_array_with_options(wrapper: true) 58 | expect(result).to eq(['hello', 3, true]) 59 | output = $stdout.string 60 | expect(output).to eq("********************************************************************************\n[PD] #{puts_debuggerer_invoker_file}:56 in PutsDebuggererInvoker.vararg_array_with_options\n > pd 'hello', 3, true, options\n => [\"hello\", 3, true]\n********************************************************************************\n") 61 | end 62 | 63 | it 'prints using pd_inspect' do 64 | result = PutsDebuggererInvoker.call_pd_inspect([1, [2, [3]]]) 65 | # TODO it is odd we are getting the PutsDebuggerInvoker file, but the Array class. See if we should fix something here. 66 | expect(result).to eq("[PD] #{puts_debuggerer_invoker_file}:68 in Array.call_pd_inspect\n > object.pd_inspect\n => [1, [2, [3]]]\n") 67 | output = $stdout.string 68 | expect(output).to eq("") 69 | end 70 | 71 | it 'prints frozen hash' do 72 | hash = {a: 1, b: 2, c: 3, h: :t, f: :t}.freeze 73 | result = PutsDebuggererInvoker.call_pd(hash) 74 | # TODO it is odd we are getting the PutsDebuggerInvoker file, but the Array class. See if we should fix something here. 75 | expect(result).to eq(hash) 76 | output = $stdout.string 77 | expect(output).to eq(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n[PD] /Users/andymaleh/code/puts_debuggerer/spec/support/puts_debuggerer_invoker.rb:60 in PutsDebuggererInvoker.call_pd\n > pd *args\n => {:a=>1, :b=>2, :c=>3, :h=>:t, :f=>:t}\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n") 78 | end 79 | 80 | context 'look into puts debuggerer blog post by tenderlove for other goodies to add' 81 | context 'deadlock detection support' 82 | context 'object allocation support' #might need to note having to load this lib first before others for this to work 83 | context 'support for console.log and/or alert in js' 84 | end 85 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # require 'simplecov' 2 | # require 'simplecov-lcov' 3 | # require 'coveralls' if ENV['TRAVIS'] 4 | # 5 | # SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true 6 | # formatters = [] 7 | # formatters << SimpleCov::Formatter::LcovFormatter 8 | # formatters << Coveralls::SimpleCov::Formatter if ENV['TRAVIS'] 9 | # SimpleCov.formatters = formatters 10 | # SimpleCov.start do 11 | # add_filter(/^\/spec\//) 12 | # end 13 | 14 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 15 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 16 | 17 | require 'rspec' 18 | 19 | # Requires supporting files with custom matchers and macros, etc, 20 | # in ./support/ and its subdirectories. 21 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 22 | 23 | RSpec.configure do |config| 24 | config.before do 25 | $stdout = StringIO.new 26 | PutsDebuggerer.printer = :puts 27 | PutsDebuggerer.print_engine = :p 28 | end 29 | 30 | config.after do 31 | PutsDebuggerer.app_path = nil 32 | PutsDebuggerer.caller = nil 33 | PutsDebuggerer.footer = nil 34 | PutsDebuggerer.formatter = nil 35 | PutsDebuggerer.header = nil 36 | PutsDebuggerer.run_at = nil 37 | PutsDebuggerer::RunDeterminer.run_at_global_number = nil 38 | PutsDebuggerer::RunDeterminer::OBJECT_RUN_AT.clear 39 | PutsDebuggerer.wrapper = nil 40 | end 41 | 42 | end 43 | 44 | require 'awesome_print' 45 | require 'logger' 46 | require 'logging' 47 | require 'pd' # tests both `require 'pd'` and `require 'puts_debuggerer'` 48 | -------------------------------------------------------------------------------- /spec/support/puts_debuggerer_invoker.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '../../../lib/puts_debuggerer') 2 | 3 | # DO NOT MOVE METHODS (LINE NUMBERS ARE BEING TESTED) ADD METHODS AT THE END ONLY 4 | module PutsDebuggererInvoker 5 | # intentional empty line 6 | # intentional empty line 7 | # intentional empty line 8 | # intentional empty line 9 | def self.dynamic_greeting(name) 10 | pd "Hello #{name}" 11 | end 12 | # intentional empty line 13 | def self.parentheses_dynamic_greeting(name) 14 | pd ("Hello #{name}") 15 | end 16 | # intentional empty line 17 | def self.numeric_squaring(n) 18 | pd n*n 19 | end 20 | # intentional empty line 21 | def self.static_nested_array 22 | pd [1, [2, 3]] 23 | end 24 | # intentional empty line 25 | def self.dynamic_nested_array(*array_including_options) 26 | pd *array_including_options 27 | end 28 | # intentional empty line 29 | def self.multi_line_dynamic_greeting(name) 30 | pd "Hello " + 31 | name.to_s 32 | end 33 | # intentional empty line 34 | def self.inside_dynamic_greeting(name) 35 | greeting = "Hello #{pd(name)}" 36 | end 37 | # intentional empty line 38 | def self.dynamic_greeting_run_at(name, run_at=nil) 39 | pd "Hello #{name}", run_at: run_at 40 | end 41 | # intentional empty line 42 | def self.multi_line_dynamic_greeting_source_line_count(name) 43 | pd "Hello " + 44 | name.to_s, source_line_count: 2 45 | end 46 | # intentional empty line 47 | def self.exception_stack_trace(error) 48 | pd error 49 | end 50 | # intentional empty line 51 | def self.vararg_array 52 | pd 'hello', 3, true 53 | end 54 | # intentional empty line 55 | def self.vararg_array_with_options(options) 56 | pd 'hello', 3, true, options 57 | end 58 | # intentional empty line 59 | def self.call_pd(*args) 60 | pd *args 61 | end 62 | # intentional empty line 63 | def self.logger_log(logger, severity, *args) 64 | logger.send(severity, *args) 65 | end 66 | # intentional empty line 67 | def self.call_pd_inspect(object) 68 | object.pd_inspect 69 | end 70 | end 71 | --------------------------------------------------------------------------------