letters 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,13 @@
1
+ source :rubygems
2
+
3
+ gem "awesome_print"
4
+ gem "activesupport"
5
+ gem "xml-simple"
6
+ gem "timecop"
7
+ gem "colorize"
8
+
9
+ if RUBY_VERSION =~ /1\.9\.\d+/
10
+ gem "debugger"
11
+ else
12
+ gem "ruby-debug"
13
+ end
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ **Letters** is a little alphabetical library that makes sophisticated debugging easy &amp; 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,4 @@
1
+ module Letters
2
+ class AssertionError < RuntimeError
3
+ end
4
+ end
@@ -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,4 @@
1
+ module Letters
2
+ class EmptyError < RuntimeError
3
+ end
4
+ 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,4 @@
1
+ module Letters
2
+ class NilError < RuntimeError
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ require "letters/core_ext"
2
+
3
+ module Letters
4
+ def self.patch!(obj)
5
+ case obj
6
+ when Class
7
+ obj.instance_eval do
8
+ include Letters::CoreExt
9
+ end
10
+ when Object
11
+ obj.extend Letters::CoreExt
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ require "active_support/core_ext/date_time/conversions"
2
+
3
+ module Letters
4
+ Time::DATE_FORMATS[:millis] = "%m/%d/%Y %H:%M:%S.%L"
5
+ end
@@ -0,0 +1,3 @@
1
+ module Letters
2
+ VERSION = "0.1.0"
3
+ 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
@@ -0,0 +1,3 @@
1
+ require "letters"
2
+ require "awesome_print"
3
+ require "timecop"
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