pg_dumper 0.0.2 → 0.1.0
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.
- data/Gemfile +3 -0
- data/Guardfile +16 -0
- data/README.md +2 -0
- data/lib/pg_dumper/vendor/escape.rb +543 -0
- data/lib/pg_dumper/version.rb +1 -1
- data/lib/pg_dumper.rb +95 -26
- data/pg_dumper.gemspec +2 -2
- data/spec/pg_dumper_spec.rb +22 -0
- metadata +40 -43
data/Gemfile
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
# Uncomment next line if Gemfile contain `gemspec' command
|
7
|
+
# watch(/^.+\.gemspec/)
|
8
|
+
end
|
9
|
+
|
10
|
+
guard 'rspec', :version => 2 do
|
11
|
+
watch(%r{^spec/.+_spec\.rb$})
|
12
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
14
|
+
end
|
15
|
+
|
16
|
+
|
data/README.md
CHANGED
@@ -0,0 +1,543 @@
|
|
1
|
+
# escape.rb - escape/unescape library for several formats
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006,2007,2009 Tanaka Akira <akr@fsij.org>
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
# list of conditions and the following disclaimer.
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote products
|
14
|
+
# derived from this software without specific prior written permission.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
17
|
+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
18
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
19
|
+
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
20
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
21
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
22
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
23
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
24
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
25
|
+
# OF SUCH DAMAGE.
|
26
|
+
|
27
|
+
# Escape module provides several escape functions.
|
28
|
+
# * URI
|
29
|
+
# * HTML
|
30
|
+
# * shell command
|
31
|
+
# * MIME parameter
|
32
|
+
module Escape
|
33
|
+
module_function
|
34
|
+
|
35
|
+
class StringWrapper
|
36
|
+
class << self
|
37
|
+
alias new_no_dup new
|
38
|
+
def new(str)
|
39
|
+
new_no_dup(str.dup)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(str)
|
44
|
+
@str = str
|
45
|
+
end
|
46
|
+
|
47
|
+
def escaped_string
|
48
|
+
@str.dup
|
49
|
+
end
|
50
|
+
|
51
|
+
alias to_s escaped_string
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"\#<#{self.class}: #{@str}>"
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
other.class == self.class && @str == other.instance_variable_get(:@str)
|
59
|
+
end
|
60
|
+
alias eql? ==
|
61
|
+
|
62
|
+
def hash
|
63
|
+
@str.hash
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class ShellEscaped < StringWrapper
|
68
|
+
end
|
69
|
+
|
70
|
+
# Escape.shell_command composes
|
71
|
+
# a sequence of words to
|
72
|
+
# a single shell command line.
|
73
|
+
# All shell meta characters are quoted and
|
74
|
+
# the words are concatenated with interleaving space.
|
75
|
+
# It returns an instance of ShellEscaped.
|
76
|
+
#
|
77
|
+
# Escape.shell_command(["ls", "/"]) #=> #<Escape::ShellEscaped: ls />
|
78
|
+
# Escape.shell_command(["echo", "*"]) #=> #<Escape::ShellEscaped: echo '*'>
|
79
|
+
#
|
80
|
+
# Note that system(*command) and
|
81
|
+
# system(Escape.shell_command(command).to_s) is roughly same.
|
82
|
+
# There are two exception as follows.
|
83
|
+
# * The first is that the later may invokes /bin/sh.
|
84
|
+
# * The second is an interpretation of an array with only one element:
|
85
|
+
# the element is parsed by the shell with the former but
|
86
|
+
# it is recognized as single word with the later.
|
87
|
+
# For example, system(*["echo foo"]) invokes echo command with an argument "foo".
|
88
|
+
# But system(Escape.shell_command(["echo foo"]).to_s) invokes "echo foo" command
|
89
|
+
# without arguments (and it probably fails).
|
90
|
+
def shell_command(command)
|
91
|
+
s = command.map {|word| shell_single_word(word) }.join(' ')
|
92
|
+
ShellEscaped.new_no_dup(s)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Escape.shell_single_word quotes shell meta characters.
|
96
|
+
# It returns an instance of ShellEscaped.
|
97
|
+
#
|
98
|
+
# The result string is always single shell word, even if
|
99
|
+
# the argument is "".
|
100
|
+
# Escape.shell_single_word("") returns #<Escape::ShellEscaped: ''>.
|
101
|
+
#
|
102
|
+
# Escape.shell_single_word("") #=> #<Escape::ShellEscaped: ''>
|
103
|
+
# Escape.shell_single_word("foo") #=> #<Escape::ShellEscaped: foo>
|
104
|
+
# Escape.shell_single_word("*") #=> #<Escape::ShellEscaped: '*'>
|
105
|
+
def shell_single_word(str)
|
106
|
+
if str.empty?
|
107
|
+
ShellEscaped.new_no_dup("''")
|
108
|
+
elsif %r{\A[0-9A-Za-z+,./:=@_-]+\z} =~ str
|
109
|
+
ShellEscaped.new(str)
|
110
|
+
else
|
111
|
+
result = ''
|
112
|
+
str.scan(/('+)|[^']+/) {
|
113
|
+
if $1
|
114
|
+
result << %q{\'} * $1.length
|
115
|
+
else
|
116
|
+
result << "'#{$&}'"
|
117
|
+
end
|
118
|
+
}
|
119
|
+
ShellEscaped.new_no_dup(result)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class InvalidHTMLForm < StandardError
|
124
|
+
end
|
125
|
+
class PercentEncoded < StringWrapper
|
126
|
+
# Escape::PercentEncoded#split_html_form decodes
|
127
|
+
# percent-encoded string as
|
128
|
+
# application/x-www-form-urlencoded
|
129
|
+
# defined by HTML specification.
|
130
|
+
#
|
131
|
+
# It recognizes "&" and ";" as a separator of key-value pairs.
|
132
|
+
#
|
133
|
+
# If it find is not valid as
|
134
|
+
# application/x-www-form-urlencoded,
|
135
|
+
# Escape::InvalidHTMLForm exception is raised.
|
136
|
+
#
|
137
|
+
# Escape::PercentEncoded.new("a=b&c=d")
|
138
|
+
# #=> [[#<Escape::PercentEncoded: a>, #<Escape::PercentEncoded: b>],
|
139
|
+
# [#<Escape::PercentEncoded: c>, #<Escape::PercentEncoded: d>]]
|
140
|
+
#
|
141
|
+
# Escape::PercentEncoded.new("a=b;c=d").split_html_form
|
142
|
+
# #=> [[#<Escape::PercentEncoded: a>, #<Escape::PercentEncoded: b>],
|
143
|
+
# [#<Escape::PercentEncoded: c>, #<Escape::PercentEncoded: d>]]
|
144
|
+
#
|
145
|
+
# Escape::PercentEncoded.new("%3D=%3F").split_html_form
|
146
|
+
# #=> [[#<Escape::PercentEncoded: %3D>, #<Escape::PercentEncoded: %3F>]]
|
147
|
+
#
|
148
|
+
def split_html_form
|
149
|
+
assoc = []
|
150
|
+
@str.split(/[&;]/, -1).each {|s|
|
151
|
+
raise InvalidHTMLForm, "invalid: #{@str}" unless /=/ =~ s
|
152
|
+
k = $`
|
153
|
+
v = $'
|
154
|
+
k.gsub!(/\+/, ' ')
|
155
|
+
v.gsub!(/\+/, ' ')
|
156
|
+
assoc << [PercentEncoded.new_no_dup(k), PercentEncoded.new_no_dup(v)]
|
157
|
+
}
|
158
|
+
assoc
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Escape.percent_encoding escapes URI non-unreserved characters using percent-encoding.
|
163
|
+
# It returns an instance of PercentEncoded.
|
164
|
+
#
|
165
|
+
# The unreserved characters are alphabet, digit, hyphen, dot, underscore and tilde.
|
166
|
+
# [RFC 3986]
|
167
|
+
#
|
168
|
+
# Escape.percent_encoding("foo") #=> #<Escape::PercentEncoded: foo>
|
169
|
+
#
|
170
|
+
# Escape.percent_encoding(' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~')
|
171
|
+
# #=> #<Escape::PercentEncoded: %20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~>
|
172
|
+
def percent_encoding(str)
|
173
|
+
s = str.gsub(%r{[^A-Za-z0-9\-._~]}n) {
|
174
|
+
'%' + $&.unpack("H2")[0].upcase
|
175
|
+
}
|
176
|
+
PercentEncoded.new_no_dup(s)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Escape.uri_segment escapes URI segment using percent-encoding.
|
180
|
+
# It returns an instance of PercentEncoded.
|
181
|
+
#
|
182
|
+
# Escape.uri_segment("a/b") #=> #<Escape::PercentEncoded: a%2Fb>
|
183
|
+
#
|
184
|
+
# The segment is "/"-splitted element after authority before query in URI, as follows.
|
185
|
+
#
|
186
|
+
# scheme://authority/segment1/segment2/.../segmentN?query#fragment
|
187
|
+
#
|
188
|
+
# See RFC 3986 for details of URI.
|
189
|
+
def uri_segment(str)
|
190
|
+
# pchar - pct-encoded = unreserved / sub-delims / ":" / "@"
|
191
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
192
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
193
|
+
s = str.gsub(%r{[^A-Za-z0-9\-._~!$&'()*+,;=:@]}n) {
|
194
|
+
'%' + $&.unpack("H2")[0].upcase
|
195
|
+
}
|
196
|
+
PercentEncoded.new_no_dup(s)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Escape.uri_path escapes URI path using percent-encoding.
|
200
|
+
#
|
201
|
+
# The given path should be one of follows.
|
202
|
+
# * a sequence of (non-escaped) segments separated by "/". (The segments cannot contains "/".)
|
203
|
+
# * an array containing (non-escaped) segments. (The segments may contains "/".)
|
204
|
+
#
|
205
|
+
# It returns an instance of PercentEncoded.
|
206
|
+
#
|
207
|
+
# Escape.uri_path("a/b/c") #=> #<Escape::PercentEncoded: a/b/c>
|
208
|
+
# Escape.uri_path("a?b/c?d/e?f") #=> #<Escape::PercentEncoded: a%3Fb/c%3Fd/e%3Ff>
|
209
|
+
# Escape.uri_path(%w[/d f]) #=> "%2Fd/f"
|
210
|
+
#
|
211
|
+
# The path is the part after authority before query in URI, as follows.
|
212
|
+
#
|
213
|
+
# scheme://authority/path#fragment
|
214
|
+
#
|
215
|
+
# See RFC 3986 for details of URI.
|
216
|
+
#
|
217
|
+
# Note that this function is not appropriate to convert OS path to URI.
|
218
|
+
def uri_path(arg)
|
219
|
+
if arg.respond_to? :to_ary
|
220
|
+
s = arg.map {|elt| uri_segment(elt) }.join('/')
|
221
|
+
else
|
222
|
+
s = arg.gsub(%r{[^/]+}n) { uri_segment($&) }
|
223
|
+
end
|
224
|
+
PercentEncoded.new_no_dup(s)
|
225
|
+
end
|
226
|
+
|
227
|
+
# :stopdoc:
|
228
|
+
def html_form_fast(pairs, sep='&')
|
229
|
+
s = pairs.map {|k, v|
|
230
|
+
# query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
|
231
|
+
# unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
|
232
|
+
# query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
|
233
|
+
# query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
|
234
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
235
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
236
|
+
# x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
|
237
|
+
k = k.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
|
238
|
+
'%' + $&.unpack("H2")[0].upcase
|
239
|
+
}
|
240
|
+
v = v.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
|
241
|
+
'%' + $&.unpack("H2")[0].upcase
|
242
|
+
}
|
243
|
+
"#{k}=#{v}"
|
244
|
+
}.join(sep)
|
245
|
+
PercentEncoded.new_no_dup(s)
|
246
|
+
end
|
247
|
+
# :startdoc:
|
248
|
+
|
249
|
+
# Escape.html_form composes HTML form key-value pairs as a x-www-form-urlencoded encoded string.
|
250
|
+
# It returns an instance of PercentEncoded.
|
251
|
+
#
|
252
|
+
# Escape.html_form takes an array of pair of strings or
|
253
|
+
# an hash from string to string.
|
254
|
+
#
|
255
|
+
# Escape.html_form([["a","b"], ["c","d"]]) #=> #<Escape::PercentEncoded: a=b&c=d>
|
256
|
+
# Escape.html_form({"a"=>"b", "c"=>"d"}) #=> #<Escape::PercentEncoded: a=b&c=d>
|
257
|
+
#
|
258
|
+
# In the array form, it is possible to use same key more than once.
|
259
|
+
# (It is required for a HTML form which contains
|
260
|
+
# checkboxes and select element with multiple attribute.)
|
261
|
+
#
|
262
|
+
# Escape.html_form([["k","1"], ["k","2"]]) #=> #<Escape::PercentEncoded: k=1&k=2>
|
263
|
+
#
|
264
|
+
# If the strings contains characters which must be escaped in x-www-form-urlencoded,
|
265
|
+
# they are escaped using %-encoding.
|
266
|
+
#
|
267
|
+
# Escape.html_form([["k=","&;="]]) #=> #<Escape::PercentEncoded: k%3D=%26%3B%3D>
|
268
|
+
#
|
269
|
+
# The separator can be specified by the optional second argument.
|
270
|
+
#
|
271
|
+
# Escape.html_form([["a","b"], ["c","d"]], ";") #=> #<Escape::PercentEncoded: a=b;c=d>
|
272
|
+
#
|
273
|
+
# See HTML 4.01 for details.
|
274
|
+
def html_form(pairs, sep='&')
|
275
|
+
r = ''
|
276
|
+
first = true
|
277
|
+
pairs.each {|k, v|
|
278
|
+
# query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
|
279
|
+
# unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
|
280
|
+
# query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
|
281
|
+
# query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
|
282
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
283
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
284
|
+
# x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
|
285
|
+
r << sep if !first
|
286
|
+
first = false
|
287
|
+
k.each_byte {|byte|
|
288
|
+
ch = byte.chr
|
289
|
+
if ch == ' '
|
290
|
+
r << "+"
|
291
|
+
elsif %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
|
292
|
+
r << "%" << ch.unpack("H2")[0].upcase
|
293
|
+
else
|
294
|
+
r << ch
|
295
|
+
end
|
296
|
+
}
|
297
|
+
r << '='
|
298
|
+
v.each_byte {|byte|
|
299
|
+
ch = byte.chr
|
300
|
+
if ch == ' '
|
301
|
+
r << "+"
|
302
|
+
elsif %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
|
303
|
+
r << "%" << ch.unpack("H2")[0].upcase
|
304
|
+
else
|
305
|
+
r << ch
|
306
|
+
end
|
307
|
+
}
|
308
|
+
}
|
309
|
+
PercentEncoded.new_no_dup(r)
|
310
|
+
end
|
311
|
+
|
312
|
+
class HTMLEscaped < StringWrapper
|
313
|
+
end
|
314
|
+
|
315
|
+
# :stopdoc:
|
316
|
+
HTML_TEXT_ESCAPE_HASH = {
|
317
|
+
'&' => '&',
|
318
|
+
'<' => '<',
|
319
|
+
'>' => '>',
|
320
|
+
}
|
321
|
+
# :startdoc:
|
322
|
+
|
323
|
+
# Escape.html_text escapes a string appropriate for HTML text using character references.
|
324
|
+
# It returns an instance of HTMLEscaped.
|
325
|
+
#
|
326
|
+
# It escapes 3 characters:
|
327
|
+
# * '&' to '&'
|
328
|
+
# * '<' to '<'
|
329
|
+
# * '>' to '>'
|
330
|
+
#
|
331
|
+
# Escape.html_text("abc") #=> #<Escape::HTMLEscaped: abc>
|
332
|
+
# Escape.html_text("a & b < c > d") #=> #<Escape::HTMLEscaped: a & b < c > d>
|
333
|
+
#
|
334
|
+
# This function is not appropriate for escaping HTML element attribute
|
335
|
+
# because quotes are not escaped.
|
336
|
+
def html_text(str)
|
337
|
+
s = str.gsub(/[&<>]/) {|ch| HTML_TEXT_ESCAPE_HASH[ch] }
|
338
|
+
HTMLEscaped.new_no_dup(s)
|
339
|
+
end
|
340
|
+
|
341
|
+
# :stopdoc:
|
342
|
+
HTML_ATTR_ESCAPE_HASH = {
|
343
|
+
'&' => '&',
|
344
|
+
'<' => '<',
|
345
|
+
'>' => '>',
|
346
|
+
'"' => '"',
|
347
|
+
}
|
348
|
+
# :startdoc:
|
349
|
+
|
350
|
+
class HTMLAttrValue < StringWrapper
|
351
|
+
end
|
352
|
+
|
353
|
+
# Escape.html_attr_value encodes a string as a double-quoted HTML attribute using character references.
|
354
|
+
# It returns an instance of HTMLAttrValue.
|
355
|
+
#
|
356
|
+
# Escape.html_attr_value("abc") #=> #<Escape::HTMLAttrValue: "abc">
|
357
|
+
# Escape.html_attr_value("a&b") #=> #<Escape::HTMLAttrValue: "a&b">
|
358
|
+
# Escape.html_attr_value("ab&<>\"c") #=> #<Escape::HTMLAttrValue: "ab&<>"c">
|
359
|
+
# Escape.html_attr_value("a'c") #=> #<Escape::HTMLAttrValue: "a'c">
|
360
|
+
#
|
361
|
+
# It escapes 4 characters:
|
362
|
+
# * '&' to '&'
|
363
|
+
# * '<' to '<'
|
364
|
+
# * '>' to '>'
|
365
|
+
# * '"' to '"'
|
366
|
+
#
|
367
|
+
def html_attr_value(str)
|
368
|
+
s = '"' + str.gsub(/[&<>"]/) {|ch| HTML_ATTR_ESCAPE_HASH[ch] } + '"'
|
369
|
+
HTMLAttrValue.new_no_dup(s)
|
370
|
+
end
|
371
|
+
|
372
|
+
# MIMEParameter represents parameter, token, quoted-string in MIME.
|
373
|
+
# parameter and token is defined in RFC 2045.
|
374
|
+
# quoted-string is defined in RFC 822.
|
375
|
+
class MIMEParameter < StringWrapper
|
376
|
+
end
|
377
|
+
|
378
|
+
# predicate for MIME token.
|
379
|
+
#
|
380
|
+
# token is a sequence of any (US-ASCII) CHAR except SPACE, CTLs, or tspecials.
|
381
|
+
def mime_token?(str)
|
382
|
+
/\A[!\#-'*+\-.0-9A-Z^-~]+\z/ =~ str ? true : false
|
383
|
+
end
|
384
|
+
|
385
|
+
# :stopdoc:
|
386
|
+
RFC2822_FWS = /(?:[ \t]*\r?\n)?[ \t]+/
|
387
|
+
# :startdoc:
|
388
|
+
|
389
|
+
# Escape.rfc2822_quoted_string escapes a string as quoted-string defined in RFC 2822.
|
390
|
+
# It returns an instance of MIMEParameter.
|
391
|
+
#
|
392
|
+
# The obsolete syntax in quoted-string is not permitted.
|
393
|
+
# For example, NUL causes ArgumentError.
|
394
|
+
#
|
395
|
+
# The given string may contain carriage returns ("\r") and line feeds ("\n").
|
396
|
+
# However they must be part of folding white space: /\r\n[ \t]/ or /\n[ \t]/.
|
397
|
+
# Escape.rfc2822_quoted_string assumes that newlines are represented as
|
398
|
+
# "\n" or "\r\n".
|
399
|
+
#
|
400
|
+
# Escape.rfc2822_quoted_string does not permit consecutive sequence of
|
401
|
+
# folding white spaces such as "\n \n ", according to RFC 2822 syntax.
|
402
|
+
def rfc2822_quoted_string(str)
|
403
|
+
if /\A(?:#{RFC2822_FWS}?[\x01-\x09\x0b\x0c\x0e-\x7f])*#{RFC2822_FWS}?\z/o !~ str
|
404
|
+
raise ArgumentError, "not representable in quoted-string of RFC 2822: #{str.inspect}"
|
405
|
+
end
|
406
|
+
s = '"' + str.gsub(/["\\]/, '\\\\\&') + '"'
|
407
|
+
MIMEParameter.new_no_dup(s)
|
408
|
+
end
|
409
|
+
|
410
|
+
# Escape.mime_parameter_value escapes a string as MIME parameter value in RFC 2045.
|
411
|
+
# It returns an instance of MIMEParameter.
|
412
|
+
#
|
413
|
+
# MIME parameter value is token or quoted-string.
|
414
|
+
# token is used if possible.
|
415
|
+
def mime_parameter_value(str)
|
416
|
+
if mime_token?(str)
|
417
|
+
MIMEParameter.new(str)
|
418
|
+
else
|
419
|
+
rfc2822_quoted_string(str)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
# Escape.mime_parameter encodes attribute and value as MIME parameter in RFC 2045.
|
424
|
+
# It returns an instance of MIMEParameter.
|
425
|
+
#
|
426
|
+
# ArgumentError is raised if attribute is not MIME token.
|
427
|
+
#
|
428
|
+
# ArgumentError is raised if value contains CR, LF or NUL.
|
429
|
+
#
|
430
|
+
# Escape.mime_parameter("n", "v") #=> #<Escape::MIMEParameter: n=v>
|
431
|
+
# Escape.mime_parameter("charset", "us-ascii") #=> #<Escape::MIMEParameter: charset=us-ascii>
|
432
|
+
# Escape.mime_parameter("boundary", "gc0pJq0M:08jU534c0p") #=> #<Escape::MIMEParameter: boundary="gc0pJq0M:08jU534c0p">
|
433
|
+
# Escape.mime_parameter("boundary", "simple boundary") #=> #<Escape::MIMEParameter: boundary="simple boundary">
|
434
|
+
def mime_parameter(attribute, value)
|
435
|
+
unless mime_token?(attribute)
|
436
|
+
raise ArgumentError, "not MIME token: #{attribute.inspect}"
|
437
|
+
end
|
438
|
+
MIMEParameter.new("#{attribute}=#{mime_parameter_value(value)}")
|
439
|
+
end
|
440
|
+
|
441
|
+
# predicate for MIME token.
|
442
|
+
#
|
443
|
+
# token is a sequence of any CHAR except CTLs or separators
|
444
|
+
def http_token?(str)
|
445
|
+
/\A[!\#-'*+\-.0-9A-Z^-z|~]+\z/ =~ str ? true : false
|
446
|
+
end
|
447
|
+
|
448
|
+
# Escape.http_quoted_string escapes a string as quoted-string defined in RFC 2616.
|
449
|
+
# It returns an instance of MIMEParameter.
|
450
|
+
#
|
451
|
+
# The given string may contain carriage returns ("\r") and line feeds ("\n").
|
452
|
+
# However they must be part of folding white space: /\r\n[ \t]/ or /\n[ \t]/.
|
453
|
+
# Escape.http_quoted_string assumes that newlines are represented as
|
454
|
+
# "\n" or "\r\n".
|
455
|
+
def http_quoted_string(str)
|
456
|
+
if /\A(?:[\0-\x09\x0b\x0c\x0e-\xff]|\r?\n[ \t])*\z/ !~ str
|
457
|
+
raise ArgumentError, "CR or LF not part of folding white space exists: #{str.inspect}"
|
458
|
+
end
|
459
|
+
s = '"' + str.gsub(/["\\]/, '\\\\\&') + '"'
|
460
|
+
MIMEParameter.new_no_dup(s)
|
461
|
+
end
|
462
|
+
|
463
|
+
# Escape.http_parameter_value escapes a string as HTTP parameter value in RFC 2616.
|
464
|
+
# It returns an instance of MIMEParameter.
|
465
|
+
#
|
466
|
+
# HTTP parameter value is token or quoted-string.
|
467
|
+
# token is used if possible.
|
468
|
+
def http_parameter_value(str)
|
469
|
+
if http_token?(str)
|
470
|
+
MIMEParameter.new(str)
|
471
|
+
else
|
472
|
+
http_quoted_string(str)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# Escape.http_parameter encodes attribute and value as HTTP parameter in RFC 2616.
|
477
|
+
# It returns an instance of MIMEParameter.
|
478
|
+
#
|
479
|
+
# ArgumentError is raised if attribute is not HTTP token.
|
480
|
+
#
|
481
|
+
# ArgumentError is raised if value is not representable in quoted-string.
|
482
|
+
#
|
483
|
+
# Escape.http_parameter("n", "v") #=> #<Escape::MIMEParameter: n=v>
|
484
|
+
# Escape.http_parameter("charset", "us-ascii") #=> #<Escape::MIMEParameter: charset=us-ascii>
|
485
|
+
# Escape.http_parameter("q", "0.2") #=> #<Escape::MIMEParameter: q=0.2>
|
486
|
+
def http_parameter(attribute, value)
|
487
|
+
unless http_token?(attribute)
|
488
|
+
raise ArgumentError, "not HTTP token: #{attribute.inspect}"
|
489
|
+
end
|
490
|
+
MIMEParameter.new("#{attribute}=#{http_parameter_value(value)}")
|
491
|
+
end
|
492
|
+
|
493
|
+
# :stopdoc:
|
494
|
+
def _parse_http_params_args(args)
|
495
|
+
pairs = []
|
496
|
+
until args.empty?
|
497
|
+
if args[0].respond_to?(:to_str) && args[1].respond_to?(:to_str)
|
498
|
+
pairs << [args.shift, args.shift]
|
499
|
+
else
|
500
|
+
raise ArgumentError, "unexpected argument: #{args.inspect}"
|
501
|
+
end
|
502
|
+
end
|
503
|
+
pairs
|
504
|
+
end
|
505
|
+
# :startdoc:
|
506
|
+
|
507
|
+
# Escape.http_params_with_sep encodes parameters and joins with sep.
|
508
|
+
#
|
509
|
+
# Escape.http_params_with_sep("; ", "foo", "bar")
|
510
|
+
# #=> #<Escape::MIMEParameter: foo=bar>
|
511
|
+
#
|
512
|
+
# Escape.http_params_with_sep("; ", "foo", "bar", "hoge", "fuga")
|
513
|
+
# #=> #<Escape::MIMEParameter: foo=bar; hoge=fuga>
|
514
|
+
#
|
515
|
+
# If args are empty, empty MIMEParameter is returned.
|
516
|
+
#
|
517
|
+
# Escape.http_params_with_sep("; ") #=> #<Escape::MIMEParameter: >
|
518
|
+
#
|
519
|
+
def http_params_with_sep(sep, *args)
|
520
|
+
pairs = _parse_http_params_args(args)
|
521
|
+
s = pairs.map {|attribute, value| http_parameter(attribute, value) }.join(sep)
|
522
|
+
MIMEParameter.new_no_dup(s)
|
523
|
+
end
|
524
|
+
|
525
|
+
# Escape.http_params_with_pre encodes parameters and joins with given prefix.
|
526
|
+
#
|
527
|
+
# Escape.http_params_with_pre("; ", "foo", "bar")
|
528
|
+
# #=> #<Escape::MIMEParameter: ; foo=bar>
|
529
|
+
#
|
530
|
+
# Escape.http_params_with_pre("; ", "foo", "bar", "hoge", "fuga")
|
531
|
+
# #=> #<Escape::MIMEParameter: ; foo=bar; hoge=fuga>
|
532
|
+
#
|
533
|
+
# If args are empty, empty MIMEParameter is returned.
|
534
|
+
#
|
535
|
+
# Escape.http_params_with_pre("; ") #=> #<Escape::MIMEParameter: >
|
536
|
+
#
|
537
|
+
def http_params_with_pre(pre, *args)
|
538
|
+
pairs = _parse_http_params_args(args)
|
539
|
+
s = pairs.map {|attribute, value| pre + http_parameter(attribute, value).to_s }.join('')
|
540
|
+
MIMEParameter.new_no_dup(s)
|
541
|
+
end
|
542
|
+
|
543
|
+
end
|
data/lib/pg_dumper/version.rb
CHANGED
data/lib/pg_dumper.rb
CHANGED
@@ -1,76 +1,145 @@
|
|
1
|
+
require 'pg_dumper/vendor/escape'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'open3'
|
4
|
+
|
1
5
|
class PgDumper
|
2
6
|
require 'pg_dumper/railtie' if defined?(Rails)
|
3
|
-
|
7
|
+
|
4
8
|
attr_reader :database
|
5
|
-
attr_reader :
|
6
|
-
|
9
|
+
attr_reader :output
|
10
|
+
|
7
11
|
def initialize database
|
8
12
|
@database = database
|
9
13
|
@args = []
|
10
14
|
@options = {}
|
11
15
|
end
|
12
|
-
|
16
|
+
|
13
17
|
def run(mode = :silent)
|
14
|
-
|
15
|
-
|
16
|
-
raise "ERROR: pg_dump executable not found" unless @binary.present?
|
18
|
+
raise "ERROR: pg_dump executable not found" unless binary
|
17
19
|
|
18
20
|
options = {}
|
19
|
-
|
21
|
+
|
20
22
|
case mode
|
21
23
|
when :silent
|
22
|
-
options[:out] = "/dev/null"
|
24
|
+
options[:out] = "/dev/null"
|
23
25
|
end
|
24
|
-
|
26
|
+
|
27
|
+
execute command, options
|
28
|
+
end
|
29
|
+
|
30
|
+
def command
|
31
|
+
Escape.shell_command([binary, *args, database]).to_s
|
25
32
|
end
|
26
|
-
|
33
|
+
|
27
34
|
def schema_only!
|
28
35
|
add_args "-s"
|
29
36
|
end
|
30
|
-
|
31
|
-
def
|
37
|
+
|
38
|
+
def create!
|
39
|
+
add_args "-C"
|
40
|
+
end
|
41
|
+
|
42
|
+
def clean!
|
32
43
|
add_args "-c"
|
33
44
|
end
|
34
|
-
|
45
|
+
|
35
46
|
def data_only!
|
36
47
|
add_args "-a", '--disable-triggers'
|
37
48
|
add_args '-T', 'schema_migrations'
|
38
49
|
end
|
39
|
-
|
50
|
+
|
40
51
|
def compress! level=9
|
41
52
|
add_args '-Z', level if level.present?
|
42
53
|
end
|
43
|
-
|
54
|
+
|
44
55
|
def verbose!
|
56
|
+
@stderr = nil
|
45
57
|
add_args "-v"
|
46
58
|
end
|
47
|
-
|
59
|
+
|
48
60
|
def pretty!
|
49
61
|
add_args '--column-inserts'
|
50
62
|
end
|
51
|
-
|
63
|
+
|
64
|
+
def silent!
|
65
|
+
# FIXME: this is not windows friendly
|
66
|
+
# try to use same solution as Bundler::NULL
|
67
|
+
@stderr = "/dev/null"
|
68
|
+
end
|
69
|
+
|
52
70
|
def connection= opts
|
53
71
|
add_args '-p', opts['port'] if opts['port'].present?
|
54
72
|
add_args '-h', opts['host'] if opts['host'].present?
|
55
73
|
add_args '-U', opts['username'] if opts['host'].present?
|
56
74
|
end
|
57
|
-
|
75
|
+
|
58
76
|
def output= filename
|
59
|
-
|
77
|
+
@output = filename
|
78
|
+
end
|
79
|
+
|
80
|
+
def output?
|
81
|
+
!!@output
|
60
82
|
end
|
61
|
-
|
83
|
+
|
84
|
+
def output
|
85
|
+
File.path(@output)
|
86
|
+
end
|
87
|
+
|
88
|
+
def tempfile
|
89
|
+
@tempfile ||= new_tempfile
|
90
|
+
end
|
91
|
+
|
92
|
+
def args
|
93
|
+
if output?
|
94
|
+
@args.dup.push('-f', output)
|
95
|
+
else
|
96
|
+
@args
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
62
100
|
private
|
101
|
+
|
102
|
+
def binary
|
103
|
+
@binary ||= find_executable
|
104
|
+
end
|
105
|
+
|
106
|
+
def execute(cmd, options)
|
107
|
+
puts [cmd, options].inspect
|
108
|
+
if output?
|
109
|
+
system(cmd, options)
|
110
|
+
else
|
111
|
+
stdout, status = Open3.capture2(cmd, options)
|
112
|
+
stdout
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
63
116
|
def find_executable
|
64
117
|
[ENV['PG_DUMP'], %x{which pg_dump}.strip].each do |pg|
|
65
|
-
return pg if pg
|
118
|
+
return pg if pg && File.exists?(pg)
|
66
119
|
end
|
67
120
|
nil
|
68
121
|
end
|
69
|
-
|
122
|
+
|
70
123
|
def add_args(*args)
|
71
124
|
@args.push *args.map!(&:to_s)
|
72
125
|
@args.uniq!
|
73
126
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
127
|
+
|
128
|
+
def stdout
|
129
|
+
@stdout || $stdout
|
130
|
+
end
|
131
|
+
|
132
|
+
def stderr
|
133
|
+
@stderr || $stderr
|
134
|
+
end
|
135
|
+
|
136
|
+
def new_tempfile
|
137
|
+
tempfile = Tempfile.new('pg_dumper')
|
138
|
+
at_exit {
|
139
|
+
tempfile.close
|
140
|
+
tempfile.unlink
|
141
|
+
}
|
142
|
+
tempfile
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/pg_dumper.gemspec
CHANGED
@@ -12,10 +12,10 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = %q{Adds Rake tasks to easily dump postgresql database with or without data}
|
13
13
|
s.description = %q{Provides abstraction layer between pg_dump utility and ruby. Also adds rake task to easily dump database with or without data.}
|
14
14
|
|
15
|
-
#s.rubyforge_project = "pg_dumper"
|
16
|
-
|
17
15
|
s.files = `git ls-files`.split("\n")
|
18
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
18
|
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency 'rspec', '~> 2.7'
|
21
21
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'pg_dumper'
|
2
|
+
|
3
|
+
describe PgDumper do
|
4
|
+
|
5
|
+
let(:database) { "database" }
|
6
|
+
let(:pg_dumper) { PgDumper.new(database) }
|
7
|
+
let(:subject) { pg_dumper }
|
8
|
+
|
9
|
+
context "with pg_dump utility" do
|
10
|
+
before { subject.stub(:find_executable){ "pg_dump" } }
|
11
|
+
its(:command) { should == "pg_dump database" }
|
12
|
+
its(:args) { should be_empty }
|
13
|
+
|
14
|
+
context "with output set" do
|
15
|
+
let(:file) { "my-file" }
|
16
|
+
before { subject.output = file }
|
17
|
+
its(:command) { should == "pg_dump -f #{file} database" }
|
18
|
+
its(:output) { should == file }
|
19
|
+
its(:args) { should == ["-f", file] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,73 +1,70 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_dumper
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
version: 0.0.2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- mikz
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
12
|
+
date: 2011-12-06 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70198585003040 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.7'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70198585003040
|
25
|
+
description: Provides abstraction layer between pg_dump utility and ruby. Also adds
|
26
|
+
rake task to easily dump database with or without data.
|
27
|
+
email:
|
23
28
|
- mikz@o2h.cz
|
24
29
|
executables: []
|
25
|
-
|
26
30
|
extensions: []
|
27
|
-
|
28
31
|
extra_rdoc_files: []
|
29
|
-
|
30
|
-
files:
|
32
|
+
files:
|
31
33
|
- .gitignore
|
32
34
|
- Gemfile
|
35
|
+
- Guardfile
|
33
36
|
- README.md
|
34
37
|
- Rakefile
|
35
38
|
- lib/pg_dumper.rb
|
36
39
|
- lib/pg_dumper/railtie.rb
|
40
|
+
- lib/pg_dumper/vendor/escape.rb
|
37
41
|
- lib/pg_dumper/version.rb
|
38
42
|
- lib/tasks/db_dump.rake
|
39
43
|
- pg_dumper.gemspec
|
40
|
-
|
41
|
-
homepage:
|
44
|
+
- spec/pg_dumper_spec.rb
|
45
|
+
homepage: ''
|
42
46
|
licenses: []
|
43
|
-
|
44
47
|
post_install_message:
|
45
48
|
rdoc_options: []
|
46
|
-
|
47
|
-
require_paths:
|
49
|
+
require_paths:
|
48
50
|
- lib
|
49
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
52
|
none: false
|
51
|
-
requirements:
|
52
|
-
- -
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
|
55
|
-
|
56
|
-
version: "0"
|
57
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
58
|
none: false
|
59
|
-
requirements:
|
60
|
-
- -
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
|
63
|
-
- 0
|
64
|
-
version: "0"
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
65
63
|
requirements: []
|
66
|
-
|
67
64
|
rubyforge_project:
|
68
|
-
rubygems_version: 1.
|
65
|
+
rubygems_version: 1.8.10
|
69
66
|
signing_key:
|
70
67
|
specification_version: 3
|
71
68
|
summary: Adds Rake tasks to easily dump postgresql database with or without data
|
72
|
-
test_files:
|
73
|
-
|
69
|
+
test_files:
|
70
|
+
- spec/pg_dumper_spec.rb
|