aspera-cli 4.25.6 → 4.26.1
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +89 -48
- data/CONTRIBUTING.md +1 -1
- data/lib/aspera/api/aoc.rb +120 -79
- data/lib/aspera/api/node.rb +103 -51
- data/lib/aspera/ascp/installation.rb +99 -32
- data/lib/aspera/assert.rb +17 -13
- data/lib/aspera/cli/extended_value.rb +7 -2
- data/lib/aspera/cli/formatter.rb +107 -95
- data/lib/aspera/cli/main.rb +69 -10
- data/lib/aspera/cli/manager.rb +158 -78
- data/lib/aspera/cli/options.schema.yaml +82 -0
- data/lib/aspera/cli/plugins/aoc.rb +247 -144
- data/lib/aspera/cli/plugins/ats.rb +3 -3
- data/lib/aspera/cli/plugins/base.rb +60 -76
- data/lib/aspera/cli/plugins/config.rb +14 -12
- data/lib/aspera/cli/plugins/console.rb +3 -3
- data/lib/aspera/cli/plugins/faspex.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +24 -23
- data/lib/aspera/cli/plugins/node.rb +67 -71
- data/lib/aspera/cli/plugins/oauth.rb +5 -12
- data/lib/aspera/cli/plugins/orchestrator.rb +13 -13
- data/lib/aspera/cli/plugins/preview.rb +116 -80
- data/lib/aspera/cli/plugins/server.rb +2 -10
- data/lib/aspera/cli/plugins/shares.rb +7 -7
- data/lib/aspera/cli/sync_actions.rb +1 -1
- data/lib/aspera/cli/transfer_agent.rb +17 -15
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +22 -18
- data/lib/aspera/dot_container.rb +7 -3
- data/lib/aspera/environment.rb +6 -5
- data/lib/aspera/formatter_interface.rb +14 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/log.rb +5 -4
- data/lib/aspera/markdown.rb +4 -1
- data/lib/aspera/oauth/factory.rb +1 -1
- data/lib/aspera/preview/file_types.rb +1 -1
- data/lib/aspera/preview/generator.rb +146 -91
- data/lib/aspera/preview/options.rb +4 -1
- data/lib/aspera/preview/terminal.rb +50 -20
- data/lib/aspera/preview/utils.rb +76 -34
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +3 -0
- data/lib/aspera/rest.rb +2 -1
- data/lib/aspera/rest_list.rb +23 -16
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +62801 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +8898 -0
- data/lib/aspera/schema/documentation.rb +107 -0
- data/lib/aspera/schema/reader.rb +75 -0
- data/lib/aspera/schema/registry.rb +63 -0
- data/lib/aspera/secret_hider.rb +3 -1
- data/lib/aspera/sync/conf.schema.yaml +0 -26
- data/lib/aspera/sync/operations.rb +9 -5
- data/lib/aspera/transfer/faux_file.rb +1 -1
- data/lib/aspera/transfer/resumer.rb +1 -1
- data/lib/aspera/transfer/spec.rb +3 -3
- data/lib/aspera/transfer/spec.schema.yaml +1 -1
- data/lib/aspera/uri_reader.rb +17 -2
- data/lib/aspera/yaml.rb +4 -2
- data.tar.gz.sig +0 -0
- metadata +13 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/transfer/spec_doc.rb +0 -76
|
@@ -10,6 +10,8 @@ module Aspera
|
|
|
10
10
|
# types of generation for video files
|
|
11
11
|
VIDEO_CONVERSION_METHODS = %i[reencode blend clips].freeze
|
|
12
12
|
VIDEO_THUMBNAIL_METHODS = %i[fixed animated].freeze
|
|
13
|
+
# methods for office document conversion
|
|
14
|
+
OFFICE_CONVERSION_METHODS = %i[soffice unoconv].freeze
|
|
13
15
|
# options used in generator
|
|
14
16
|
# for scaling see: https://trac.ffmpeg.org/wiki/Scaling
|
|
15
17
|
# iw/ih : input width or height
|
|
@@ -20,11 +22,12 @@ module Aspera
|
|
|
20
22
|
{name: :thumb_vid_fraction, default: 0.1, description: 'png: video: time percent position of snapshot'},
|
|
21
23
|
{name: :thumb_img_size, default: 800, description: 'png: non-video: height (and width)'},
|
|
22
24
|
{name: :thumb_text_font, default: 'Courier', description: 'png: plaintext: font for text rendering: `magick identify -list font`'},
|
|
25
|
+
{name: :office_conversion, default: :soffice, description: 'office: method for office document conversion', values: OFFICE_CONVERSION_METHODS},
|
|
23
26
|
{name: :video_conversion, default: :reencode, description: 'mp4: method for preview generation', values: VIDEO_CONVERSION_METHODS},
|
|
24
27
|
{name: :video_png_conv, default: :fixed, description: 'mp4: method for thumbnail generation', values: VIDEO_THUMBNAIL_METHODS},
|
|
25
28
|
{name: :video_scale, default: "'min(iw,360)':-2", description: 'mp4: all: video scale (ffmpeg scale argument)'},
|
|
26
29
|
{name: :video_start_sec, default: 10, description: 'mp4: all: start offset (seconds) of video preview'},
|
|
27
|
-
{name: :reencode_ffmpeg, default: {}, description: 'mp4: reencode: options to ffmpeg'},
|
|
30
|
+
{name: :reencode_ffmpeg, default: {}, description: 'mp4: reencode: options to ffmpeg, keys: `in`, `out`'},
|
|
28
31
|
{name: :blend_keyframes, default: 30, description: 'mp4: blend: # key frames'},
|
|
29
32
|
{name: :blend_pauseframes, default: 3, description: 'mp4: blend: # pause frames'},
|
|
30
33
|
{name: :blend_transframes, default: 5, description: 'mp4: blend: # transition blend frames'},
|
|
@@ -11,15 +11,22 @@ require 'aspera/environment'
|
|
|
11
11
|
module Aspera
|
|
12
12
|
module Preview
|
|
13
13
|
module Backend
|
|
14
|
-
#
|
|
14
|
+
# Base decoder that rescales image data to the current terminal geometry.
|
|
15
15
|
class Base
|
|
16
|
+
# @param reserve [Integer] number of terminal rows reserved for non-image output
|
|
17
|
+
# @param double [Boolean] when `true`, render two image rows in one terminal row
|
|
18
|
+
# @param font_ratio [Float] terminal font aspect ratio: height divided by width
|
|
16
19
|
def initialize(reserve:, double:, font_ratio:)
|
|
17
20
|
@reserve = reserve
|
|
18
21
|
@height_ratio = double ? 2.0 : 1.0
|
|
19
22
|
@font_ratio = font_ratio
|
|
20
23
|
end
|
|
21
24
|
Aspera.require_method!(:terminal_pixels)
|
|
22
|
-
#
|
|
25
|
+
# Compute output dimensions that fit inside the terminal while preserving aspect ratio.
|
|
26
|
+
#
|
|
27
|
+
# @param rows [Integer] source image height in pixels
|
|
28
|
+
# @param columns [Integer] source image width in pixels
|
|
29
|
+
# @return [Array<Integer>] scaled width and height for terminal rendering
|
|
23
30
|
def terminal_scaling(rows, columns)
|
|
24
31
|
(term_rows, term_columns) = IO.console.winsize || [24, 80]
|
|
25
32
|
term_rows = [term_rows - @reserve, 2].max
|
|
@@ -29,22 +36,30 @@ module Aspera
|
|
|
29
36
|
end
|
|
30
37
|
|
|
31
38
|
class RMagick < Base
|
|
39
|
+
# Initialize the RMagick-backed decoder for a binary image payload.
|
|
40
|
+
#
|
|
41
|
+
# @param blob [String] encoded image binary content
|
|
42
|
+
# @param kwargs [Hash] forwarding options accepted by [`initialize`](lib/aspera/preview/terminal.rb:16)
|
|
32
43
|
def initialize(blob, **kwargs)
|
|
33
44
|
super(**kwargs)
|
|
34
|
-
#
|
|
45
|
+
# Load lazily because this dependency is optional.
|
|
35
46
|
require 'rmagick' # https://rmagick.github.io/index.html
|
|
36
47
|
@image = Magick::ImageList.new.from_blob(blob)
|
|
37
48
|
end
|
|
38
49
|
|
|
50
|
+
# Decode the image and return RGB pixels scaled for terminal rendering.
|
|
51
|
+
#
|
|
52
|
+
# @return [Array<Array<Array<Integer>>>] rows of `[red, green, blue]` pixel triplets
|
|
39
53
|
def terminal_pixels
|
|
40
|
-
#
|
|
54
|
+
# ImageMagick channel depth is typically 8 or 16 bits.
|
|
55
|
+
# See: `magick xc: -format "%q" info:`
|
|
41
56
|
shift_for_8_bit = Magick::MAGICKCORE_QUANTUM_DEPTH - 8
|
|
42
|
-
#
|
|
57
|
+
# Extract RGB values and normalize them to 8-bit channels for Rainbow.
|
|
43
58
|
pixel_colors = []
|
|
44
59
|
@image.scale(*terminal_scaling(@image.rows, @image.columns)).each_pixel do |pixel, col, row|
|
|
45
60
|
pixel_rgb = [pixel.red, pixel.green, pixel.blue]
|
|
46
61
|
pixel_rgb = pixel_rgb.map{ |color| color >> shift_for_8_bit} unless shift_for_8_bit.eql?(0)
|
|
47
|
-
#
|
|
62
|
+
# Initialize the destination 2D pixel matrix row by row.
|
|
48
63
|
pixel_colors[row] ||= []
|
|
49
64
|
pixel_colors[row][col] = pixel_rgb
|
|
50
65
|
end
|
|
@@ -53,12 +68,19 @@ module Aspera
|
|
|
53
68
|
end
|
|
54
69
|
|
|
55
70
|
class ChunkyPNG < Base
|
|
71
|
+
# Initialize the ChunkyPNG-backed decoder for a PNG payload.
|
|
72
|
+
#
|
|
73
|
+
# @param blob [String] PNG binary content
|
|
74
|
+
# @param kwargs [Hash] forwarding options accepted by [`initialize`](lib/aspera/preview/terminal.rb:16)
|
|
56
75
|
def initialize(blob, **kwargs)
|
|
57
76
|
super(**kwargs)
|
|
58
77
|
require 'chunky_png'
|
|
59
78
|
@png = ::ChunkyPNG::Image.from_blob(blob)
|
|
60
79
|
end
|
|
61
80
|
|
|
81
|
+
# Resize the PNG using nearest-neighbor sampling and return RGB pixel rows.
|
|
82
|
+
#
|
|
83
|
+
# @return [Array<Array<Array<Integer>>>] rows of `[red, green, blue]` pixel triplets
|
|
62
84
|
def terminal_pixels
|
|
63
85
|
src_w = @png.width
|
|
64
86
|
src_h = @png.height
|
|
@@ -75,7 +97,7 @@ module Aspera
|
|
|
75
97
|
sx = (dx * x_ratio).floor
|
|
76
98
|
sx = src_w - 1 if sx >= src_w
|
|
77
99
|
rgba = @png.get_pixel(sx, sy)
|
|
78
|
-
# ChunkyPNG stores as 0xRRGGBBAA; extract 8-bit channels
|
|
100
|
+
# ChunkyPNG stores pixels as 0xRRGGBBAA; extract 8-bit RGB channels.
|
|
79
101
|
pixel_colors[dy][dx] = %i[r g b].map{ |i| ::ChunkyPNG::Color.send(i, rgba)}
|
|
80
102
|
end
|
|
81
103
|
end
|
|
@@ -84,19 +106,21 @@ module Aspera
|
|
|
84
106
|
end
|
|
85
107
|
end
|
|
86
108
|
|
|
87
|
-
#
|
|
88
|
-
#
|
|
109
|
+
# Render an image for terminal output.
|
|
110
|
+
# Uses either colored text blocks or the iTerm2 inline-image protocol when available.
|
|
89
111
|
class Terminal
|
|
90
|
-
# Rainbow only supports 8-bit
|
|
91
|
-
#
|
|
112
|
+
# Rainbow only supports 8-bit color values.
|
|
113
|
+
# Environment variables inspected to detect compatible terminal implementations.
|
|
92
114
|
TERM_ENV_VARS = %w[TERM_PROGRAM LC_TERMINAL].freeze
|
|
93
|
-
#
|
|
115
|
+
# Terminal identifiers known to support the iTerm2 inline-image protocol.
|
|
94
116
|
ITERM_NAMES = %w[iTerm WezTerm mintty].freeze
|
|
95
|
-
#
|
|
96
|
-
#
|
|
117
|
+
# Fallback font aspect ratio used to estimate how many image pixels fit in a character cell.
|
|
118
|
+
# Ratio = font height / font width.
|
|
97
119
|
DEFAULT_FONT_RATIO = 32.0 / 14.0
|
|
98
120
|
private_constant :TERM_ENV_VARS, :ITERM_NAMES, :DEFAULT_FONT_RATIO
|
|
99
121
|
class << self
|
|
122
|
+
# Render an image blob for display in the current terminal.
|
|
123
|
+
#
|
|
100
124
|
# @param blob [String] The image as a binary string
|
|
101
125
|
# @param text [Boolean] `true` to display the image as text, `false` to use iTerm2 if supported
|
|
102
126
|
# @param reserve [Integer] Number of lines to reserve for other text than the image
|
|
@@ -124,7 +148,7 @@ module Aspera
|
|
|
124
148
|
return iterm_display_image(blob) if iterm_supported?
|
|
125
149
|
raise 'Cannot decode picture.'
|
|
126
150
|
end
|
|
127
|
-
#
|
|
151
|
+
# Convert decoded pixels into terminal glyphs.
|
|
128
152
|
text_pixels = []
|
|
129
153
|
pixel_colors.each_with_index do |row_data, row|
|
|
130
154
|
next if double && (row.odd? || row.eql?(pixel_colors.length - 1))
|
|
@@ -140,11 +164,14 @@ module Aspera
|
|
|
140
164
|
return text_pixels.join
|
|
141
165
|
end
|
|
142
166
|
|
|
143
|
-
#
|
|
167
|
+
# Build the iTerm2 inline-image escape sequence.
|
|
144
168
|
# https://iterm2.com/documentation-images.html
|
|
169
|
+
#
|
|
170
|
+
# @param blob [String] image binary content
|
|
171
|
+
# @return [String] escape sequence that displays the image inline
|
|
145
172
|
def iterm_display_image(blob)
|
|
146
173
|
# image = Magick::ImageList.new.from_blob(blob)
|
|
147
|
-
#
|
|
174
|
+
# Parameters accepted by the iTerm2 inline-image protocol.
|
|
148
175
|
arguments = {
|
|
149
176
|
inline: 1,
|
|
150
177
|
preserveAspectRatio: 1,
|
|
@@ -152,12 +179,15 @@ module Aspera
|
|
|
152
179
|
# width: image.columns,
|
|
153
180
|
# height: image.rows
|
|
154
181
|
}.map{ |k, v| "#{k}=#{v}"}.join(';')
|
|
155
|
-
#
|
|
156
|
-
#
|
|
182
|
+
# `\a` is BEL and `\e` is ESC.
|
|
183
|
+
# See: https://github.com/ruby/ruby/blob/master/doc/syntax/literals.rdoc#label-Strings
|
|
184
|
+
# Return the full escape sequence expected by iTerm2-compatible terminals.
|
|
157
185
|
return "\e]1337;File=#{arguments}:#{Base64.strict_encode64(blob)}\a"
|
|
158
186
|
end
|
|
159
187
|
|
|
160
|
-
#
|
|
188
|
+
# Detect whether the current terminal supports iTerm2 inline images.
|
|
189
|
+
#
|
|
190
|
+
# @return [Boolean] `true` when the current terminal advertises iTerm2 image support
|
|
161
191
|
def iterm_supported?
|
|
162
192
|
TERM_ENV_VARS.each do |env_var|
|
|
163
193
|
return true if ITERM_NAMES.any?{ |term| ENV[env_var]&.include?(term)}
|
data/lib/aspera/preview/utils.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# cspell:ignore ffprobe optipng unoconv
|
|
3
|
+
# cspell:ignore ffprobe optipng unoconv soffice
|
|
4
4
|
require 'aspera/log'
|
|
5
5
|
require 'aspera/assert'
|
|
6
6
|
require 'English'
|
|
@@ -11,32 +11,30 @@ require 'open3'
|
|
|
11
11
|
module Aspera
|
|
12
12
|
module Preview
|
|
13
13
|
class Utils
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
EXTERNAL_TOOLS = %i[ffmpeg ffprobe magick optipng unoconv].freeze
|
|
14
|
+
# External binaries used
|
|
15
|
+
EXTERNAL_TOOLS = %i[ffmpeg ffprobe magick optipng unoconv soffice].freeze
|
|
16
|
+
# File name format for temporary files, used by both ffmpeg and ruby(Kernel.format)
|
|
18
17
|
TEMP_FORMAT = 'img%04d.jpg'
|
|
18
|
+
# default parameters for ffmpeg
|
|
19
19
|
FFMPEG_DEFAULT_PARAMS = [
|
|
20
20
|
'-y', # overwrite output without asking
|
|
21
21
|
'-loglevel', 'error' # show only errors and up
|
|
22
22
|
].freeze
|
|
23
|
-
private_constant :
|
|
23
|
+
private_constant :EXTERNAL_TOOLS, :TEMP_FORMAT, :FFMPEG_DEFAULT_PARAMS
|
|
24
24
|
|
|
25
25
|
class << self
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# surround with single quotes, and escape single quotes
|
|
30
|
-
return %Q{'#{argument.gsub("'"){ |_s| %q{'"'"'}}}'}
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# check that external tools can be executed
|
|
26
|
+
# Check that external tools can be executed
|
|
27
|
+
# @param skip_types [Array<Symbol>] list of tools to skip
|
|
28
|
+
# @return [nil]
|
|
34
29
|
def check_tools(skip_types = [])
|
|
35
30
|
tools_to_check = EXTERNAL_TOOLS.dup
|
|
36
|
-
|
|
31
|
+
if skip_types.include?(:office)
|
|
32
|
+
tools_to_check.delete(:unoconv)
|
|
33
|
+
tools_to_check.delete(:soffice)
|
|
34
|
+
end
|
|
37
35
|
# Check for binaries
|
|
38
36
|
tools_to_check.each do |command_sym|
|
|
39
|
-
|
|
37
|
+
silent_execute(command_sym, '-h')
|
|
40
38
|
rescue Errno::ENOENT => e
|
|
41
39
|
raise "missing #{command_sym} binary: #{e}"
|
|
42
40
|
rescue
|
|
@@ -44,42 +42,47 @@ module Aspera
|
|
|
44
42
|
end
|
|
45
43
|
end
|
|
46
44
|
|
|
47
|
-
# Execute external command
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), out: File::NULL, err: File::NULL)
|
|
45
|
+
# Execute external command, verify it is in the supported list
|
|
46
|
+
def execute(*args, **kwargs)
|
|
47
|
+
Aspera.assert_values(args.first, EXTERNAL_TOOLS){'command'}
|
|
48
|
+
Environment.secure_execute(*args, **kwargs)
|
|
52
49
|
end
|
|
53
50
|
|
|
54
|
-
# Execute external command
|
|
55
|
-
# @return [
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
# Execute external command silently
|
|
52
|
+
# @return [nil]
|
|
53
|
+
def silent_execute(*args)
|
|
54
|
+
execute(*args, out: File::NULL, err: File::NULL)
|
|
55
|
+
nil
|
|
59
56
|
end
|
|
60
57
|
|
|
58
|
+
# Execute `ffmpeg`
|
|
59
|
+
# @return [nil]
|
|
61
60
|
def ffmpeg(gl_p: FFMPEG_DEFAULT_PARAMS, in_p: [], in_f:, out_p: [], out_f:)
|
|
62
61
|
Aspera.assert_type(gl_p, Array)
|
|
63
62
|
Aspera.assert_type(in_p, Array)
|
|
64
63
|
Aspera.assert_type(out_p, Array)
|
|
65
|
-
|
|
64
|
+
silent_execute(:ffmpeg, *gl_p, *in_p, '-i', in_f, *out_p, out_f)
|
|
66
65
|
end
|
|
67
66
|
|
|
68
67
|
# @return Float in seconds
|
|
69
68
|
def video_get_duration(input_file)
|
|
70
|
-
return
|
|
69
|
+
return execute(
|
|
70
|
+
:ffprobe,
|
|
71
71
|
'-loglevel', 'error',
|
|
72
72
|
'-show_entries', 'format=duration',
|
|
73
73
|
'-print_format', 'default=noprint_wrappers=1:nokey=1', # cspell:disable-line
|
|
74
|
-
input_file
|
|
75
|
-
|
|
74
|
+
input_file,
|
|
75
|
+
mode: :capture
|
|
76
|
+
).first.to_f
|
|
76
77
|
end
|
|
77
78
|
|
|
79
|
+
# File output format, including temp folder
|
|
78
80
|
def ffmpeg_fmt(temp_folder)
|
|
79
81
|
return File.join(temp_folder, TEMP_FORMAT)
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def get_tmp_num_filepath(temp_folder, file_number)
|
|
85
|
+
# Format using {Kernel.format}
|
|
83
86
|
return File.join(temp_folder, format(TEMP_FORMAT, file_number))
|
|
84
87
|
end
|
|
85
88
|
|
|
@@ -97,19 +100,58 @@ module Aspera
|
|
|
97
100
|
1.upto(count) do |i|
|
|
98
101
|
percent = i * 100 / (count + 1)
|
|
99
102
|
filename = get_tmp_num_filepath(temp_folder, index_begin + i)
|
|
100
|
-
|
|
103
|
+
silent_execute(:magick, 'composite', '-blend', percent, img2, img1, filename)
|
|
101
104
|
end
|
|
102
105
|
end
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
# Dump a frame from a video file
|
|
108
|
+
# @param input_file [String] the input file path
|
|
109
|
+
# @param offset_seconds [Integer] the offset in seconds
|
|
110
|
+
# @param scale [String] the scale of the output frame
|
|
111
|
+
# @param output_file [String] the output file path
|
|
112
|
+
# @return [nil]
|
|
113
|
+
def video_dump_frame(input_file, offset_seconds, scale, output_file)
|
|
106
114
|
ffmpeg(
|
|
107
115
|
in_f: input_file,
|
|
108
116
|
in_p: ['-ss', offset_seconds],
|
|
109
117
|
out_f: output_file,
|
|
110
118
|
out_p: ['-frames:v', 1, '-filter:v', "scale=#{scale}"]
|
|
111
119
|
)
|
|
112
|
-
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Parse the output of `magick identify -list font` command
|
|
123
|
+
# @param output [String] the output from `magick -list font`
|
|
124
|
+
# @return [Hash] with keys :path and :fonts
|
|
125
|
+
# :path [String] the path to the type.xml file
|
|
126
|
+
# :fonts [Array<Hash>] array of font hashes with keys:
|
|
127
|
+
# :name, :family, :style, :stretch, :weight, :metrics, :glyphs, :index
|
|
128
|
+
def parse_magick_fonts(output)
|
|
129
|
+
result = {path: nil, fonts: []}
|
|
130
|
+
current_font = nil
|
|
131
|
+
output.each_line do |line|
|
|
132
|
+
line = line.strip
|
|
133
|
+
# Parse the Path line
|
|
134
|
+
if line.start_with?('Path:')
|
|
135
|
+
result[:path] = line.sub(/^Path:\s*/, '')
|
|
136
|
+
# Parse Font name
|
|
137
|
+
elsif line.start_with?('Font:')
|
|
138
|
+
# Save previous font if exists
|
|
139
|
+
result[:fonts] << current_font if current_font
|
|
140
|
+
# Start new font
|
|
141
|
+
current_font = {name: line.sub(/^Font:\s*/, '')}
|
|
142
|
+
# Parse font properties
|
|
143
|
+
elsif current_font && line.include?(':')
|
|
144
|
+
key, value = line.split(':', 2)
|
|
145
|
+
key = key.strip.gsub(/\s+/, '_').to_sym
|
|
146
|
+
value = value.strip
|
|
147
|
+
# Convert numeric values
|
|
148
|
+
value = value.to_i if key == :weight || key == :index
|
|
149
|
+
current_font[key] = value
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
# Don't forget the last font
|
|
153
|
+
result[:fonts] << current_font if current_font
|
|
154
|
+
result
|
|
113
155
|
end
|
|
114
156
|
end
|
|
115
157
|
end
|
|
@@ -27,7 +27,7 @@ module Aspera
|
|
|
27
27
|
sdk_directory
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
# @return the path to folder where SDK is installed
|
|
30
|
+
# @return the path to folder where SDK is or should be installed
|
|
31
31
|
def sdk_directory
|
|
32
32
|
Aspera.assert(!@sdk_dir.nil?){'SDK path was not initialized'}
|
|
33
33
|
@sdk_dir
|
|
@@ -10,6 +10,9 @@ module URI
|
|
|
10
10
|
# save original method that finds proxy in URI::Generic, it uses env var http_proxy
|
|
11
11
|
alias_method :find_proxy_orig, :find_proxy
|
|
12
12
|
class << self
|
|
13
|
+
# Register a custom proxy finder block
|
|
14
|
+
# @yieldparam url [String] The URL to find proxy for
|
|
15
|
+
# @yieldreturn [String, nil] Proxy URL or nil to fallback to original method
|
|
13
16
|
def register_proxy_finder
|
|
14
17
|
Aspera.assert(block_given?)
|
|
15
18
|
# overload the method in URI : call user's provided block and fallback to original method
|
data/lib/aspera/rest.rb
CHANGED
|
@@ -78,6 +78,7 @@ module Aspera
|
|
|
78
78
|
|
|
79
79
|
# Indicate that the given Hash query uses php style for array parameters
|
|
80
80
|
# @param query [Hash] A key can have Array value and result will use PHP format: a[]=1&a[]=2
|
|
81
|
+
# @return [Hash] The query parameters.
|
|
81
82
|
def php_style(query)
|
|
82
83
|
Aspera.assert_type(query, Hash){'query'}
|
|
83
84
|
query[:x_array_php_style] = true
|
|
@@ -165,7 +166,7 @@ module Aspera
|
|
|
165
166
|
next unless url_match
|
|
166
167
|
url = url_match[1]
|
|
167
168
|
# Extract parameters after the URL
|
|
168
|
-
params_str = link_part[url_match.end(0)
|
|
169
|
+
params_str = link_part[url_match.end(0)..]
|
|
169
170
|
# Check if this link has the specified rel (with or without quotes, case insensitive)
|
|
170
171
|
next unless /;\s*rel\s*=\s*"?#{Regexp.escape(rel)}"?/i.match?(params_str)
|
|
171
172
|
return url
|
data/lib/aspera/rest_list.rb
CHANGED
|
@@ -11,33 +11,39 @@ module Aspera
|
|
|
11
11
|
|
|
12
12
|
# Query entity by general search (read with parameter `q`)
|
|
13
13
|
#
|
|
14
|
-
# @param
|
|
15
|
-
# @param
|
|
16
|
-
# @param
|
|
14
|
+
# @param entity [String] Path of entity in API
|
|
15
|
+
# @param value [String] Value of field of searched entity
|
|
16
|
+
# @param field [String] Field of searched entity
|
|
17
|
+
# @param query [Hash] Optional additional search query parameters
|
|
17
18
|
# @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
|
|
18
|
-
def
|
|
19
|
-
query
|
|
19
|
+
def lookup_with_q(entity, value:, field: 'name', query: {})
|
|
20
|
+
Aspera.assert_type(query, Hash){'query'}
|
|
21
|
+
Aspera.assert_type(field, String){'field'}
|
|
20
22
|
# returns entities matching the query (it matches against several fields in case insensitive way)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
# We don't do paging, as anyway, we look for only one match
|
|
24
|
+
matching_items = read(entity, query.merge({'q' => value}))
|
|
25
|
+
# API style: {totalcount:, ...} cspell: disable-line: TODO: is that total_count ?
|
|
26
|
+
# @type [Array<Hash{String => String}>]
|
|
27
|
+
matching_items = matching_items[entity] if matching_items.is_a?(Hash)
|
|
24
28
|
Aspera.assert_type(matching_items, Array)
|
|
25
29
|
case matching_items.length
|
|
26
30
|
when 1 then return matching_items.first
|
|
27
|
-
when 0 then raise EntityNotFound, %Q{No such #{
|
|
31
|
+
when 0 then raise EntityNotFound, %Q{No such #{entity}: "#{value}"}
|
|
28
32
|
else
|
|
29
33
|
# multiple case insensitive partial matches, try case insensitive full match
|
|
30
|
-
# (anyway AoC does not allow creation of 2 entities with same case insensitive
|
|
31
|
-
|
|
32
|
-
case
|
|
33
|
-
when 1 then return
|
|
34
|
-
when 0 then raise %Q(#{
|
|
35
|
-
else raise "Two entities cannot have the same case insensitive
|
|
34
|
+
# (anyway AoC does not allow creation of 2 entities with same case insensitive field value)
|
|
35
|
+
value_matches = matching_items.select{ |i| i[field].casecmp?(value)}
|
|
36
|
+
case value_matches.length
|
|
37
|
+
when 1 then return value_matches.first
|
|
38
|
+
when 0 then raise %Q(#{entity}: Multiple case insensitive partial match for: "#{value}" in #{matching_items.map{ |i| i[field]}.join(', ')} but no case insensitive full match. Please be more specific or give exact #{field}.)
|
|
39
|
+
else raise "Two entities cannot have the same case insensitive #{field}: #{value_matches.map{ |i| i[field]}}"
|
|
36
40
|
end
|
|
37
41
|
end
|
|
38
42
|
end
|
|
39
43
|
|
|
40
|
-
# Get a (full or partial) list of all entities of a given type
|
|
44
|
+
# Get a (full or partial) list of all entities of a given type.
|
|
45
|
+
# Using query: `offset` + `limit`
|
|
46
|
+
# And response `total_count`
|
|
41
47
|
# @param entity [String,Symbol] API endpoint of entity to list
|
|
42
48
|
# @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
|
|
43
49
|
# @param query [Hash,nil] Additional query parameters
|
|
@@ -114,6 +120,7 @@ module Aspera
|
|
|
114
120
|
return found.first if found.length.eql?(1)
|
|
115
121
|
raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
|
|
116
122
|
end
|
|
123
|
+
|
|
117
124
|
PER_PAGE_DEFAULT = 1000
|
|
118
125
|
private_constant :PER_PAGE_DEFAULT
|
|
119
126
|
module_function :lookup_entity_generic
|