poke-your-api 0.4.1 → 0.4.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9349d971fd268f0c75b1d9409088e4b33491ce5fa95f4fa29a780af02400904
4
- data.tar.gz: 36e07caa239aef944dbad213412e48f6e7a8acc68b035d3e61ad6d1fd0dea5df
3
+ metadata.gz: 2b1af586770454d02923775c16441dddcd89fe5a1f548963c2b4bf1e413c980f
4
+ data.tar.gz: 0d069e7c30c835b3a7c6d8bdb8a71a8028aa918af7901b3d305f84688579f801
5
5
  SHA512:
6
- metadata.gz: b097dcf106434d01689f31191fd73036c4a7c4c488c5db41acc43db7dac3cb6e8e33fa1dbbd101ad864ee9da020adb978aab9be9d76421c4d0da41f175a4e9dc
7
- data.tar.gz: 022a899d6bdc476e15148725c65ff5e8eec0698db1b236de74c664fd2b118fba9e9831520af2696dc65bc6e061901c919aaa5f77f79b6e4367190c81b316b1dd
6
+ metadata.gz: be055ccc527d773f94b12dcc5d9dbe8eb014a45a290fc232511dffe32eef3eedf8c7ca2c27f63446e810e9f1f0ed64b91b3a8017e18d14d7dbf2e99a95f0c98f
7
+ data.tar.gz: 51f82084918632f89ba63722519f9503628fb48f5f2783d88a55c3b3b93f874158d42404448b96240ba08a7548b209ff83634cafc2de6d2cf9ae4523bed18495
data/lib/poke/cli.rb CHANGED
@@ -52,6 +52,8 @@ module Poke
52
52
  method_option :open, aliases: '-o', type: :string, desc: 'Open request in the editor'
53
53
  method_option :set_name, aliases: '-N', type: :string, desc: 'Set request name'
54
54
  method_option :name, aliases: '-n', type: :string, desc: 'Find request by name'
55
+ method_option :print, aliases: '-p', type: :boolean,
56
+ desc: 'Print a shareable curl command without executing it'
55
57
  def curl(*)
56
58
  if options[:help]
57
59
  invoke :help, ['curl']
@@ -55,6 +55,11 @@ module Poke
55
55
  env = @options.fetch(:env, request.group.config.default_env)
56
56
  raise Poke::GroupConfig::InvalidEnv unless request.group.config.valid_env?(env)
57
57
 
58
+ if @options[:print]
59
+ print_resolved_curl(request, env, output)
60
+ return
61
+ end
62
+
58
63
  curl_command, comments = build_command(request.path)
59
64
 
60
65
  table = TTY::Table.new(
@@ -97,6 +102,11 @@ module Poke
97
102
  output << "\n#{File.read(Poke::Config.response_path)}\n"
98
103
  end
99
104
 
105
+ def print_resolved_curl(request, env, output)
106
+ parser = CurlParser.new(File.read(request.path))
107
+ output << "#{parser.to_resolved_command(request.group.config.variables(env), multiline: true)}\n"
108
+ end
109
+
100
110
  def build_command(path)
101
111
  content = File.read(path)
102
112
  parser = CurlParser.new(content)
@@ -10,38 +10,42 @@ module Poke
10
10
  @content = content
11
11
  @comments = []
12
12
  @arguments = []
13
+ @tokens = []
13
14
  @url = nil
14
15
  parse
15
16
  end
16
17
 
17
18
  def to_command
18
- parts = ['curl']
19
- parts.concat(@arguments)
20
- parts << @url if @url
21
- parts.join(' ')
19
+ ['curl', *@tokens].join(' ')
22
20
  end
23
21
 
24
22
  def to_command_with_line_continuation
25
- parts = ['curl']
26
- parts.concat(@arguments)
27
- parts << @url if @url
28
-
29
- # Join with line continuation for better readability
30
- parts.join(" \\\n ")
23
+ ['curl', *@tokens].join(" \\\n ")
24
+ end
25
+
26
+ # Fill in environment variables and shell-quote the result so it can be copied and run without poke.
27
+ # Single-quoted text is left as-is, matching shell expansion rules.
28
+ def to_resolved_command(variables, multiline: false)
29
+ variables = variables.to_h.transform_keys(&:to_s)
30
+ tokens = @tokens.map { |token| expand_shell_word(token, variables) }
31
+ return ['curl', *tokens].join(' ') unless multiline
32
+
33
+ multiline_command(tokens)
31
34
  end
32
35
 
33
36
  def add_argument(arg)
34
37
  @arguments << arg
38
+ @tokens << arg
35
39
  end
36
40
 
37
41
  def remove_argument(pattern)
38
42
  @arguments.reject! { |arg| arg.match?(pattern) }
43
+ @tokens.reject! { |arg| arg.match?(pattern) }
39
44
  end
40
45
 
41
46
  def replace_argument(pattern, replacement)
42
- @arguments.map! do |arg|
43
- arg.match?(pattern) ? replacement : arg
44
- end
47
+ @arguments.map! { |arg| arg.match?(pattern) ? replacement : arg }
48
+ @tokens.map! { |arg| arg.match?(pattern) ? replacement : arg }
45
49
  end
46
50
 
47
51
  private
@@ -108,6 +112,7 @@ module Poke
108
112
  args = split_arguments(content)
109
113
 
110
114
  args.each do |arg|
115
+ @tokens << arg
111
116
  if arg.start_with?('-')
112
117
  @arguments << arg
113
118
  elsif @url.nil?
@@ -119,6 +124,126 @@ module Poke
119
124
  end
120
125
  end
121
126
 
127
+ def multiline_command(tokens)
128
+ lines = []
129
+ index = 0
130
+
131
+ while index < tokens.length
132
+ token = tokens[index]
133
+ nxt = tokens[index + 1]
134
+ if (body = json_body_line(token, nxt))
135
+ lines << body[:text]
136
+ index += body[:consumed]
137
+ elsif token.start_with?('-') && nxt && !nxt.start_with?('-')
138
+ lines << "#{token} #{nxt}"
139
+ index += 2
140
+ else
141
+ lines << token
142
+ index += 1
143
+ end
144
+ end
145
+
146
+ ['curl', *lines].join(" \\\n ")
147
+ end
148
+
149
+ def json_body_line(token, nxt)
150
+ flag, value, consumed = split_body_flag(token, nxt)
151
+ return nil unless flag
152
+
153
+ pretty = pretty_json_value(value)
154
+ return nil unless pretty
155
+
156
+ { text: "#{flag} #{pretty}", consumed: consumed }
157
+ end
158
+
159
+ def split_body_flag(token, nxt)
160
+ JSON_BODY_FLAGS.each do |flag|
161
+ return [flag, nxt, 2] if token == flag && nxt && !nxt.start_with?('-')
162
+
163
+ if flag.start_with?('--') && token.start_with?("#{flag}=") && token.length > flag.length + 1
164
+ return [flag, token[(flag.length + 1)..], 1]
165
+ end
166
+
167
+ if flag == '-d' && token.start_with?('-d') && !token.start_with?('--') && token.length > 2
168
+ return ['-d', token[2..], 1]
169
+ end
170
+ end
171
+ nil
172
+ end
173
+
174
+ def pretty_json_value(value)
175
+ raw = unquote_shell(value)
176
+ return nil if raw.nil?
177
+
178
+ parsed = JSON.parse(raw)
179
+ return nil unless parsed.is_a?(Hash) || parsed.is_a?(Array)
180
+
181
+ pretty = JSON.pretty_generate(parsed)
182
+ return nil unless pretty.include?("\n")
183
+
184
+ quote_single(indent_json_block(pretty))
185
+ rescue JSON::ParserError
186
+ nil
187
+ end
188
+
189
+ def indent_json_block(pretty)
190
+ pretty.lines.map.with_index { |line, index| index.zero? ? line : " #{line}" }.join
191
+ end
192
+
193
+ def quote_single(value)
194
+ "'#{value.gsub("'") { "'\\''" }}'"
195
+ end
196
+
197
+ def unquote_shell(word)
198
+ if word.start_with?("'")
199
+ decode_single_quoted(word)
200
+ elsif word.start_with?('"') && word.end_with?('"') && word.length >= 2
201
+ decode_double_quoted(word)
202
+ else
203
+ word
204
+ end
205
+ end
206
+
207
+ def decode_single_quoted(word)
208
+ result = +''
209
+ index = 1
210
+ return nil unless word[0] == "'"
211
+
212
+ while index < word.length
213
+ if word[index] == "'"
214
+ index += 1
215
+ return result if index == word.length
216
+ return nil unless word[index, 2] == "\\'" && word[index + 2] == "'"
217
+
218
+ result << "'"
219
+ index += 3
220
+ else
221
+ result << word[index]
222
+ index += 1
223
+ end
224
+ end
225
+
226
+ nil
227
+ end
228
+
229
+ def decode_double_quoted(word)
230
+ inner = word[1..-2]
231
+ result = +''
232
+ index = 0
233
+
234
+ while index < inner.length
235
+ if inner[index] == '\\' && index + 1 < inner.length
236
+ result << inner[index + 1]
237
+ index += 2
238
+ else
239
+ result << inner[index]
240
+ index += 1
241
+ end
242
+ end
243
+
244
+ result
245
+ end
246
+
122
247
  def split_arguments(line)
123
248
  args = []
124
249
  current_arg = ""
@@ -129,7 +254,15 @@ module Poke
129
254
  while i < line.length
130
255
  char = line[i]
131
256
 
132
- if !in_quotes && char.match?(/\s/)
257
+ if !in_quotes && char == '\\' && i + 1 < line.length
258
+ current_arg += char + line[i + 1]
259
+ i += 2
260
+ next
261
+ elsif in_quotes && quote_char == '"' && char == '\\' && i + 1 < line.length
262
+ current_arg += char + line[i + 1]
263
+ i += 2
264
+ next
265
+ elsif !in_quotes && char.match?(/\s/)
133
266
  if !current_arg.empty?
134
267
  args << current_arg
135
268
  current_arg = ""
@@ -152,5 +285,73 @@ module Poke
152
285
  args << current_arg unless current_arg.empty?
153
286
  args
154
287
  end
288
+
289
+ def expand_shell_word(word, variables)
290
+ result = +''
291
+ index = 0
292
+ quote = nil
293
+
294
+ while index < word.length
295
+ char = word[index]
296
+
297
+ if char == '\\' && quote != "'" && index + 1 < word.length
298
+ result << char << word[index + 1]
299
+ index += 2
300
+ next
301
+ end
302
+
303
+ if quote.nil? && (char == "'" || char == '"')
304
+ quote = char
305
+ result << char
306
+ index += 1
307
+ next
308
+ end
309
+
310
+ if quote == char
311
+ quote = nil
312
+ result << char
313
+ index += 1
314
+ next
315
+ end
316
+
317
+ if quote != "'" && char == '$'
318
+ name, consumed = read_variable(word, index)
319
+ if name && variables.key?(name)
320
+ result << substitute_value(variables[name].to_s, quote)
321
+ index += consumed
322
+ next
323
+ end
324
+ end
325
+
326
+ result << char
327
+ index += 1
328
+ end
329
+
330
+ result
331
+ end
332
+
333
+ def read_variable(word, index)
334
+ suffix = word[index..]
335
+ if (match = suffix.match(/\A\$\{([A-Za-z_][A-Za-z0-9_]*)\}/))
336
+ [match[1], match[0].length]
337
+ elsif (match = suffix.match(/\A\$([A-Za-z_][A-Za-z0-9_]*)/))
338
+ [match[1], match[0].length]
339
+ end
340
+ end
341
+
342
+ def substitute_value(value, quote)
343
+ return escape_double_quoted(value) if quote == '"'
344
+ return value if value.match?(UNQUOTED_SAFE)
345
+
346
+ "'#{value.gsub("'") { "'\\''" }}'"
347
+ end
348
+
349
+ def escape_double_quoted(value)
350
+ value.gsub(/[\\$"`]/) { |char| "\\#{char}" }
351
+ end
352
+
353
+ UNQUOTED_SAFE = /\A[A-Za-z0-9_\-.,:@%+\/=]+\z/.freeze
354
+ JSON_BODY_FLAGS = %w[--data-ascii --data-binary --data-raw --data --json -d].freeze
355
+ private_constant :UNQUOTED_SAFE, :JSON_BODY_FLAGS
155
356
  end
156
357
  end
data/lib/poke/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Poke
4
- VERSION = '0.4.1'
4
+ VERSION = '0.4.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poke-your-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Bator
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-04-30 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: pastel
@@ -108,7 +107,6 @@ dependencies:
108
107
  - - "~>"
109
108
  - !ruby/object:Gem::Version
110
109
  version: '0.12'
111
- description:
112
110
  email:
113
111
  - jan.bator@gmail.com
114
112
  executables:
@@ -146,7 +144,6 @@ licenses:
146
144
  metadata:
147
145
  homepage_uri: https://github.com/MrBananaLord/poke
148
146
  source_code_uri: https://github.com/MrBananaLord/poke
149
- post_install_message:
150
147
  rdoc_options: []
151
148
  require_paths:
152
149
  - lib
@@ -161,8 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
158
  - !ruby/object:Gem::Version
162
159
  version: '0'
163
160
  requirements: []
164
- rubygems_version: 3.4.1
165
- signing_key:
161
+ rubygems_version: 3.6.9
166
162
  specification_version: 4
167
163
  summary: manage curl requests
168
164
  test_files: []