csv-curl 0.1.0 → 0.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 (6) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/README.md +54 -0
  4. data/bin/csv-curl +47 -18
  5. data/csv-curl.gemspec +2 -2
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d04f4437667318ac6a0d869e1ebbc6b72de7d6d1c86ecd55b98dd8d74ae79a5b
4
- data.tar.gz: 944c0ef6d3837bbd38e4d1bd11b4e3d36fab95274452cb121f9440e34b0f9748
3
+ metadata.gz: 381850913b0f63297ab7a5186c9108880d09774930604bd965f7958b573bdeca
4
+ data.tar.gz: df854cee03e5e4f2c1bc68da0d1c37a845ab8ad9fcb9b19aa0c8f024064999c1
5
5
  SHA512:
6
- metadata.gz: c432715c775b43c27f38b6e21b9410a934ce87a528a17c32f86ac906de98185d17e3e84bcb700dab4f767d7fae4f0dd3df4eda5cdb475e200e785db5f5fa0b01
7
- data.tar.gz: db278224c3a4e4d1f702de80495f4758796916ac69ca363cebe9c94cd580948ec8a3adae0315994f4d62f3c5126831519ddac915cc21fabf59b43aab9a847035
6
+ metadata.gz: 9582c3888f042d002b1d229d5433c2bfa7c9d4e8cf7845d3caed560219614f393eca5fa659c8f2f286c7222ebcee4b53bbdccbe576356998a2d79df89cd6a755
7
+ data.tar.gz: c45dd8f7fc333b38b493b2b4904956b678c498c7fa281f41b90a6d7960e3037ab5bdd3732c2a366087e949d3673de0f69aed6ce21e0be18e3bacba443c6109f1
data/.gitignore CHANGED
@@ -50,3 +50,6 @@ build-iPhoneSimulator/
50
50
  .rvmrc
51
51
 
52
52
  *~
53
+
54
+ csv-utils.response
55
+ csv-utils.request
data/README.md CHANGED
@@ -1 +1,55 @@
1
1
  # csv-curl
2
+
3
+ Utility for making multiple curl requests using a CSV file.
4
+
5
+ ### Usage
6
+ ```
7
+ Usage: csv-curl[OPTIONS] [CURL OPTIONS]
8
+ --template FILE Path to the template file
9
+ --csv CSV_FILE Path to the CSV file
10
+ --exec UTILITY Utility to run on each response
11
+ ```
12
+
13
+ ### Template
14
+
15
+ File containing the payload to pass to the curl command, the "-d@<file>" option.
16
+
17
+ ### CSV File
18
+
19
+ A CSV file containing the replacement values to use. It must have a header. And have a header for all replacement variables.
20
+
21
+ ### Utility
22
+
23
+ This is an additional command to run on the response body from the curl command.
24
+
25
+ Example to output only the "id" from a JSON body
26
+
27
+ ```
28
+ csv-curl --csv emails.csv 'https://example.com/user_lookup?email={{param:email}}' --exec 'jq ".id" -r'
29
+ ```
30
+
31
+ ### Replacement variables
32
+
33
+ A replacement variable is enclosed in handlebars **{{** **format:** **name** **}}**. Replacement variables can be used in the arguments to curl, in the template and in the utility.
34
+
35
+ *(format and name a separated by a colon)*
36
+
37
+ - **name** matches a header in the CSV file
38
+ - **format** escape sequence to apply the the replacement value
39
+ - _json_ JSON encode
40
+ - _param_ URL encode
41
+ - _base64_ Base64 encode
42
+ - _urlsafe64_ URL safe Base64 encode
43
+ - _hex_ Hex encode
44
+ - _shell_ Shell encode
45
+
46
+ Example: lookup users by email and save response by user id.
47
+
48
+ ```
49
+ csv-curl --csv users.csv 'https://example.com/user_lookup?email={{param:email}}' --exec 'cat > {{id}}.json'
50
+
51
+ # users.csv
52
+ id,email
53
+ 5,bob@example.com
54
+ 8,johndoe@example.com
55
+ ```
@@ -11,30 +11,51 @@ require 'digest/md5'
11
11
  options = {
12
12
  template_file: nil,
13
13
  csv_file: nil,
14
- response_filter: nil,
14
+ exec: nil,
15
15
  default_format: 'json'
16
16
  }
17
17
 
18
- OptionParser.new do |opts|
19
- opts.banner = "Usage: " + File.basename(__FILE__) + " [CURL OPTIONS]"
18
+ OPTPARSE = OptionParser.new do |opts|
19
+ opts.banner = "Usage: " + File.basename(__FILE__) + "[OPTIONS] [CURL OPTIONS]"
20
20
 
21
- opts.on('--template REQUEST', 'Request template') do |v|
21
+ opts.on('--template FILE', 'Path to the template file') do |v|
22
22
  options[:template_file] = v
23
23
  end
24
24
 
25
- opts.on('--csv CSV_FILE', 'Request data') do |v|
25
+ opts.on('--csv CSV_FILE', 'Path to the CSV file') do |v|
26
26
  options[:csv_file] = v
27
27
  end
28
28
 
29
- opts.on('--response-filter COMMAND', 'Command to run on each response') do |v|
30
- options[:response_filter] = v
29
+ opts.on('--exec UTILITY', 'Utility to run on each response') do |v|
30
+ options[:exec] = v
31
31
  end
32
- end.parse!
32
+ end
33
+
34
+ CSV_CURL_ARGV = []
35
+ ARGV.size.times.to_a.reverse.each do |idx|
36
+ case ARGV[idx]
37
+ when '--template',
38
+ '--csv',
39
+ '--exec'
40
+ CSV_CURL_ARGV << ARGV.delete_at(idx)
41
+ CSV_CURL_ARGV << ARGV.delete_at(idx)
42
+ end
43
+ end
44
+ OPTPARSE.parse(CSV_CURL_ARGV)
45
+
46
+ def failed(msg, errno = 1)
47
+ $stderr.puts msg
48
+ if errno == 1
49
+ puts ""
50
+ puts OPTPARSE
51
+ end
52
+ exit errno
53
+ end
33
54
 
34
- raise('no csv input file specified') unless options[:csv_file]
35
- raise("csv input file #{options[:csv_file]} not found") unless File.exist?(options[:csv_file])
55
+ failed('no csv input file specified') unless options[:csv_file]
56
+ failed("csv input file #{options[:csv_file]} not found") unless File.exist?(options[:csv_file])
36
57
 
37
- raise("template file #{options[:template_file]} not found") if options[:template_file] && !File.exist?(options[:template_file])
58
+ failed("template file #{options[:template_file]} not found") if options[:template_file] && !File.exist?(options[:template_file])
38
59
 
39
60
  template = options[:template_file] ? File.read(options[:template_file]) : nil
40
61
  input = CSV.open(options[:csv_file])
@@ -44,18 +65,20 @@ required_replacements = ARGV.map do |arg|
44
65
  arg.scan(/\{\{(.*?)\}\}/)
45
66
  end
46
67
 
47
- required_replacements += options[:response_filter].scan(/\{\{(.*?)\}\}/) if options[:response_filter]
68
+ required_replacements += options[:exec].scan(/\{\{(.*?)\}\}/) if options[:exec]
48
69
  required_replacements += template.scan(/\{\{(.*?)\}\}/) if template
49
70
  required_replacements.flatten!
50
71
  required_replacements.map! { |v| v.split(':', 2).last }
51
72
  required_replacements.uniq!
52
73
 
53
74
  missing_replacements = required_replacements - input_headers
54
- raise("missing replacement values for #{missing_replacements.join(', ')}") unless missing_replacements.empty?
75
+ failed("missing replacement values for #{missing_replacements.join(', ')}", 2) unless missing_replacements.empty?
55
76
 
56
77
  TMP_REQUEST_BODY_FILE = 'csv-utils.request'
57
78
 
58
79
  def format_value(value, format)
80
+ return '' unless value
81
+
59
82
  case format
60
83
  when 'json'
61
84
  value = value.to_json
@@ -67,6 +90,8 @@ def format_value(value, format)
67
90
  CGI.escape(value)
68
91
  when 'base64'
69
92
  Base64.strict_encode64(value)
93
+ when 'urlsafe64'
94
+ Base64.urlsafe_encode64(value)
70
95
  when 'hex'
71
96
  value.unpack('H*').first
72
97
  when 'shellword',
@@ -92,7 +117,7 @@ end
92
117
 
93
118
  def run_command_safely(cmd)
94
119
  res = `#{cmd}`
95
- raise("failed to run command: #{cmd}") unless $?.success?
120
+ failed("failed to run command: #{cmd}", 2) unless $?.success?
96
121
  res
97
122
  end
98
123
 
@@ -104,9 +129,9 @@ def build_curl_command(curl_args, data, request_file, response_file)
104
129
  cmd
105
130
  end
106
131
 
107
- def build_response_filter_command(response_filter, data, response_file)
132
+ def build_exec_command(exec, data, response_file)
108
133
  cmd = "cat #{response_file} | "
109
- cmd += generate_string(response_filter, data)
134
+ cmd += generate_string(exec, data)
110
135
  cmd
111
136
  end
112
137
 
@@ -128,12 +153,16 @@ while (row = input.shift)
128
153
  run_command_safely(cmd)
129
154
 
130
155
  output =
131
- if options[:response_filter]
132
- cmd = build_response_filter_command(options[:response_filter], data, response_file)
156
+ if options[:exec]
157
+ cmd = build_exec_command(options[:exec], data, response_file)
133
158
  run_command_safely(cmd)
134
159
  else
135
160
  File.read(response_file)
136
161
  end
137
162
 
138
163
  puts output
164
+
165
+ File.unlink(response_file) if File.exist?(response_file)
139
166
  end
167
+
168
+ File.unlink(TMP_REQUEST_BODY_FILE) if File.exist?(TMP_REQUEST_BODY_FILE)
@@ -2,14 +2,14 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'csv-curl'
5
- s.version = '0.1.0'
5
+ s.version = '0.2.0'
6
6
  s.licenses = ['MIT']
7
7
  s.summary = 'CSV Curl'
8
8
  s.description = 'Tools making mulitple calls using curl'
9
9
  s.authors = ['Doug Youch']
10
10
  s.email = 'dougyouch@gmail.com'
11
11
  s.homepage = 'https://github.com/dougyouch/csv-curl'
12
- s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
12
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples)/}) }
13
13
  s.bindir = 'bin'
14
14
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csv-curl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Doug Youch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-22 00:00:00.000000000 Z
11
+ date: 2019-08-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Tools making mulitple calls using curl
14
14
  email: dougyouch@gmail.com