sqlconv 1.1 → 1.2.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.
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