sqlconv 1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sqlconv +81 -76
  3. data/sqlconv.gemspec +1 -1
  4. metadata +6 -8
  5. data/.ruby-version +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dad0487cbd81b9dd742216fcbf77cff8cdbe0409599faf1719c52a6eab74429d
4
- data.tar.gz: 6472f28fe595994f837c861d2aaa45974fa7327e9473fcf0aaeab1cb424d5713
3
+ metadata.gz: e823f40e2647848b38ea093b7127f31ea1984ca5f178826d595e72fa7f08c71b
4
+ data.tar.gz: 6467e992cf18952ec3dc357ef60f93d6ba95d488ea9a1c8edb6d3c6efe576f29
5
5
  SHA512:
6
- metadata.gz: fa417046fe5c9c5ca098cfade1b042a59d418f7f27e076726d7c6f56a2ac82ae6b66a7a7d2885a6061b6f249636452d673bd352b2ace8085b17dedb3b5ff5d37
7
- data.tar.gz: db52a84b235ecda37b54604013922ab68cd53dd702fe62d0888a411714f3ad3db78fa5db18ffef1f0cdd8c0a8583161fd9eee1fb6441e7a0d1905f58fa010420
6
+ metadata.gz: e725ce923d38cdc32c5a824f07e0734ebfaa06a371c5a4ca0b1ccf671880f89d5e68b413f1f40d4900ce95fcce61a78977ac768779de0c677c1c0f90461d654b
7
+ data.tar.gz: ee0fb55e02a53abee3f1a8cebfc2be79e5001f460ff7f45f8989cbfb3fcb5e412f03f5a55833823d9cd184afb8b7d7d5c967db4f4269b9ec99e1d54e314633a1
data/bin/sqlconv CHANGED
@@ -1,15 +1,59 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ VERSION = "1.2.0"
4
+
3
5
  STDOUT.sync = true
4
6
 
5
- def die(str)
6
- warn str
7
- exit
7
+ require "censive"
8
+ require "optparse"
9
+ require "strscan"
10
+
11
+ trap("INT" ) { abort "\n" }
12
+
13
+ def die!; abort "#{File.basename($0)}: invalid usage, use -h for help"; end
14
+
15
+ OptionParser.new.instance_eval do
16
+ @banner = "usage: #{program_name} <options> <src_table>(:[sel1,sel2,...]) [dst_table][:][col1,col2,...] <dumpfile.sql or ARGF>"
17
+
18
+ on "--csv" , "Output comma separated values instead of SQL"
19
+ on "--psv" , "Output pipe separated values instead of SQL"
20
+ on "--tsv" , "Output tab separated values instead of SQL"
21
+ on "-p", "--plugin <plugin1.rb,...>", "Comma separated list of plugins"
22
+ on "-r", "--replace" , "Use 'replace into' instead of 'insert into'"
23
+ on "-s", "--show" , "Show column indexes and values for the first row"
24
+ on "-t", "--table" , "Display output as a formatted table"
25
+
26
+ on "-h", "--help" , "Show help and command usage" do Kernel.abort to_s; end
27
+ on "-v", "--version" , "Show version number" do Kernel.abort "#{program_name} #{VERSION}"; end
28
+
29
+ self
30
+ end.parse!(into: opts={}) rescue abort($!.message)
31
+
32
+ csvs = opts[:csv ] and mode = "csv"
33
+ pipe = opts[:psv ] and mode = "psv"
34
+ tabs = opts[:tsv ] and mode = "tsv"
35
+ nice = opts[:table ] and mode = "table"
36
+ repl = opts[:replace] and mode = "replace"
37
+ show = opts[:show ] and mode = "show"
38
+ plug = opts[:plugin].to_s.downcase.split(",")
39
+
40
+ die! if [csvs, pipe, tabs, nice, repl, show].compact.size > 1
41
+
42
+ if ARGV.shift =~ /^([a-z][-\w]*):?(.+)?$/i
43
+ tab1 = $1
44
+ map1 = $2
45
+ end
46
+
47
+ if !ARGV.empty? and !File.exist?(ARGV.first)
48
+ if ARGV.shift =~ /^((?>[a-z]?[-\w]*)(?::|$))?(.+)?$/i
49
+ $1.to_s.size > 0 and tab2 = $1.chomp(":")
50
+ $2.to_s.size > 0 and map2 = $2.squeeze(",").sub(/^,+/,"").sub(/,+$/,"")
51
+ end
52
+ die! if $0.empty?
8
53
  end
9
54
 
10
- require 'strscan'
55
+ # ==[ Helpers ]==
11
56
 
12
- # parsing helpers
13
57
  class StringScanner
14
58
  def scan_for(regx)
15
59
  data = scan_until(Regexp === regx ? regx : /#{regx}/)
@@ -64,20 +108,23 @@ end
64
108
  def table(cols, rows)
65
109
  cols.is_a?(Array) && cols.size > 0 or return
66
110
  rows.is_a?(Array) && rows.size > 0 or return
67
- join = " | "
111
+ join = " "
68
112
  both = [cols] + rows
69
113
  flip = both.transpose
70
114
  wide = flip.map {|row| row.map {|col| col.to_s.size }.max }
71
115
  pict = wide.map {|len| "%-#{len}.#{len}s" }.join(join)
72
116
  pict = [join, pict, join].join.strip
73
- line = (pict % ([""] * cols.size)).tr("| ", "+-")
117
+ base = (pict % ([""] * cols.size))[1...-1]
118
+ ltop = "┌" + base.tr("│ ", "┬─") + "┐"
119
+ lmid = "├" + base.tr("│ ", "┼─") + "┤"
120
+ lbot = "└" + base.tr("│ ", "┴─") + "┘"
74
121
  seen = -1
75
- puts "", line
122
+ puts "", ltop
76
123
  both.each do |vals|
77
124
  puts pict % vals
78
- puts line if (seen += 1) == 0
125
+ puts lmid if (seen += 1) == 0
79
126
  end
80
- puts line, "#{seen} rows displayed", ""
127
+ puts lbot, "#{seen} rows displayed", ""
81
128
  end
82
129
 
83
130
  def escape(str)
@@ -86,7 +133,7 @@ end
86
133
 
87
134
  def unescape(str, nulls=false)
88
135
  str =~ /\A['"]/ and return str[1..-2].gsub("|","~").gsub("''", "'")
89
- str == 'NULL' and return '' unless nulls
136
+ str == "NULL" and return "" unless nulls
90
137
  str
91
138
  end
92
139
 
@@ -115,13 +162,18 @@ def conv(tab1, map1, tab2, map2, mode, dump)
115
162
  # find source table
116
163
  data.string = dump.read # dump.read(5000) # TODO: Add streaming support
117
164
  into = data.scan_for(/insert into (['"`]?)#{tab1}\1 values /io)
118
- into or die "unable to find insert statements for the '#{tab1}' table"
165
+ into or abort "unable to find insert statements for the '#{tab1}' table"
119
166
 
120
167
  # if needed, output pipes header
121
- if mode == "pipes"
122
- puts map2.gsub(',','|') if map2
168
+ case mode
169
+ when "psv", "tsv"
170
+ puts map2.gsub(",", mode == "psv" ? "|" : "\t") if map2
123
171
  lean = true
124
- elsif mode == "table"
172
+ when "csv"
173
+ $csv = Censive.writer(out: $stdout)
174
+ $csv << map2.split(",") if map2
175
+ lean = true
176
+ when "table"
125
177
  rows = []
126
178
  lean = true
127
179
  end
@@ -132,7 +184,7 @@ def conv(tab1, map1, tab2, map2, mode, dump)
132
184
  # parse insert statements
133
185
  if data.scan_str("(") or data.scan_str(into + "(")
134
186
  cols = data.scan_while(/('.*?(?<!\\)'|(?>[^',()]+)|,)/, 2)
135
- cols.empty? and die "bad sql parse: '#{line}'"
187
+ cols.empty? and abort "bad sql parse: '#{line}'"
136
188
  cols.map! {|item| unescape(item)} if lean
137
189
  data.scan(/\)[;,]\s*/)
138
190
  else
@@ -143,15 +195,10 @@ def conv(tab1, map1, tab2, map2, mode, dump)
143
195
  unless len1
144
196
  len1 = cols.size
145
197
  if mode == "show"
146
- max = 32
147
- sep = "+-----+-#{'-' * max}-+"
148
- puts sep, "| col | %-*.*s |" % [max, max, 'data'], sep
149
- len1.times do |pos|
150
- val = cols[pos]
151
- val[max-3..-1] = '...' if val.size > max
152
- puts "| %3d | %-*.*s | " % [pos + 1, max, max, val]
198
+ data = cols.map.with_index do |data, i|
199
+ [i + 1, data.size > 32 ? data[...-3] + "..." : data]
153
200
  end
154
- puts sep
201
+ table %w[ col data], data
155
202
  exit
156
203
  end
157
204
  need.each do |item|
@@ -182,7 +229,7 @@ def conv(tab1, map1, tab2, map2, mode, dump)
182
229
  val = val[0, item.from]
183
230
  ours.push(val)
184
231
  else
185
- defined?(item.func) == "method" or die "undefined function '#{item.func}'"
232
+ defined?(item.func) == "method" or abort "undefined function '#{item.func}'"
186
233
  ours.push *(send item.func, *Array[cols[item.from-1]])
187
234
  end
188
235
  when item.text # literal
@@ -207,7 +254,7 @@ def conv(tab1, map1, tab2, map2, mode, dump)
207
254
  # perform one-time check on destination column counts
208
255
  unless len2
209
256
  if map2 and (len2 = map2.split(",").size) != ours.size
210
- warn "destination column mismatch (#{len2} defined but #{ours.size} generated)"
257
+ warn "destination column mismatch (#{ours.size} sourced but #{len2} targeted)"
211
258
  cols &&= nil
212
259
  else
213
260
  len2 = ours.size
@@ -216,64 +263,22 @@ def conv(tab1, map1, tab2, map2, mode, dump)
216
263
  end
217
264
 
218
265
  # generate output
219
- if mode == "pipes"
220
- puts ours * "|"
221
- elsif mode == "table"
222
- rows << ours.dup
223
- else
224
- puts [pref, ours * ",", ");"].join
266
+ case mode
267
+ when "psv" then puts ours * "|"
268
+ when "tsv" then puts ours * "\t"
269
+ when "csv" then $csv << ours
270
+ when "table" then rows << ours.dup
271
+ else puts [pref, ours * ",", ");"].join
225
272
  end
226
273
  end
227
274
 
228
275
  # output table
229
276
  if mode == "table"
230
- cols = map2 ? map2.split(',') : rows[0].size.times.map {|i| "col#{i+1}"}
277
+ cols = map2 ? map2.split(",") : rows[0].size.times.map {|i| "col#{i+1}"}
231
278
  table cols, rows
232
279
  end
233
280
  end
234
281
 
235
- # ==[ invoke the cli ]==
236
-
237
- argv = -1
238
- ARGV.size.times do
239
- if ARGV[argv += 1] == "-x"
240
- begin
241
- require plugin = ARGV[argv += 1]
242
- rescue LoadError
243
- die "unable to load the '#{plugin}' plugin"
244
- end
245
- ARGV.slice!((argv -= 2) + 1, 2)
246
- end
247
- end
248
-
249
- # mutually exclusive options
250
- if ARGV.delete "-s" then mode = "show"
251
- elsif ARGV.delete "-r" then mode = "replace"
252
- elsif ARGV.delete "-p" then mode = "pipes"
253
- elsif ARGV.delete "-t" then mode = "table"; end
254
-
255
- if ARGV.shift =~ /^([a-z][-\w]*):?(.+)?$/
256
- tab1 = $1
257
- map1 = $2
258
- end
259
-
260
- if ARGV.size > 0 and !File.exists?(ARGV.first)
261
- if ARGV.shift =~ /^((?>[a-z]?[-\w]*)(?::|$))?(.+)?$/
262
- $1.to_s.size > 0 and tab2 = $1.chomp(":")
263
- $2.to_s.size > 0 and map2 = $2.squeeze(',').sub(/^,+/,'').sub(/,+$/,'')
264
- end
265
- tab1 = nil if $0.size == 0 # no match, show usage
266
- end
267
-
268
- tab1 or die [
269
- "Usage: #{File.basename $0} <options> " +
270
- "<src_table>(:[sel1,sel2,...]) " +
271
- "[dst_table][:][col1,col2,...] file",
272
- " -p (output pipe separated values instead of SQL",
273
- " -r (use 'replace into' instead of 'insert into')",
274
- " -s (show column indexes and values for the first row)",
275
- " -t (display output as a formatted table)",
276
- " -x <plugin1.rb> [-x <plugin2.rb>]...]",
277
- ] * "\n"
282
+ # ==[ Let 'er rip! ]==
278
283
 
279
284
  conv tab1, map1, tab2 || tab1, map2, mode, ARGF
data/sqlconv.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "sqlconv"
5
- s.version = "1.1"
5
+ s.version = `grep '^VERSION' bin/sqlconv | cut -f 2 -d '"'`
6
6
  s.author = "Steve Shreeve"
7
7
  s.email = "steve.shreeve@gmail.com"
8
8
  s.summary = "Handy utility to massage MySQL dump files"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlconv
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Shreeve
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-29 00:00:00.000000000 Z
11
+ date: 2023-03-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Allows mapping columns from a source to a destination table
14
14
  email: steve.shreeve@gmail.com
@@ -17,7 +17,6 @@ executables:
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - ".ruby-version"
21
20
  - Gemfile
22
21
  - LICENSE
23
22
  - README.md
@@ -27,7 +26,7 @@ homepage: https://github.com/shreeve/sqlconv
27
26
  licenses:
28
27
  - MIT
29
28
  metadata: {}
30
- post_install_message:
29
+ post_install_message:
31
30
  rdoc_options: []
32
31
  require_paths:
33
32
  - lib
@@ -42,9 +41,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
42
41
  - !ruby/object:Gem::Version
43
42
  version: '0'
44
43
  requirements: []
45
- rubyforge_project:
46
- rubygems_version: 2.7.7
47
- signing_key:
44
+ rubygems_version: 3.4.8
45
+ signing_key:
48
46
  specification_version: 4
49
47
  summary: Handy utility to massage MySQL dump files
50
48
  test_files: []
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 2.5