Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

mayor rewriting - initial commit

  • Loading branch information...
commit a64d6fda5cc0b8f62024f0d8f253fa0bc6deebf9 0 parents
@ddnexus authored
26 .gitignore
@@ -0,0 +1,26 @@
+## MAC OS
+.DS_Store
+
+## TEXTMATE
+*.tmproj
+tmtags
+
+## EMACS
+*~
+\#*
+.\#*
+
+## VIM
+*.swp
+
+## PROJECT::GENERAL
+coverage
+rdoc
+pkg
+
+## Eclipse
+.loadpath
+.project
+
+tutorial/
+test/
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010-2011 Domizio Demichelis
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
463 README.markdown
@@ -0,0 +1,463 @@
+# irt
+
+Interactive Ruby Testing - Use irb or rails console for testing.
+
+## Foreword
+
+I like to try my code step by step with irb: it's like playing. But then, when everything
+is working as I want, I have to write the tests for future changes, and... that's like working!
+It's also somehow silly, because I know that everything is ok because I have just tried it thoroughly,
+but I have to re-write the same steps and pack everything into a test-suite.
+That's not DRY at all, so I wrote IRT.
+
+After using it for a while I added also quite a lot of other features, not only useful for testing,
+but also for easy inspecting and debugging. I hope it will be useful for you too.
+
+### Feedback!!!
+
+This is feedback-driven software. Just send me a line about you and/or what you think about IRT:
+that will be a wonderful contribution that will help me to keep improving (and documenting) this software.
+
+My email address is ddnexus at gmail.com ... waiting for your. Ciao.
+
+## What is IRT?
+
+IRT is a very customized irb / rails console that adds a lot of useful features to the standard irb.
+
+IRT records all the steps of your interactive session with irb (or rails console), and can re-run
+them as tests, and when something goes wrong, IRT opens an interactive irb (irt) session at the failure
+line, giving you immediate feedback and in place editing for easy fixing.
+
+Don't you feel frustrated when a standard test fails, printing a bunch of stuff difficult to distinguish,
+and showing NOTHING about the test code that produced the failure?
+
+When something fails IRT assumes that you don't want just to know that something went wrong,
+but that:
+- you want to know exactly what are the resulting diffs
+- you want to look at the code that failed without searching it
+- you want to play with it IMMEDIATELY in an interactive session, right in the context of the failure
+- you want to eventually edit and possibly fix it, right in the console
+- you want to rerun what went wrong right away, without expecting that the entire suite finishes
+
+IRT does all that for you automatically, besides, it adds a few commands that you can use to better interact with your
+code and with your testing, plus a few visual aids to your sessions, to make your life a lot easier.
+
+You can have a quick enlightening look by reading the [IRT Tutorial](https://github.com/ddnexus/irt/raw/master/irt-tutorial.pdf "IRT Tutorial")
+first, then if you want more details you can read this documentation.
+
+## Installation
+
+ $ [sudo] gem install irt
+
+### Executable Usage
+
+ $ irt --help
+
+### Command/Directives Usage
+
+ >> irt_help
+
+## Colored and Styled Output
+
+This is a nice feature, enabled by default in the terminals that support it, that is really
+helpful to visually catch what you need in the mess of the terminal input/output.
+
+IRT uses colors consistently, so you will have an instant feedback about what a text or a label is referring to.
+
+- cyan for files, saved values (and object 'a' in a diff)
+- magenta for interactive sessions
+- black/white for generic stdin/stdout and inspecting sessions (e.g. 'irt my_obj')
+- blue for the Virtual Log and generic labels
+- yellow for result echo (not setting last value), for binding sessions and for tests with diffs
+- green for result echo (setting last value) (and object 'b' in a diff)
+- red for erros, exceptions and rerun
+
+Besides IRT is using reversed and bold styles to compose a more readable/graphical output.
+
+### ANSI colors for Windoze
+
+The Windoze shell does not support ANSI escape codes natively, so if dumping Windoze is a luxury that you
+cannot afford, you could use another shell (e.g. the bash that comes with git for windows works), or you can
+enable it unless you are running Windows 7 (which has still no known ANSI support at the moment of this writing).
+
+If you want to enable it there is an [official Microsoft page](http://support.microsoft.com/kb/101875 "How to Enable ANSI.SYS in a Command Window")
+about that matter, or you can eventually find useful this
+[simple tutorial](http://www.windowsnetworking.com/kbase/WindowsTips/Windows2000/UserTips/Miscellaneous/CommandInterpreterAnsiSupport.html "Command Interpreter Ansi Support").
+
+## Sessions / Modes
+
+There are 4 irt modes / session types: file, interactive, inspect, binding.
+
+### File Mode (cyan)
+
+IRT always start in file mode, which simply means that it will execute the code in a file.
+Indeed you launch irt passing a path argument of an existing file or dir. If the path does not refer
+to any existing file, irt will ask you to confirm that you want to create that file. Eventually
+if you don't pass any path argument, irt will create a temporary file.
+
+A new or temporary created file contains just one statement: 'irt' which will open an interactive session.
+
+Notice: When you pass a dir as the path, irt will recursively execute all the '.irt' files in it, so suffixing
+with '.irt' the files is not just a convention.
+
+### Interactive Sessions (magenta)
+
+IRT opens an interactive session when you manually add the 'irt' directive in an irt file,
+or automatically, when an exception is raised or when a test has some diffs (fails).
+
+The interactive session retains all the variables and the last returned value at the last evalued line,
+so you have the possibility to play with your variables and methods, inspect, try and fix what you want,
+and specially use the irt commands to manage your environment.
+
+When you close an interactive session with 'exit' (or 'x' or 'q'), IRT will continue to run the file from the point
+it left, retaining also the variables you eventually changed or added, passing back also the last value.
+(In practice it's like everything happened in the session has happened in the file).
+
+### Inspecting Sessions (black/white)
+
+You can open an inspecting session with the command 'irt <obj>'.
+The 'self' of the new session will be the <obj> itself, so you can inspect it as you would be in its definition class.
+
+When you close the session with 'exit' (or 'x' or 'q'), IRT will not pass back anything from the inspecting session.
+
+### Binding Sessions (yellow)
+
+You can open a binding session from any file being evaluated with the directive 'irt binding'.
+The 'self' of the new session will be the 'self' at the line you called it, so you can play with local variables
+and methods as you would do it at that line.
+
+If you use 'nano' or 'vi' in a binding session you will open the file that contains the 'bin_irt binding'
+call at that line: very handy to edit your code in place.
+
+When you close the session with 'exit' (or 'x' or 'q'), IRT will not pass back anything from the binding session.
+
+## Virtual Log
+
+The Virtual Log is a special filtered-and-extended history of what has been executed at any given time.
+It records ALL the lines executed from a file till that moment, and all the RELEVANT steps
+you did in an interactive session (inspecting and binding sessions are ignored).
+
+RELEVANT is anything that is changing something in the code you are executing (plus comments and blank lines
+used for description and formatting).
+
+### Ignored steps
+
+If you are in an interactive session, make a typo and get an error,
+that's not relevant for your code so the typo and the error don't get recorded in the log.
+
+When you just inspect a variable, using p, pp, ap, puts, y, ... or use any irt command...
+that are not relevant steps that you want to rerun the next time, so they don't get recorded in the log.
+
+Also , if you are in an inspecting or binding session,
+that stesps are not relevant for your tests, so they don't get recorded in the log.
+
+### Log Management
+
+You can type 'log' (or simply 'l') to have the tail of your log, or type 'full_log' (or simply 'll')
+to see all the logged lines from the start.
+
+The lines in the log are graphically grouped in differently colored hunks: cyan for file lines,
+magenta for interactive session lines.
+
+The log contains the reference line numbers of the steps: notice that for interactive sessions
+they might not be continuous if some step has been filtered out. The numbers could also be repeated
+if some step has generated more lines, like it might happen with 'add_test' (or 'tt').
+
+You can copy and save your last steps in a file to rerun later. You can use 'print_lines'
+(or 'pl') to print the last session lines without any added reference number, for easy copying,
+or if your system supports it you can use 'copy_lines' (or 'cl') and have them right in the clipboard, ready to paste.
+You can also do the same with all the session lines using 'print_all_lines' (or 'pll')
+or copy them all 'copy_all_lines' (or 'cll').
+
+That 'pl'-copy or 'cl' plus the 'vi' or 'nano' irt command (or the 'cnn' and 'cvi' commands)
+are a very time saver combination. See the tutorial for details.
+
+## Testing
+
+Unlike the traditional testing cycle, where you write your test before or after coding,
+IRT writes the tests for you DURING the coding: you will have just to copy and paste them
+into an irt file.
+
+Adding a test is as easy as typing 'tt' (or 'add_test') in the console at any given time.
+When you type 'tt' irt serializes the current (last) value returned by the last line of code,
+and adds one test statement to your log. If you paste the log in the irt file,
+you will have it executed the next time you will run the file.
+
+Your tipical testing cycle with IRT is:
+
+- write/change some code in your IDE
+- run it with irt and check some value from your code
+- add a test ('tt') whenever you want it
+- copy the last log lines into the file and save
+- rerun the modified test file ('rr')
+
+When a test fails IRT shows you a very readable yaml dump with the differences between
+the expected and actual values, so you can immediately find any little problem even inside
+a very complex object.
+
+Besides, when a test fails IRT can show you the tail of the running file, (use 'l' or configure
+IRT.tail_on_irt = true for automatic tail) so you have an instant feedback about where the
+failure comes from. It also opens an interactive session at that point with all
+the variables loaded, so you can imediately and interactively try and fix what went wrong.
+
+If you want to edit the running file, just type 'nano' or 'vi' without any argument and you will open
+the file at the current line. Edit, save and exit from the editor, and you will continue your session
+from the point you left. You can also 'rerun' the same file (or 'rr') when you need to reload the whole code.
+
+## Editing Tools
+
+### In Place Editing
+
+You can open the current executed file at the current line by just typing 'nano' or 'vi'
+and the editor with that name will be opened (in insert mode). Paste and/or edit and save what
+you want and 'rerun' (or 'rr') the file to try/test the changes.
+
+You can also open the current executed file in your preferred (GUI) editor with 'edit'.
+If you don't like the default editor, you have just to set the IRT.edit_command_format in the ~/.irtrc file
+(see "Configuration" below).
+
+You will also find the info about how to automatically have your files syntax highlighted when opened in vi
+or nano. See "Goodies" below.
+
+### Copy-Open
+
+You can combine the copy to clipboard feature, with the in place edit feature by using one of the
+commands 'cnano', 'cvi' or 'cedit', so saving a lot of boring steps. It use the copy_to_clipboard
+command from your system. see below.
+
+### Copy to Clipboard Command
+
+IRT provides a few commands that will use an external command of your system to copy the
+last lines to the clipboard: 'copy_lines' (or 'cl'), 'cnano', 'cvi', 'cedit' use that command
+aviding you the boring task to select the output from the terminal and copy it.
+
+It uses 'pbcopy' on MacOS (which should be already installed on any mac),
+'xclip' on linux/unix (wihch you might need to install) and 'clip' on Windoze
+(which is not supported on all WinOS flavours).
+
+You can however set the IRT.copy_to_clipboard_command to any command capable of piping
+the stdin to the clipboard.
+
+### Note about CLI text editors
+
+I have never been a big fan of CLI editors like vi or nano, but I really appreciate them
+when combined with IRT. Having the file I need to edit, opened at the right line at the touch of a 2 letter
+command ('nn' or 'vi') is relly fast and powerful.
+
+You have just to know a few very basic commands
+like paste, save, quit, and eventually a couple of other, and you will save a lot of time and steps.
+
+For those (like me) that are not used to CLI editors here's a quick reference for for paste save and quit,
+(and some edit) that you have to use after a copy-open command from IRT (like 'cnn' or 'cvi'):
+
+
+ NANO
+ paste from clipboard with your usual OS command
+ quit Ctrl-X
+ type 'y'<enter> confirming that you want also to save
+ type 'n' confirming that you don't want to save
+ Editing
+ copy (line) Alt/Esc-6
+ cut (line) Ctrl-K
+ uncut (paste) Ctrl-U
+
+ VI-VIM
+ paste from clipboard with your usual OS command
+ quit Esc (return to command mode)
+ type ':wq'<enter> if you want to save and quit
+ type ':q!'<enter> if you want quit without save
+
+ Vi has different modes. You have to know at least how to switch mode:
+ to insert type 'i' when in command mode
+ to command type Esc when in insert mode
+
+ Editing
+ cut (line) [Esc (return to command mode)]
+ type 'dd' (or 'cc' that will return to insert mode)
+ paste (line) [Esc (return to command mode)]
+ type 'p'
+
+## Inspecting Tools
+
+### Call irt from your code
+
+You can add 'irt binding' anywhere in your code and have irt opened interactively
+to play with your variables and methods during execution (see Binding Sessions)
+
+### Object diff
+
+IRT can compare complex objects and shows the diffs. You can run 'vdiff obj_a, obj_b'
+and have a nice and easy to check graphical diff report of the yaml dump of the 2 objects
+
+### Kernel#capture
+
+You can hijack the output of a block to a variable to inspect and test:
+
+ output = capture { some_statement_that_write_to_stdout }
+
+### Object#own_methods
+
+Get the list of the methods implemented by the object itself (not inherited).
+
+### Inspecting libs
+
+'pp', 'yaml' (and 'ap' if installed) are loaded, so you can use 'pp', 'ap' and 'y' commands to have
+a better looking inspection of your objects. Besides they are also enhanced a bit: when invoked
+with no arguments, they use the last value (_) as the default (e.g. just type 'y' instead 'y _')
+
+## General Tools
+
+### IRT Help
+
+The IRT Commands are the methods that you can call from any IRT console session, while the Directives are
+the methods that you can call from any file. Type 'irt_help' in any IRT session to have the complete list.
+
+### Status line
+
+The status line shows the nesting status of your sessions: each time you open a new
+session or exit from the current session it is automatically printed.
+You can also print it with 'status' (or 'ss') at any time.
+
+### System Shortcuts
+
+Save some typing for common system calls like ls, cat, vi, nano
+
+### FileUtils
+
+All the FileUtils methods are included as commands: just call them in the session
+and they will not be logged; if they are part of your testing, use them as usual
+
+ >> rm_rf 'dir/to/remove' # not logged because it's an irt command
+ >> FileUtils.rm_rf 'dir/to/remove' # logged because it's a regular statement
+
+### File insert/eval
+
+You can split your tests and reuse them in other files as you whould do with 'partials' template files.
+Use "insert_file 'file/path'" to insert a file into another. It will be evaluated by IRT as
+it were written right in the including file itself. Take that into account with variables and last_values.
+Besides, you should NOT suffix them with '.irt', so they will not ignored by the irt executable scanning the dirs.
+
+### Code completion and irb-history
+
+Code completion and irb-history are enabled by default (just use the tab and up and down arrows even between sessions)
+
+### Syntax Highlight
+
+In the [goodies dir](https://github.com/ddnexus/irt/tree/master/goodies?raw=true) you can find
+a few info about how to use syntax highlight for .irt files in nano and vi, along with a complete 'irt.nanorc' file.
+
+## Configuration
+
+### Upgrade
+
+In order to preserve your eventual customization to the ~/.irtrc file, upgrading from one version
+to another requires that you manually delete (or rename) the auto-generated ~/.irtrc file, so irt will
+create a new one. Then you can edit it adding your own customization again. If you want to check whether
+something is changed or not, here is the [latest irtrc template](https://github.com/ddnexus/irt/raw/master/irtrc).
+
+### Options
+
+If you want to add your custom '~/.irbrc' file, try to load it at the top: if it doesn't
+play well with IRT, then copy and paste just part of it.
+
+You can also change a few configuration options in the ~/.irtrc file. The following are the defaults
+which should work quite well without any change:
+
+ # will open an interactie session if a test has diffs
+ # IRT.irt_on_diffs = true
+
+ # will print the log tail when an interactive session is opened
+ # IRT.tail_on_irt = false
+
+ # the lines you want to be printed as the tail
+ # IRT.log.tail_size = 10
+
+ # loads irt_helper.rb files automatically
+ # IRT.autoload_helper_files = true
+
+ # force true/false regardless the terminal ANSI support
+ # IRT.force_color = true
+
+ # the command to pipe to the copied lines that should set the clipboard
+ # default to 'pbcopy' on mac, 'xclip -selection c' on linux/unix and 'clip' on windoze
+ # IRT.copy_to_clipboard_command = 'your command'
+
+ # the format to build the command to launch nano
+ # IRT.nano_command_format = 'nano +%2$d %1$s'
+
+ # the format to build the command to launch vi
+ # IRT.vi_command_format ="vi -c 'startinsert' %1$s +%2$d"
+
+ # add your command format if you want to use another editor than nano or vi
+ # default 'open -t %1$s' on MacOX; 'kde-open %1$s' or 'gnome-open %1$s' un unix/linux; '%1$s' on windoze
+ # IRT.edit_command_format ="your_preferred_GUI_editor %1$s +%2$d"
+
+ # any log-ignored-echo command you want to add
+ # IRT.log.ignored_echo_commands << %w[commandA commandB ...]
+
+ # any log-ignored command you want to add (includes all the log-ignored-echo commands)
+ # IRT.log.ignored_commands << %w[commandC commandD ...]
+
+ # any command that will not set the last value (includes all the log-ignored commands)
+ # IRT.log.non_setting_commands << %w[commandE commandF ...]
+
+### Colors
+
+The default color styles of IRT should be OK in most situation, anyway, if you raeally don't like the colors,
+you can switch off the color completely (IRT.force_color = false) or you can also
+redefine the colors by redefining them in your .irtrc file.
+
+The following are the default Colorer styles, change them at will:
+
+ Colorer.def_custom_styles({ :bold => :bold,
+ :reversed => :reversed,
+ :null => :clear,
+
+ :log_color => :blue,
+ :file_color => :cyan,
+ :interactive_color => :magenta,
+ :inspect_color => :clear,
+ :binding_color => :yellow,
+ :actual_color => :green,
+ :ignored_color => :yellow,
+
+ :error_color => :red,
+ :ok_color => :green,
+ :diff_color => :yellow,
+ :diff_a_color => :cyan,
+ :diff_b_color => :green } , true)
+
+### Note about IRT.autoload_helper_files
+
+When autoload_helper_files is true (default) IRT will require all the 'irt_helper.rb' named files in
+the descending path fom the current working dir (which is considered the test-root dir) to the dir containing
+the irt file being executed. That is useful to add special methods or overriding to your test files without
+worring about requiring them from your test files.
+
+For example:
+
+ working_dir
+ irt_helper.rb #1
+ first_level
+ <irt_helper.rb #2
+ testA.irt
+ testB.irt
+ second_level
+ irt_helper.rb #3
+ test1.irt
+ test2.irt
+
+If you are running test1.irt and test2.irt from the working_dir IRT will automatically require the
+irt_helper.rb #1, #2 and #3. If you are running the testA.irt and testB.irt, IRT will automatically
+require the irt_helper.rb #1, #2. But if you run the same from the first_level dir, the irt_helper.rb #1
+will not be loaded, so be careful to be in the right dir to make it work properly.
+
+### Rails 3
+
+You must add the gem to your Gemfile, to make the bundler happy:
+
+ gem 'irt', :group => :console
+
+## Copyright
+
+Copyright (c) 2010-2011 Domizio Demichelis. See LICENSE for details.
60 Rakefile
@@ -0,0 +1,60 @@
+name = 'irt'
+
+def ensure_clean(action, force=false)
+ if !force && ! `git status -s`.empty?
+ puts <<-EOS.gsub(/^ {6}/, '')
+ Rake task aborted: the working tree is dirty!
+ If you know what you are doing you can use \`rake #{action}[force]\`"
+ EOS
+ exit(1)
+ end
+end
+
+desc "Install the gem"
+task :install, :force do |t, args|
+ ensure_clean(:install, args.force)
+ orig_version = version = File.read('VERSION').strip
+ begin
+ commit_id = `git log -1 --format="%h" HEAD`.strip
+ version = "#{orig_version}.#{commit_id}"
+ File.open('VERSION', 'w') {|f| f.puts version }
+ gem_name = "#{name}-#{version}.gem"
+ sh %(gem build #{name}.gemspec)
+ sh %(gem install #{gem_name} --local)
+ puts <<-EOS.gsub(/^ {6}/, '')
+
+ *******************************************************************************
+ * NOTICE *
+ *******************************************************************************
+ * The version id of locally installed gems is comparable to a --pre version: *
+ * i.e. it is alphabetically ordered (not numerically ordered), besides it *
+ * includes the sah1 commit id which is not aphabetically ordered, so be sure *
+ * your application picks the version you really intend to use *
+ *******************************************************************************
+
+ EOS
+ ensure
+ remove_entry_secure gem_name, true
+ File.open('VERSION', 'w') {|f| f.puts orig_version }
+ end
+end
+
+desc %(Remove all the "#{name}" installed gems and executables and install this version)
+task :clean_install, :force do |t, args|
+ ensure_clean(:install, args.force)
+ sh %(gem uninstall #{name} --all --ignore-dependencies --executables)
+ Rake::Task['install'].invoke(args.force)
+end
+
+desc "Push the gem to rubygems.org"
+task :push, :force do |t, args|
+ begin
+ ensure_clean(:push, args.force)
+ version = File.read('VERSION').strip
+ gem_name = "#{name}-#{version}.gem"
+ sh %(gem build #{name}.gemspec)
+ sh %(gem push #{gem_name})
+ ensure
+ remove_entry_secure gem_name, true
+ end
+end
1  VERSION
@@ -0,0 +1 @@
+1.0.0
97 bin/irt
@@ -0,0 +1,97 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'irt'
+require 'fileutils'
+require 'tempfile'
+
+banner = "irt #{IRT::VERSION} (c) 2010-2011 Domizio Demichelis".log_color.bold
+
+usage = %(
+Description:
+ Ruby Interactive Testing - Use irb or rails console for testing.
+
+Usage:
+ irt
+ similar to `irb` (but uses an empty tmp file)
+
+ irt PATH [options] [rails_env]
+ PATH path to a single file or dir (<dir>/**/*.irt files)
+ if it doesn't exists, a new file will be created and opened
+ options all the options supported by irb or rails console
+ rails_env the optional rails environment to load (must be last arg)
+
+ irt -v|--version
+ shows the version and exits
+
+ irt -h|--help
+ shows the help and exits
+
+)
+
+irtrc = File.expand_path '~/.irtrc'
+FileUtils.cp(File.expand_path('../irtrc', IRT.lib_path), irtrc) unless File.exists?(irtrc)
+
+argv = ARGV.dup
+
+case argv.first
+when NilClass
+ tmp_file = Tempfile.new(%w[tmp- .irt])
+ tmp_file << "irt"
+ tmp_file.flush
+ path = tmp_file.path
+
+when /^(-h|--help)$/
+ puts banner + usage
+ exit
+
+when /^(-v|--version)$/
+ puts IRT::VERSION
+ exit
+
+else
+ path = File.expand_path(argv.shift)
+
+ unless File.exists?(path)
+ input = nil
+ while input.nil? || !input.match(/^(y|yes|n|no)$/i)
+ print %(Do you want to create the file "#{path}"? ).interactive_color + "[<enter>=y|n] ".ok_color
+ input = $stdin.gets.strip.downcase
+ input = 'y' if input.empty?
+ end
+ exit(1) if input == 'n'
+ dirname = File.dirname(path)
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
+ File.open(path, 'w') {|f| f.puts 'irt' }
+ end
+
+end # end case
+
+files = File.directory?(path) ? Dir.glob(File.join(path, '**/*.irt')): [path]
+if files.empty?
+ puts "No *.irt file to run in #{path}"
+ exit
+end
+
+command = 'irb'
+if File.exists?('./config/environment.rb')
+ if File.exists?('./script/rails')
+ command = 'rails c'
+ elsif File.exists?('./script/console')
+ command = 'ruby script/console'
+ end
+ argv << 'development' if command != 'irb' && (argv.last.nil? || argv.last =~ /^-/)
+end
+
+puts banner
+
+files.each do |file|
+ IRT.log.print_running file
+ ENV["IRT_FILE"] = file
+ ENV["IRT_COMMAND"] = "#{command} #{file} #{argv * ' '}".strip
+ ENV["IRBRC"] = irtrc
+ unless system(ENV["IRT_COMMAND"])
+ puts "\e[0m" if Colorer.color
+ exit(1)
+ end
+end
6 goodies/nano/README
@@ -0,0 +1,6 @@
+In order to syntax-highlight *.irt files as ruby files in nano, you must add the following lines to your .nanorc file:
+
+include "/path/to/irt.nanorc"
+
+(change the "/path/to/irt.nanorc" to the real path where you store the irt.nanorc file included in this dir)
+
86 goodies/nano/irt.nanorc
@@ -0,0 +1,86 @@
+# Ruby syntax file with additional irt keywords - Distributed with the irt gem
+# The regular expressions in this file have been copied
+# and adapted from many sources
+
+# Automatically use for '.rb' and '.irt' files
+syntax "ruby" ".*\.*(rb|irt)$"
+
+# Shebang
+color brightcyan "^#!.*"
+
+# Operators
+color brightmagenta "\<not\>|\<and\>|\<or\>"
+color brightmagenta "\*\*|!|~|\*|/|%|\+|-|&|<<|>>|\|\^|>|>=|<|<="
+color brightmagenta "<=>|\|\||!=|=~|!~|&&|\+=|-=|=|\.\.|\.\.\."
+
+# Keywords
+color brightmagenta "\<(BEGIN|END|alias|and|begin|break|case)\>"
+color brightmagenta "\<(class|def|defined|do|else|elsif|end)\>"
+color brightmagenta "\<(ensure|for|if|in|module|next|not|or|redo)\>"
+color brightmagenta "\<(rescue|retry|return|self|super|then|undef)\>"
+color brightmagenta "\<(unless|until|when|while|yield)\>"
+
+# Load
+color brightgreen "\<(load|require)\>"
+
+# FILE LINE END and similar markers
+color brightyellow "\<__[A-Z_]+__\>"
+
+# irt Keywords
+color brightmagenta "\<(_eql|_yaml_eql|last_value_eql|last_yaml_eql)\?"
+color brightmagenta "\<(desc|irt|capture|irt_at_exit|eval_file|insert_file)\>"
+
+# Constants
+color brightblue "(\$)?\<[A-Z]+[0-9A-Z_a-z]*\>"
+color brightblue "::[A-Z]"
+
+# Predefined Constants
+color brightred "\<(TRUE|FALSE|NIL|STDIN|STDOUT|STDERR|ENV|ARGF|ARGV|DATA|RUBY_VERSION|RUBY_RELEASE_DATE|RUBY_PLATFORM)\>"
+
+# Predefined Variables
+color brightred "\$[^A-Za-z]" "\$-.\>" "\$std(err|in|out)\>"
+
+# Object Variables
+color green "(@|@@)\<[^:][a-z]+[0-9A-Z_a-z]*\>"
+
+# Symbols
+icolor red "[^:]:\<[0-9A-Z_]+\>"
+
+# false, nil, true
+color yellow "\<(false|nil|true)\>"
+
+# Above must not match 'nil?'
+color red "\<nil\?"
+
+# Iterators
+color brightgreen "\|\w*\|"
+
+# Regular expressions
+# color green "/(\\.|[^\\/])*/[imox]*"
+
+## Regular expressions
+color brightmagenta "/([^/]|(\\/))*/[iomx]*" "%r\{([^}]|(\\}))*\}[iomx]*"
+
+# Shell command expansion is in `backticks` or like %x{this}.
+color cyan "`[^`]*`" "%x\{[^}]*\}"
+
+## Strings, double-quoted
+color brightcyan ""([^"]|(\\"))*"" "%[QW]?\{[^}]*\}" "%[QW]?\([^)]*\)" "%[QW]?<[^>]*>" "%[QW]?\[[^]]*\]" "%[QW]?\$[^$]*\$" "%[QW]?\^[^^]*\^" "%[QW]?![^!]*!"
+
+## Strings, single-quoted
+color cyan "'([^']|(\\'))*'" "%[qw]\{[^}]*\}" "%[qw]\([^)]*\)" "%[qw]<[^>]*>" "%[qw]\[[^]]*\]" "%[qw]\$[^$]*\$" "%[qw]\^[^^]*\^" "%[qw]![^!]*!"
+
+# Escapes
+color red "\\[0-7][0-7][0-7]|\\x[0-9a-fA-F][0-9a-fA-F]"
+color red "\\[abefnrs]"
+color red "(\\c|\\C-|\\M-|\\M-\\C-)."
+
+# Expression substitution
+color green "#\{[^}]*\}"
+
+# Comments
+color cyan "#[^{].*$" "#$"
+color brightcyan "##[^{].*$" "##$"
+
+# Multiline comments
+color cyan start="^=begin" end="^=end"
5 goodies/vi/README
@@ -0,0 +1,5 @@
+In order to syntax-highlight *.irt files as ruby files in vi, you must add the following lines to your .vimrc file:
+
+syntax on
+filetype on
+au BufRead,BufNewFile *.irt set filetype=ruby
BIN  irt-tutorial.pdf
Binary file not shown
27 irt.gemspec
@@ -0,0 +1,27 @@
+name = File.basename( __FILE__, '.gemspec' )
+version = File.read(File.expand_path('../VERSION', __FILE__)).strip
+require 'date'
+
+Gem::Specification.new do |s|
+
+ s.authors = ["Domizio Demichelis"]
+ s.email = 'dd.nexus@gmail.com'
+ s.homepage = 'http://github.com/ddnexus/irt'
+ s.summary = 'Interactive Ruby Testing - Use irb or rails console for testing.'
+ s.description = 'IRT records and rerun the steps of your interactive irb or rails console session, ignoring the inspecting commands and reporting test diffs.'
+
+ s.add_runtime_dependency('differ', [">= 0.1.1"])
+ s.add_runtime_dependency('colorer', [">= 0.5.0"])
+
+ s.executables = ["irt"]
+ s.files = `git ls-files -z`.split("\0") - %w[irt-tutorial.pdf]
+
+ s.name = name
+ s.version = version
+ s.date = Date.today.to_s
+
+ s.required_rubygems_version = ">= 1.3.6"
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+
+end
48 irtrc
@@ -0,0 +1,48 @@
+# IRT RC file
+
+require 'rubygems'
+require 'irt'
+
+# ITR conf options
+
+# will open an interactie session if a test has diffs
+# IRT.irt_on_diffs = true
+
+# will print the log tail when an interactive session is opened
+# IRT.tail_on_irt = false
+
+# the lines you want to be printed as the tail
+# IRT.log.tail_size = 10
+
+# loads irt_helper.rb files automatically
+# IRT.autoload_helper_files = true
+
+# force true/false regardless the terminal ANSI support
+# IRT.force_color = true
+
+# the command that should set the clipboard to the last lines from STDIN
+# default to 'pbcopy' on mac, 'xclip -selection c' on linux/unix and 'clip' on windoze
+# IRT.copy_to_clipboard_command = 'your command'
+
+# the format to build the command to launch nano
+# IRT.nano_command_format = 'nano +%2$d %1$s'
+
+# the format to build the command to launch vi
+# IRT.vi_command_format ="vi -c 'startinsert' %1$s +%2$d"
+
+# add your command format if you want to use another editor than nano or vi
+# default 'open -t %1$s' on MacOX; 'kde-open %1$s' or 'gnome-open %1$s' un unix/linux; '%1$s' on windoze
+# IRT.edit_command_format ="your_preferred_GUI_editor %1$s +%2$d"
+
+# any log-ignored-echo command you want to add
+# IRT.log.ignored_echo_commands << %w[commandA commandB ...]
+
+# any log-ignored command you want to add (includes all the log-ignored-echo commands)
+# IRT.log.ignored_commands << %w[commandC commandD ...]
+
+# any command that will not set the last value (includes all the log-ignored commands)
+# IRT.log.non_setting_commands << %w[commandE commandF ...]
+
+require 'irt/init'
+
+# add your stuff here
129 lib/irt.rb
@@ -0,0 +1,129 @@
+
+begin
+ require 'ap'
+rescue LoadError
+end
+
+require 'pp'
+require 'yaml'
+require 'rbconfig'
+require 'pathname'
+require 'irt/extensions/kernel'
+require 'irt/extensions/object'
+require 'irt/extensions/irb'
+require 'irb/completion'
+require 'colorer'
+require 'irt/log'
+require 'irt/hunks'
+require 'irt/differ'
+require 'irt/directives'
+
+module IRT
+
+ VERSION = File.read(File.expand_path('../../VERSION', __FILE__)).strip
+
+ class BindingError < RuntimeError ; end
+ extend self
+
+ attr_accessor :irt_on_diffs, :tail_on_irt,
+ :full_exit, :exception_raised, :session_no, :autoload_helper_files,
+ :copy_to_clipboard_command, :nano_command_format, :vi_command_format, :edit_command_format
+ attr_reader :log, :file, :differ, :os
+
+ def directives
+ IRT::Directives
+ end
+
+ def force_color=(bool)
+ Colorer.color = bool
+ end
+
+ def init
+ @session_no = 0
+ @log = Log.new
+ @log.status << [File.basename($0), :file]
+ @differ = IRT::Differ
+ @irt_on_diffs = true
+ @tail_on_irt = false
+ @autoload_helper_files = true
+ Colorer.def_custom_styles :bold => :bold,
+ :reversed => :reversed,
+ :null => :clear,
+
+ :log_color => :blue,
+ :file_color => :cyan,
+ :interactive_color => :magenta,
+ :inspect_color => :clear,
+ :binding_color => :yellow,
+ :actual_color => :green,
+ :ignored_color => :yellow,
+
+ :error_color => :red,
+ :ok_color => :green,
+ :diff_color => :yellow,
+ :diff_a_color => :cyan,
+ :diff_b_color => :green
+ @os = get_os
+ @copy_to_clipboard_command = case @os
+ when :windows
+ 'clip'
+ when :macosx
+ 'pbcopy'
+ when :linux
+ 'xclip -selection c'
+ when :unix
+ 'xclip -selection c'
+ end
+ @edit_command_format = case @os
+ when :windows
+ '%1$s'
+ when :macosx
+ 'open -t %1$s'
+ when :linux
+ get_unix_linux_open_command
+ when :unix
+ get_unix_linux_open_command
+ end
+ @vi_command_format = "vi -c 'startinsert' %1$s +%2$d"
+ @nano_command_format = 'nano +%2$d %1$s'
+ end
+
+ def lib_path
+ File.expand_path '../../lib', __FILE__
+ end
+
+ # this fixes a little imperfection of the YAML::dump method
+ # which adds a space at the end of the class
+ def IRT.yaml_dump(val)
+ yml = "\n" + YAML.dump(val)
+ yml.gsub(/ +\n/, "\n")
+ end
+
+private
+
+ def get_unix_linux_open_command
+ case ENV['DESKTOP_SESSION']
+ when /kde/i
+ 'kde-open %1$s'
+ when /gnome/i
+ 'gnome-open %1$s'
+ end
+ end
+
+ def get_os
+ case RbConfig::CONFIG['host_os']
+ when /mswin|msys|mingw32|windows/i
+ :windows
+ when /darwin|mac os/i
+ :macosx
+ when /linux/i
+ :linux
+ when /solaris|bsd/i
+ :unix
+ else
+ :unknown
+ end
+ end
+
+end
+IRT.init
94 lib/irt/commands/help.rb
@@ -0,0 +1,94 @@
+module IRT
+ module Commands
+ module Help
+
+ def irt_help
+ puts %(
+#{" NOTICE ".log_color.reversed.bold}
+- The #{"Commands".interactive_color.bold} are methods generally available in any IRT session
+- The #{"Directives".file_color.bold} are methods available in any file but not in IRT sessions
+- The #{"Extensions".log_color.bold} are methods available anywhere
+
+#{" Inspecting Commands ".interactive_color.reversed.bold}
+ irt object Opens an inspecting session into object
+ vdiff|vd obj_a, obj_b Prints the visual diff of the yaml dump of 2 objects
+ cat args Similar to system cat
+ ls args Similar to system ls
+
+#{" Log Commands ".interactive_color.reversed.bold}
+ log|l [limit] Prints limit or 'tail_size' lines of the virtual log
+ full_log|ll Prints all the lines in the virtual log
+ print_lines|pl Prints the last lines of the current session
+ without numbers (for easy copying)
+ print_all_lines|pll Like print_line but prints all the sessions lines
+
+#{" Editing Commands ".interactive_color.reversed.bold}
+ vi Uses vi to open the current evalued file at the
+ last evalued line for in place edit
+ vi file[, line_no] Uses vi to open <file> [at the last evalued line]
+ for in place editing
+ nano|nn Uses nano to open the current evalued file at the
+ last evalued line for in place editing
+ nano|nn file[, line_no] Uses nano to open file [at the last evalued line]
+ for in place edit
+ edit|ed Uses your default GUY editor to open the current
+ evaluated file
+ edit|ed file Uses your default GUY editor to open file
+
+#{" Copy-Edit Commands ".interactive_color.reversed.bold + " (use copy_to_clipboard_command)".interactive_color.bold}
+ copy_lines|cl Copy the last session lines
+ copy_all_lines|cll Copy the lines of all the sessions
+ cnano|cnn Like nano, but copy the last session lines first
+ cvi Like vi, but copy the last session lines first
+ cedit|ced Like edit, but copy the last session lines first
+
+#{" Test Commands ".interactive_color.reversed.bold + " (only available in interactive sessions)".interactive_color.bold}
+ add_desc|dd desc Adds a description for the test in the log
+ add_test|tt Adds a test in the log, checking the last value (_)
+ by automatically choosing the :_eql?, or :_yaml_eql?
+ method, depending on the type of the last value (_)
+ add_test|tt desc Like add_test but adds a 'desc' directive first
+
+#{" FileUtils Commands ".interactive_color.reversed.bold}
+ All the FileUtils methods are availabe as IRT Commands
+ (e.g. pwd, touch, mkdir, mv, cp, rm, rm_rf, compare_files, ...)
+
+#{" Enhanced Commands ".interactive_color.reversed.bold}
+ p, pp, ap, y When invoked with no arguments print the last_value
+ (e.g. just type 'y' instead 'y _')
+
+#{" Misc Commands ".interactive_color.reversed.bold}
+ x|q Aliases for exit (from the current session)
+ xx|qq Aliases for abort (abort the irt process)
+ status|ss Prints the session status line
+ rerun|rr Reruns the same file
+ irt_help|hh Shows this screen
+
+#{" Session Directives ".file_color.reversed.bold}
+ irt Opens an interactive session which retains the
+ current variables and the last value (_)
+ irt binding Opens a binding session at the line of the call
+
+#{" Test Directives ".file_color.reversed.bold + " (auto added by the Test Commands)".file_color.bold}
+ desc description Adds a description to the next test
+ _eql? val Runs a test checking _ == val
+ _yaml_eql? yaml_dump Runs a test checking y _ == yaml_dump
+
+#{" Helper Directives ".file_color.reversed.bold}
+ insert_file file Evaluates file as it were inserted at that line
+ eval_file Alias for eval_file
+ irt_at_exit block Ensures execution of block at exit (useful for
+ cleanup of test env)
+
+#{" Extensions ".log_color.reversed.bold}
+ Kernel#capture block Executes block and returns a string containing the
+ captured stdout
+ Object#own_methods Returns the methods implemented by the receiver
+ itself (not inherited)
+)
+ end
+ alias_method :hh, :irt_help
+
+ end
+ end
+end
47 lib/irt/commands/log.rb
@@ -0,0 +1,47 @@
+module IRT
+ module Commands
+ module Log
+
+ def log(limit=nil)
+ IRT.log.print limit || IRT.log.tail_size
+ IRT.log.print_status
+ end
+ alias_method :l, :log
+
+ def full_log
+ IRT.log.print
+ IRT.log.print_status
+ end
+ alias_method :ll, :full_log
+
+ def status
+ IRT.log.print_status
+ end
+ alias_method :ss, :status
+
+ def print_lines
+ lines_str = IRT.log.last.lines_str
+ return if lines_str.empty?
+ puts
+ puts lines_str
+ puts
+ end
+ alias_method :pl, :print_lines
+
+ def print_all_lines
+ lines = []
+ IRT.log.reject{|h| h.class == IRT::Log::FileHunk }.each do |h|
+ ls = h.lines_str
+ lines << ls unless ls.empty?
+ end
+ unless lines.empty?
+ puts
+ puts lines.join("\n\n")
+ puts
+ end
+ end
+ alias_method :pll, :print_all_lines
+
+ end
+ end
+end
32 lib/irt/commands/misc.rb
@@ -0,0 +1,32 @@
+module IRT
+ module Commands
+ module Misc
+
+ def vdiff(a,b)
+ puts IRT::Differ.new(a,b, :value, {:a_marker => 'a',
+ :b_marker => 'b',
+ :a_label => '',
+ :b_label => ''}).output
+ end
+ alias_method :vd, :vdiff
+
+ # rerun the same file
+ def rerun
+ IRB.irb_at_exit
+ str = "Rerunning: `#{ENV['IRT_COMMAND']}`"
+ puts
+ puts " #{str} ".error_color.bold.reversed.or("*** #{str} ***")
+ puts
+ IRT.log.print_running ENV["IRT_FILE"]
+ exec ENV["IRT_COMMAND"]
+ end
+ alias_method :rr, :rerun
+
+ def x
+ exit
+ end
+ alias_method :q, :x
+
+ end
+ end
+end
71 lib/irt/commands/system.rb
@@ -0,0 +1,71 @@
+module IRT
+ module Commands
+ module System
+
+ def cat(*args)
+ return system "cat #{IRB.CurrentContext.file_line_pointers.first}" if args.empty?
+ system "cat #{args * ' '}"
+ end
+
+ def ls(*args)
+ args = %w[.] if args.empty?
+ system "ls #{args * ' '}"
+ end
+
+ def copy_lines
+ copy_to_clipboard :print_lines
+ end
+ alias_method :cl, :copy_lines
+
+ def copy_all_lines
+ copy_to_clipboard :print_all_lines
+ end
+ alias_method :cll, :copy_all_lines
+
+ %w[vi nano edit].each do |n|
+ eval <<-EOE
+ def #{n}(*args)
+ run_editor(:#{n}, *args)
+ end
+ def c#{n}(*args)
+ copy_lines
+ #{n} *args
+ end
+ EOE
+ end
+ alias_method :nn, :nano
+ alias_method :ed, :edit
+ alias_method :cnn, :cnano
+ alias_method :ced, :cedit
+
+ private
+
+ def run_editor(cmd, *args)
+ command = if args.empty?
+ cmd_format = IRT.send("#{cmd}_command_format".to_sym)
+ raise NotImplementedError unless cmd_format
+ f, l = IRB.CurrentContext.file_line_pointers
+ sprintf cmd_format, f, l
+ else
+ "#{cmd} #{args * ' '}"
+ end
+ system command
+ end
+
+ def copy_to_clipboard(cmd)
+ raise NotImplementedError, "No known copy_to_clipboard_command for this system." unless IRT.copy_to_clipboard_command
+ lines_str = capture { send(cmd) }
+ return unless lines_str.match(/\w/m)
+ begin
+ IO.popen(IRT.copy_to_clipboard_command, 'w') do |io|
+ io.puts lines_str.strip
+ end
+ print lines_str
+ rescue Exception
+ raise NotImplementedError, "This system does not appear to support the \`#{IRT.copy_to_clipboard_command}\` command."
+ end
+ end
+
+ end
+ end
+end
50 lib/irt/commands/test.rb
@@ -0,0 +1,50 @@
+module IRT
+ module Commands
+ module Test
+
+ extend self
+
+ def add_desc(description)
+ mode = IRB.CurrentContext.irt_mode
+ raise NotImplementedError, "You cannot add a test description in #{mode} mode." unless mode == :interactive
+ desc_str = %(desc "#{description}")
+ IRB.CurrentContext.current_line = desc_str
+ puts
+ puts desc_str.interactive_color
+ puts
+ end
+ alias_method :dd, :add_desc
+
+ def add_test(description='')
+ mode = IRB.CurrentContext.irt_mode
+ raise NotImplementedError, "You cannot add a test in #{mode} mode." unless mode == :interactive
+ context = IRB.CurrentContext
+ last_value = context.last_value
+ begin
+ evaled = context.workspace.evaluate(self, last_value.inspect)
+ rescue Exception
+ end
+ # the eval of the last_value.inspect == the last_value
+ test_str = if evaled == last_value
+ # same as _? but easier to read for multiline strings without escaping chars
+ if last_value.is_a?(String) && last_value.match(/\n/)
+ str = last_value.split("\n").map{|l| l.inspect.sub(/^"(.*)"$/,'\1') }.join("\n")
+ last_value.match(/\n$/) ? "_eql? <<EOS\n#{str}\nEOS" : "_eql? %(#{str})"
+ else
+ "_eql?( #{last_value.inspect} )"
+ end
+ else # need YAML
+ "_yaml_eql? %(#{IRT.yaml_dump(last_value)})"
+ end
+ desc_str = description.empty? ? '' : %(desc "#{description}"\n)
+ str = desc_str + test_str
+ context.current_line = str
+ puts
+ puts str.interactive_color
+ puts
+ end
+ alias_method :tt, :add_test
+
+ end
+ end
+end
87 lib/irt/differ.rb
@@ -0,0 +1,87 @@
+require 'differ'
+module IRT
+ class Differ
+
+ def initialize(a, b, kind=:value, options={})
+ if kind == :value
+ a = IRT.yaml_dump a
+ b = IRT.yaml_dump b
+ end
+ @a = a
+ @b = b
+ @options = { :a_label => 'saved',
+ :b_label => 'actual',
+ :a_marker => '~',
+ :b_marker => '!' }.merge options
+ IRT::Differ::Format.options = @options
+ @diff = ::Differ.diff_by_line(@b, @a)
+ end
+
+ def output
+ out = "\n"
+ out << " = same ".reversed.bold.or('***** diff ')
+ out << " #{@options[:a_marker]} #{@options[:a_label]} ".diff_a_color.reversed.bold.
+ or(" (#{@options[:a_marker]} #{@options[:a_label]}) ")
+ out << " #{@options[:b_marker]} #{@options[:b_label]} ".diff_b_color.reversed.bold.
+ or(" (#{@options[:b_marker]} #{@options[:b_label]}) ")
+ out << "\n"
+ diff = @diff.format_as(IRT::Differ::Format)
+ out << diff.sub(/^\n/,'')
+ out << "\n"
+ out
+ end
+
+ module Format
+ extend self
+
+ attr_accessor :options
+
+ def format(change)
+ (change.is_a?(String) && as_same(change)) ||
+ (change.change? && as_change(change)) ||
+ (change.delete? && as_delete(change)) ||
+ (change.insert? && as_insert(change)) ||
+ ''
+ end
+
+ private
+
+ def process(string, mark='=', color=:null, bold=:null)
+ string.sub(/^\n/,'').split("\n").map do |s|
+ " #{mark} ".send(color).send(bold).reversed.or(" #{mark} ") + ' ' + s.send(color)
+ end.join("\n") + "\n"
+ end
+
+ def as_same(string)
+ process string
+ end
+
+ def as_insert(change)
+ process(change.insert(), options[:b_marker], :diff_b_color, :bold)
+ end
+
+ def as_delete(change)
+ process(change.delete(), options[:a_marker], :diff_a_color, :bold)
+ end
+
+ def as_change(change)
+ as_delete(change) << as_insert(change)
+ end
+ end
+
+ end
+end
+
+module Differ
+ class Diff
+
+ def format_as(f)
+ f = Differ.format_for(f)
+ @raw.inject('') do |sum, part|
+ part = f.format(part)
+ sum << part
+ end
+ end
+
+ end
+end
16 lib/irt/directives.rb
@@ -0,0 +1,16 @@
+require 'irt/directives/test'
+require 'irt/directives/session'
+
+module IRT
+ module Directives
+
+ extend self
+ extend Test
+ extend Session
+
+ def irt_at_exit(&block)
+ IRB.conf[:AT_EXIT] << proc(&block)
+ end
+
+ end
+end
79 lib/irt/directives/session.rb
@@ -0,0 +1,79 @@
+module IRT
+ module Directives
+ module Session
+
+ extend self
+
+ def irt(bind)
+ raise NotImplementedError, "You must pass binding" unless bind.is_a?(Binding)
+ new_session :binding, bind
+ end
+
+ # Evaluate a file as it were inserted at that line
+ # a relative file_path is considered to be relative to the including file
+ # i.e. '../file_in_the_same_dir.irt'
+ def eval_file(file_path)
+ parent_context = IRB.CurrentContext
+ new_io = IRB::FileInputMethod.new(File.expand_path(file_path, parent_context.io.file_name))
+ new_irb = IRB::Irb.new(parent_context.workspace, new_io)
+ new_irb.context.irb_name = File.basename(new_io.file_name)
+ new_irb.context.irb_path = new_io.file_name
+ eval(new_irb.context, :file)
+ IRT.irt_exit
+ end
+ alias_method :insert_file, :eval_file
+
+ private
+
+ def new_session(mode, obj=nil)
+ IRT.log.print if IRT.tail_on_irt
+ ws = obj ? IRB::WorkSpace.new(obj) : IRB.CurrentContext.workspace
+ new_irb = IRB::Irb.new(ws)
+ IRT.session_no += 1
+ main_name = mode == :inspect ?
+ IRB.CurrentContext.current_line.match(/^\s*(?:irb|irt|irt_inspect)\s+(.*)$/).captures[0].strip :
+ new_irb.context.workspace.main.to_s
+ main_name = main_name[0..30] + '...' if main_name.size > 30
+ new_irb.context.irb_name = "irt##{IRT.session_no}(#{main_name})"
+ new_irb.context.irb_path = "(irt##{IRT.session_no})"
+ set_binding_file_pointers(new_irb.context) if mode == :binding
+ eval(new_irb.context, mode)
+ end
+
+ def eval(new_context, mode)
+ new_context.parent_context = IRB.CurrentContext
+ new_context.set_last_value( IRB.CurrentContext.last_value ) unless (mode == :inspect || mode == :binding)
+ new_context.irt_mode = mode
+ IRB.conf[:MAIN_CONTEXT] = new_context
+ IRT.log.add_hunk
+ IRT.log.status << [new_context.irb_name, mode]
+ IRT.log.print_status unless mode == :file
+ catch(:IRB_EXIT) { new_context.irb.eval_input }
+ end
+
+ # used for open the last file for editing
+ def set_binding_file_pointers(context)
+ caller.each do |c|
+ file, line = c.sub(/:in .*$/,'').split(':', 2)
+ next if File.expand_path(file).match(/^#{IRT.lib_path}/) # exclude irt internal callers
+ context.binding_file = file
+ context.binding_line_no = line
+ break
+ end
+ end
+
+ def IRT.irt_exit
+ exiting_context = IRB.conf[:MAIN_CONTEXT]
+ resuming_context = exiting_context.parent_context
+ exiting_mode = exiting_context.irt_mode
+ resuming_context.set_last_value( exiting_context.last_value ) \
+ unless (exiting_mode == :inspect || exiting_mode == :binding)
+ IRT.log.pop_status
+ IRT.log.print_status unless resuming_context.irt_mode == :file
+ IRB.conf[:MAIN_CONTEXT] = resuming_context
+ IRT.log.add_hunk
+ end
+
+ end
+ end
+end
83 lib/irt/directives/test.rb
@@ -0,0 +1,83 @@
+module IRT
+ module Directives
+ module Test
+ extend self
+
+ @@tests = 0
+ @@oks = 0
+ @@diffs = 0
+ @@errors = 0
+ @@last_desc = nil
+
+ def desc(description)
+ @@last_desc = description
+ end
+
+ def _eql?(saved)
+ actual = IRB.CurrentContext.last_value
+ run_test(saved, actual, :value)
+ end
+ alias_method :test_value_eql?, :_eql?
+ alias_method :last_value_eql?, :_eql?
+
+ def _yaml_eql?(saved)
+ actual = IRT.yaml_dump(IRB.CurrentContext.last_value)
+ run_test(saved, actual, :yaml)
+ end
+ alias_method :last_yaml_eql?, :_yaml_eql?
+ alias_method :test_yaml_eql?, :_yaml_eql?
+
+ def test_summary #:nodoc:
+ return unless @@tests > 0
+ if @@tests == @@oks
+ str = @@tests == 1 ? " The TEST is OK! " : " All #{@@tests} TESTs are OK! "
+ puts str.ok_color.bold
+ else
+ puts "#{@@tests} TEST#{'s' unless @@tests == 1}: ".bold +
+ "#{@@oks} OK#{'s' unless @@oks == 1}, ".ok_color.bold +
+ "#{@@diffs} DIFF#{'s' unless @@diffs == 1}, ".diff_color.bold +
+ "#{@@errors} ERROR#{'s' unless @@errors == 1}.".error_color.bold
+ end
+ end
+
+ def load_helper_files
+ irt_file_path = Pathname.new($0).realpath
+ container_path = Pathname.getwd.parent
+ down_path = irt_file_path.relative_path_from container_path
+ down_path.dirname.descend do |p|
+ helper_path = container_path.join(p, 'irt_helper.rb')
+ begin
+ require helper_path
+ rescue LoadError
+ end
+ end
+ end
+
+ private
+
+ def run_test(saved, actual, kind)
+ context = IRB.CurrentContext
+ tno = '%3d' % @@tests += 1
+ d = @@last_desc || 'Test'
+ @@last_desc = nil
+ begin
+ if saved == actual
+ @@oks += 1
+ puts "#{tno}. OK!".ok_color.bold + " #{d}".ok_color
+ else
+ @@diffs += 1
+ puts "#{tno}. DIFFS!".diff_color.bold + %( #{d}\n ).diff_color +
+ %( at #{context.irb_path}: #{context.last_line_no} ).file_color.reversed.bold
+ puts IRT.differ.new(saved, actual, kind).output
+ IRT::Directives::Session.send(:new_session, :interactive) if IRT.irt_on_diffs
+ end
+ rescue Exception
+ @@errors += 1
+ puts "#{tno}. ERROR! ".error_color.bold + d.error_color
+ raise
+ end
+ end
+
+ end
+ end
+end
163 lib/irt/extensions/irb.rb
@@ -0,0 +1,163 @@
+require 'irb'
+require 'irb/context'
+require 'fileutils'
+
+require 'irt/commands/log'
+require 'irt/commands/test'
+require 'irt/commands/system'
+require 'irt/commands/misc'
+require 'irt/commands/help'
+
+module IRB #:nodoc:
+
+ module ExtendCommandBundle
+
+ include IRT::Commands::Log
+ include IRT::Commands::Test
+ include IRT::Commands::System
+ include IRT::Commands::Misc
+ include IRT::Commands::Help
+ include FileUtils
+
+ alias_method :xx, :abort
+ alias_method :qq, :abort
+
+ def irt(obj=nil)
+ mode = case obj
+ when nil
+ :interactive
+ when Binding
+ :binding
+ else
+ :inspect
+ end
+ raise NotImplementedError, "You cannot pass binding from here" if mode == :binding
+ raise NotImplementedError, "You cannot open another interactive session" \
+ if mode == :interactive && IRB.CurrentContext.irt_mode != :file
+ IRT::Directives::Session.send :new_session, mode, obj
+ end
+ alias_method :open_session, :irt # legacy method
+ alias_method :irb, :irt
+
+
+ %w[p y pp ap].each do |m|
+ define_method(m) do |*args|
+ args = [IRB.CurrentContext.last_value] if args.empty?
+ super *args
+ end
+ end
+
+ end
+
+ class Context
+
+ attr_accessor :parent_context, :current_line, :binding_file, :binding_line_no
+ attr_reader :last_line_no
+ attr_writer :irt_mode
+
+ def file_line_pointers
+ file = line = nil
+ c = self
+ until file && line
+ case c.irt_mode
+ when :binding
+ file = c.binding_file
+ line = c.binding_line_no
+ when :file
+ file = c.irb_path
+ line = c.last_line_no
+ end
+ c = c.parent_context
+ end
+ [file, line]
+ end
+
+ def irt_mode
+ @irt_mode ||= :file
+ end
+
+ alias_method :evaluate_and_set_last_value, :evaluate
+ def evaluate(line, line_no)
+ @current_line = line
+ if @exception_raised
+ IRT::Directives::Session.send(:new_session, :interactive) if irt_mode == :file
+ @exception_raised = false
+ end
+ log_file_line(line_no) if irt_mode == :file
+ begin
+ # skip setting last_value for non_setting_commands
+ if line =~ /^\s*(#{IRT.log.non_setting_commands * '|'})\b/
+ self.echo = false
+ res = @workspace.evaluate(self, line, irb_path, line_no)
+ if line =~ /^\s*(#{IRT.log.ignored_echo_commands * '|'})\b/
+ output_ignored_echo_value(res)
+ end
+ else
+ self.echo = irt_mode == :file ? false : true
+ evaluate_and_set_last_value(line, line_no)
+ end
+ rescue Exception
+ @exception_raised = true
+ print "\e[31m" if Colorer.color?
+ raise
+ else
+ log_session_line(line, line_no) unless irt_mode == :file
+ end
+ end
+
+ %w[prompt_i prompt_s prompt_c prompt_n].each do |m|
+ define_method(m) do
+ pr = instance_variable_get("@#{m}")
+ pr.send "#{irt_mode}_color"
+ end
+ end
+
+ def return_format(color=:actual_color)
+ @return_format.send color
+ end
+
+private
+
+ def log_file_line(line_no)
+ log_lines(@current_line, line_no)
+ end
+
+ def log_session_line(line, line_no)
+ # @current_line might get changed by the IRT::Commands::Test methods,
+ # while the line arg is the original command
+ segments = line.chomp.split("\n", -1)
+ last_segment = segments.pop
+ if last_segment.match(/^[ \t]*(#{IRT::Commands::Test.own_methods * '|'})\b/)
+ log_lines segments.map{|s| "#{s}\n"}.join, line_no
+ @last_line_no = @last_line_no ? @last_line_no + 1 : line_no
+ @current_line.each_line do |l|
+ IRT.log.add_line l, @last_line_no
+ end
+ else
+ log_lines(@current_line, line_no)
+ end
+ end
+
+ def log_lines(str, line_no)
+ i = -1
+ str.each_line do |l|
+ @last_line_no = line_no + i+=1
+ IRT.log.add_line l, @last_line_no
+ end
+ end
+
+ def output_ignored_echo_value(value)
+ if inspect?
+ printf return_format(:ignored_color), value.inspect
+ else
+ printf return_format, value
+ end
+ end
+ end
+
+ def IRB.irb_exit(irb, ret)
+ IRT.irt_exit
+ throw :IRB_EXIT, ret
+ end
+
+end
13 lib/irt/extensions/kernel.rb
@@ -0,0 +1,13 @@
+require 'stringio'
+module Kernel
+
+ def capture
+ out = StringIO.new
+ $stdout = out
+ yield
+ return out.string
+ ensure
+ $stdout = STDOUT
+ end
+
+end
7 lib/irt/extensions/object.rb
@@ -0,0 +1,7 @@
+class Object
+
+ def own_methods
+ methods - self.class.methods
+ end
+
+end
115 lib/irt/hunks.rb
@@ -0,0 +1,115 @@
+module IRT
+ class Log
+
+ class Hunk < Array
+
+ attr_reader :header, :color
+
+ def initialize(header)
+ @header = header
+ end
+
+ def add_line(content, line_no)
+ self << [content.chomp, line_no]
+ end
+
+ def render(wanted=nil)
+ return unless size > 0
+ Log.print_border
+ render_header
+ wanted ||= size
+ last(wanted).each do |content, line_no|
+ Log.print_border
+ render_line(content, line_no)
+ end
+ end
+
+ def lines_str
+ map{|content, line_no| content }.join("\n")
+ end
+
+ def last_line_no
+ last && last[1]
+ end
+
+ def header_name
+ @header
+ end
+
+ def render_header
+ puts " #{header_name} ".send(color).bold.reversed.or("***** #{header_name} *****")
+ end
+
+ def render_line(content, line_no)
+ lcontent = content.send(color)
+ lno = ('%3d ' % line_no).send(color).reversed
+ puts "#{lno} #{lcontent}"
+ end
+
+ def joinable?(other)
+ header == other.header
+ end
+
+ def inspect
+ %(<#{self.class.name} #{header_name}>)
+ end
+
+ end
+
+
+ class FileHunk < Hunk
+
+ def initialize(header=nil)
+ @color = :file_color
+ @header = header || IRB.CurrentContext.irb_path
+ end
+
+ def header_name
+ File.basename(@header)
+ end
+
+ end
+
+
+ class InteractiveHunk < Hunk
+
+ def initialize
+ @color = :interactive_color
+ @header = IRB.CurrentContext.irb_name
+ end
+
+ def add_line(content, line_no)
+ ignored = IRT.log.ignored_commands + %w[_]
+ return if content.match(/^\s*(#{ignored * '|'})\b/)
+ super
+ end
+
+ end
+
+
+ class InspectHunk < InteractiveHunk
+
+ def initialize
+ super
+ @color = :inspect_color
+ end
+
+ def add_line(content, line_no)
+ end
+
+ end
+
+ class BindingHunk < InteractiveHunk
+
+ def initialize
+ super
+ @color = :binding_color
+ end
+
+ def add_line(content, line_no)
+ end
+
+ end
+
+ end
+end
24 lib/irt/init.rb
@@ -0,0 +1,24 @@
+module IRB #:nodoc:
+ conf[:PROMPT][:IRT] = { :PROMPT_I => "%02n >> ",
+ :PROMPT_S => ' "> ',
+ :PROMPT_C => "%02n ?> ",
+ :PROMPT_N => "%02n -> ",
+ :RETURN => " => %s\n" }
+ conf[:PROMPT_MODE] = :IRT
+ conf[:ECHO] = false
+ conf[:VERBOSE] = false
+ conf[:AT_EXIT] << proc{IRT::Directives.test_summary}
+ conf[:AP_NAME] = 'irt'
+ conf[:SAVE_HISTORY] = 100
+ conf[:HISTORY_FILE] = File.expand_path '~/.irt-history'
+ conf[:AT_EXIT] << proc{ print "\e[0m" if Colorer.color? } # reset colors
+end
+
+def method_missing(method, *args, &block)
+ (IRB.CurrentContext.irt_mode == :file || method == :irt) && IRT::Directives.respond_to?(method) ?
+ IRT::Directives.send(method, *args, &block) :
+ super
+end
+
+IRT.directives.load_helper_files if IRT.autoload_helper_files
+
87 lib/irt/log.rb
@@ -0,0 +1,87 @@
+module IRT
+ class Log < Array
+
+ attr_accessor :ignored_commands, :ignored_echo_commands, :non_setting_commands, :tail_size, :status
+
+ def initialize(tail_size=10)
+ @ignored_echo_commands = FileUtils.own_methods
+ @ignored_commands = @ignored_echo_commands +
+ IRB::ExtendCommandBundle.instance_methods +
+ %w[ p pp ap y puts print irt irb ]
+ @non_setting_commands = @ignored_commands + IRT::Directives.own_methods
+ @tail_size = tail_size
+ @status = []
+ push FileHunk.new($0) # IRB.CurrentContext.irb_path is nil at this time
+ end
+
+ def add_hunk
+ mode = IRB.CurrentContext.irt_mode
+ push eval("#{mode.to_s.capitalize + 'Hunk'}.new")
+ end
+
+ def add_line(content, line_no)
+ last.add_line(content, line_no)
+ end
+
+ def self.print_border
+ print ' '.log_color.reversed.or('')
+ end
+
+ def print(limit=nil) # nil prints all
+ hist = dup
+ hist.delete_if{|h| h.empty? }
+ to_render = hist.reduce(self.class.new) do |his, hunk|
+ hu = hunk.dup
+ (his.empty? || !his.last.joinable?(hu)) ? (his << hu) : his.last.concat(hu)
+ his
+ end
+ to_print = 0
+ if limit.nil? || to_render.map(&:size).inject(:+) <= limit
+ to_print = to_render.map(&:size).inject(:+)
+ latest_lines = nil
+ tails_str = ''
+ else
+ rest = limit
+ to_render.reverse.each do |h|
+ to_print += 1
+ if rest > h.size
+ rest = rest - h.size
+ next
+ else
+ latest_lines = rest
+ break
+ end
+ end
+ tail_str = ' Tail'
+ end
+ to_render = to_render.last(to_print)
+ puts
+ puts " Virtual Log#{tail_str} ".log_color.bold.reversed.or('***** IRT Log *****')
+ to_render.shift.render(latest_lines)
+ to_render.each{|h| h.render }
+ puts
+ end
+
+ def print_status
+ segments = status.map {|name,mode| status_segment(name,mode)}
+ puts segments.join(">>".log_color.bold)
+ end
+
+ def pop_status
+ name, mode = status.pop
+ return if mode == :file
+ puts " <<".log_color.bold + status_segment(name, mode)
+ puts
+ end
+
+ def print_running(file)
+ puts " Running: #{file} ".file_color.reversed.bold.or("*** Runing: #{file} ***")
+ end
+
+ private
+
+ def status_segment(name, mode)
+ " #{name} ".send("#{mode}_color".to_sym).bold.reversed.or("[#{name}]")
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.