curlyq 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44e01914de08789721522e24e506fe88a49106610fac9a16736efcba0916be88
4
- data.tar.gz: ec2887fee0dab67c64c0095f59091e867445b22559674b31f2eea64d8f4b9fea
3
+ metadata.gz: 6109483b8869733f9e21ecab9bc8bcda0aa3b58ca1f13f9b96fe7739d019df1f
4
+ data.tar.gz: 98a8d46fe68bc88ea030dfb8e04262fbab5418005390ff79693d6f636a3bf276
5
5
  SHA512:
6
- metadata.gz: aa8338482e3d9414e6347195abc7ba8645adbc120f13469f219df7f2aa5fa1ba3c209740c608f728539969502965f6c29afc9d65b5ba411c8387b26ebd640c9d
7
- data.tar.gz: 4fe071cb872a259795163da084851afdb5d003f7607a82a5ab6fe868f7f2edae22f0caa31327dfe1bad31e28e48fddde1857d882344359db8bf47b8579aef22c
6
+ metadata.gz: 1d75b4af2d6c1fadb83501fa707184ef41d061c08de14666b86d296048e8f21540fe2ad53a79985d5b042c93fa629cdbe8d101828edbb02832d1b55b920d5834
7
+ data.tar.gz: 238855918e3e765a2edf1864dd2663a959b099cfa5f1b89942f94eb20ba428c1700adee85590879662f0cf8de659328fbe752e8648ee210eefe0769639c57da2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### 0.0.10
2
+
3
+ 2024-01-17 13:50
4
+
5
+ #### IMPROVED
6
+
7
+ - Update YARD documentation
8
+ - Breaking change, ensure all return types are Arrays, even with single objects, to aid in scriptability
9
+ - Screenshot test suite
10
+
1
11
  ### 0.0.9
2
12
 
3
13
  2024-01-16 12:38
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- curlyq (0.0.9)
4
+ curlyq (0.0.10)
5
5
  gli (~> 2.21.0)
6
6
  nokogiri (~> 1.16.0)
7
7
  selenium-webdriver (~> 4.16.0)
data/README.md CHANGED
@@ -13,7 +13,7 @@ _If you find this useful, feel free to [buy me some coffee][donate]._
13
13
  [jq]: https://github.com/jqlang/jq "Command-line JSON processor"
14
14
  [yq]: https://github.com/mikefarah/yq "yq is a portable command-line YAML, JSON, XML, CSV, TOML and properties processor"
15
15
 
16
- The current version of `curlyq` is 0.0.9
16
+ The current version of `curlyq` is 0.0.10
17
17
  .
18
18
 
19
19
  CurlyQ is a utility that provides a simple interface for curl, with additional features for things like extracting images and links, finding elements by CSS selector or XPath, getting detailed header info, and more. It's designed to be part of a scripting pipeline, outputting everything as structured data (JSON or YAML). It also has rudimentary support for making calls to JSON endpoints easier, but it's expected that you'll use something like [jq] to parse the output.
@@ -47,7 +47,7 @@ SYNOPSIS
47
47
  curlyq [global options] command [command options] [arguments...]
48
48
 
49
49
  VERSION
50
- 0.0.9
50
+ 0.0.10
51
51
 
52
52
  GLOBAL OPTIONS
53
53
  --help - Show this message
data/Rakefile CHANGED
@@ -56,6 +56,23 @@ task :test, :pattern, :threads, :max_tests do |_, args|
56
56
  ThreadedTests.new.run(pattern: pattern, max_threads: args[:threads].to_i, max_tests: args[:max_tests])
57
57
  end
58
58
 
59
+ desc 'Install current gem in all versions of asdf-controlled ruby'
60
+ task :install do
61
+ Rake::Task['clobber'].invoke
62
+ Rake::Task['package'].invoke
63
+ Dir.chdir 'pkg'
64
+ file = Dir.glob('*.gem').last
65
+
66
+ current_ruby = `asdf current ruby`.match(/(\d.\d+.\d+)/)[1]
67
+
68
+ `asdf list ruby`.split.map { |ruby| ruby.strip.sub(/^*/, '') }.each do |ruby|
69
+ `asdf shell ruby #{ruby}`
70
+ puts `gem install #{file}`
71
+ end
72
+
73
+ `asdf shell ruby #{current_ruby}`
74
+ end
75
+
59
76
  desc 'Development version check'
60
77
  task :ver do
61
78
  gver = `git ver`
data/bin/curlyq CHANGED
@@ -49,7 +49,7 @@ end
49
49
  def self.print_out(output, yaml, raw: false, pretty: true)
50
50
  output = output.to_data if output.respond_to?(:to_data)
51
51
  # Was intended to flatten single responses, but not getting an array back is unpredictable
52
- # output = output[0] if output&.is_a?(Array) && output.count == 1
52
+ output = output.clean_output
53
53
  if output.is_a?(String)
54
54
  print output
55
55
  elsif raw
@@ -144,14 +144,9 @@ command %i[html curl] do |c|
144
144
  end
145
145
  output.delete_if(&:nil?)
146
146
  output.delete_if(&:empty?)
147
- # output = output[0] if output.count == 1
148
147
  output.map! { |o| o[options[:raw].to_sym] } if options[:raw]
149
148
 
150
- if output.is_a?(Array)
151
- while output.length == 1
152
- output = output[0]
153
- end
154
- end
149
+ output = output.clean_output
155
150
 
156
151
  print_out(output, global_options[:yaml], raw: options[:raw], pretty: global_options[:pretty])
157
152
  end
@@ -246,7 +241,7 @@ command :json do |c|
246
241
  end
247
242
  end
248
243
 
249
- # output = output[0] if output.count == 1
244
+ output = output.clean_output
250
245
 
251
246
  print_out(output, global_options[:yaml], pretty: global_options[:pretty])
252
247
  end
@@ -356,9 +351,7 @@ command :tags do |c|
356
351
  end
357
352
  end
358
353
 
359
- while output.is_a?(Array) && output.count == 1
360
- output = output[0]
361
- end
354
+ output = output.clean_output
362
355
 
363
356
  if options[:source]
364
357
  puts output.to_html
@@ -482,7 +475,7 @@ command :headlinks do |c|
482
475
  end
483
476
  end
484
477
 
485
- output = output[0] if output.count == 1
478
+ output = output.clean_output
486
479
 
487
480
  print_out(output, global_options[:yaml], pretty: global_options[:pretty])
488
481
  end
@@ -533,7 +526,7 @@ command :scrape do |c|
533
526
 
534
527
  output.delete_if(&:empty?)
535
528
 
536
- output = output[0] if output.count == 1
529
+ output = output.clean_output
537
530
 
538
531
  if options[:raw]
539
532
  output.map! { |o| o[options[:raw].to_sym] }
data/lib/curly/array.rb CHANGED
@@ -66,7 +66,7 @@ class ::Array
66
66
  replace dedup_links
67
67
  end
68
68
 
69
- #---------------------------------------------------------
69
+ ##
70
70
  ## Run a query on array elements
71
71
  ##
72
72
  ## @param path [String] dot.syntax path to compare
@@ -80,16 +80,29 @@ class ::Array
80
80
  res
81
81
  end
82
82
 
83
+ ##
84
+ ## Gets the value of every item in the array
85
+ ##
86
+ ## @param path The query path (dot syntax)
87
+ ##
88
+ ## @return [Array] array of values
89
+ ##
83
90
  def get_value(path)
84
91
  map { |el| el.get_value(path) }
85
92
  end
86
93
 
94
+ ##
95
+ ## Convert every item in the array to HTML
96
+ ##
97
+ ## @return [String] Html representation of the object.
98
+ ##
87
99
  def to_html
88
100
  map(&:to_html)
89
101
  end
90
102
 
91
103
  ##
92
- ## Test if a tag contains an attribute matching filter queries
104
+ ## Test if a tag contains an attribute matching filter
105
+ ## queries
93
106
  ##
94
107
  ## @param tag_name [String] The tag name
95
108
  ## @param classes [String] The classes to match
@@ -101,6 +114,8 @@ class ::Array
101
114
  ## @param value [String] The value to match
102
115
  ## @param descendant [Boolean] Check descendant tags
103
116
  ##
117
+ ## @return [Boolean] tag matches
118
+ ##
104
119
  def tag_match(tag_name, classes, id, attribute, operator, value, descendant: false)
105
120
  tag = self
106
121
  keep = true
@@ -154,4 +169,26 @@ class ::Array
154
169
  keep
155
170
  end
156
171
  end
172
+
173
+ ##
174
+ ## Clean up output, shrink single-item arrays, ensure array output
175
+ ##
176
+ ## @return [Array] cleaned up array
177
+ ##
178
+ def clean_output
179
+ output = dup
180
+ while output.is_a?(Array) && output.count == 1
181
+ output = output[0]
182
+ end
183
+ output.ensure_array
184
+ end
185
+
186
+ ##
187
+ ## Ensure that an object is an array
188
+ ##
189
+ ## @return [Array] object as Array
190
+ ##
191
+ def ensure_array
192
+ return self
193
+ end
157
194
  end
@@ -16,6 +16,12 @@ module Curl
16
16
  attr_reader :url, :code, :meta, :links, :head, :body,
17
17
  :title, :description, :body_links, :body_images
18
18
 
19
+ # Convert self to a hash of data
20
+ #
21
+ # @param url [String] A base url to fall back to
22
+ #
23
+ # @return [Hash] a hash of data
24
+ #
19
25
  def to_data(url: nil)
20
26
  {
21
27
  url: @url || url,
@@ -68,12 +74,23 @@ module Curl
68
74
  @url = url.nil? ? options[:url] : url
69
75
  end
70
76
 
77
+ ##
78
+ # Parse raw HTML source instead of curling
79
+ #
80
+ # @param source [String] The source
81
+ #
82
+ #
83
+ # @return [Hash] Hash of data after processing #
84
+ #
71
85
  def parse(source)
72
86
  @body = source
73
87
  { url: @url, code: @code, headers: @headers, meta: @meta, links: @links, head: @head, body: source,
74
88
  source: source.strip, body_links: content_links, body_images: content_images }
75
89
  end
76
90
 
91
+ ##
92
+ ## Curl a url, either with curl or Selenium based on browser settings
93
+ ##
77
94
  def curl
78
95
  res = if @url && @browser && @browser != :none
79
96
  source = curl_dynamic_html
@@ -283,6 +300,11 @@ module Curl
283
300
  output
284
301
  end
285
302
 
303
+ ##
304
+ ## String representation
305
+ ##
306
+ ## @return String representation of the object.
307
+ ##
286
308
  def to_s
287
309
  headers = @headers.nil? ? 0 : @headers.count
288
310
  meta = @meta.nil? ? 0 : @meta.count
data/lib/curly/hash.rb CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  # Hash helpers
4
4
  class ::Hash
5
+ ## Convert a Curly object to data hash
6
+ ##
7
+ ## @return [Hash] return a hash with keys renamed and
8
+ ## cleaned up
9
+ ##
10
+ ## @param url [String] A url to fall back to
11
+ ## @param clean [Boolean] Clean extra spaces and newlines in sources
12
+ ##
5
13
  def to_data(url: nil, clean: false)
6
14
  if key?(:body_links)
7
15
  {
@@ -23,22 +31,32 @@ class ::Hash
23
31
  end
24
32
  end
25
33
 
34
+ ##
35
+ ## Return the raw HTML of the object
36
+ ##
37
+ ## @return [String] Html representation of the object.
38
+ ##
26
39
  def to_html
27
40
  if key?(:source)
28
41
  self[:source]
29
42
  end
30
43
  end
31
44
 
45
+ ##
46
+ ## Get a value from the hash using a dot-syntax query
47
+ ##
48
+ ## @param query [String] The query (dot notation)
49
+ ##
50
+ ## @return [Object] result of querying the hash
51
+ ##
32
52
  def get_value(query)
33
53
  return nil if self.empty?
34
54
  stringify_keys!
35
55
 
36
56
  query.split('.').inject(self) do |v, k|
37
- if v.is_a? Array
38
- return v.map { |el| el.get_value(k) }
39
- end
57
+ return v.map { |el| el.get_value(k) } if v.is_a? Array
40
58
  # k = k.to_i if v.is_a? Array
41
- next unless v.key?(k)
59
+ next v unless v.key?(k)
42
60
 
43
61
  v.fetch(k)
44
62
  end
@@ -48,7 +66,7 @@ class ::Hash
48
66
  #
49
67
  # @param path [String] The path
50
68
  #
51
- # @return Result of path query
69
+ # @return [Object] Result of path query
52
70
  #
53
71
  def dot_query(path, root = nil, full_tag: true)
54
72
  res = stringify_keys
@@ -63,7 +81,6 @@ class ::Hash
63
81
  "[#{inter}]"
64
82
  end
65
83
 
66
- enumerate = false
67
84
  out = []
68
85
  q = path.split(/(?<![\d.])\./)
69
86
 
@@ -152,6 +169,14 @@ class ::Hash
152
169
  out
153
170
  end
154
171
 
172
+ ##
173
+ ## Test if values in an array match an operator
174
+ ##
175
+ ## @param array [Array] The array
176
+ ## @param key [String] The key
177
+ ## @param comp [String] The comparison, e.g. *= or $=
178
+ ##
179
+ ## @return [Boolean] true if array contains match
155
180
  def array_match(array, key, comp)
156
181
  keep = false
157
182
  array.each do |el|
@@ -353,7 +378,32 @@ class ::Hash
353
378
  end
354
379
  end
355
380
 
381
+ ##
382
+ ## Destructive version of #stringify_keys
383
+ ##
384
+ ## @see #stringify_keys
385
+ ##
356
386
  def stringify_keys!
357
387
  replace stringify_keys
358
388
  end
389
+
390
+ ##
391
+ ## Clean up empty arrays and return an array with one or
392
+ ## more elements
393
+ ##
394
+ ## @return [Array] output array
395
+ ##
396
+ def clean_output
397
+ output = ensure_array
398
+ output.clean_output
399
+ end
400
+
401
+ ##
402
+ ## Ensure that an object is an array
403
+ ##
404
+ ## @return [Array] object as Array
405
+ ##
406
+ def ensure_array
407
+ return [self]
408
+ end
359
409
  end
@@ -0,0 +1,11 @@
1
+ # Numeric helpers
2
+ class ::Numeric
3
+ ##
4
+ ## Return an array version of self
5
+ ##
6
+ ## @return [Array] self enclosed in an array
7
+ ##
8
+ def ensure_array
9
+ [self]
10
+ end
11
+ end
data/lib/curly/string.rb CHANGED
@@ -6,6 +6,11 @@
6
6
  ## @return [String] cleaned string
7
7
  ##
8
8
  class ::String
9
+ ## Remove extra spaces and newlines, compress space
10
+ ## between tags
11
+ ##
12
+ ## @return [String] cleaned string
13
+ ##
9
14
  def clean
10
15
  gsub(/[\t\n ]+/m, ' ').gsub(/> +</, '><')
11
16
  end
@@ -40,7 +45,7 @@ class ::String
40
45
  ##
41
46
  ## Convert an image type string to a symbol
42
47
  ##
43
- ## @return Symbol :srcset, :img, :opengraph, :all
48
+ ## @return [Symbol] :srcset, :img, :opengraph, :all
44
49
  ##
45
50
  def normalize_image_type(default = :all)
46
51
  case self.to_s
@@ -58,7 +63,7 @@ class ::String
58
63
  ##
59
64
  ## Convert a browser type string to a symbol
60
65
  ##
61
- ## @return Symbol :chrome, :firefox
66
+ ## @return [Symbol] :chrome, :firefox
62
67
  ##
63
68
  def normalize_browser_type(default = :none)
64
69
  case self.to_s
@@ -74,7 +79,7 @@ class ::String
74
79
  ##
75
80
  ## Convert a screenshot type string to a symbol
76
81
  ##
77
- ## @return Symbol :full_page, :print_page, :visible
82
+ ## @return [Symbol] :full_page, :print_page, :visible
78
83
  ##
79
84
  def normalize_screenshot_type(default = :none)
80
85
  case self.to_s
@@ -88,4 +93,23 @@ class ::String
88
93
  default.is_a?(Symbol) ? default.to_sym : default.normalize_browser_type
89
94
  end
90
95
  end
96
+
97
+ ##
98
+ ## Clean up output and return a single-item array
99
+ ##
100
+ ## @return [Array] output array
101
+ ##
102
+ def clean_output
103
+ output = ensure_array
104
+ output.clean_output
105
+ end
106
+
107
+ ##
108
+ ## Ensure that an object is an array
109
+ ##
110
+ ## @return [Array] object as Array
111
+ ##
112
+ def ensure_array
113
+ return [self]
114
+ end
91
115
  end
data/lib/curly/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # Top level module for CurlyQ
1
2
  module Curly
2
- VERSION = '0.0.9'
3
+ # Current version number
4
+ VERSION = '0.0.10'
3
5
  end
data/lib/curly.rb CHANGED
@@ -4,6 +4,7 @@ require 'curly/version'
4
4
  require 'curly/hash'
5
5
  require 'curly/string'
6
6
  require 'curly/array'
7
+ require 'curly/numeric'
7
8
  require 'json'
8
9
  require 'yaml'
9
10
  require 'uri'
data/src/_README.md CHANGED
@@ -13,7 +13,7 @@ _If you find this useful, feel free to [buy me some coffee][donate]._
13
13
  [jq]: https://github.com/jqlang/jq "Command-line JSON processor"
14
14
  [yq]: https://github.com/mikefarah/yq "yq is a portable command-line YAML, JSON, XML, CSV, TOML and properties processor"
15
15
 
16
- The current version of `curlyq` is <!--VER-->0.0.4<!--END VER-->.
16
+ The current version of `curlyq` is <!--VER-->0.0.9<!--END VER-->.
17
17
 
18
18
  CurlyQ is a utility that provides a simple interface for curl, with additional features for things like extracting images and links, finding elements by CSS selector or XPath, getting detailed header info, and more. It's designed to be part of a scripting pipeline, outputting everything as structured data (JSON or YAML). It also has rudimentary support for making calls to JSON endpoints easier, but it's expected that you'll use something like [jq] to parse the output.
19
19
 
@@ -17,8 +17,9 @@ class CurlyQHeadlinksTest < Test::Unit::TestCase
17
17
  result = curlyq('headlinks', '-q', '[rel=stylesheet]', 'https://brettterpstra.com')
18
18
  json = JSON.parse(result)
19
19
 
20
- assert_match(/stylesheet/, json['rel'], 'Should have retrieved a single result with rel stylesheet')
21
- assert_match(/screen\.\d+\.css$/, json['href'], 'Stylesheet should be correct primary stylesheet')
20
+ assert_equal(Array, json.class, 'Result should be an array')
21
+ assert_match(/stylesheet/, json[0]['rel'], 'Should have retrieved a single result with rel stylesheet')
22
+ assert_match(/screen\.\d+\.css$/, json[0]['href'], 'Stylesheet should be correct primary stylesheet')
22
23
  end
23
24
 
24
25
  def test_headlinks
@@ -14,12 +14,12 @@ class CurlyQHtmlTest < Test::Unit::TestCase
14
14
  result = curlyq('html', '-s', '#main article .aligncenter', '-q', 'images[1]', 'https://brettterpstra.com')
15
15
  json = JSON.parse(result)
16
16
 
17
- assert_match(/aligncenter/, json['class'], 'Should have found an image with class "aligncenter"')
17
+ assert_match(/aligncenter/, json[0]['class'], 'Should have found an image with class "aligncenter"')
18
18
  end
19
19
 
20
20
  def test_html_query
21
21
  result = curlyq('html', '-q', 'meta.title', 'https://brettterpstra.com/2024/01/10/introducing-curlyq-a-pipeline-oriented-curl-helper/')
22
-
23
- assert_match(/Introducing CurlyQ/, result, 'Should have retrived the page title')
22
+ json = JSON.parse(result)
23
+ assert_match(/Introducing CurlyQ/, json[0], 'Should have retrived the page title')
24
24
  end
25
25
  end
@@ -11,12 +11,42 @@ class CurlyQScrapeTest < Test::Unit::TestCase
11
11
  include CurlyQHelpers
12
12
 
13
13
  def setup
14
+ @screenshot = File.join(File.dirname(__FILE__), 'screenshot_test')
15
+ FileUtils.rm_f("#{@screenshot}.pdf") if File.exist?("#{@screenshot}.pdf")
16
+ FileUtils.rm_f('screenshot_test.png') if File.exist?("#{@screenshot}.png")
17
+ FileUtils.rm_f("#{@screenshot}_full.png") if File.exist?("#{@screenshot}_full.png")
14
18
  end
15
19
 
16
- def test_scrape
20
+ def teardown
21
+ FileUtils.rm_f("#{@screenshot}.pdf") if File.exist?("#{@screenshot}.pdf")
22
+ FileUtils.rm_f('screenshot_test.png') if File.exist?("#{@screenshot}.png")
23
+ FileUtils.rm_f("#{@screenshot}_full.png") if File.exist?("#{@screenshot}_full.png")
24
+ end
25
+
26
+ def test_scrape_firefox
17
27
  result = curlyq('scrape', '-b', 'firefox', '-q', 'links[rel=me&content*=mastodon][0]', 'https://brettterpstra.com/2024/01/10/introducing-curlyq-a-pipeline-oriented-curl-helper/')
18
28
  json = JSON.parse(result)
19
29
 
20
- assert_match(/Mastodon/, json['content'], 'Should have retrieved a Mastodon link')
30
+ assert_equal(Array, json.class, 'Result should be an Array')
31
+ assert_match(/Mastodon/, json[0]['content'], 'Should have retrieved a Mastodon link')
32
+ end
33
+
34
+ def test_scrape_chrome
35
+ result = curlyq('scrape', '-b', 'chrome', '-q', 'links[rel=me&content*=mastodon][0]', 'https://brettterpstra.com/2024/01/10/introducing-curlyq-a-pipeline-oriented-curl-helper/')
36
+ json = JSON.parse(result)
37
+
38
+ assert_equal(Array, json.class, 'Result should be an Array')
39
+ assert_match(/Mastodon/, json[0]['content'], 'Should have retrieved a Mastodon link')
40
+ end
41
+
42
+ def test_screenshot
43
+ curlyq('screenshot', '-b', 'firefox', '-o', @screenshot, '-t', 'print', 'https://brettterpstra.com')
44
+ assert(File.exist?("#{@screenshot}.pdf"), 'PDF Screenshot should exist')
45
+
46
+ curlyq('screenshot', '-b', 'chrome', '-o', @screenshot, '-t', 'visible', 'https://brettterpstra.com')
47
+ assert(File.exist?("#{@screenshot}.png"), 'PNG Screenshot should exist')
48
+
49
+ curlyq('screenshot', '-b', 'firefox', '-o', "#{@screenshot}_full", '-t', 'full', 'https://brettterpstra.com')
50
+ assert(File.exist?("#{@screenshot}_full.png"), 'PNG Screenshot should exist')
21
51
  end
22
52
  end
@@ -14,18 +14,26 @@ class CurlyQTagsTest < Test::Unit::TestCase
14
14
  end
15
15
 
16
16
  def test_tags
17
- result = curlyq('tags', '--search', '#main .post h3', '-q', 'attrs[id*=what]', 'https://brettterpstra.com/2024/01/10/introducing-curlyq-a-pipeline-oriented-curl-helper/')
17
+ result = curlyq('tags', '--search', '#main .post h3', 'https://brettterpstra.com/2024/01/10/introducing-curlyq-a-pipeline-oriented-curl-helper/')
18
18
  json = JSON.parse(result)
19
19
 
20
- assert_equal(json.count, 1, 'Should have 1 result')
21
- assert_match(/whats-next/, json[0]['attrs']['id'], 'Should have matched #whats-next')
20
+ assert_equal(Array, json.class, 'Should be an array of matches')
21
+ assert_equal(6, json.count, 'Should be six results')
22
22
  end
23
23
 
24
24
  def test_clean
25
25
  result = curlyq('tags', '--search', '#main section.related', '--clean', 'https://brettterpstra.com/2024/01/10/introducing-curlyq-a-pipeline-oriented-curl-helper/')
26
26
  json = JSON.parse(result)
27
27
 
28
- assert_equal(json.count, 1, 'Should have 1 result')
28
+ assert_equal(Array, json.class, 'Should be a single Array')
29
+ assert_equal(1, json.count, 'Should be one element')
29
30
  assert_match(%r{Last.fm</h5></a></li>}, json[0]['source'], 'Should have matched #whats-next')
30
31
  end
32
+
33
+ def test_query
34
+ result = curlyq('tags', '--search', '#main .post h3', '-q', '[attrs.id*=what].source', 'https://brettterpstra.com/2024/01/10/introducing-curlyq-a-pipeline-oriented-curl-helper/')
35
+ json = JSON.parse(result)
36
+ assert_equal(Array, json.class, 'Should be an array')
37
+ assert_match(%r{^<h3 id="whats-next">What’s Next</h3>$}, json[0], 'Should have returned just source')
38
+ end
31
39
  end
@@ -1,5 +1,6 @@
1
1
  require 'open3'
2
2
  require 'time'
3
+ require 'fileutils'
3
4
  $LOAD_PATH.unshift File.join(__dir__, '..', '..', 'lib')
4
5
  require 'curly'
5
6
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curlyq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-16 00:00:00.000000000 Z
11
+ date: 2024-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -236,6 +236,7 @@ files:
236
236
  - lib/curly/curl/html.rb
237
237
  - lib/curly/curl/json.rb
238
238
  - lib/curly/hash.rb
239
+ - lib/curly/numeric.rb
239
240
  - lib/curly/string.rb
240
241
  - lib/curly/version.rb
241
242
  - src/_README.md