sdr-client 0.69.0 → 0.72.0

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: 7612a41d64ee04bd9fca5cb7c354f5e80aefa7e31b823482f74159c66147ed09
4
- data.tar.gz: e2658b4e3c02989b6256e26bd5c2d80036ac653b2bf2ad7cb203f370205351bb
3
+ metadata.gz: 899fec033d20f23ab887672fcfe7c706f9c6e5b02b7fd6975db5560be96b788c
4
+ data.tar.gz: cb68aa02ff81a59f50bdbc5003407765caa25d3e73d64f663e40c3990d1509df
5
5
  SHA512:
6
- metadata.gz: 6c317cb59a717ec584859be96d02c15398938cc15c54b7b6484adf30c8209954a0e8dd72201e1d53eacab9504f46df6f1b843eac4c076d7392da0944017b917a
7
- data.tar.gz: 8affcce27dfc1105d4c89852d5cec2a4c1326d5453d4f83b8a52fe2e94257608a3e520e0ec4df7479d83eeb40e5bb2e75b8369294038fd6af49f1fa5786aa93d
6
+ metadata.gz: a53a001759ad7c941c1713999f1dfcd15a43c4200fff5644605dd277f2e8ce4c52497e8e1662a7b43fb896860f20dfc42380a8249fa47702e9106a5ecf4bf1c1
7
+ data.tar.gz: f1de6ce23106680288c2994e7ba085a9ef4aed3cbf2ecbbb315c8c4601a01aba134d5bcfd3b316fb7a242a99ebcad89cbf8f22929e2099cad3f001f62ac3c74a
data/.rubocop.yml CHANGED
@@ -19,7 +19,9 @@ Metrics/BlockLength:
19
19
 
20
20
  Metrics/ClassLength:
21
21
  Exclude:
22
+ - 'lib/sdr_client/cli.rb'
22
23
  - 'lib/sdr_client/deposit/request.rb'
24
+ - 'lib/sdr_client/update.rb'
23
25
 
24
26
  Naming/FileName:
25
27
  Exclude:
@@ -137,3 +139,7 @@ RSpec/FactoryBot/SyntaxMethods: # new in 2.7
137
139
  Enabled: true
138
140
  RSpec/Rails/AvoidSetupHook: # new in 2.4
139
141
  Enabled: true
142
+ RSpec/BeEq: # new in 2.9.0
143
+ Enabled: true
144
+ RSpec/BeNil: # new in 2.9.0
145
+ Enabled: true
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config --auto-gen-only-exclude`
3
- # on 2022-02-15 02:08:38 UTC using RuboCop version 1.25.1.
3
+ # on 2022-03-11 00:50:54 UTC using RuboCop version 1.25.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -26,7 +26,21 @@ Lint/UnusedMethodArgument:
26
26
  - 'lib/sdr_client/deposit/file_type_file_set_strategy.rb'
27
27
  - 'lib/sdr_client/deposit/image_file_set_strategy.rb'
28
28
 
29
- # Offense count: 10
29
+ # Offense count: 2
30
+ # Configuration parameters: IgnoredMethods, CountRepeatedAttributes, Max.
31
+ Metrics/AbcSize:
32
+ Exclude:
33
+ - 'lib/sdr_client/cli.rb'
34
+ - 'lib/sdr_client/update.rb'
35
+
36
+ # Offense count: 2
37
+ # Configuration parameters: IgnoredMethods, Max.
38
+ Metrics/CyclomaticComplexity:
39
+ Exclude:
40
+ - 'lib/sdr_client/cli.rb'
41
+ - 'lib/sdr_client/update.rb'
42
+
43
+ # Offense count: 11
30
44
  # Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
31
45
  Metrics/MethodLength:
32
46
  Exclude:
@@ -36,13 +50,15 @@ Metrics/MethodLength:
36
50
  - 'lib/sdr_client/deposit/process.rb'
37
51
  - 'lib/sdr_client/deposit/request.rb'
38
52
  - 'lib/sdr_client/login.rb'
53
+ - 'lib/sdr_client/update.rb'
39
54
 
40
- # Offense count: 6
55
+ # Offense count: 10
41
56
  # Configuration parameters: Max, CountAsOne.
42
57
  RSpec/ExampleLength:
43
58
  Exclude:
44
59
  - 'spec/sdr_client/deposit_spec.rb'
45
60
  - 'spec/sdr_client/model_deposit_spec.rb'
61
+ - 'spec/sdr_client/update_spec.rb'
46
62
 
47
63
  # Offense count: 3
48
64
  # Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly.
@@ -60,7 +76,7 @@ RSpec/MessageSpies:
60
76
  Exclude:
61
77
  - 'spec/sdr_client/deposit_spec.rb'
62
78
 
63
- # Offense count: 10
79
+ # Offense count: 12
64
80
  # Configuration parameters: Max.
65
81
  RSpec/MultipleExpectations:
66
82
  Exclude:
@@ -106,6 +122,12 @@ Style/KeywordParametersOrder:
106
122
  - 'lib/sdr_client/deposit.rb'
107
123
  - 'lib/sdr_client/deposit/request.rb'
108
124
 
125
+ # Offense count: 1
126
+ # Cop supports --auto-correct.
127
+ Style/MultilineTernaryOperator:
128
+ Exclude:
129
+ - 'lib/sdr_client/update.rb'
130
+
109
131
  # Offense count: 2
110
132
  # Cop supports --auto-correct-all.
111
133
  # Configuration parameters: Mode.
@@ -114,12 +136,14 @@ Style/StringConcatenation:
114
136
  - 'lib/sdr_client/deposit/create_resource.rb'
115
137
  - 'spec/sdr_client/deposit_spec.rb'
116
138
 
117
- # Offense count: 16
139
+ # Offense count: 19
118
140
  # Cop supports --auto-correct.
119
141
  # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
120
142
  # URISchemes: http, https
121
143
  Layout/LineLength:
122
144
  Exclude:
145
+ - 'lib/sdr_client/cli.rb'
123
146
  - 'lib/sdr_client/deposit/model_process.rb'
147
+ - 'lib/sdr_client/update.rb'
124
148
  - 'spec/sdr_client/deposit/model_process_spec.rb'
125
149
  - 'spec/sdr_client/deposit/process_spec.rb'
data/README.md CHANGED
@@ -5,54 +5,94 @@
5
5
 
6
6
  # Sdr::Client
7
7
 
8
- This is a CLI for interacting with the Stanford Digital Repository API.
9
- The code for the SDR API server is at https://github.com/sul-dlss/sdr-api
8
+ This is a Ruby-based CLI for interacting with the Stanford Digital Repository API. The code for the SDR API server is at https://github.com/sul-dlss/sdr-api
10
9
 
11
10
  This provides a way for consumers to easily and correctly deposit files to the SDR without requiring access to the `/dor` NFS mount or to use Hydrus. A primary design goal was for this to have as few dependencies as possible so that it can be easily distributed by `gem install sdr-client` and then it can be used as a CLI.
12
11
 
13
12
  ## Install
14
13
 
14
+ We recommend using the latest 3.x release of Ruby.
15
+
15
16
  `gem install sdr-client`
16
17
 
17
18
  ## Usage
18
19
 
20
+ Get general help (e.g., list out commands):
21
+ ```
22
+ sdr help
23
+ # or:
24
+ sdr -h
25
+ # or:
26
+ sdr --help
27
+ ```
28
+
29
+ Get help for a specific command:
30
+ ```
31
+ sdr help register
32
+ ```
33
+
19
34
  Log in:
20
35
  ```
21
- sdr --service-url http://sdr-api-server:3000 login
36
+ sdr login --url https://sdr-api-server:3000
37
+ ```
38
+
39
+ Display version of sdr-client:
40
+ ```
41
+ sdr version
22
42
  ```
23
43
 
24
44
  Register a new object:
25
45
  ```
26
- sdr --service-url https://sdr-api-server:3000 register --label 'hey there' \
46
+ sdr register file1.png file2.png --url https://sdr-api-server:3000 \
47
+ --label 'hey there' \
27
48
  --admin-policy 'druid:bk123gh4567' \
28
49
  --collection 'druid:gh456kw9876' \
29
- --source-id 'googlebooks:stanford_12345' file1.png file2.png
50
+ --source-id 'googlebooks:stanford_12345'
30
51
  ```
31
52
 
32
53
  Deposit (register + accession) a new object:
33
54
  ```
34
- sdr --service-url https://sdr-api-server:3000 deposit --label 'hey there' \
55
+ sdr deposit file1.png file2.png --url https://sdr-api-server:3000 \
56
+ --label 'hey there' \
35
57
  --admin-policy 'druid:bk123gh4567' \
36
58
  --collection 'druid:gh456kw9876' \
37
- --source-id 'googlebooks:stanford_12345' file1.png file2.png
59
+ --source-id 'googlebooks:stanford_12345'
38
60
  ```
39
61
 
40
62
  Deposit a new object, providing metadata for files:
41
63
  ```
42
- sdr --service-url https://sdr-api-server:3000 deposit --label 'hey there' \
64
+ sdr deposit image42.jp2 ocr.html --url https://sdr-api-server:3000 \
65
+ --label 'hey there' \
43
66
  --files-metadata '{"image42.jp2":{"mime_type":"image/jp2"},"ocr.html":{"use":"transcription"}}'
44
67
  --admin-policy 'druid:bk123gh4567' \
45
68
  --collection 'druid:gh456kw9876' \
46
- --source-id 'googlebooks:stanford_12345' image42.jp2 ocr.html
69
+ --source-id 'googlebooks:stanford_12345'
47
70
  ```
48
71
 
49
72
  View the object:
50
73
  ```
51
- sdr --service-url https://sdr-api-server:3000 get druid:bw581ng3176
52
- {"type":"http://cocina.sul.stanford.edu/models/document.jsonld","externalIdentifier":"druid:bw581ng3176","label":"Something something better title","version":1,"access":{"access":"stanford","copyright":"This work is copyrighted by the creator.","download":"stanford","useAndReproductionStatement":"This document is available only to the Stanford faculty, staff and student community."},"administrative":{"hasAdminPolicy":"druid:zx485kb6348"},"description":{"title":[{"value":"Something something better title"}],"contributor":[{"name":[{"value":"Hodge, Amy"}],"type":"person","role":[{"value":"Author"},{"value":"author","uri":"http://id.loc.gov/vocabulary/relators/aut","source":{"code":"marcrelator","uri":"http://id.loc.gov/vocabulary/relators/"}},{"value":"Creator"}]}],"form":[{"structuredValue":[{"value":"Text","type":"type"},{"value":"Report","type":"subtype"}],"type":"resource type","source":{"value":"Stanford self-deposit resource types"}},{"value":"reports","type":"genre","uri":"http://vocab.getty.edu/aat/300027267","source":{"code":"aat"}},{"value":"text","type":"resource type","source":{"value":"MODS resource types"}}],"note":[{"value":";alkdfjlsadkjf;l","type":"summary"},{"value":"amyhodge@stanford.edu","type":"contact","displayLabel":"Contact"}],"subject":[{"value":"lkfj","type":"topic"},{"value":";kfj","type":"topic"},{"value":"fjwelkb","type":"topic"}]},"identification":{"sourceId":"hydrus:20"},"structural":{"contains":[{"type":"http://cocina.sul.stanford.edu/models/resources/file.jsonld","externalIdentifier":"bw581ng3176_1","label":"Test file","version":1,"structural":{"contains":[{"type":"http://cocina.sul.stanford.edu/models/file.jsonld","externalIdentifier":"druid:bw581ng3176/test.txt","label":"test.txt","filename":"test.txt","size":11,"version":1,"hasMimeType":"text/plain","hasMessageDigests":[{"type":"sha1","digest":"5d39343e4bb48abd97f759828282f5ebbac56c5e"},{"type":"md5","digest":"63b8812b0c05722a9d6c51cbd2bfb54b"}],"access":{"access":"world","download":"world"},"administrative":{"sdrPreserve":true,"shelve":true}}]}}]}}
74
+ sdr get druid:bw581ng3176 --url https://sdr-api-server:3000
75
+ {"type":"https://cocina.sul.stanford.edu/models/document","externalIdentifier":"druid:bw581ng3176","label":"Something something better title","version":1,"access":{"view":"stanford","copyright":"This work is copyrighted by the creator.","download":"stanford","useAndReproductionStatement":"This document is available only to the Stanford faculty, staff and student community."},"administrative":{"hasAdminPolicy":"druid:zx485kb6348"},"description":{"title":[{"value":"Something something better title"}],"contributor":[{"name":[{"value":"Hodge, Amy"}],"type":"person","role":[{"value":"Author"},{"value":"author","uri":"http://id.loc.gov/vocabulary/relators/aut","source":{"code":"marcrelator","uri":"http://id.loc.gov/vocabulary/relators/"}},{"value":"Creator"}]}],"form":[{"structuredValue":[{"value":"Text","type":"type"},{"value":"Report","type":"subtype"}],"type":"resource type","source":{"value":"Stanford self-deposit resource types"}},{"value":"reports","type":"genre","uri":"http://vocab.getty.edu/aat/300027267","source":{"code":"aat"}},{"value":"text","type":"resource type","source":{"value":"MODS resource types"}}],"note":[{"value":";alkdfjlsadkjf;l","type":"summary"},{"value":"amyhodge@stanford.edu","type":"contact","displayLabel":"Contact"}],"subject":[{"value":"lkfj","type":"topic"},{"value":";kfj","type":"topic"},{"value":"fjwelkb","type":"topic"}]},"identification":{"sourceId":"hydrus:20"},"structural":{"contains":[{"type":"https://cocina.sul.stanford.edu/models/resources/file","externalIdentifier":"bw581ng3176_1","label":"Test file","version":1,"structural":{"contains":[{"type":"https://cocina.sul.stanford.edu/models/file","externalIdentifier":"druid:bw581ng3176/test.txt","label":"test.txt","filename":"test.txt","size":11,"version":1,"hasMimeType":"text/plain","hasMessageDigests":[{"type":"sha1","digest":"5d39343e4bb48abd97f759828282f5ebbac56c5e"},{"type":"md5","digest":"63b8812b0c05722a9d6c51cbd2bfb54b"}],"access":{"view":"world","download":"world"},"administrative":{"sdrPreserve":true,"shelve":true}}]}}]}}
53
76
  ```
54
77
 
55
- Display version of sdr-client:
78
+ Update an object:
56
79
  ```
57
- sdr version
80
+ # Change admin policy object (APO)
81
+ sdr update druid:bb408qn5061 --url https://sdr-api-server:3000 --admin-policy druid:bx911tp9024
82
+ # Change collection
83
+ sdr update druid:bb408qn5061 --url https://sdr-api-server:3000 --collection druid:pb756dt1672
84
+ # Change copyright
85
+ sdr update druid:bb408qn5061 --url https://sdr-api-server:3000 --copyright "Here is a new copyright statement"
86
+ # Change use and reproduction statement
87
+ sdr update druid:bb408qn5061 --url https://sdr-api-server:3000 --use-and-reproduction "Here are the terms of use..."
88
+ # Change license
89
+ sdr update druid:bb408qn5061 --url https://sdr-api-server:3000 --license "https://www.apache.org/licenses/LICENSE-2.0"
90
+ # Change access controls
91
+ sdr update druid:bb408qn5061 --url https://sdr-api-server:3000 --view "location-based" --download "none" --location "music" --cdl false
58
92
  ```
93
+
94
+ ## Testing
95
+
96
+ To test running sdr-client against the SDR API, which itself has dependencies on other SDR services, we tend to test against our running SDR QA environment. Make sure you are connected to VPN throughout your testing, and pass `https://sdr-api-qa.stanford.edu` as the value to the `--url` flag for the commands above.
97
+
98
+ **WARNING**: if you omit the `--url` flag, the sdr-client CLI by default operates against the production environment.
data/exe/sdr CHANGED
@@ -2,119 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  $LOAD_PATH.unshift 'lib'
5
- require 'optparse'
6
- require 'sdr_client'
7
-
8
- options = {}
9
-
10
- global = OptionParser.new do |opts|
11
- opts.on('--service-url URL', 'Connect to the host at this URL') do |url|
12
- options[:url] = url
13
- end
14
- opts.on('-h', '--help', 'Display this screen') do
15
- SdrClient::CLI.help
16
- end
17
- end
18
-
19
- global.order!
20
- command = ARGV.shift
21
-
22
- deposit_options = OptionParser.new do |opts|
23
- opts.banner = "Usage: sdr #{command} [options]"
24
- opts.on('--label LABEL', 'The object label') do |label|
25
- options[:label] = label
26
- end
27
-
28
- opts.on('--admin-policy ADMIN_POLICY', 'The druid identifier of the admin policy object') do |apo|
29
- options[:apo] = apo
30
- end
31
-
32
- opts.on('--type TYPE', 'The object type to create. ' \
33
- 'One of: "image", "book", "document", "map", "manuscript", "media", ' \
34
- '"three_dimensional", "object", "collection", or "admin_policy"') do |type|
35
- if %w[image book document map manuscript media three_dimensional object collection admin_policy].include?(type)
36
- options[:type] = "http://cocina.sul.stanford.edu/models/#{type}.jsonld"
37
- end
38
- end
39
-
40
- opts.on('--collection COLLECTION', 'The druid identifier of the collection object') do |collection|
41
- options[:collection] = collection
42
- end
43
-
44
- opts.on('--catkey CATKEY', 'The catkey for this item') do |catkey|
45
- options[:catkey] = catkey
46
- end
47
-
48
- opts.on('--source-id SOURCE_ID', 'The source id for this object') do |source_id|
49
- options[:source_id] = source_id
50
- end
51
-
52
- opts.on('--copyright COPYRIGHT', 'The copyright statement') do |copyright|
53
- options[:copyright] = copyright
54
- end
55
5
 
56
- opts.on('--use-statement STATEMENT', 'The use and reproduction statement') do |use_statement|
57
- options[:use_statement] = use_statement
58
- end
59
-
60
- opts.on('--viewing-direction DIRECTION', 'The viewing direction (if a book). ' \
61
- 'Either "left-to-right" or "right-to-left"') do |viewing_direction|
62
- options[:viewing_direction] = viewing_direction if %w[left-to-right right-to-left].include?(viewing_direction)
63
- end
64
-
65
- opts.on('--access LEVEL', 'The access level for this object. ' \
66
- 'Either "world", "stanford", "location-based", "citation-only" or "dark"') do |level|
67
- options[:access] = level if %w[world stanford location-based citation-only dark].include?(level)
68
- end
69
-
70
- opts.on('--files-metadata FILES_METADATA', 'A JSON object representing per-file metadata') do |files_metadata|
71
- options[:files_metadata] = JSON.parse(files_metadata)
72
- end
73
-
74
- opts.on('--strategy STRATEGY',
75
- 'The strategy to use for distributing files into filesets. Either "default" or "filename"') do |strategy|
76
- strategy_class = case strategy
77
- when 'filename'
78
- SdrClient::Deposit::MatchingFileGroupingStrategy
79
- when 'default'
80
- SdrClient::Deposit::SingleFileGroupingStrategy
81
- else
82
- warn "Unknown strategy #{strategy}"
83
- exit(1)
84
- end
85
- options[:grouping_strategy] = strategy_class
86
- end
87
-
88
- opts.on('-h', '--help', 'Display this screen') do
89
- puts opts
90
- exit
91
- end
92
- end
93
-
94
- SdrClient::CLI.help unless command
95
-
96
- subcommands = {
97
- 'get' => OptionParser.new,
98
- 'deposit' => deposit_options,
99
- 'register' => deposit_options,
100
- 'login' => OptionParser.new,
101
- 'version' => OptionParser.new
102
- }
103
-
104
- unless subcommands.key?(command)
105
- puts "unknown command '#{command}'"
106
- SdrClient::CLI.help
107
- end
108
-
109
- subcommands[command].order!
110
-
111
- options[:url] ||= 'https://sdr-api-prod.stanford.edu'
6
+ require 'sdr_client'
112
7
 
113
- begin
114
- SdrClient::CLI.start(command, options, ARGV)
115
- rescue StandardError => e
116
- warn "There was a problem making your request:\n\n"
117
- warn e.message
118
- puts
119
- puts subcommands[command].help
120
- end
8
+ SdrClient::CLI.start(ARGV)
@@ -1,101 +1,172 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module SdrClient
4
- # The command line interface
5
- module CLI
6
- HELP = <<~HELP
7
- DESCRIPTION:
8
- The SDR Command Line Interface is a tool to interact with the Stanford Digital Repository.
9
-
10
- SYNOPSIS:
11
- sdr [options] <command>
12
-
13
- To see help text for each command you can run:
14
-
15
- sdr [options] <command> help
3
+ require 'thor'
16
4
 
17
- OPTIONS:
18
- --service-url (string)
19
- Override the command's default URL with the given URL.
20
-
21
- -h, --help
22
- Displays this screen
5
+ module SdrClient
6
+ # The SDR command-line interface
7
+ class CLI < Thor
8
+ include Thor::Actions
9
+
10
+ # Make sure Thor commands preserve exit statuses
11
+ # @see https://github.com/rails/thor/wiki/Making-An-Executable
12
+ def self.exit_on_failure?
13
+ true
14
+ end
23
15
 
16
+ # Print out help and exit with error code if command not found
17
+ def self.handle_no_command_error(command)
18
+ puts "Command '#{command}' not found, displaying help:"
19
+ puts
20
+ puts help
21
+ exit(1)
22
+ end
24
23
 
25
- COMMANDS:
26
- get
27
- Retrieve an object from the SDR
24
+ def self.default_url
25
+ 'https://sdr-api-prod.stanford.edu'
26
+ end
28
27
 
29
- deposit
30
- Accession an object into the SDR
28
+ package_name 'sdr'
31
29
 
32
- register
33
- Create a draft object in SDR and retrieve a Druid identifier.
30
+ class_option :url, desc: 'URL of SDR API endpoint', type: :string, default: default_url
34
31
 
35
- login
36
- Will prompt for email & password and exchange it for an login token, which it saves in ~/.sdr/token
32
+ desc 'get DRUID', 'Retrieve an object from the SDR'
33
+ def get(druid)
34
+ say SdrClient::Find.run(druid, url: options[:url])
35
+ rescue SdrClient::Credentials::NoCredentialsError
36
+ say_error 'Log in first'
37
+ exit(1)
38
+ end
37
39
 
38
- version
39
- Display the sdr-client version
40
+ desc 'login', 'Prompt for email & password and create a login token (saved in ~/.sdr/token)'
41
+ def login
42
+ status = SdrClient::Login.run(
43
+ url: options[:url],
44
+ login_service: lambda do
45
+ {
46
+ email: ask('Email:'),
47
+ password: ask('Password:', echo: false)
48
+ }
49
+ end
50
+ )
51
+ return puts unless status.failure?
52
+
53
+ say_error status.failure
54
+ exit(1)
55
+ end
40
56
 
41
- HELP
57
+ desc 'version', 'Display the SDR CLI version'
58
+ def version
59
+ say SdrClient::VERSION
60
+ end
42
61
 
43
- def self.start(command, options, arguments = [])
44
- case command
45
- when 'get'
46
- puts SdrClient::Find.run(arguments.first, **options)
47
- when 'deposit', 'register'
48
- deposit(command, options, arguments)
49
- when 'login'
50
- status = SdrClient::Login.run(options)
51
- puts status.failure if status.failure?
52
- when 'version'
53
- puts SdrClient::VERSION
54
- else
55
- raise "Unknown command #{command}"
56
- end
62
+ desc 'update DRUID', 'Update an object in the SDR'
63
+ option :skip_polling, type: :boolean, default: false, aliases: '-s', desc: 'Print out job ID instead of polling for result'
64
+ option :apo, desc: 'Druid identifier of the admin policy object', aliases: '--admin-policy'
65
+ option :collection, desc: 'Druid identifier of the collection object'
66
+ option :copyright, desc: 'Copyright statement'
67
+ option :use_and_reproduction, desc: 'Use and reproduction statement'
68
+ option :license, desc: 'License URI'
69
+ option :view, enum: %w[world stanford location-based citation-only dark], desc: 'Access view level for the object'
70
+ option :download, enum: %w[world stanford location-based none], desc: 'Access download level for the object'
71
+ option :location, enum: %w[spec music ars art hoover m&m], desc: 'Access location for the object'
72
+ option :cdl, type: :boolean, default: false
73
+ def update(druid)
74
+ validate_druid!(druid)
75
+ job_id = SdrClient::Update.run(druid, **options)
76
+ poll_for_job_complete(job_id: job_id, url: options[:url]) # TODO: add an option that skips this
57
77
  rescue SdrClient::Credentials::NoCredentialsError
58
- puts 'Log in first'
78
+ say_error 'Log in first'
59
79
  exit(1)
60
80
  end
61
81
 
62
- def self.deposit(command, options, arguments)
63
- options[:files] = arguments if arguments.present?
64
- display_errors(validate_deposit_options(options))
65
- job_id = SdrClient::Deposit.run(accession: command == 'deposit', **options)
66
- poll_for_job_complete(job_id: job_id, url: options[:url]) # TODO: add an option that skips this
82
+ desc 'deposit OPTIONAL_FILES', 'Deposit (accession) an object into the SDR'
83
+ option :skip_polling, type: :boolean, default: false, aliases: '-s', desc: 'Print out job ID instead of polling for result'
84
+ option :apo, required: true, desc: 'Druid identifier of the admin policy object', aliases: '--admin-policy'
85
+ option :source_id, required: true, desc: 'Source ID for this object'
86
+ option :label, desc: 'Object label'
87
+ option :type, enum: %w[image book document map manuscript media three_dimensional object collection admin_policy], desc: 'The object type'
88
+ option :collection, desc: 'Druid identifier of the collection object'
89
+ option :catkey, desc: 'Catkey for this item'
90
+ option :copyright, desc: 'Copyright statement'
91
+ option :use_and_reproduction, desc: 'Use and reproduction statement'
92
+ option :viewing_direction, enum: %w[left-to-right right-to-left], desc: 'Viewing direction (if a book)'
93
+ option :view, enum: %w[world stanford location-based citation-only dark], desc: 'Access view level for the object'
94
+ option :files_metadata, desc: 'JSON string representing per-file metadata'
95
+ option :grouping_strategy, enum: %w[default filename], desc: 'Strategy for grouping files into filesets'
96
+ def deposit(*files)
97
+ register_or_deposit(files: files, accession: true)
67
98
  end
68
99
 
69
- def self.display_errors(errors)
70
- return if errors.empty?
100
+ desc 'register OPTIONAL_FILES', 'Create a draft object in the SDR and retrieve a Druid identifier'
101
+ option :skip_polling, type: :boolean, default: false, aliases: '-s', desc: 'Print out job ID instead of polling for result'
102
+ option :apo, required: true, desc: 'Druid identifier of the admin policy object', aliases: '--admin-policy'
103
+ option :source_id, required: true, desc: 'Source ID for this object'
104
+ option :label, desc: 'Object label'
105
+ option :type, enum: %w[image book document map manuscript media three_dimensional object collection admin_policy], desc: 'The object type'
106
+ option :collection, desc: 'Druid identifier of the collection object'
107
+ option :catkey, desc: 'Catkey for this item'
108
+ option :copyright, desc: 'Copyright statement'
109
+ option :use_and_reproduction, desc: 'Use and reproduction statement'
110
+ option :viewing_direction, enum: %w[left-to-right right-to-left], desc: 'Viewing direction (if a book)'
111
+ option :view, enum: %w[world stanford location-based citation-only dark], desc: 'Access view level for the object'
112
+ option :files_metadata, desc: 'JSON string representing per-file metadata'
113
+ option :grouping_strategy, enum: %w[default filename], desc: 'Strategy for grouping files into filesets'
114
+ def register(*files)
115
+ register_or_deposit(files: files, accession: false)
116
+ end
71
117
 
72
- raise errors.map { |k, v| "#{k} #{v}" }.join("\n")
118
+ private
119
+
120
+ def register_or_deposit(files:, accession:)
121
+ opts = munge_options(options, files)
122
+ skip_polling = opts.delete(:skip_polling)
123
+ job_id = SdrClient::Deposit.run(accession: accession, **opts)
124
+ return if skip_polling
125
+
126
+ poll_for_job_complete(job_id: job_id, url: opts[:url])
127
+ rescue SdrClient::Credentials::NoCredentialsError
128
+ say_error 'Log in first'
129
+ exit(1)
73
130
  end
74
131
 
75
- def self.validate_deposit_options(options)
76
- {}.tap do |errors|
77
- errors['admin-policy'] = 'is a required argument' unless options[:apo]
78
- errors['source-id'] = 'is a required argument' unless options[:source_id]
132
+ def munge_options(options, files)
133
+ options.to_h.symbolize_keys.tap do |opts|
134
+ opts[:files] = files if files.present?
135
+ opts[:type] = Cocina::Models::ObjectType.public_send(options[:type]) if options[:type]
136
+ opts[:files_metadata] = JSON.parse(options[:files_metadata]) if options[:files_metadata]
137
+ if options[:grouping_strategy]
138
+ opts[:grouping_strategy] = if options[:grouping_strategy] == 'filename'
139
+ SdrClient::Deposit::MatchingFileGroupingStrategy
140
+ else
141
+ SdrClient::Deposit::SingleFileGroupingStrategy
142
+ end
143
+ end
79
144
  end
80
145
  end
81
146
 
82
- def self.help
83
- puts HELP
84
- exit
147
+ def validate_druid!(druid)
148
+ return if druid.present?
149
+
150
+ say_error "Not a valid druid: #{druid.inspect}"
151
+ exit(1)
85
152
  end
86
153
 
87
- def self.poll_for_job_complete(job_id:, url:)
154
+ def poll_for_job_complete(job_id:, url:)
155
+ # the extra args to `say` prevent appending a newline
156
+ say('SDR is processing your request', nil, false)
88
157
  result = nil
89
- 1.upto(5) do |_n|
158
+ (1).upto(60) do
90
159
  result = SdrClient::BackgroundJobResults.show(url: url, job_id: job_id)
91
- break unless %w[pending processing].include? result['status']
160
+ break unless %w[pending processing].include?(result['status'])
92
161
 
93
- sleep 5
162
+ # the extra args to `say` prevent appending a newline
163
+ say('.', nil, false)
164
+ sleep 1
94
165
  end
95
166
  if result['status'] == 'complete'
96
- puts result.dig('output', 'druid')
167
+ say " success! (druid: #{result.dig('output', 'druid')})"
97
168
  else
98
- warn "Job #{job_id} did not complete\n#{result.inspect}"
169
+ say_error "Job #{job_id} did not complete\n#{result.inspect}"
99
170
  end
100
171
  end
101
172
  end
@@ -6,16 +6,16 @@ module SdrClient
6
6
  class File
7
7
  # rubocop:disable Metrics/ParameterLists
8
8
  def initialize(external_identifier:, label:, filename:,
9
- access: 'dark', download: 'none', preserve: true, shelve: true,
9
+ view: 'dark', download: 'none', preserve: true, shelve: true,
10
10
  publish: true, mime_type: nil, md5: nil, sha1: nil,
11
11
  use: nil)
12
12
  @external_identifier = external_identifier
13
13
  @label = label
14
14
  @filename = filename
15
- @access = access
15
+ @view = view
16
16
  @download = download
17
17
  @preserve = preserve
18
- @shelve = access == 'dark' ? false : shelve
18
+ @shelve = view == 'dark' ? false : shelve
19
19
  @publish = publish
20
20
  @mime_type = mime_type
21
21
  @md5 = md5
@@ -26,12 +26,12 @@ module SdrClient
26
26
 
27
27
  def as_json
28
28
  {
29
- type: 'http://cocina.sul.stanford.edu/models/file.jsonld',
29
+ type: Cocina::Models::ObjectType.file,
30
30
  label: @label,
31
31
  filename: @filename,
32
32
  externalIdentifier: @external_identifier,
33
33
  access: {
34
- access: @access,
34
+ view: @view,
35
35
  download: @download
36
36
  },
37
37
  administrative: {
@@ -7,7 +7,7 @@ module SdrClient
7
7
  # @param [Array<SdrClient::Deposit::Files>] files the files that are part of this strategy
8
8
  # @return [String] The URI that represents the type of file_set
9
9
  def self.run(files: [])
10
- Cocina::Models::Vocab::Resources.file
10
+ Cocina::Models::FileSetType.file
11
11
  end
12
12
  end
13
13
  end
@@ -7,7 +7,7 @@ module SdrClient
7
7
  # @param [Array<SdrClient::Deposit::Files>] files the files that are part of this strategy
8
8
  # @return [String] The URI that represents the type of file_set
9
9
  def self.run(files: [])
10
- Cocina::Models::Vocab::Resources.image
10
+ Cocina::Models::FileSetType.image
11
11
  end
12
12
  end
13
13
  end
@@ -7,15 +7,15 @@ module SdrClient
7
7
  # @param [String] label the required object label
8
8
  # @param [Time|nil] embargo_release_date when the item should be released from embargo or nil if no embargo
9
9
  # @param [String] embargo_access access after embargo has expired if embargoed
10
- # @param [String] type (http://cocina.sul.stanford.edu/models/object.jsonld) the required object type.
10
+ # @param [String] type (https://cocina.sul.stanford.edu/models/object) the required object type.
11
11
  # @param [Array<FileSet>] file_sets the file sets to attach.
12
12
  # @param [Hash<String, Hash<String, String>>] files_metadata file name, hash of additional file metadata
13
13
  # Additional metadata includes access, preserve, shelve, publish, md5, sha1
14
14
  # rubocop:disable Metrics/ParameterLists
15
15
  def initialize(label: nil,
16
- access: 'dark',
16
+ view: 'dark',
17
17
  download: 'none',
18
- use_statement: nil,
18
+ use_and_reproduction: nil,
19
19
  copyright: nil,
20
20
  apo:,
21
21
  collection: nil,
@@ -24,7 +24,7 @@ module SdrClient
24
24
  embargo_release_date: nil,
25
25
  embargo_access: 'world',
26
26
  embargo_download: 'world',
27
- type: 'http://cocina.sul.stanford.edu/models/object.jsonld',
27
+ type: Cocina::Models::ObjectType.object,
28
28
  viewing_direction: nil,
29
29
  file_sets: [],
30
30
  files_metadata: {})
@@ -36,9 +36,9 @@ module SdrClient
36
36
  @embargo_release_date = embargo_release_date
37
37
  @embargo_access = embargo_access
38
38
  @embargo_download = embargo_download
39
- @access = access
39
+ @view = view
40
40
  @download = download
41
- @use_statement = use_statement
41
+ @use_and_reproduction = use_and_reproduction
42
42
  @copyright = copyright
43
43
  @apo = apo
44
44
  @file_sets = file_sets
@@ -62,7 +62,7 @@ module SdrClient
62
62
  # @return [Request] a clone of this request with the file_sets added
63
63
  def with_file_sets(file_sets)
64
64
  Request.new(label: label,
65
- access: access,
65
+ view: view,
66
66
  download: download,
67
67
  apo: apo,
68
68
  collection: collection,
@@ -73,7 +73,7 @@ module SdrClient
73
73
  embargo_access: embargo_access,
74
74
  embargo_download: embargo_download,
75
75
  type: type,
76
- use_statement: use_statement,
76
+ use_and_reproduction: use_and_reproduction,
77
77
  viewing_direction: viewing_direction,
78
78
  file_sets: file_sets,
79
79
  files_metadata: files_metadata)
@@ -83,7 +83,7 @@ module SdrClient
83
83
  # @return [Hash] the metadata for the file
84
84
  def for(filename)
85
85
  metadata = files_metadata.fetch(filename, {}).with_indifferent_access
86
- metadata[:access] = access unless metadata.key?(:access)
86
+ metadata[:view] = view unless metadata.key?(:view)
87
87
  metadata[:download] = download unless metadata.key?(:download)
88
88
  metadata
89
89
  end
@@ -92,9 +92,9 @@ module SdrClient
92
92
 
93
93
  private
94
94
 
95
- attr_reader :access, :label, :file_sets, :source_id, :catkey, :apo, :collection,
95
+ attr_reader :view, :label, :file_sets, :source_id, :catkey, :apo, :collection,
96
96
  :files_metadata, :embargo_release_date, :embargo_access, :embargo_download,
97
- :viewing_direction, :use_statement, :copyright, :download
97
+ :viewing_direction, :use_and_reproduction, :copyright, :download
98
98
 
99
99
  def administrative
100
100
  {
@@ -118,16 +118,16 @@ module SdrClient
118
118
 
119
119
  def access_struct
120
120
  {
121
- access: access,
121
+ view: view,
122
122
  download: download
123
123
  }.tap do |json|
124
- json[:useAndReproductionStatement] = use_statement if use_statement
124
+ json[:useAndReproductionStatement] = use_and_reproduction if use_and_reproduction
125
125
  json[:copyright] = copyright if copyright
126
126
 
127
127
  if embargo_release_date
128
128
  json[:embargo] = {
129
129
  releaseDate: embargo_release_date.strftime('%FT%T%:z'),
130
- access: embargo_access,
130
+ view: embargo_access,
131
131
  download: embargo_download
132
132
  }
133
133
  end
@@ -35,7 +35,7 @@ module SdrClient
35
35
 
36
36
  def metadata_request
37
37
  json = metadata.to_json
38
- logger.debug("Starting upload metadata: #{json}")
38
+ logger.debug("Starting update metadata: #{json}")
39
39
 
40
40
  connection.put(path(metadata), json,
41
41
  'Content-Type' => 'application/json',
@@ -5,16 +5,16 @@ require 'logger'
5
5
  module SdrClient
6
6
  # The namespace for the "deposit" command
7
7
  module Deposit
8
- BOOK_TYPE = 'http://cocina.sul.stanford.edu/models/book.jsonld'
8
+ BOOK_TYPE = Cocina::Models::ObjectType.book
9
9
  # rubocop:disable Metrics/ParameterLists
10
10
  # rubocop:disable Metrics/MethodLength
11
11
  # @return [String] job id for the background job result
12
12
  def self.run(label: nil,
13
13
  type: BOOK_TYPE,
14
14
  viewing_direction: nil,
15
- access: 'dark',
15
+ view: 'dark',
16
16
  download: 'none',
17
- use_statement: nil,
17
+ use_and_reproduction: nil,
18
18
  copyright: nil,
19
19
  apo:,
20
20
  collection: nil,
@@ -33,10 +33,10 @@ module SdrClient
33
33
  augmented_metadata = FileMetadataBuilder.build(files: files, files_metadata: files_metadata)
34
34
  metadata = Request.new(label: label,
35
35
  type: type,
36
- access: access,
36
+ view: view,
37
37
  download: download,
38
38
  apo: apo,
39
- use_statement: use_statement,
39
+ use_and_reproduction: use_and_reproduction,
40
40
  copyright: copyright,
41
41
  collection: collection,
42
42
  source_id: source_id,
@@ -6,12 +6,15 @@ module SdrClient
6
6
  # The namespace for the "get" command
7
7
  module Find
8
8
  DRO_PATH = '/v1/resources/%<id>s'
9
- # @return [String] job id for the background job result
9
+ # @return [String] JSON for the given Cocina object or an error
10
10
  def self.run(druid, url:, logger: Logger.new($stdout))
11
11
  connection = Connection.new(url: url)
12
12
  path = format(DRO_PATH, id: druid)
13
13
  logger.info("Retrieving metadata from: #{path}")
14
14
  response = connection.get(path)
15
+ unless response.success?
16
+ logger.error("There was an HTTP #{response.status} error making the request: #{response.body}")
17
+ end
15
18
  response.body
16
19
  end
17
20
  end
@@ -8,7 +8,9 @@ module SdrClient
8
8
 
9
9
  # @return [Result] the status of the call
10
10
  def self.run(url:, login_service: LoginPrompt, credential_store: Credentials)
11
- request_json = JSON.generate(login_service.run)
11
+ request_json = JSON.generate(
12
+ login_service.respond_to?(:run) ? login_service.run : login_service.call
13
+ )
12
14
  response = Faraday.post(url + LOGIN_PATH, request_json, 'Content-Type' => 'application/json')
13
15
  case response.status
14
16
  when 200
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ # The namespace for the "update" command
5
+ class Update
6
+ # @return [String] job id for the background job result
7
+ def self.run(druid, **options)
8
+ new(druid, **options).run
9
+ end
10
+
11
+ def initialize(druid, **options)
12
+ @druid = druid
13
+ @url = options.fetch(:url)
14
+ @options = options
15
+ end
16
+
17
+ # @return [String] job id for the background job result
18
+ def run
19
+ SdrClient::Deposit::UpdateResource.run(
20
+ metadata: updated_cocina_item,
21
+ logger: options[:logger] || Logger.new($stdout),
22
+ connection: SdrClient::Connection.new(url: url)
23
+ )
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :druid, :logger, :options, :url
29
+
30
+ def updated_cocina_item
31
+ @updated_cocina_item ||=
32
+ original_cocina_item.then { |cocina_item| update_apo(cocina_item) }
33
+ .then { |cocina_item| update_collection(cocina_item) }
34
+ .then { |cocina_item| update_copyright(cocina_item) }
35
+ .then { |cocina_item| update_use_and_reproduction(cocina_item) }
36
+ .then { |cocina_item| update_license(cocina_item) }
37
+ .then { |cocina_item| update_access(cocina_item) }
38
+ end
39
+
40
+ def original_cocina_item
41
+ Cocina::Models.build(
42
+ JSON.parse(
43
+ SdrClient::Find.run(druid, url: url)
44
+ )
45
+ )
46
+ end
47
+
48
+ # Update the APO of a Cocina item if the options specify a new one, else return the original
49
+ def update_apo(cocina_item)
50
+ return cocina_item unless options[:apo]
51
+
52
+ cocina_item.new(
53
+ administrative: cocina_item.administrative.new(
54
+ hasAdminPolicy: options[:apo]
55
+ )
56
+ )
57
+ end
58
+
59
+ # Update the collection of a Cocina item if the options specify a new one, else return the original
60
+ def update_collection(cocina_item)
61
+ return cocina_item unless options[:collection]
62
+
63
+ cocina_item.new(
64
+ structural: cocina_item.structural.new(
65
+ isMemberOf: Array(options[:collection])
66
+ )
67
+ )
68
+ end
69
+
70
+ # Update the copyright of a Cocina item if the options specify a new one, else return the original
71
+ def update_copyright(cocina_item)
72
+ return cocina_item unless options[:copyright]
73
+
74
+ cocina_item.new(
75
+ access: cocina_item.access.new(
76
+ copyright: options[:copyright]
77
+ )
78
+ )
79
+ end
80
+
81
+ # Update the use and reproduction statement of a Cocina item if the options specify a new one, else return the original
82
+ def update_use_and_reproduction(cocina_item)
83
+ return cocina_item unless options[:use_and_reproduction]
84
+
85
+ cocina_item.new(
86
+ access: cocina_item.access.new(
87
+ useAndReproductionStatement: options[:use_and_reproduction]
88
+ )
89
+ )
90
+ end
91
+
92
+ # Update the license of a Cocina item if the options specify a new one, else return the original
93
+ def update_license(cocina_item)
94
+ return cocina_item unless options[:license]
95
+
96
+ cocina_item.new(
97
+ access: cocina_item.access.new(
98
+ license: options[:license]
99
+ )
100
+ )
101
+ end
102
+
103
+ # rubocop:disable Style/DoubleNegation
104
+ # Update the access of a Cocina item if the options specify a new one, else return the original
105
+ def update_access(cocina_item)
106
+ return cocina_item unless options[:view] || options[:download] || options[:location] || options[:cdl]
107
+
108
+ cocina_item.new(
109
+ access: cocina_item.access.new(
110
+ view: options[:view],
111
+ download: options[:download],
112
+ location: options[:location],
113
+ controlledDigitalLending: !!options[:cdl]
114
+ ),
115
+ structural: cocina_item.structural.new(
116
+ contains: cocina_item.structural.contains.map do |file_set|
117
+ file_set.new(
118
+ structural: file_set.structural.new(
119
+ contains: file_set.structural.contains.map do |file|
120
+ file.new(
121
+ access: file.access.new(
122
+ view: options[:view],
123
+ download: options[:download],
124
+ location: options[:location],
125
+ controlledDigitalLending: !!options[:cdl]
126
+ ),
127
+ administrative: options[:view] == 'dark' ?
128
+ { publish: false, shelve: false, sdrPreserve: file.administrative.sdrPreserve } :
129
+ file.administrative
130
+ )
131
+ end
132
+ )
133
+ )
134
+ end
135
+ )
136
+ )
137
+ end
138
+ # rubocop:enable Style/DoubleNegation
139
+ end
140
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SdrClient
4
- VERSION = '0.69.0'
4
+ VERSION = '0.72.0'
5
5
  end
data/lib/sdr_client.rb CHANGED
@@ -10,6 +10,7 @@ require 'cocina/models'
10
10
 
11
11
  require 'sdr_client/version'
12
12
  require 'sdr_client/deposit'
13
+ require 'sdr_client/update'
13
14
  require 'sdr_client/credentials'
14
15
  require 'sdr_client/find'
15
16
  require 'sdr_client/login'
data/sdr-client.gemspec CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = ['lib']
29
29
 
30
30
  spec.add_dependency 'activesupport'
31
- spec.add_dependency 'cocina-models', '~> 0.67.0'
31
+ spec.add_dependency 'cocina-models', '~> 0.69.0'
32
32
  spec.add_dependency 'dry-monads'
33
33
  spec.add_dependency 'faraday', '>= 0.16'
34
34
 
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency 'rubocop-rake'
40
40
  spec.add_development_dependency 'rubocop-rspec', '~> 2.1'
41
41
  spec.add_development_dependency 'simplecov'
42
+ spec.add_development_dependency 'thor'
42
43
  spec.add_development_dependency 'webmock', '~> 3.7'
43
44
  spec.metadata['rubygems_mfa_required'] = 'true'
44
45
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sdr-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.69.0
4
+ version: 0.72.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-03 00:00:00.000000000 Z
11
+ date: 2022-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.67.0
33
+ version: 0.69.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.67.0
40
+ version: 0.69.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: dry-monads
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: thor
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: webmock
169
183
  requirement: !ruby/object:Gem::Requirement
@@ -232,6 +246,7 @@ files:
232
246
  - lib/sdr_client/find.rb
233
247
  - lib/sdr_client/login.rb
234
248
  - lib/sdr_client/login_prompt.rb
249
+ - lib/sdr_client/update.rb
235
250
  - lib/sdr_client/version.rb
236
251
  - sdr-client.gemspec
237
252
  homepage: https://github.com/sul-dlss/sdr-client
@@ -241,7 +256,7 @@ metadata:
241
256
  source_code_uri: https://github.com/sul-dlss/sdr-client
242
257
  changelog_uri: https://github.com/sul-dlss/sdr-client/releases
243
258
  rubygems_mfa_required: 'true'
244
- post_install_message:
259
+ post_install_message:
245
260
  rdoc_options: []
246
261
  require_paths:
247
262
  - lib
@@ -257,7 +272,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
257
272
  version: '0'
258
273
  requirements: []
259
274
  rubygems_version: 3.2.32
260
- signing_key:
275
+ signing_key:
261
276
  specification_version: 4
262
277
  summary: The CLI for https://github.com/sul-dlss/sdr-api
263
278
  test_files: []