lines 0.2.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +6 -14
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/CHANGELOG.md +16 -0
- data/Gemfile +1 -8
- data/LICENSE.txt +1 -1
- data/README.md +21 -64
- data/examples/cli.rb +17 -0
- data/lib/lines.rb +111 -395
- data/lib/lines/common.rb +37 -0
- data/lib/lines/generator.rb +168 -0
- data/lib/lines/parser.rb +182 -0
- data/lib/lines/version.rb +1 -1
- data/lines.gemspec +10 -8
- data/spec/lines_generator_bench.rb +45 -0
- data/spec/lines_generator_spec.rb +65 -0
- data/spec/lines_parser_bench.rb +50 -0
- data/spec/{lines_loader_spec.rb → lines_parser_spec.rb} +28 -7
- data/spec/spec_helper.rb +2 -6
- metadata +57 -28
- data/lib/lines/active_record.rb +0 -70
- data/lib/lines/loader.rb +0 -229
- data/lib/lines/logger.rb +0 -61
- data/lib/lines/rack_logger.rb +0 -39
- data/spec/bench.rb +0 -46
- data/spec/lines_spec.rb +0 -190
- data/spec/parse-bench +0 -26
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
NTI3YzJhY2M5YjMzZDE2NWI3NjdjYmE0MDZjOWU0MDg1MjY0ZTY2NmYzYThh
|
10
|
-
ZjkxNTQ4ZjZjMzQ0OGRiMTZmNTc5ZDhmYjI3ODIwMDg4MDg0NTFmYWE4OWU3
|
11
|
-
MmUyYzgxOTU0MjQ0N2NhNDFmNThkZmU3Y2NjOWMyZWUxMWIwMmU=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
Njc3MjM5ZjJmMmY0MWE4OWM1ZmFlYmUyNzdlMmQ0OWFmZTVmZTllZjc0YWRk
|
14
|
-
ZDczMjM4YmEwYTMwYTU1OTU5Y2E1OWIwNjAzMGNiYTc1NTdiNGJhNWUzZjE5
|
15
|
-
ZjhiNDlhYWU1ZGM4MTBmZTQ3YmY4ZmQyNjMxOWU5NDMyZjZiMWU=
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 938854f5278c30f9d2cceb17de859d5293f1eba3
|
4
|
+
data.tar.gz: 76e588d5c75ea0df0cbd138e8c6dce199b27ea85
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a7fcb7a10b27c6900c0bda6365081cefe0b968c5551089f0741490101b4ec88002694f08c145700cabea3433a28e427d08dd3bd54c268d844ebffd0b6ded74a7
|
7
|
+
data.tar.gz: 113168e5ba0ece27faaa8eba50babd78487b74804d27e4a02d1893cae3574a6bdf90cad8bab2a18953bedddbe773b0f8f0f3395bb18c2d680ed0d68b56fd4cfe
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,20 @@
|
|
1
1
|
|
2
|
+
0.9.1 / 2014-11-12
|
3
|
+
==================
|
4
|
+
|
5
|
+
Bump after broken release process.
|
6
|
+
|
7
|
+
0.9.0 / 2014-11-12
|
8
|
+
==================
|
9
|
+
|
10
|
+
Moved the logging part to a separate library called u-log.
|
11
|
+
http://rubygems.org/gems/u-log
|
12
|
+
|
13
|
+
* REMOVED: All logging parts
|
14
|
+
* NEW: max_bytesize directive to limit line length
|
15
|
+
* FIX: BasicObject serialization for ruby 2.1+
|
16
|
+
* Tons of cleanup
|
17
|
+
|
2
18
|
0.2.0 / 2013-07-15
|
3
19
|
==================
|
4
20
|
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,90 +1,47 @@
|
|
1
1
|
Lines - structured logs for humans
|
2
2
|
==================================
|
3
3
|
[](https://travis-ci.org/zimbatm/lines-ruby)
|
5
5
|
|
6
|
-
|
6
|
+
A ruby implementation of the
|
7
7
|
[lines](https://github.com/zimbatm/lines) format.
|
8
8
|
|
9
|
-
* Log everything in development AND production.
|
10
|
-
* Logs should be easy to read, grep and parse.
|
11
|
-
* Logging something should never fail.
|
12
|
-
* Let the system handle the storage. Write to syslog or STDERR.
|
13
|
-
* No log levels necessary. Just log whatever you want.
|
14
|
-
|
15
9
|
STATUS: WORK IN PROGRESS
|
16
10
|
========================
|
17
11
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
Lines.id is a unique ID generator that seems quite handy but I'm not sure if
|
22
|
-
it should be part of the lib or not.
|
23
|
-
|
24
|
-
It would be nice to expose a method that resolves a context into a hash. It's
|
25
|
-
useful to share the context with other tools like an error reporter. Btw,
|
26
|
-
Sentry/Raven is great.
|
27
|
-
|
28
|
-
There is a parser in the lib but no credible direct consumption path.
|
29
|
-
|
30
|
-
Quick intro
|
31
|
-
-----------
|
12
|
+
Example
|
13
|
+
-------
|
32
14
|
|
33
15
|
```ruby
|
34
16
|
require 'lines'
|
35
17
|
|
36
|
-
|
37
|
-
Lines.use($stdout, Syslog)
|
18
|
+
Lines.dump(foo: 3) #=> "foo=3"
|
38
19
|
|
39
|
-
|
40
|
-
|
20
|
+
Lines.load("foo=3") #=> {"foo"=>3}
|
21
|
+
```
|
41
22
|
|
42
|
-
|
43
|
-
|
23
|
+
Uses
|
24
|
+
----
|
44
25
|
|
45
|
-
|
46
|
-
# a hash
|
47
|
-
Lines.log("Hey", count: 3) # logs: at=2013-07-14T14:19:28Z msg=Hey count=3
|
26
|
+
CLI pipes format
|
48
27
|
|
49
|
-
|
50
|
-
class MyClass < ActiveRecord::Base
|
51
|
-
attr_reader :lines
|
52
|
-
def initialize
|
53
|
-
@lines = Lines.context(my_class_id: self.id)
|
54
|
-
end
|
28
|
+
Structued logging
|
55
29
|
|
56
|
-
def do_something
|
57
|
-
lines.log("Something happened")
|
58
|
-
# logs: at=2013-07-14T14:19:28Z msg='Something happeend' my_class_id: 2324
|
59
|
-
end
|
60
|
-
end
|
61
|
-
```
|
62
30
|
|
63
|
-
|
64
|
-
|
31
|
+
Generator TODO
|
32
|
+
--------------
|
65
33
|
|
66
|
-
|
67
|
-
* Thread safe (if the IO#write is)
|
68
|
-
* Designed to not raise exceptions (unless it's an IO issue)
|
69
|
-
* Lines.logger is a backward-compatible Logger in case you want to retrofit
|
70
|
-
* require "lines/active_record" for sane ActiveRecord logs
|
71
|
-
* "lines/rack_logger" is a logging middleware for Rack
|
72
|
-
* Lines.load and Lines.dump to parse and generate 'lines'
|
34
|
+
Add a max_length option
|
73
35
|
|
74
|
-
|
75
|
-
https://github.com/zimbatm/lograge/tree/lines-output
|
36
|
+
Make sure the output is encoded as a UTF-8 string
|
76
37
|
|
77
|
-
|
78
|
-
|
38
|
+
Parser TODO
|
39
|
+
-----------
|
79
40
|
|
80
|
-
|
81
|
-
easy to put too much data.
|
41
|
+
Implement the max_nesting option
|
82
42
|
|
83
|
-
|
84
|
-
|
43
|
+
Different parsing modes. Strict and non-strict. Type templates.
|
44
|
+
|
45
|
+
Multi-line parsing.
|
85
46
|
|
86
|
-
Inspired by
|
87
|
-
-----------
|
88
47
|
|
89
|
-
* Scrolls : https://github.com/asenchi/scrolls
|
90
|
-
* Lograge : https://github.com/roidrage/lograge
|
data/examples/cli.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Why use -- prefixes in command-line programs ?
|
3
|
+
#
|
4
|
+
# Here's how we can use lines for a nicer experience:
|
5
|
+
#
|
6
|
+
# ./cli.rb foo=333 bar=baz 'xxx=[3 4 #t]'
|
7
|
+
#
|
8
|
+
# Actually zsh makes it less friendly because [] and {} are interpreted
|
9
|
+
#
|
10
|
+
# FIXME: cli.rb foo='a b'
|
11
|
+
# FIXME: cli.rb --foo=abc
|
12
|
+
|
13
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
14
|
+
require 'lines'
|
15
|
+
p ARGV
|
16
|
+
args = Lines.load(ARGV.join(' '))
|
17
|
+
p args
|
data/lib/lines.rb
CHANGED
@@ -1,413 +1,129 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
# Serializing object. Responds to #dump(hash)
|
43
|
-
def dumper; @dumper ||= Dumper.new end
|
44
|
-
|
45
|
-
# Returns a backward-compatibile Logger
|
46
|
-
def logger
|
47
|
-
@logger ||= (
|
48
|
-
require 'lines/logger'
|
49
|
-
Logger.new(self)
|
50
|
-
)
|
51
|
-
end
|
52
|
-
|
53
|
-
# Used to configure lines.
|
54
|
-
#
|
55
|
-
# outputs - allows any kind of IO or Syslog
|
56
|
-
#
|
57
|
-
# Usage:
|
58
|
-
#
|
59
|
-
# Lines.use(Syslog, $stderr, at: proc{ Time.now })
|
60
|
-
def use(*outputs)
|
61
|
-
if outputs.last.kind_of?(Hash)
|
62
|
-
@global = outputs.pop
|
63
|
-
else
|
64
|
-
@global = {}
|
65
|
-
end
|
66
|
-
@outputters = outputs.flatten.map{|o| to_outputter o}
|
67
|
-
end
|
68
|
-
|
69
|
-
# The main function. Used to record objects in the logs as lines.
|
70
|
-
#
|
71
|
-
# obj - a ruby hash. coerced to +{"msg"=>obj}+ otherwise
|
72
|
-
# args - complementary values to put in the line
|
73
|
-
def log(obj, args={})
|
74
|
-
obj = prepare_obj(obj, args)
|
75
|
-
@outputters.each{|out| out.output(dumper, obj) }
|
76
|
-
nil
|
77
|
-
end
|
78
|
-
|
79
|
-
# Add data to the logs
|
80
|
-
#
|
81
|
-
# data - a ruby hash
|
82
|
-
#
|
83
|
-
# return a Context instance
|
84
|
-
def context(data={})
|
85
|
-
new_context = Context.new ensure_hash!(data)
|
86
|
-
yield new_context if block_given?
|
87
|
-
new_context
|
88
|
-
end
|
89
|
-
|
90
|
-
def ensure_hash!(obj) # :nodoc:
|
91
|
-
return {} unless obj
|
92
|
-
return obj if obj.kind_of?(Hash)
|
93
|
-
return obj.to_h if obj.respond_to?(:to_h)
|
94
|
-
{msg: obj}
|
95
|
-
end
|
96
|
-
|
97
|
-
# Parses a lines-formatted string
|
98
|
-
def load(string)
|
99
|
-
loader.load(string)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Generates a lines-formatted string from the given object
|
103
|
-
def dump(obj)
|
104
|
-
dumper.dump ensure_hash!(obj)
|
105
|
-
end
|
106
|
-
|
107
|
-
protected
|
108
|
-
|
109
|
-
def prepare_obj(obj, args={})
|
110
|
-
if obj.kind_of?(Exception)
|
111
|
-
ex = obj
|
112
|
-
obj = {ex: ex.class, msg: ex.to_s}
|
113
|
-
if ex.respond_to?(:backtrace) && ex.backtrace
|
114
|
-
obj[:backtrace] = ex.backtrace
|
115
|
-
end
|
116
|
-
else
|
117
|
-
obj = ensure_hash!(obj)
|
118
|
-
end
|
119
|
-
|
120
|
-
args = ensure_hash!(args)
|
121
|
-
|
122
|
-
g = global.inject({}) do |h, (k,v)|
|
123
|
-
h[k] = (v.respond_to?(:call) ? v.call : v) rescue $!
|
124
|
-
h
|
125
|
-
end
|
126
|
-
|
127
|
-
g.merge(obj.merge(args))
|
128
|
-
end
|
129
|
-
|
130
|
-
def to_outputter(out)
|
131
|
-
return out if out.respond_to?(:output)
|
132
|
-
return StreamOutputter.new(out) if out.respond_to?(:write)
|
133
|
-
return SyslogOutputter.new if out == ::Syslog
|
134
|
-
raise ArgumentError, "unknown outputter #{out.inspect}"
|
135
|
-
end
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'lines/parser'
|
4
|
+
require 'lines/generator'
|
5
|
+
require 'lines/version'
|
6
|
+
|
7
|
+
# Lines is an opinionated structured log format
|
8
|
+
module Lines; extend self
|
9
|
+
# The global default options for the Lines.parse and Lines.load method:
|
10
|
+
# symbolize_names: false
|
11
|
+
attr_reader :parse_default_options
|
12
|
+
@parse_default_options = {
|
13
|
+
symbolize_names: false,
|
14
|
+
}
|
15
|
+
|
16
|
+
# The global default options for the Lines.dump method:
|
17
|
+
# max_nesting: 4
|
18
|
+
# max_size: 2048,
|
19
|
+
attr_reader :generate_default_options
|
20
|
+
@generate_default_options = {
|
21
|
+
max_nesting: 4,
|
22
|
+
max_bytesize: 2048,
|
23
|
+
}
|
24
|
+
|
25
|
+
attr_accessor :parser
|
26
|
+
@parser = Parser
|
27
|
+
|
28
|
+
attr_accessor :generator
|
29
|
+
@generator = Generator
|
30
|
+
|
31
|
+
# Parse the Lines string _source_ into a Ruby data structure and return it.
|
32
|
+
#
|
33
|
+
# _options_ can have the following keys:
|
34
|
+
# * *symbolize_names*: If set to true, returns symbols for the names
|
35
|
+
# (keys) in a Lines object. Otherwise strings are returned. Strings are
|
36
|
+
# the default.
|
37
|
+
def parse(source, options = {})
|
38
|
+
opts = Lines.parse_default_options.merge(options.to_hash)
|
39
|
+
@parser.parse(source.to_str, opts)
|
136
40
|
end
|
137
41
|
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
42
|
+
# Generate a Lines string from the Ruby data structure _obj_ and return
|
43
|
+
# it.
|
44
|
+
#
|
45
|
+
# _options_ can have the following keys:
|
46
|
+
# * *max_nesting*: The maximum depth of nesting allowed in the data
|
47
|
+
# structures from which Lines is to be generated. It defaults to 4.
|
48
|
+
# * *max_bytesize*: The maximum number of bytes for a line to be constructed
|
49
|
+
# on. It defaults to 2048.
|
50
|
+
def generate(obj, options = {})
|
51
|
+
opts = Lines.generate_default_options.merge(options.to_hash)
|
52
|
+
@generator.generate(obj.to_hash, opts)
|
150
53
|
end
|
151
54
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
55
|
+
|
56
|
+
# Load a ruby data structure from a Lines _source_ and return it. A source can
|
57
|
+
# either be a string-like object, an IO-like object, or an object responding
|
58
|
+
# to the read method. If _proc_ was given, it will be called with any nested
|
59
|
+
# Ruby object as an argument recursively in depth first order. To modify the
|
60
|
+
# default options pass in the optional _options_ argument as well.
|
61
|
+
#
|
62
|
+
# The default options for the parser can be changed via the
|
63
|
+
# parse_default_options method.
|
64
|
+
#
|
65
|
+
# This method is part of the implementation of the load/dump interface of
|
66
|
+
# Marshal and YAML.
|
67
|
+
def load(source, proc = nil, options = {})
|
68
|
+
if source.respond_to? :to_str
|
69
|
+
source = source.to_str
|
70
|
+
elsif source.respond_to? :to_io
|
71
|
+
source = source.to_io.read
|
72
|
+
elsif source.respond_to?(:read)
|
73
|
+
source = source.read
|
74
|
+
end
|
75
|
+
result = parse(source, options)
|
76
|
+
recurse_proc(result, &proc) if proc
|
77
|
+
result
|
167
78
|
end
|
168
79
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
'critical' => ::Syslog::LOG_CRIT,
|
181
|
-
}.freeze
|
182
|
-
|
183
|
-
def initialize(syslog = ::Syslog)
|
184
|
-
@syslog = syslog
|
185
|
-
end
|
186
|
-
|
187
|
-
def output(dumper, obj)
|
188
|
-
prepare_syslog obj[:app]
|
189
|
-
|
190
|
-
obj = obj.dup
|
191
|
-
obj.delete(:pid) # It's going to be part of the message
|
192
|
-
obj.delete(:at) # Also part of the message
|
193
|
-
obj.delete(:app) # And again
|
194
|
-
|
195
|
-
level = extract_pri(obj)
|
196
|
-
|
197
|
-
@syslog.log(level, "%s", dumper.dump(obj))
|
198
|
-
end
|
199
|
-
|
200
|
-
protected
|
201
|
-
|
202
|
-
def prepare_syslog(app_name)
|
203
|
-
return if @syslog.opened?
|
204
|
-
app_name ||= File.basename($0)
|
205
|
-
@syslog.open(app_name,
|
206
|
-
::Syslog::LOG_PID | ::Syslog::LOG_CONS | ::Syslog::LOG_NDELAY,
|
207
|
-
::Syslog::LOG_USER)
|
208
|
-
end
|
209
|
-
|
210
|
-
def extract_pri(h)
|
211
|
-
pri = h.delete(:pri).to_s.downcase
|
212
|
-
PRI2SYSLOG[pri] || ::Syslog::LOG_INFO
|
80
|
+
# Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_
|
81
|
+
def recurse_proc(result, &proc)
|
82
|
+
case result
|
83
|
+
when Array
|
84
|
+
result.each { |x| recurse_proc x, &proc }
|
85
|
+
proc.call result
|
86
|
+
when Hash
|
87
|
+
result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
|
88
|
+
proc.call result
|
89
|
+
else
|
90
|
+
proc.call result
|
213
91
|
end
|
214
92
|
end
|
93
|
+
protected :recurse_proc
|
215
94
|
|
216
|
-
#
|
217
|
-
#
|
218
|
-
# We really want to never fail at dumping because you know, they're logs.
|
219
|
-
# It's better to get a slightly less readable log that no logs at all.
|
220
|
-
#
|
221
|
-
# We're trying to be helpful for humans. It means that if possible we want
|
222
|
-
# to make things shorter and more readable. It also means that ideally
|
223
|
-
# we would like the parsing to be isomorphic but approximations are alright.
|
224
|
-
# For example a symbol might become a string.
|
225
|
-
#
|
226
|
-
# Basically, values are either composite (dictionaries and arrays), quoted
|
227
|
-
# strings or litterals. Litterals are strings that can be parsed to
|
228
|
-
# something else depending if the language supports it or not.
|
229
|
-
# Litterals never contain white-spaces or other weird (very precise !) characters.
|
95
|
+
# Dumps _obj_ as a Lines string, i.e. calls generate on the object and returns
|
96
|
+
# the result.
|
230
97
|
#
|
231
|
-
#
|
232
|
-
# the
|
233
|
-
# the nil / null litteral is written as "nil"
|
98
|
+
# If anIO (an IO-like object or an object that responds to the write method)
|
99
|
+
# was given, the resulting JSON is written to it.
|
234
100
|
#
|
235
|
-
#
|
101
|
+
# If the number of nested arrays or objects exceeds _limit_, the object or
|
102
|
+
# array is replaced with {...} or [...] respectively.
|
103
|
+
# This argument is similar (but not exactly the same!) to the _limit_
|
104
|
+
# argument in Marshal.dump.
|
236
105
|
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
# alternatively if your language supports a time range it could be serialized
|
240
|
-
# to the same value (and parsed back as well).
|
106
|
+
# The default options for the generator can be changed via the
|
107
|
+
# generate_default_options method.
|
241
108
|
#
|
242
|
-
#
|
243
|
-
#
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
SPACE = ' '
|
251
|
-
LIT_TRUE = '#t'
|
252
|
-
LIT_FALSE = '#f'
|
253
|
-
LIT_NIL = 'nil'
|
254
|
-
OPEN_BRACE = '{'
|
255
|
-
SHUT_BRACE = '}'
|
256
|
-
OPEN_BRACKET = '['
|
257
|
-
SHUT_BRACKET = ']'
|
258
|
-
SINGLE_QUOTE = "'"
|
259
|
-
DOUBLE_QUOTE = '"'
|
260
|
-
|
261
|
-
constants.each(&:freeze)
|
262
|
-
|
263
|
-
def dump(obj) #=> String
|
264
|
-
objenc_internal(obj)
|
265
|
-
end
|
266
|
-
|
267
|
-
# Used to introduce new ruby litterals.
|
268
|
-
#
|
269
|
-
# Usage:
|
270
|
-
#
|
271
|
-
# Point = Struct.new(:x, :y)
|
272
|
-
# Lines.dumper.map(Point) do |p|
|
273
|
-
# "#{p.x}x#{p.y}"
|
274
|
-
# end
|
275
|
-
#
|
276
|
-
# Lines.log msg: Point.new(3, 5)
|
277
|
-
# # logs: msg=3x5
|
278
|
-
#
|
279
|
-
def map(klass, &rule)
|
280
|
-
@mapping[klass] = rule
|
281
|
-
end
|
282
|
-
|
283
|
-
# After a certain depth, arrays are replaced with [...] and objects with
|
284
|
-
# {...}. Default is 4.
|
285
|
-
attr_accessor :max_depth
|
286
|
-
|
287
|
-
protected
|
288
|
-
|
289
|
-
attr_reader :mapping
|
290
|
-
|
291
|
-
def initialize
|
292
|
-
@mapping = {}
|
293
|
-
@max_depth = 4
|
294
|
-
end
|
295
|
-
|
296
|
-
def objenc_internal(x, depth=0)
|
297
|
-
depth += 1
|
298
|
-
if depth > max_depth
|
299
|
-
'...'
|
300
|
-
else
|
301
|
-
x.map{|k,v| "#{keyenc(k)}=#{valenc(v, depth)}" }.join(SPACE)
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
def keyenc(k)
|
306
|
-
case k
|
307
|
-
when String, Symbol then strenc(k)
|
308
|
-
else
|
309
|
-
strenc(k.inspect)
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
def valenc(x, depth)
|
314
|
-
case x
|
315
|
-
when Hash then objenc(x, depth)
|
316
|
-
when Array then arrenc(x, depth)
|
317
|
-
when String, Symbol then strenc(x)
|
318
|
-
when Numeric then numenc(x)
|
319
|
-
when Time then timeenc(x)
|
320
|
-
when Date then dateenc(x)
|
321
|
-
when true then LIT_TRUE
|
322
|
-
when false then LIT_FALSE
|
323
|
-
when nil then LIT_NIL
|
324
|
-
else
|
325
|
-
litenc(x)
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
def objenc(x, depth)
|
330
|
-
OPEN_BRACE + objenc_internal(x, depth) + SHUT_BRACE
|
331
|
-
end
|
332
|
-
|
333
|
-
def arrenc(a, depth)
|
334
|
-
depth += 1
|
335
|
-
# num + unit. Eg: 3ms
|
336
|
-
if a.size == 2 && a.first.kind_of?(Numeric) && is_literal?(a.last.to_s)
|
337
|
-
"#{numenc(a.first)}:#{strenc(a.last)}"
|
338
|
-
elsif depth > max_depth
|
339
|
-
'[...]'
|
340
|
-
else
|
341
|
-
OPEN_BRACKET + a.map{|x| valenc(x, depth)}.join(' ') + SHUT_BRACKET
|
342
|
-
end
|
343
|
-
end
|
344
|
-
|
345
|
-
def strenc(s)
|
346
|
-
s = s.to_s
|
347
|
-
unless is_literal?(s)
|
348
|
-
s = s.inspect
|
349
|
-
unless s[1..-2].include?(SINGLE_QUOTE)
|
350
|
-
s.gsub!(SINGLE_QUOTE, "\\'")
|
351
|
-
s.gsub!('\"', DOUBLE_QUOTE)
|
352
|
-
s[0] = s[-1] = SINGLE_QUOTE
|
353
|
-
end
|
354
|
-
end
|
355
|
-
s
|
356
|
-
end
|
357
|
-
|
358
|
-
def numenc(n)
|
359
|
-
#case n
|
360
|
-
# when Float
|
361
|
-
# "%.3f" % n
|
362
|
-
#else
|
363
|
-
n.to_s
|
364
|
-
#end
|
365
|
-
end
|
366
|
-
|
367
|
-
def litenc(x)
|
368
|
-
klass = (x.class.ancestors & mapping.keys).first
|
369
|
-
if klass
|
370
|
-
mapping[klass].call(x)
|
371
|
-
else
|
372
|
-
strenc(x.inspect)
|
109
|
+
# This method is part of the implementation of the load/dump interface of
|
110
|
+
# Marshal and YAML.
|
111
|
+
def dump(obj, anIO = nil, limit = nil)
|
112
|
+
if anIO and limit.nil?
|
113
|
+
anIO = anIO.to_io if anIO.respond_to?(:to_io)
|
114
|
+
unless anIO.respond_to?(:write)
|
115
|
+
limit = anIO
|
116
|
+
anIO = nil
|
373
117
|
end
|
374
|
-
rescue
|
375
|
-
klass = (class << x; self; end).ancestors.first
|
376
|
-
strenc("#<#{klass}:0x#{x.__id__.to_s(16)}>")
|
377
|
-
end
|
378
|
-
|
379
|
-
def timeenc(t)
|
380
|
-
t.utc.iso8601
|
381
|
-
end
|
382
|
-
|
383
|
-
def dateenc(d)
|
384
|
-
d.iso8601
|
385
118
|
end
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
module UniqueIDs
|
395
|
-
# A small utility to generate unique IDs that are as short as possible.
|
396
|
-
#
|
397
|
-
# It's useful to link contextes together
|
398
|
-
#
|
399
|
-
# See http://preshing.com/20110504/hash-collision-probabilities
|
400
|
-
def id(collision_chance=1.0/10e9, over_x_messages=10e3)
|
401
|
-
# Assuming that the distribution is perfectly random
|
402
|
-
# how many bits do we need so that the chance of collision over_x_messages
|
403
|
-
# is lower thant collision_chance ?
|
404
|
-
number_of_possible_numbers = (over_x_messages ** 2) / (2 * collision_chance)
|
405
|
-
num_bytes = (Math.log2(number_of_possible_numbers) / 8).ceil
|
406
|
-
SecureRandom.urlsafe_base64(num_bytes)
|
119
|
+
opts = {}
|
120
|
+
opts[:max_nesting] = limit if limit
|
121
|
+
result = generate(obj, opts)
|
122
|
+
if anIO
|
123
|
+
anIO.write result
|
124
|
+
anIO
|
125
|
+
else
|
126
|
+
result
|
407
127
|
end
|
408
128
|
end
|
409
|
-
extend UniqueIDs
|
410
129
|
end
|
411
|
-
|
412
|
-
# default config
|
413
|
-
Lines.use($stderr)
|