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 CHANGED
@@ -2,3 +2,6 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in pg_dumper.gemspec
4
4
  gemspec
5
+
6
+ gem 'guard-rspec'
7
+ gem 'guard-bundler'
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
@@ -15,4 +15,6 @@ Dumps database schema **with data**.
15
15
  ### db:dump:schema
16
16
  Dumps database schema **without data**.
17
17
 
18
+ # Credits
19
+ PgDumper uses awesome [Escape](https://github.com/akr/escape) library so thanks to [Tanaka Akira](https://github.com/akr).
18
20
 
@@ -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
+ '&' => '&amp;',
318
+ '<' => '&lt;',
319
+ '>' => '&gt;',
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 '&amp;'
328
+ # * '<' to '&lt;'
329
+ # * '>' to '&gt;'
330
+ #
331
+ # Escape.html_text("abc") #=> #<Escape::HTMLEscaped: abc>
332
+ # Escape.html_text("a & b < c > d") #=> #<Escape::HTMLEscaped: a &amp; b &lt; c &gt; 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
+ '&' => '&amp;',
344
+ '<' => '&lt;',
345
+ '>' => '&gt;',
346
+ '"' => '&quot;',
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&amp;b">
358
+ # Escape.html_attr_value("ab&<>\"c") #=> #<Escape::HTMLAttrValue: "ab&amp;&lt;&gt;&quot;c">
359
+ # Escape.html_attr_value("a'c") #=> #<Escape::HTMLAttrValue: "a'c">
360
+ #
361
+ # It escapes 4 characters:
362
+ # * '&' to '&amp;'
363
+ # * '<' to '&lt;'
364
+ # * '>' to '&gt;'
365
+ # * '"' to '&quot;'
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
@@ -1,3 +1,3 @@
1
1
  class PgDumper
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
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 :args
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
- @binary ||= find_executable
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
- system @binary, *args, database, options
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 recreate!
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
- add_args "-f", filename
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.present? && File.exists?(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
- end
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
- prerelease: false
5
- segments:
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
- date: 2011-02-01 00:00:00 +01:00
18
- default_executable:
19
- dependencies: []
20
-
21
- description: Provides abstraction layer between pg_dump utility and ruby. Also adds rake task to easily dump database with or without data.
22
- email:
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
- has_rdoc: true
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
- segments:
55
- - 0
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
- segments:
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.3.7
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