pg_dumper 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|