forked from davejacobs/letters
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit -- lost history before this commit
- Loading branch information
1 parent
d50f4e2
commit 8e8e2c1
Showing
12 changed files
with
635 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Gemfile.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]()\* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Letters | ||
class EmptyError < RuntimeError | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Letters | ||
class NilError < RuntimeError | ||
end | ||
end |
Oops, something went wrong.