Permalink
Browse files

initial commit -- lost history before this commit

  • Loading branch information...
1 parent d50f4e2 commit 8e8e2c1b9e74777aeb79f72a3e0408781e3b3265 @davejacobs committed Sep 20, 2012
View
@@ -0,0 +1 @@
+Gemfile.lock
View
No changes.
View
@@ -0,0 +1,11 @@
+source :rubygems
+
+gem "awesome_print"
+gem "activesupport"
+gem "xml-simple"
+
+if RUBY_VERSION =~ /1\.9\.\d+/
+ gem "debugger"
+else
+ gem "ruby-debug"
+end
View
@@ -0,0 +1,96 @@
+Letters
+-------
+
+**Letters** is a little alphabetical library that makes debugging fun.
+
+### Debugging with the alphabet ###
+
+Most engineers have a limited toolkit for debugging. For some, it's actually just the `print` statement. For others, it's `print` + the debugger. Those tools are good, but they are the lowest level of how we can debug in Ruby. With Letters, I want to start to think about `print` and `debugger` as building blocks and not as structures in themselves.
+
+Letters aims sophisticated debugging as easy as typing out the alphabet.
+
+### Installation ###
+
+If you're using RubyGems, install Letters with:
+
+ gem install letters
+
+By default, requiring `"letters"` monkey-patches Object. It goes without saying that if you're using Letters in an app that has environments, you probably only want to use it in development.
+
+### Debugging with letters ###
+
+With Letters installed, you have a suite of methods available wherever you want them in your code -- at the end of any expression, in the middle of any pipeline. Most of these methods will output some form of information, though there are more sophisticated ones that pass around control of the application.
+
+Let's start with the `z` method as an example. It is the building block for all other letter methods. Add it to the end of any object to inspect it and return the same object:
+
+ { foo: "bar" }.z
+ # => { foo: "bar" }
+ # prints { foo: "bar" }
+
+That's simple enough, but not really useful. Things get interesting when you're in a pipeline:
+
+ words.grep(/interesting/).
+ map(&:downcase).
+ slice(0..2).
+ reject {|w| w.length < 3 }.
+ group_by(&:length).
+ values_at(5, 10)
+ join(", ")
+
+If I want to know the state of your code after lines 3 and 5, all I have to do is add `.z` to each one:
+
+ words.grep(/interesting/).
+ map(&:downcase).
+ slice(0..2).z.
+ reject {|w| w.length < 3 }.
+ group_by(&:length).z.
+ values_at(5, 10)
+ join(", ")
+
+Because the `z` method (and every other Letters method) returns the original object, introducing it is only ever for side effects -- they won't change the output of your code.
+
+This is significantly easier than breaking apart the pipeline using variable assignment or a hefty `tap` block.
+
+### The current API ###
+
+Here are my past and future plans for Letters. So far, I have implemented all debug functions below except those marked with an asterisk (\*):
+
+*(Note that if you don't want to patch `Hash` and `Array` with such small method names, you can explicitly require "letters/core_ext" instead. Letters::CoreExt will be available for you to `include` in any object or class you'd like. Requiring "letters" on its own will add the alphabet methods to `Hash` and `Array`.)*
+
+- *A* -
+- *B* - Beep (for coarse-grained time-analysis)
+- *C* - Print callstack
+- *D* - Enter the debugger
+- *D1/D2 pairs* - diff two data structures or objects
+- *E* - Empty check -- raise error if receiver is empty
+- *F* - Write to file (format can be default or specified)
+- *G* -
+- *H* -
+- *I* - Gain control from nearest transmitter (with value)\*
+- *J* - Jump into object's context (execute methods inside object's context)
+- *K* -
+- *L* - Logger (Rails or otherwise) -- only works if `logger` is instantiated
+- *M* - Mark with message to be printed when object is garbage-collected\*
+- *N* - Nil check -- raise error if receiver is nil
+- *O* - List all instantiated objects\*
+- *P* - Print to STDOUT (format can be default or specified)
+- *Q* -
+- *R* - RI documentation for class
+- *S* - Bump [safety level]()
+- *T* - [Taint object]()
+- *U* - Untaint object
+- *V* -
+- *W* -
+- *X* - Transmit control to nearest intercepter, passing object\*
+- *Y* -
+- *Z* -
+
+### Formats ###
+
+The following formats are going to be supported:
+
+- YAML
+- JSON
+- XML
+- Ruby Pretty Print
+- Ruby [Awesome print]()\*
View
@@ -0,0 +1,32 @@
+require "letters/helpers"
+require "letters/core_ext"
+
+module Letters
+ def self.object_for_diff=(object)
+ @@object = object
+ end
+
+ def self.object_for_diff
+ @@object if defined?(@@object)
+ end
+end
+
+class Array
+ include Letters::CoreExt
+end
+
+class Hash
+ include Letters::CoreExt
+end
+
+class String
+ include Letters::CoreExt
+end
+
+class NilClass
+ include Letters::CoreExt
+end
+
+# class Object
+ # include Letters::CoreExt
+# end
@@ -0,0 +1,132 @@
+require "letters/helpers"
+require "letters/empty_error"
+require "letters/nil_error"
+
+module Letters
+ module CoreExt
+ DELIM = '-' * 20
+
+ # Beep
+ def b(opts={})
+ tap do
+ $stdout.puts opts[:message] if opts[:message]
+ $stdout.puts "\a"
+ end
+ end
+
+ # Callstack
+ def c(opts={})
+ tap do
+ $stdout.puts opts[:message] if opts[:message]
+ $stdout.puts caller
+ end
+ end
+
+ # Debug
+ def d(opts={})
+ tap do
+ $stdout.puts opts[:message] if opts[:message]
+ Helpers.call_debugger
+ end
+ end
+
+ def d1
+ tap do |o|
+ Letters.object_for_diff = o
+ end
+ end
+
+ def d2(opts={})
+ require "awesome_print"
+ opts = { format: "ap" }.merge opts
+ tap do |o|
+ diff = Helpers.diff(Letters.object_for_diff, o)
+ Helpers.out diff, :format => opts[:format]
+ Letters.object_for_diff = nil
+ end
+ end
+
+ # Empty check
+ def e(opts={})
+ tap do |o|
+ raise EmptyError if o.empty?
+ end
+ end
+
+ # File
+ def f(opts={})
+ opts = { name: "log", format: "yaml" }.merge opts
+ tap do |o|
+ File.open(opts[:name], "w+") do |file|
+ Helpers.out o, :stream => file, :format => opts[:format]
+ end
+ end
+ end
+
+ # Jump
+ def j(&block)
+ tap do |o|
+ o.instance_eval &block
+ end
+ end
+
+ # Log
+ def l(opts={})
+ opts = { level: :info, format: :yaml }.merge opts
+ tap do |o|
+ begin
+ logger.send(opts[:level], opts[:message]) if opts[:message]
+ logger.send(opts[:level], Helpers.send(opts[:format], o))
+ rescue
+ $stdout.puts "[warning] No logger available"
+ end
+ end
+ end
+
+ # Nil check
+ def n(opts={})
+ tap do |o|
+ raise NilError if o.nil?
+ end
+ end
+
+ # Print to STDOUT
+ def p(opts={})
+ opts = { format: :ap }.merge opts
+ tap do |o|
+ Helpers.out o, :stream => $stdout, :format => opts[:format]
+ end
+ end
+
+ # RI
+ def r(opts={})
+ require "rdoc/ri/driver"
+ tap do |o|
+ $stdout.puts opts[:message] if opts[:message]
+ system "ri #{o.class}"
+ end
+ end
+
+ # Change safety level
+ def s(level=nil)
+ tap do
+ level ||= $SAFE + 1
+ Helpers.change_safety level
+ end
+ end
+
+ # Taint object
+ def t
+ tap do |o|
+ o.taint
+ end
+ end
+
+ # Untaint object
+ def u
+ tap do |o|
+ o.untaint
+ end
+ end
+ end
+end
@@ -0,0 +1,4 @@
+module Letters
+ class EmptyError < RuntimeError
+ end
+end
@@ -0,0 +1,59 @@
+module Letters
+ module Helpers
+ def self.diff(obj1, obj2)
+ case obj2
+ when Hash
+ {
+ removed: obj1.reject {|k, v| obj2.include? k },
+ added: obj2.reject {|k, v| obj1.include? k },
+ updated: obj2.select {|k, v| obj1.include?(k) && obj1[k] != v }
+ }
+ when String
+ diff(obj1.split("\n"), obj2.split("\n"))
+ else
+ {
+ removed: Array(obj1 - obj2),
+ added: Array(obj2 - obj1)
+ }
+ end
+ rescue
+ raise "cannot diff the two marked objects"
+ end
+
+ def self.out(object, opts={})
+ opts = { stream: $stdout, format: :ap }.merge opts
+ opts[:stream].puts Helpers.send(opts[:format], object)
+ end
+
+ def self.ap(object)
+ require "awesome_print"
+ object.awesome_inspect
+ end
+
+ def self.pp(object)
+ require "pp"
+ object.pretty_inspect
+ end
+
+ def self.xml(object)
+ require "xmlsimple"
+ XmlSimple.xml_out(object, { "KeepRoot" => true })
+ end
+
+ def self.yaml(object)
+ require "yaml"
+ object.to_yaml
+ end
+
+ # This provides a mockable method for testing
+ def self.call_debugger
+ require "ruby-debug"
+ debugger
+ nil
+ end
+
+ def self.change_safety(safety)
+ $SAFE = safety
+ end
+ end
+end
@@ -0,0 +1,4 @@
+module Letters
+ class NilError < RuntimeError
+ end
+end
Oops, something went wrong.

0 comments on commit 8e8e2c1

Please sign in to comment.