berkeley_library-util 0.1.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.
- checksums.yaml +7 -0
- data/.github/workflows/build.yml +18 -0
- data/.gitignore +240 -0
- data/.idea/.gitignore +8 -0
- data/.idea/inspectionProfiles/Project_Default.xml +21 -0
- data/.idea/misc.xml +6 -0
- data/.idea/modules.xml +8 -0
- data/.idea/util.iml +123 -0
- data/.idea/vcs.xml +6 -0
- data/.rubocop.yml +334 -0
- data/.ruby-version +1 -0
- data/.simplecov +8 -0
- data/.yardopts +1 -0
- data/CHANGES.md +3 -0
- data/Gemfile +3 -0
- data/LICENSE.md +21 -0
- data/README.md +21 -0
- data/Rakefile +20 -0
- data/berkeley_library-util.gemspec +42 -0
- data/lib/berkeley_library/util/arrays.rb +178 -0
- data/lib/berkeley_library/util/module_info.rb +16 -0
- data/lib/berkeley_library/util/paths.rb +111 -0
- data/lib/berkeley_library/util/stringios.rb +30 -0
- data/lib/berkeley_library/util/strings.rb +42 -0
- data/lib/berkeley_library/util/sys_exits.rb +15 -0
- data/lib/berkeley_library/util/times.rb +22 -0
- data/lib/berkeley_library/util/uris/appender.rb +162 -0
- data/lib/berkeley_library/util/uris/requester.rb +62 -0
- data/lib/berkeley_library/util/uris/validator.rb +32 -0
- data/lib/berkeley_library/util/uris.rb +44 -0
- data/lib/berkeley_library/util.rb +1 -0
- data/rakelib/bundle.rake +8 -0
- data/rakelib/coverage.rake +11 -0
- data/rakelib/gem.rake +54 -0
- data/rakelib/rubocop.rake +18 -0
- data/rakelib/spec.rake +2 -0
- data/spec/.rubocop.yml +40 -0
- data/spec/berkeley_library/util/arrays_spec.rb +340 -0
- data/spec/berkeley_library/util/paths_spec.rb +90 -0
- data/spec/berkeley_library/util/stringios_spec.rb +34 -0
- data/spec/berkeley_library/util/strings_spec.rb +59 -0
- data/spec/berkeley_library/util/times_spec.rb +39 -0
- data/spec/berkeley_library/util/uris/requester_spec.rb +75 -0
- data/spec/berkeley_library/util/uris/validator_spec.rb +51 -0
- data/spec/berkeley_library/util/uris_spec.rb +133 -0
- data/spec/spec_helper.rb +30 -0
- metadata +321 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'berkeley_library/util/stringios'
|
2
|
+
|
3
|
+
module BerkeleyLibrary
|
4
|
+
module Util
|
5
|
+
# This module, modeled on the {https://golang.org/pkg/path/ Go `path` package},
|
6
|
+
# provides utility routines for modifying paths separated by forward slashes,
|
7
|
+
# such as URL paths. For system-dependent file paths, use
|
8
|
+
# {https://ruby-doc.org/stdlib-2.7.0/libdoc/pathname/rdoc/Pathname.html `Pathname`}
|
9
|
+
# instead.
|
10
|
+
module Paths
|
11
|
+
include BerkeleyLibrary::Util::StringIOs
|
12
|
+
|
13
|
+
class << self
|
14
|
+
include Paths
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the shortest path name equivalent to `path` by purely lexical
|
18
|
+
# processing by:
|
19
|
+
#
|
20
|
+
# 1. replacing runs of multiple `/` with a single `/`
|
21
|
+
# 2. eliminating all `.` (current directory) elements
|
22
|
+
# 3. eliminating all `<child>/..` in favor of directly
|
23
|
+
# referencing the parent directory
|
24
|
+
# 4. replaing all `/..` at the beginning of the path
|
25
|
+
# with a single leading `/`
|
26
|
+
#
|
27
|
+
# The returned path ends in a slash only if it is the root `/`.
|
28
|
+
# @see https://9p.io/sys/doc/lexnames.html Rob Pike, "Lexical File Names in Plan 9 or Getting Dot-Dot Right"
|
29
|
+
#
|
30
|
+
# @param path [String, nil] the path to clean
|
31
|
+
# @return [String, nil] the cleaned path, or `nil` for a nil path.
|
32
|
+
def clean(path)
|
33
|
+
return unless path
|
34
|
+
return '.' if ['', '.'].include?(path)
|
35
|
+
|
36
|
+
StringIO.new.tap do |out|
|
37
|
+
out << '/' if path[0] == '/'
|
38
|
+
dotdot = (r = out.size)
|
39
|
+
r, dotdot = process_next(r, dotdot, path, out) while r < path.size
|
40
|
+
out << '.' if out.pos == 0
|
41
|
+
end.string
|
42
|
+
end
|
43
|
+
|
44
|
+
# Joins any number of path elements into a single path, separating
|
45
|
+
# them with slashes, ignoring empty elements and passing the result
|
46
|
+
# to {Paths#clean}.
|
47
|
+
#
|
48
|
+
# @param elements [Array<String>] the elements to join
|
49
|
+
# @return [String] the joined path
|
50
|
+
def join(*elements)
|
51
|
+
elements = elements.reject { |e| [nil, ''].include?(e) }
|
52
|
+
joined_raw = elements.join('/')
|
53
|
+
return '' if joined_raw == ''
|
54
|
+
|
55
|
+
clean(joined_raw)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def process_next(r, dotdot, path, out)
|
61
|
+
# empty path element, or .
|
62
|
+
return r + 1, dotdot if empty_or_dot?(r, path)
|
63
|
+
# .. element: remove to last /
|
64
|
+
return handle_dotdot(r, dotdot, path, out) if dotdot?(r, path)
|
65
|
+
|
66
|
+
# real path element
|
67
|
+
[append_from(r, path, out), dotdot]
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_dotdot(r, dotdot, path, out)
|
71
|
+
if out.pos > dotdot
|
72
|
+
backtrack_to_dotdot(out, dotdot)
|
73
|
+
elsif path[0] != '/'
|
74
|
+
dotdot = append_dotdot(out)
|
75
|
+
end
|
76
|
+
|
77
|
+
[r + 2, dotdot]
|
78
|
+
end
|
79
|
+
|
80
|
+
def dotdot?(r, path)
|
81
|
+
path[r] == '.' && (r + 2 == path.size || path[r + 2] == '/')
|
82
|
+
end
|
83
|
+
|
84
|
+
def empty_or_dot?(r, path)
|
85
|
+
path[r] == '/' || (path[r] == '.' && (r + 1 == path.size || path[r + 1] == '/'))
|
86
|
+
end
|
87
|
+
|
88
|
+
def append_from(r, path, out)
|
89
|
+
out << '/' if (path[0] == '/' && out.pos != 1) || (path[0] != '/' && out.pos != 0)
|
90
|
+
while r < path.size && path[r] != '/'
|
91
|
+
out << path[r]
|
92
|
+
r += 1
|
93
|
+
end
|
94
|
+
r
|
95
|
+
end
|
96
|
+
|
97
|
+
def append_dotdot(out)
|
98
|
+
out << '/' if out.pos > 1
|
99
|
+
out << '..'
|
100
|
+
out.pos
|
101
|
+
end
|
102
|
+
|
103
|
+
def backtrack_to_dotdot(out, dotdot)
|
104
|
+
out.seek(-1, IO::SEEK_CUR)
|
105
|
+
out.seek(-1, IO::SEEK_CUR) while out.pos > dotdot && getbyte(out, out.pos) != 47 # '/' is ASCII 37
|
106
|
+
out.truncate(out.pos)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module BerkeleyLibrary
|
4
|
+
module Util
|
5
|
+
module StringIOs
|
6
|
+
class << self
|
7
|
+
include StringIOs
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the byte (**not** character) at the specified byte index
|
11
|
+
# in the specified `StringIO`.
|
12
|
+
#
|
13
|
+
# @param s [StringIO] the StringIO to search in
|
14
|
+
# @param i [Integer] the byte index
|
15
|
+
# @return [Integer, nil] the byte, or nil if the byte index is invalid.
|
16
|
+
def getbyte(s, i)
|
17
|
+
return if i >= s.size
|
18
|
+
return if s.size + i < 0
|
19
|
+
|
20
|
+
pos_orig = s.pos
|
21
|
+
begin
|
22
|
+
s.seek(i >= 0 ? i : s.size + i)
|
23
|
+
s.getbyte
|
24
|
+
ensure
|
25
|
+
s.seek(pos_orig)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module BerkeleyLibrary
|
2
|
+
module Util
|
3
|
+
module Strings
|
4
|
+
|
5
|
+
ASCII_0 = '0'.ord
|
6
|
+
ASCII_9 = '9'.ord
|
7
|
+
|
8
|
+
def ascii_numeric?(s)
|
9
|
+
s.chars.all? do |c|
|
10
|
+
ord = c.ord
|
11
|
+
ord >= ASCII_0 && ord <= ASCII_9
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Locates the point at which two strings differ
|
16
|
+
#
|
17
|
+
# @return [Integer, nil] the index of the first character in either string
|
18
|
+
# that differs from the other, or `nil` if the strings are identical,
|
19
|
+
# or are not strings
|
20
|
+
def diff_index(s1, s2)
|
21
|
+
return unless string_like?(s1, s2)
|
22
|
+
|
23
|
+
shorter, longer = s1.size > s2.size ? [s2, s1] : [s1, s2]
|
24
|
+
shorter.chars.each_with_index do |c, i|
|
25
|
+
return i if c != longer[i]
|
26
|
+
end
|
27
|
+
shorter.length if shorter.length < longer.length # otherwise they're equal
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
include Strings
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def string_like?(*strs)
|
37
|
+
strs.all? { |s| s.respond_to?(:chars) && s.respond_to?(:size) }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BerkeleyLibrary
|
2
|
+
module Util
|
3
|
+
# cf. BSD sysexits.h https://cgit.freebsd.org/src/tree/include/sysexits.h?h=releng/2.0
|
4
|
+
module SysExits
|
5
|
+
# successful termination
|
6
|
+
EX_OK = 0
|
7
|
+
|
8
|
+
# command line usage error
|
9
|
+
EX_USAGE = 64
|
10
|
+
|
11
|
+
# internal software error
|
12
|
+
EX_SOFTWARE = 70 # command line usage error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module BerkeleyLibrary
|
4
|
+
module Util
|
5
|
+
module Times
|
6
|
+
class << self
|
7
|
+
include Times
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param time [Time, Date] the time
|
11
|
+
# @return the UTC time corresponding to `time`
|
12
|
+
def ensure_utc(time)
|
13
|
+
return unless time
|
14
|
+
return time if time.respond_to?(:utc?) && time.utc?
|
15
|
+
return time.getutc if time.respond_to?(:getutc)
|
16
|
+
return time.to_time.getutc if time.respond_to?(:to_time)
|
17
|
+
|
18
|
+
raise ArgumentError, "Not a date or time: #{time.inspect}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'berkeley_library/util/paths'
|
2
|
+
require 'uri'
|
3
|
+
require 'typesafe_enum'
|
4
|
+
|
5
|
+
module BerkeleyLibrary
|
6
|
+
module Util
|
7
|
+
module URIs
|
8
|
+
|
9
|
+
# Appends the specified paths to the path of the specified URI, removing any extraneous slashes,
|
10
|
+
# and builds a new URI with that path and the same scheme, host, query, fragment, etc.
|
11
|
+
# as the original.
|
12
|
+
class Appender
|
13
|
+
attr_reader :original_uri, :elements
|
14
|
+
|
15
|
+
# Creates and invokes a new {Appender}.
|
16
|
+
#
|
17
|
+
# @param uri [URI, String] the original URI
|
18
|
+
# @param elements [Array<String, Symbol>] the URI elements to join.
|
19
|
+
# @raise URI::InvalidComponentError if appending the specified elements would create an invalid URI
|
20
|
+
def initialize(uri, *elements)
|
21
|
+
raise ArgumentError, 'uri cannot be nil' unless (@original_uri = URIs.uri_or_nil(uri))
|
22
|
+
|
23
|
+
@elements = elements.map(&:to_s)
|
24
|
+
@elements.each_with_index do |element, elem_index|
|
25
|
+
next start_query_at(elem_index) if element.include?('?')
|
26
|
+
next start_fragment_at(elem_index) if element.include?('#')
|
27
|
+
|
28
|
+
add_element(element)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the new URI.
|
33
|
+
#
|
34
|
+
# @return [URI] a new URI appending the joined path elements.
|
35
|
+
# @raise URI::InvalidComponentError if appending the specified elements would create an invalid URI
|
36
|
+
def to_uri
|
37
|
+
original_uri.dup.tap do |new_uri|
|
38
|
+
new_uri.path = Paths.join(original_uri.path, *path_elements)
|
39
|
+
new_uri.query = query unless query_elements.empty?
|
40
|
+
new_uri.fragment = fragment unless fragment_elements.empty?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def state
|
47
|
+
@state ||= :path
|
48
|
+
end
|
49
|
+
|
50
|
+
def in_query?
|
51
|
+
state == :query
|
52
|
+
end
|
53
|
+
|
54
|
+
def in_fragment?
|
55
|
+
state == :fragment
|
56
|
+
end
|
57
|
+
|
58
|
+
def query
|
59
|
+
query_elements.join
|
60
|
+
end
|
61
|
+
|
62
|
+
def fragment
|
63
|
+
fragment_elements.join
|
64
|
+
end
|
65
|
+
|
66
|
+
def path_elements
|
67
|
+
@path_elements ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
def query_elements
|
71
|
+
@query_elements ||= [].tap { |e| e << original_uri.query if original_uri.query }
|
72
|
+
end
|
73
|
+
|
74
|
+
def fragment_elements
|
75
|
+
@fragment_elements ||= [].tap { |e| e << original_uri.fragment if original_uri.fragment }
|
76
|
+
end
|
77
|
+
|
78
|
+
def start_query_at(elem_index)
|
79
|
+
raise URI::InvalidComponentError, err_query_after_fragment(elem_index) if in_fragment?
|
80
|
+
raise URI::InvalidComponentError, err_too_many_queries(elem_index) unless query_elements.empty?
|
81
|
+
|
82
|
+
handle_query_start(elem_index)
|
83
|
+
@state = :query
|
84
|
+
end
|
85
|
+
|
86
|
+
def start_fragment_at(elem_index)
|
87
|
+
raise URI::InvalidComponentError, err_too_many_fragments(elem_index) unless fragment_elements.empty?
|
88
|
+
raise URI::InvalidComponentError, err_query_after_fragment(elem_index) if query_after_fragment?(elem_index)
|
89
|
+
|
90
|
+
handle_fragment_start(elem_index)
|
91
|
+
@state = :fragment
|
92
|
+
end
|
93
|
+
|
94
|
+
def query_after_fragment?(elem_index)
|
95
|
+
e = elements[elem_index]
|
96
|
+
e.index('?', e.index('#'))
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_element(e)
|
100
|
+
return fragment_elements << e if in_fragment?
|
101
|
+
return query_elements << e if in_query? || (e.include?('&') && !query_elements.empty?)
|
102
|
+
|
103
|
+
path_elements << e
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_query_start(elem_index)
|
107
|
+
element = elements[elem_index]
|
108
|
+
|
109
|
+
# if there's anything before the '?', we treat that excess as a path element
|
110
|
+
excess, q_start = split_around(element, element.index('?'))
|
111
|
+
q_start = push_fragment_start(elem_index, q_start)
|
112
|
+
|
113
|
+
query_elements << q_start
|
114
|
+
path_elements << excess
|
115
|
+
end
|
116
|
+
|
117
|
+
# if the fragment starts in the middle of this element, we keep the part before
|
118
|
+
# the fragment delimiter '#', and push the rest (w/'#') back onto the next element
|
119
|
+
# to be parsed in the next iteration
|
120
|
+
def push_fragment_start(elem_index, q_start)
|
121
|
+
return q_start unless (f_index = q_start.index('#'))
|
122
|
+
|
123
|
+
next_index = elem_index + 1
|
124
|
+
q_start, q_next = split_around(q_start, f_index) # NOTE: this doesn't return the '#'
|
125
|
+
elements[next_index] = "##{q_next}#{elements[next_index]}" # so we prepend one here
|
126
|
+
q_start
|
127
|
+
end
|
128
|
+
|
129
|
+
def handle_fragment_start(elem_index)
|
130
|
+
element = elements[elem_index]
|
131
|
+
|
132
|
+
# if there's anything before the '#', we treat that excess as a path element,
|
133
|
+
# or as a query element if there's a query
|
134
|
+
excess, f_start = split_around(element, element.index('#'))
|
135
|
+
|
136
|
+
fragment_elements << f_start
|
137
|
+
if in_query?
|
138
|
+
query_elements << excess
|
139
|
+
else
|
140
|
+
path_elements << excess
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def split_around(s, i)
|
145
|
+
[s[0...i], s[(i + 1)..]]
|
146
|
+
end
|
147
|
+
|
148
|
+
def err_too_many_queries(elem_index)
|
149
|
+
"#{elements[elem_index].inspect}: URI already has a query string: #{query.inspect}"
|
150
|
+
end
|
151
|
+
|
152
|
+
def err_query_after_fragment(elem_index)
|
153
|
+
"#{elements[elem_index].inspect}: Query delimiter '?' cannot follow fragment delimeter '#'"
|
154
|
+
end
|
155
|
+
|
156
|
+
def err_too_many_fragments(elem_index)
|
157
|
+
"#{elements[elem_index].inspect}: URI already has a fragment: #{fragment.inspect}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'berkeley_library/util/uris/appender'
|
3
|
+
require 'berkeley_library/util/uris/validator'
|
4
|
+
require 'berkeley_library/logging'
|
5
|
+
|
6
|
+
module BerkeleyLibrary
|
7
|
+
module Util
|
8
|
+
module URIs
|
9
|
+
module Requester
|
10
|
+
class << self
|
11
|
+
include BerkeleyLibrary::Logging
|
12
|
+
|
13
|
+
# Performs a GET request.
|
14
|
+
#
|
15
|
+
# @param uri [URI, String] the URI to GET
|
16
|
+
# @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
|
17
|
+
# @param headers [Hash] the request headers.
|
18
|
+
# @return [String] the body as a string.
|
19
|
+
# @raise [RestClient::Exception] in the event of an error.
|
20
|
+
def get(uri, params: {}, headers: {})
|
21
|
+
url_str = url_str_with_params(uri, params)
|
22
|
+
resp = get_or_raise(url_str, headers)
|
23
|
+
resp.body
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def url_str_with_params(uri, params)
|
29
|
+
raise ArgumentError, 'uri cannot be nil' unless (url_str = Validator.url_str_or_nil(uri))
|
30
|
+
|
31
|
+
elements = [].tap do |ee|
|
32
|
+
ee << url_str
|
33
|
+
ee << '?' unless url_str.include?('?')
|
34
|
+
ee << URI.encode_www_form(params)
|
35
|
+
end
|
36
|
+
|
37
|
+
uri = Appender.new(*elements).to_uri
|
38
|
+
uri.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_or_raise(url_str, headers)
|
42
|
+
resp = RestClient.get(url_str, headers)
|
43
|
+
begin
|
44
|
+
return resp if (status = resp.code) == 200
|
45
|
+
|
46
|
+
raise(exception_for(resp, status))
|
47
|
+
ensure
|
48
|
+
logger.info("GET #{url_str} returned #{status}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def exception_for(resp, status)
|
53
|
+
RestClient::RequestFailed.new(resp, status).tap do |ex|
|
54
|
+
status_message = RestClient::STATUSES[status] || '(Unknown)'
|
55
|
+
ex.message = "#{status} #{status_message}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module BerkeleyLibrary
|
4
|
+
module Util
|
5
|
+
module URIs
|
6
|
+
module Validator
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Returns the specified URL as a URI.
|
10
|
+
# @param url [String, URI] the URL.
|
11
|
+
# @return [URI] the URI.
|
12
|
+
# @raise [URI::InvalidURIError] if `url` cannot be parsed as a URI.
|
13
|
+
def uri_or_nil(url)
|
14
|
+
return unless url
|
15
|
+
|
16
|
+
# noinspection RubyMismatchedReturnType
|
17
|
+
url.is_a?(URI) ? url : URI.parse(url.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the specified URL as a string.
|
21
|
+
# @param url [String, URI] the URL.
|
22
|
+
# @return [String] the URL.
|
23
|
+
# @raise [URI::InvalidURIError] if `url` cannot be parsed as a URI.
|
24
|
+
def url_str_or_nil(url)
|
25
|
+
uri = Validator.uri_or_nil(url)
|
26
|
+
uri.to_s if uri
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'berkeley_library/util/uris/appender'
|
2
|
+
require 'berkeley_library/util/uris/requester'
|
3
|
+
require 'berkeley_library/util/uris/validator'
|
4
|
+
|
5
|
+
module BerkeleyLibrary
|
6
|
+
module Util
|
7
|
+
module URIs
|
8
|
+
class << self
|
9
|
+
include URIs
|
10
|
+
end
|
11
|
+
|
12
|
+
# Appends the specified paths to the path of the specified URI, removing any extraneous slashes
|
13
|
+
# and merging additional query parameters, and returns a new URI with that path and the same scheme,
|
14
|
+
# host, query, fragment, etc. as the original.
|
15
|
+
#
|
16
|
+
# @param uri [URI, String] the original URI
|
17
|
+
# @param elements [Array<String, Symbol>] the URI elements to join.
|
18
|
+
# @return [URI] a new URI appending the joined path elements.
|
19
|
+
# @raise URI::InvalidComponentError if appending the specified elements would create an invalid URI
|
20
|
+
def append(uri, *elements)
|
21
|
+
Appender.new(uri, *elements).to_uri
|
22
|
+
end
|
23
|
+
|
24
|
+
# Performs a GET request.
|
25
|
+
#
|
26
|
+
# @param uri [URI, String] the URI to GET
|
27
|
+
# @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
|
28
|
+
# @param headers [Hash] the request headers.
|
29
|
+
# @return [String] the body as a string.
|
30
|
+
# @raise [RestClient::Exception] in the event of an error.
|
31
|
+
def get(uri, params: {}, headers: {})
|
32
|
+
Requester.get(uri, params: params, headers: headers)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the specified URL as a URI.
|
36
|
+
# @param url [String, URI] the URL.
|
37
|
+
# @return [URI] the URI.
|
38
|
+
# @raise [URI::InvalidURIError] if `url` cannot be parsed as a URI.
|
39
|
+
def uri_or_nil(url)
|
40
|
+
Validator.uri_or_nil(url)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir.glob(File.expand_path('util/*.rb', __dir__)).sort.each(&method(:require))
|
data/rakelib/bundle.rake
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'ci/reporter/rake/rspec'
|
2
|
+
|
3
|
+
# Configure CI::Reporter report generation
|
4
|
+
ENV['GENERATE_REPORTS'] ||= 'true'
|
5
|
+
ENV['CI_REPORTS'] = 'artifacts/rspec'
|
6
|
+
|
7
|
+
desc 'Run all specs in spec directory, with coverage'
|
8
|
+
task coverage: ['ci:setup:rspec'] do
|
9
|
+
ENV['COVERAGE'] ||= 'true'
|
10
|
+
Rake::Task[:spec].invoke
|
11
|
+
end
|
data/rakelib/gem.rake
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems/gem_runner'
|
2
|
+
require 'berkeley_library/util/module_info'
|
3
|
+
|
4
|
+
gem_root_module = BerkeleyLibrary::Util
|
5
|
+
|
6
|
+
class << gem_root_module
|
7
|
+
def project_root
|
8
|
+
@project_root ||= File.expand_path('..', __dir__)
|
9
|
+
end
|
10
|
+
|
11
|
+
def artifacts_dir
|
12
|
+
return project_root unless ENV['CI']
|
13
|
+
|
14
|
+
@artifacts_dir ||= File.join(project_root, 'artifacts')
|
15
|
+
end
|
16
|
+
|
17
|
+
def gemspec_file
|
18
|
+
@gemspec_file ||= begin
|
19
|
+
gemspec_files = Dir.glob(File.expand_path('*.gemspec', project_root))
|
20
|
+
raise ArgumentError, "Too many .gemspecs: #{gemspec_files.join(', ')}" if gemspec_files.size > 1
|
21
|
+
raise ArgumentError, 'No .gemspec file found' if gemspec_files.empty?
|
22
|
+
|
23
|
+
gemspec_files[0]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def gemspec_basename
|
28
|
+
File.basename(gemspec_file)
|
29
|
+
end
|
30
|
+
|
31
|
+
def output_file
|
32
|
+
@output_file ||= begin
|
33
|
+
gem_name = File.basename(gemspec_file, '.*')
|
34
|
+
version = self::ModuleInfo::VERSION
|
35
|
+
basename = "#{gem_name}-#{version}.gem"
|
36
|
+
File.join(artifacts_dir, basename)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def output_file_relative
|
41
|
+
return File.basename(output_file) unless ENV['CI']
|
42
|
+
|
43
|
+
@output_file_relative ||= begin
|
44
|
+
artifacts_dir_relative = File.basename(artifacts_dir)
|
45
|
+
File.join(artifacts_dir_relative, File.basename(output_file))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Build #{gem_root_module.gemspec_basename} as #{gem_root_module.output_file_relative}"
|
51
|
+
task :gem do
|
52
|
+
args = ['build', gem_root_module.gemspec_file, "--output=#{gem_root_module.output_file}"]
|
53
|
+
Gem::GemRunner.new.run(args)
|
54
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubocop'
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
|
4
|
+
desc 'Run rubocop with HTML output'
|
5
|
+
RuboCop::RakeTask.new(:rubocop) do |cop|
|
6
|
+
output = ENV['RUBOCOP_OUTPUT'] || 'artifacts/rubocop/index.html'
|
7
|
+
puts "Writing RuboCop inspection report to #{output}"
|
8
|
+
|
9
|
+
cop.verbose = false
|
10
|
+
cop.formatters = ['html']
|
11
|
+
cop.options = ['--out', output]
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Run RuboCop with auto-correct, and output results to console'
|
15
|
+
task :ra do
|
16
|
+
# b/c we want console output, we can't just use `rubocop:auto_correct`
|
17
|
+
RuboCop::CLI.new.run(['--safe-auto-correct'])
|
18
|
+
end
|
data/rakelib/spec.rake
ADDED
data/spec/.rubocop.yml
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
inherit_from: ../.rubocop.yml
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
# Exclude generated files
|
5
|
+
Exclude:
|
6
|
+
- 'suite/**/*'
|
7
|
+
|
8
|
+
Style/ClassAndModuleChildren:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Style/MultilineBlockChain:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Style/ParallelAssignment:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Layout/LineLength:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Metrics/AbcSize:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Metrics/BlockLength:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Metrics/ClassLength:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Metrics/ModuleLength:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Metrics/MethodLength:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
############################################################
|
36
|
+
# Added in Rubocop 0.89
|
37
|
+
|
38
|
+
# Sometimes we're testing the operator
|
39
|
+
Lint/BinaryOperatorWithIdenticalOperands:
|
40
|
+
Enabled: false
|