letters 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +20 -0
- data/Gemfile +13 -0
- data/README.md +50 -0
- data/lib/letters.rb +24 -0
- data/lib/letters/assertion_error.rb +4 -0
- data/lib/letters/core_ext.rb +159 -0
- data/lib/letters/empty_error.rb +4 -0
- data/lib/letters/helpers.rb +110 -0
- data/lib/letters/nil_error.rb +4 -0
- data/lib/letters/patch.rb +14 -0
- data/lib/letters/time_formats.rb +5 -0
- data/lib/letters/version.rb +3 -0
- data/spec/letters/core_ext_spec.rb +250 -0
- data/spec/letters/helpers_spec.rb +105 -0
- data/spec/letters/patch_spec.rb +23 -0
- data/spec/letters/time_formats_spec.rb +11 -0
- data/spec/spec_helper.rb +3 -0
- metadata +145 -0
data/COPYING
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 David Jacobs <http://wit.io>
|
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 included
|
12
|
+
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 NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
19
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
20
|
+
DEALINGS IN THE SOFTWARE.
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
**Letters** is a little alphabetical library that makes sophisticated debugging easy & fun.
|
2
|
+
|
3
|
+
For many of us, troubleshooting begins and ends with the `print` statement. Others recruit the debugger, too. (Maybe you use `print` statements to look at changes over time but the debugger to focus on a small bit of code.) These tools are good, but they are the lowest level of how we can debug in Ruby. Letters leverages `print`, the debugger, control transfer, computer beeps, and other side-effects for more well-rounded visibility into code and state.
|
4
|
+
|
5
|
+
### Installation ###
|
6
|
+
|
7
|
+
If you're using RubyGems, install Letters with:
|
8
|
+
|
9
|
+
#!plain
|
10
|
+
gem install letters
|
11
|
+
|
12
|
+
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.
|
13
|
+
|
14
|
+
### Debugging with letters ###
|
15
|
+
|
16
|
+
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.
|
17
|
+
|
18
|
+
Let's start with the `p` method as an example. It is one of the most familiar methods. Calling it prints the receiver to STDOUT and returns the receiver:
|
19
|
+
|
20
|
+
#!ruby
|
21
|
+
{ foo: "bar" }.p
|
22
|
+
# => { foo: "bar" }
|
23
|
+
# prints { foo: "bar" }
|
24
|
+
|
25
|
+
That's simple enough, but not really useful. Things get interesting when you're in a pipeline:
|
26
|
+
|
27
|
+
#!ruby
|
28
|
+
words.grep(/interesting/).
|
29
|
+
map(&:downcase).
|
30
|
+
group_by(&:length).
|
31
|
+
values_at(5, 10)
|
32
|
+
slice(0..2).
|
33
|
+
join(", ")
|
34
|
+
|
35
|
+
If I want to know the state of your code after lines 3 and 5, all I have to do is add `.p` to each one:
|
36
|
+
|
37
|
+
#!ruby
|
38
|
+
words.grep(/interesting/).
|
39
|
+
map(&:downcase).
|
40
|
+
group_by(&:length).p.
|
41
|
+
values_at(5, 10)
|
42
|
+
slice(0..2).p.
|
43
|
+
join(", ")
|
44
|
+
|
45
|
+
Because the `p` method (and nearly every Letters method) returns the original object, introducing it is only ever for side effects -- it won't change the output of your code.
|
46
|
+
|
47
|
+
This is significantly easier than breaking apart the pipeline using variable assignment or a hefty `tap` block.
|
48
|
+
|
49
|
+
The `p` method takes options, too, so you can add a prefix message to the output or choose another output format -- like [YAML]() or [pretty print]().
|
50
|
+
|
data/lib/letters.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "letters/patch"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
module Letters
|
5
|
+
def self.object_for_diff=(object)
|
6
|
+
@@object = object
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.object_for_diff
|
10
|
+
@@object if defined?(@@object)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Letters.patch! Numeric
|
15
|
+
Letters.patch! Symbol
|
16
|
+
Letters.patch! String
|
17
|
+
Letters.patch! Regexp
|
18
|
+
Letters.patch! Array
|
19
|
+
Letters.patch! Set
|
20
|
+
Letters.patch! Hash
|
21
|
+
Letters.patch! Range
|
22
|
+
Letters.patch! NilClass
|
23
|
+
Letters.patch! TrueClass
|
24
|
+
Letters.patch! FalseClass
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require "letters/helpers"
|
2
|
+
require "letters/assertion_error"
|
3
|
+
require "letters/empty_error"
|
4
|
+
require "letters/nil_error"
|
5
|
+
require "letters/time_formats"
|
6
|
+
|
7
|
+
module Letters
|
8
|
+
module CoreExt
|
9
|
+
DELIM = "-" * 20
|
10
|
+
|
11
|
+
# Assert
|
12
|
+
def a(opts={}, &block)
|
13
|
+
opts = { error_class: AssertionError }.merge opts
|
14
|
+
tap do |o|
|
15
|
+
Helpers.message opts
|
16
|
+
if block_given? && !o.instance_eval(&block)
|
17
|
+
raise opts[:error_class]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Beep
|
23
|
+
def b
|
24
|
+
tap do
|
25
|
+
$stdout.print "\a"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Callstack
|
30
|
+
def c(opts={})
|
31
|
+
tap do
|
32
|
+
Helpers.message opts
|
33
|
+
Helpers.out Helpers.pretty_callstack(caller 4), opts
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Debug
|
38
|
+
def d
|
39
|
+
tap do
|
40
|
+
Helpers.call_debugger
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Diff 1
|
45
|
+
def d1
|
46
|
+
tap do |o|
|
47
|
+
Letters.object_for_diff = o
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Diff 2
|
52
|
+
def d2(opts={})
|
53
|
+
require "awesome_print"
|
54
|
+
opts = { format: "ap" }.merge opts
|
55
|
+
tap do |o|
|
56
|
+
diff = Helpers.diff(Letters.object_for_diff, o)
|
57
|
+
Helpers.message opts
|
58
|
+
Helpers.out diff, opts
|
59
|
+
Letters.object_for_diff = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Empty check
|
64
|
+
def e(opts={})
|
65
|
+
opts.merge! :error_class => EmptyError
|
66
|
+
tap do |o|
|
67
|
+
Helpers.message opts
|
68
|
+
o.a(opts) { !empty? }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# File
|
73
|
+
def f(opts={})
|
74
|
+
opts = { format: "yaml", name: "log" }.merge opts
|
75
|
+
tap do |o|
|
76
|
+
File.open(opts[:name], "w+") do |file|
|
77
|
+
# Override :stream
|
78
|
+
opts.merge! :stream => file
|
79
|
+
Helpers.message opts
|
80
|
+
Helpers.out o, opts
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Jump
|
86
|
+
def j(&block)
|
87
|
+
tap do |o|
|
88
|
+
o.instance_eval &block
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Log
|
93
|
+
def l(opts={})
|
94
|
+
opts = { level: "info", format: "yaml" }.merge opts
|
95
|
+
tap do |o|
|
96
|
+
begin
|
97
|
+
logger.send(opts[:level], opts[:message]) if opts[:message]
|
98
|
+
logger.send(opts[:level], Helpers.send(opts[:format], o))
|
99
|
+
rescue
|
100
|
+
$stdout.puts "[warning] No logger available"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Taint and untaint object
|
106
|
+
def m(taint=true)
|
107
|
+
tap do |o|
|
108
|
+
if taint
|
109
|
+
o.taint
|
110
|
+
else
|
111
|
+
o.untaint
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Nil check
|
117
|
+
def n(opts={})
|
118
|
+
opts.merge! :error_class => NilError
|
119
|
+
tap do |o|
|
120
|
+
o.a(opts) { !nil? }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Print to STDOUT
|
125
|
+
def p(opts={}, &block)
|
126
|
+
opts = { format: "ap", stream: $stdout }.merge opts
|
127
|
+
tap do |o|
|
128
|
+
Helpers.message opts
|
129
|
+
obj = block_given? ? o.instance_eval(&block) : o
|
130
|
+
Helpers.out obj, opts
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# RI
|
135
|
+
def r(method=nil)
|
136
|
+
tap do |o|
|
137
|
+
method_or_empty = method ? "##{method}" : method
|
138
|
+
system "ri #{o.class}#{method_or_empty}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Change safety level
|
143
|
+
def s(level=nil)
|
144
|
+
tap do
|
145
|
+
level ||= $SAFE + 1
|
146
|
+
Helpers.change_safety level
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Timestamp
|
151
|
+
def t(opts={})
|
152
|
+
opts = { time_format: "millis" }.merge opts
|
153
|
+
tap do
|
154
|
+
Helpers.message opts
|
155
|
+
Helpers.out Time.now.to_s(opts[:time_format].to_sym), opts
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "colorize"
|
2
|
+
|
3
|
+
module Letters
|
4
|
+
module Helpers
|
5
|
+
def self.diff(obj1, obj2)
|
6
|
+
case obj2
|
7
|
+
when Hash
|
8
|
+
{
|
9
|
+
removed: obj1.reject {|k, v| obj2.include? k },
|
10
|
+
added: obj2.reject {|k, v| obj1.include? k },
|
11
|
+
updated: obj2.select {|k, v| obj1.include?(k) && obj1[k] != v }
|
12
|
+
}
|
13
|
+
when String
|
14
|
+
diff(obj1.split("\n"), obj2.split("\n"))
|
15
|
+
else
|
16
|
+
{
|
17
|
+
removed: Array(obj1 - obj2),
|
18
|
+
added: Array(obj2 - obj1)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
rescue
|
22
|
+
raise "cannot diff the two marked objects"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.message(opts={})
|
26
|
+
out(opts[:message], opts) if opts[:message]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.out(object, opts={})
|
30
|
+
opts = { stream: $stdout, format: "string" }.merge opts
|
31
|
+
opts[:stream].puts Helpers.send(opts[:format], object)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.ap(object)
|
35
|
+
require "awesome_print"
|
36
|
+
object.awesome_inspect
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.json(object)
|
40
|
+
require "json"
|
41
|
+
object.to_json
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.pp(object)
|
45
|
+
require "pp"
|
46
|
+
object.pretty_inspect
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.string(object)
|
50
|
+
object.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.xml(object)
|
54
|
+
require "xmlsimple"
|
55
|
+
XmlSimple.xml_out(object, { "KeepRoot" => true })
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.yaml(object)
|
59
|
+
require "yaml"
|
60
|
+
object.to_yaml
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.pretty_callstack(callstack)
|
64
|
+
home = ENV["MY_RUBY_HOME"]
|
65
|
+
|
66
|
+
parsed = callstack.map do |entry|
|
67
|
+
line, line_no, method_name = entry.split ":"
|
68
|
+
|
69
|
+
{
|
70
|
+
line: line.gsub(home + "/", "").green,
|
71
|
+
line_no: line_no.yellow,
|
72
|
+
method_name: method_name.scan(/`([^\']+)'/).first.first.light_blue
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
headers = {
|
77
|
+
line: "Line".green,
|
78
|
+
line_no: "No.".yellow,
|
79
|
+
method_name: "Method".light_blue
|
80
|
+
}
|
81
|
+
|
82
|
+
parsed.unshift headers
|
83
|
+
|
84
|
+
longest_line =
|
85
|
+
parsed.map {|entry| entry[:line] }.
|
86
|
+
sort_by(&:length).
|
87
|
+
last
|
88
|
+
|
89
|
+
longest_method =
|
90
|
+
parsed.map {|entry| entry[:method_name] }.
|
91
|
+
sort_by(&:length).
|
92
|
+
last
|
93
|
+
|
94
|
+
formatter = "%#{longest_method.length}{method_name} %-#{longest_line.length}{line} %{line_no}\n"
|
95
|
+
|
96
|
+
parsed.map {|h| formatter % h }.join
|
97
|
+
end
|
98
|
+
|
99
|
+
# This provides a mockable method for testing
|
100
|
+
def self.call_debugger
|
101
|
+
require "ruby-debug"
|
102
|
+
debugger
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.change_safety(safety)
|
107
|
+
$SAFE = safety
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Letters
|
4
|
+
describe CoreExt do
|
5
|
+
let(:hash) { Hash.new }
|
6
|
+
|
7
|
+
before do
|
8
|
+
@old_dir = Dir.getwd
|
9
|
+
FileUtils.mkdir_p "tmp"
|
10
|
+
Dir.chdir "tmp"
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
Dir.chdir @old_dir
|
15
|
+
FileUtils.rm_rf "tmp"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "all letter methods but #e and #n return the original object" do
|
19
|
+
# Prevent output and debugging
|
20
|
+
Helpers.should_receive(:call_debugger).any_number_of_times
|
21
|
+
$stdout.should_receive(:puts).any_number_of_times
|
22
|
+
hash.should_receive(:system).any_number_of_times
|
23
|
+
Helpers.should_receive(:change_safety).any_number_of_times
|
24
|
+
|
25
|
+
("a".."z").to_a.reject do |letter|
|
26
|
+
letter =~ /[ejn]/
|
27
|
+
end.select do |letter|
|
28
|
+
hash.respond_to? letter
|
29
|
+
end.each do |letter|
|
30
|
+
hash.send(letter).should == hash
|
31
|
+
end
|
32
|
+
|
33
|
+
# Methods that can take a block
|
34
|
+
hash.j { nil }.should == hash
|
35
|
+
hash.p { nil }.should == hash
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#a (assert)" do
|
39
|
+
it "jumps into the receiver's calling context" do
|
40
|
+
lambda do
|
41
|
+
[1, 2, 3].a { count }
|
42
|
+
end.should_not raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raises a Letters::AssertionError if the block returns false" do
|
46
|
+
lambda do
|
47
|
+
[1, 2, 3].a { count > 3 }
|
48
|
+
end.should raise_error(Letters::AssertionError)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "raises a Letters::AssertionError if the block returns nil" do
|
52
|
+
lambda do
|
53
|
+
[1, 2, 3].a { nil }
|
54
|
+
end.should raise_error(Letters::AssertionError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "does nothing if the block returns a truthy value" do
|
58
|
+
[1, 2, 3].a { count < 4 }.should == [1, 2, 3]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#c (callstack)" do
|
63
|
+
it "outputs the current call trace then returns the object" do
|
64
|
+
$stdout.should_receive(:puts).with kind_of String
|
65
|
+
hash.c
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#d (debug)" do
|
70
|
+
it "enters the debugger and then returns the object" do
|
71
|
+
Helpers.should_receive(:call_debugger)
|
72
|
+
hash.d
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#d1, #d2 (smart object diff)" do
|
77
|
+
it "outputs the difference between two arrays" do
|
78
|
+
arr1, arr2 = [1, 2, 3], [3, 4, 5]
|
79
|
+
expected_diff = Helpers.diff(arr1, arr2)
|
80
|
+
$stdout.should_receive(:puts).with(expected_diff.awesome_inspect).once
|
81
|
+
|
82
|
+
arr1.d1.should == arr1
|
83
|
+
arr2.d2.should == arr2
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#e (empty check)" do
|
88
|
+
it "raises an error if the receiver is empty" do
|
89
|
+
lambda { "".e }.should raise_error(EmptyError)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "does nothing if the receiver is not empty" do
|
93
|
+
lambda { "string".e }.should_not raise_error
|
94
|
+
"string".n.should == "string"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#f (file)" do
|
99
|
+
describe "when no filename or output format are given" do
|
100
|
+
it "writes the object as YAML to a file named 'log'" do
|
101
|
+
File.exist?("log").should_not be_true
|
102
|
+
hash.f
|
103
|
+
File.exist?("log").should be_true
|
104
|
+
File.read("log").should == hash.to_yaml
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "when a file name, but no output format is given" do
|
109
|
+
it "writes the object as YAML to the named file" do
|
110
|
+
hash.f(:name => "object")
|
111
|
+
File.exist?("object").should be_true
|
112
|
+
File.read("object").should == hash.to_yaml
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "when an output format, but no file name is given" do
|
117
|
+
it "writes the object as that format to a file named 'log'" do
|
118
|
+
hash.f(:format => :ap)
|
119
|
+
File.exist?("log").should be_true
|
120
|
+
File.read("log").chomp.should == hash.awesome_inspect
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "#j (jump)" do
|
126
|
+
it "jumps into the object's context" do
|
127
|
+
a = nil
|
128
|
+
hash.j { a = count }
|
129
|
+
a.should == 0
|
130
|
+
end
|
131
|
+
|
132
|
+
it "allows for IO, even in object context" do
|
133
|
+
$stdout.should_receive(:puts).with(0)
|
134
|
+
hash.j { puts count }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "#l (log)" do
|
139
|
+
it "logs the object if a logger is present and then returns the object" do
|
140
|
+
logger = double 'logger'
|
141
|
+
logger.should_receive(:info).with(hash.to_yaml)
|
142
|
+
hash.should_receive(:logger).and_return(logger)
|
143
|
+
hash.l
|
144
|
+
end
|
145
|
+
|
146
|
+
it "prints an warning if a logger is not present and then returns the object" do
|
147
|
+
$stdout.should_receive(:puts).with("[warning] No logger available")
|
148
|
+
hash.l
|
149
|
+
end
|
150
|
+
|
151
|
+
it "logs the object if a logger is present and then returns the object" do
|
152
|
+
logger = double 'logger'
|
153
|
+
logger.should_receive(:info).never
|
154
|
+
logger.should_receive(:error).with(hash.to_yaml)
|
155
|
+
hash.should_receive(:logger).and_return(logger)
|
156
|
+
hash.l(:level => "error")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "#m (mark object as tainted/untainted)" do
|
161
|
+
it "with no argument or `true`, marks the receiver as tainted" do
|
162
|
+
lambda do
|
163
|
+
hash.m
|
164
|
+
end.should change { hash.tainted? }.from(false).to(true)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "when passed `false`, marks the receiver as untainted" do
|
168
|
+
hash.taint
|
169
|
+
lambda do
|
170
|
+
hash.m(false)
|
171
|
+
end.should change { hash.tainted? }.from(true).to(false)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "#n (nil check)" do
|
176
|
+
it "raises an error if the receiver is nil" do
|
177
|
+
lambda { nil.n }.should raise_error(NilError)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "does nothing if the receiver is not nil" do
|
181
|
+
lambda { hash.n }.should_not raise_error
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "#p (print)" do
|
186
|
+
describe "when no format is given" do
|
187
|
+
it "writes the object as awesome_print to STDOUT" do
|
188
|
+
$stdout.should_receive(:puts).with(hash.awesome_inspect)
|
189
|
+
hash.p
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "when a format is given" do
|
194
|
+
it "writes the object as that format to STDOUT" do
|
195
|
+
$stdout.should_receive(:puts).with(hash.to_yaml)
|
196
|
+
hash.p(:format => :yaml)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "when a block is given" do
|
201
|
+
it "write the result of the block, executed in the object's context" do
|
202
|
+
$stdout.should_receive(:puts).with(hash.length.awesome_inspect)
|
203
|
+
hash.p { length }.should == hash
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "#r (ri)" do
|
209
|
+
it "displays RI information for the receiver's class, if available" do
|
210
|
+
hash.should_receive(:system).with("ri Hash")
|
211
|
+
hash.r
|
212
|
+
end
|
213
|
+
|
214
|
+
it "can narrow its scope to a single method" do
|
215
|
+
hash.should_receive(:system).with("ri Hash#new")
|
216
|
+
hash.r(:new)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe "#s (safety)" do
|
221
|
+
it "without argument, bumps the safety level by one" do
|
222
|
+
Helpers.should_receive(:change_safety).with(1)
|
223
|
+
hash.s
|
224
|
+
end
|
225
|
+
|
226
|
+
it "bumps the safety level to the specified level if possible" do
|
227
|
+
Helpers.should_receive(:change_safety).with(4)
|
228
|
+
hash.s(4)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "throws an exception if the specified level is invalid" do
|
232
|
+
# Simulate changing safety level from higher level
|
233
|
+
Helpers.should_receive(:change_safety).with(3).and_raise
|
234
|
+
lambda do
|
235
|
+
hash.s(3)
|
236
|
+
end.should raise_error
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe "#t (timestamp)" do
|
241
|
+
it "without :stream, prints the current time to STDOUT" do
|
242
|
+
time = Time.now
|
243
|
+
Timecop.freeze(time) do
|
244
|
+
$stdout.should_receive(:puts).with(time.to_s(:millis)).twice
|
245
|
+
{}.t.select {|k,v| k =~ /foo/ }.t
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Letters
|
4
|
+
describe Helpers do
|
5
|
+
let(:hash) { Hash.new }
|
6
|
+
|
7
|
+
describe ".diff" do
|
8
|
+
it "returns the difference between two arrays" do
|
9
|
+
array1 = [1, 2, 3]
|
10
|
+
array2 = [3, 4, 5]
|
11
|
+
Helpers.diff(array1, array2).should == {
|
12
|
+
removed: [1, 2],
|
13
|
+
added: [4, 5]
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns the difference between two hashes" do
|
18
|
+
hash1 = { a: "foo", b: "bar" }
|
19
|
+
hash2 = { b: "baz", c: "bat" }
|
20
|
+
|
21
|
+
Helpers.diff(hash1, hash2).should == {
|
22
|
+
removed: { a: "foo" },
|
23
|
+
added: { c: "bat" },
|
24
|
+
updated: { b: "baz" }
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the difference between two sets" do
|
29
|
+
set1 = Set.new([1, 2, 3])
|
30
|
+
set2 = Set.new([3, 4, 5])
|
31
|
+
Helpers.diff(set1, set2).should == {
|
32
|
+
removed: [1, 2],
|
33
|
+
added: [4, 5]
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns the difference between two strings" do
|
38
|
+
string1 = "Line 1\nLine 2"
|
39
|
+
string2 = "Line 1 modified\nLine 2\nLine 3"
|
40
|
+
Helpers.diff(string1, string2).should == {
|
41
|
+
removed: ["Line 1"],
|
42
|
+
added: ["Line 1 modified", "Line 3"]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns the difference between two objects of the same hierarchy" do
|
47
|
+
DiffableClass = Class.new do
|
48
|
+
attr_accessor :vals
|
49
|
+
|
50
|
+
def initialize(vals)
|
51
|
+
self.vals = vals
|
52
|
+
end
|
53
|
+
|
54
|
+
def -(other)
|
55
|
+
vals - other.vals
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
dc1 = DiffableClass.new([1, 2, 3])
|
60
|
+
dc2 = DiffableClass.new([3, 4, 5])
|
61
|
+
Helpers.diff(dc1, dc2).should == {
|
62
|
+
removed: [1, 2],
|
63
|
+
added: [4, 5]
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
it "throws an exception if the objects are not of the same type" do
|
68
|
+
lambda do
|
69
|
+
Helpers.diff(Object.new, Hash.new)
|
70
|
+
end.should raise_error
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe ".awesome_print" do
|
75
|
+
it "outputs the YAML representation of the object then returns the object" do
|
76
|
+
Helpers.ap(hash).should == "{}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ".pretty_print" do
|
81
|
+
it "outputs the pretty-print representation of the object and then returns the object" do
|
82
|
+
Helpers.pp(hash).should == "{}\n"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe ".string" do
|
87
|
+
it "outputs the representation of the object returned by #to_s" do
|
88
|
+
complex_hash = { foo: "bar", baz: [1, 2, 3] }
|
89
|
+
Helpers.string(complex_hash).should == complex_hash.to_s
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe ".xml" do
|
94
|
+
it "outputs the XML representation of the object and then returns the object" do
|
95
|
+
Helpers.xml(hash).should == "<opt></opt>\n"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe ".yaml" do
|
100
|
+
it "outputs the YAML representation of the object and then returns the object" do
|
101
|
+
Helpers.yaml(hash).strip.should == "--- {}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Letters
|
4
|
+
describe ".patch!" do
|
5
|
+
before do
|
6
|
+
# Hide output
|
7
|
+
$stdout.should_receive(:puts)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "adds Letters::CoreExt to classes" do
|
11
|
+
Klass = Class.new
|
12
|
+
Letters.patch! Klass
|
13
|
+
k = Klass.new
|
14
|
+
k.p.should == k
|
15
|
+
end
|
16
|
+
|
17
|
+
it "adds Letters::CoreExt to objects" do
|
18
|
+
obj = Object.new
|
19
|
+
Letters.patch! obj
|
20
|
+
obj.p.should == obj
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Letters
|
4
|
+
describe "time formats" do
|
5
|
+
it "allows for easy manipulation of timestamp displays" do
|
6
|
+
Time.utc(2012, "jan", 1, 13, 15, 1).tap do |time|
|
7
|
+
time.to_s(:millis).should == "01/01/2012 13:15:01.000"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: letters
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Jacobs
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-25 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: awesome_print
|
16
|
+
requirement: &70179670061620 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70179670061620
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
requirement: &70179670061180 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70179670061180
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: xml-simple
|
38
|
+
requirement: &70179670060760 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70179670060760
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: colorize
|
49
|
+
requirement: &70179670060340 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70179670060340
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: debugger
|
60
|
+
requirement: &70179670059920 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70179670059920
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: timecop
|
71
|
+
requirement: &70179670091740 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70179670091740
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rspec
|
82
|
+
requirement: &70179670091320 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70179670091320
|
91
|
+
description: Letters brings Ruby debugging into the 21st century. It leverages print,
|
92
|
+
the debugger, control transfer, even computer beeps to let you see into your code's
|
93
|
+
state.
|
94
|
+
email: david@wit.io
|
95
|
+
executables: []
|
96
|
+
extensions: []
|
97
|
+
extra_rdoc_files: []
|
98
|
+
files:
|
99
|
+
- lib/letters/assertion_error.rb
|
100
|
+
- lib/letters/core_ext.rb
|
101
|
+
- lib/letters/empty_error.rb
|
102
|
+
- lib/letters/helpers.rb
|
103
|
+
- lib/letters/nil_error.rb
|
104
|
+
- lib/letters/patch.rb
|
105
|
+
- lib/letters/time_formats.rb
|
106
|
+
- lib/letters/version.rb
|
107
|
+
- lib/letters.rb
|
108
|
+
- README.md
|
109
|
+
- COPYING
|
110
|
+
- Gemfile
|
111
|
+
- spec/letters/core_ext_spec.rb
|
112
|
+
- spec/letters/helpers_spec.rb
|
113
|
+
- spec/letters/patch_spec.rb
|
114
|
+
- spec/letters/time_formats_spec.rb
|
115
|
+
- spec/spec_helper.rb
|
116
|
+
homepage: http://github.com/davejacobs/letters
|
117
|
+
licenses: []
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 1.3.6
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 1.8.15
|
137
|
+
signing_key:
|
138
|
+
specification_version: 3
|
139
|
+
summary: A tiny debugging library for Ruby
|
140
|
+
test_files:
|
141
|
+
- spec/letters/core_ext_spec.rb
|
142
|
+
- spec/letters/helpers_spec.rb
|
143
|
+
- spec/letters/patch_spec.rb
|
144
|
+
- spec/letters/time_formats_spec.rb
|
145
|
+
- spec/spec_helper.rb
|