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 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