neocities 0.0.18 → 0.0.19

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: 322b624aea971dd7a650eac8a9b6011e0f3d13ff8e4a5bb2f1a423b87bb446ac
4
- data.tar.gz: c28f4d3296f36bb8a556fa210cc79a19a765e511fbf3b45111f93738b26014d4
3
+ metadata.gz: c5baf997b1c58eaf0d7b2c7487623ab49966d72f0a637b0bbf32111a0608ad12
4
+ data.tar.gz: 16b8cf7786e0cf714f41f00e49bb97038ffaf1de1c72ba656e0223b9a3c9aa15
5
5
  SHA512:
6
- metadata.gz: ec195af25579bd74b8763ec1bc2d997ede505157fb2e6da9e27bfc4854a03526c57d8d05c593e9a10bfa8c0597649f75be03962d338933cbf763c9c75be9e110
7
- data.tar.gz: 45ea7e5ff1bc0addafe30ca5bb9a8e3b23dabd168b01b0169ab254895d8041bb098687cfb982c5161d2d5a9d4ed2634937064c87b1f3b0bcb76b8ea1d944d031
6
+ metadata.gz: b1459d3bff5633233a3968a4fab5033df7b8048435786e5c2642aa5fe4123061ed69f957e186cff1c0f9ba45b0249e188c21a96a56b80df87bacd1cd3532727c
7
+ data.tar.gz: 64eeed0bbf5fe5381d2eb8d9641cd00832a4be0369e32ad3da4a4ab9544976c4f8574c40274a6a3f64b1146259939fd21e71533628143c0562241735e0bce112
data/Gemfile CHANGED
@@ -1,2 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
+
4
+ # cursor whirlies (for loading)
5
+ gem 'whirly'
6
+ gem 'paint'
data/lib/neocities/cli.rb CHANGED
@@ -3,11 +3,14 @@ require 'pastel'
3
3
  require 'tty/table'
4
4
  require 'tty/prompt'
5
5
  require 'fileutils'
6
+ require 'json' # for reading configs
7
+ require 'whirly' # for loader spinner
8
+
6
9
  require File.join(File.dirname(__FILE__), 'client')
7
10
 
8
11
  module Neocities
9
12
  class CLI
10
- SUBCOMMANDS = %w{upload delete list info push logout pizza}
13
+ SUBCOMMANDS = %w{upload delete list info push logout pizza pull}
11
14
  HELP_SUBCOMMANDS = ['-h', '--help', 'help']
12
15
  PENELOPE_MOUTHS = %w{^ o ~ - v U}
13
16
  PENELOPE_EYES = %w{o ~ O}
@@ -19,7 +22,7 @@ module Neocities
19
22
  @subargs = @argv[1..@argv.length]
20
23
  @prompt = TTY::Prompt.new
21
24
  @api_key = ENV['NEOCITIES_API_KEY'] || nil
22
- @app_config_path = File.join self.class.app_config_path('neocities'), 'config'
25
+ @app_config_path = File.join self.class.app_config_path('neocities'), 'config.json' # added json extension
23
26
  end
24
27
 
25
28
  def display_response(resp)
@@ -42,18 +45,28 @@ module Neocities
42
45
  exit
43
46
  end
44
47
 
45
- display_help_and_exit if @subcmd.nil? || @argv.include?(HELP_SUBCOMMANDS) || !SUBCOMMANDS.include?(@subcmd)
46
- send "display_#{@subcmd}_help_and_exit" if @subargs.empty?
48
+ if HELP_SUBCOMMANDS.include?(@subcmd) && SUBCOMMANDS.include?(@subargs[0])
49
+ send "display_#{@subargs[0]}_help_and_exit"
50
+ elsif @subcmd.nil? || !SUBCOMMANDS.include?(@subcmd)
51
+ display_help_and_exit
52
+ elsif @subargs.join("").match(HELP_SUBCOMMANDS.join('|')) && @subcmd != "info"
53
+ send "display_#{@subcmd}_help_and_exit"
54
+ end
47
55
 
48
56
  if !@api_key
49
57
  begin
50
- @api_key = File.read @app_config_path
51
- # Remove any trailing whitespace causing HTTP requests to fail
52
- @api_key = @api_key.strip
58
+ file = File.read @app_config_path
59
+ data = JSON.load file
60
+
61
+ if data
62
+ @api_key = data["API_KEY"].strip # Remove any trailing whitespace causing HTTP requests to fail
63
+ @sitename = data["SITENAME"] # Store the sitename to be able to reference it later
64
+ @last_pull = data["LAST_PULL"] # Store the last time a pull was performed so that we only fetch from updated files
65
+ end
53
66
  rescue Errno::ENOENT
54
67
  @api_key = nil
55
68
  end
56
- end
69
+ end
57
70
 
58
71
  if @api_key.nil?
59
72
  puts "Please login to get your API key:"
@@ -67,8 +80,14 @@ module Neocities
67
80
 
68
81
  resp = @client.key
69
82
  if resp[:api_key]
83
+ conf = {
84
+ "API_KEY": resp[:api_key],
85
+ "SITENAME": @sitename,
86
+ }
87
+
70
88
  FileUtils.mkdir_p Pathname(@app_config_path).dirname
71
- File.write @app_config_path, resp[:api_key]
89
+ File.write @app_config_path, conf.to_json
90
+
72
91
  puts "The api key for #{@pastel.bold @sitename} has been stored in #{@pastel.bold @app_config_path}."
73
92
  else
74
93
  display_response resp
@@ -82,6 +101,7 @@ module Neocities
82
101
  end
83
102
 
84
103
  def delete
104
+ display_delete_help_and_exit if @subargs.empty?
85
105
  @subargs.each do |file|
86
106
  puts @pastel.bold("Deleting #{file} ...")
87
107
  resp = @client.delete file
@@ -92,16 +112,18 @@ module Neocities
92
112
 
93
113
  def logout
94
114
  confirmed = false
95
- loop {
115
+ loop do
96
116
  case @subargs[0]
97
117
  when '-y' then @subargs.shift; confirmed = true
98
- when /^-/ then puts(@pastel.red.bold("Unknown option: #{@subargs[0].inspect}")); display_logout_help_and_exit
118
+ when /^-/ then puts(@pastel.red.bold("Unknown option: #{@subargs[0].inspect}")); break
99
119
  else break
100
120
  end
101
- }
121
+ end
102
122
  if confirmed
103
123
  FileUtils.rm @app_config_path
104
124
  puts @pastel.bold("Your api key has been removed.")
125
+ else
126
+ display_logout_help_and_exit
105
127
  end
106
128
  end
107
129
 
@@ -115,7 +137,7 @@ module Neocities
115
137
 
116
138
  out = []
117
139
 
118
- resp[:info].each do |k,v|
140
+ resp[:info].each do |k, v|
119
141
  v = Time.parse(v).localtime if v && (k == :created_at || k == :last_updated)
120
142
  out.push [@pastel.bold(k), v]
121
143
  end
@@ -125,6 +147,7 @@ module Neocities
125
147
  end
126
148
 
127
149
  def list
150
+ display_list_help_and_exit if @subargs.empty?
128
151
  if @subargs.delete('-d') == '-d'
129
152
  @detail = true
130
153
  end
@@ -161,11 +184,12 @@ module Neocities
161
184
  end
162
185
 
163
186
  def push
187
+ display_push_help_and_exit if @subargs.empty?
164
188
  @no_gitignore = false
165
189
  @excluded_files = []
166
190
  @dry_run = false
167
191
  @prune = false
168
- loop {
192
+ loop do
169
193
  case @subargs[0]
170
194
  when '--no-gitignore' then @subargs.shift; @no_gitignore = true
171
195
  when '-e' then @subargs.shift; @excluded_files.push(@subargs.shift)
@@ -174,7 +198,7 @@ module Neocities
174
198
  when /^-/ then puts(@pastel.red.bold("Unknown option: #{@subargs[0].inspect}")); display_push_help_and_exit
175
199
  else break
176
200
  end
177
- }
201
+ end
178
202
 
179
203
  if @dry_run
180
204
  puts @pastel.green.bold("Doing a dry run, not actually pushing anything")
@@ -237,13 +261,11 @@ module Neocities
237
261
  end
238
262
  end
239
263
 
240
- paths.select! {|p| !@excluded_files.include?(p)}
264
+ paths.select! { |p| !@excluded_files.include?(p) }
241
265
 
242
- paths.select! {|p|
243
- !@excluded_files.include?(Pathname.new(p).dirname.to_s)
244
- }
266
+ paths.select! { |p| !@excluded_files.include?(Pathname.new(p).dirname.to_s) }
245
267
 
246
- paths.collect! {|path| Pathname path}
268
+ paths.collect! { |path| Pathname path }
247
269
 
248
270
  paths.each do |path|
249
271
  next if path.directory?
@@ -266,13 +288,13 @@ module Neocities
266
288
  display_upload_help_and_exit if @subargs.empty?
267
289
  @dir = ''
268
290
 
269
- loop {
291
+ loop do
270
292
  case @subargs[0]
271
293
  when '-d' then @subargs.shift; @dir = @subargs.shift
272
294
  when /^-/ then puts(@pastel.red.bold("Unknown option: #{@subargs[0].inspect}")); display_upload_help_and_exit
273
295
  else break
274
296
  end
275
- }
297
+ end
276
298
 
277
299
  @subargs.each do |path|
278
300
  path = Pathname path
@@ -295,6 +317,39 @@ module Neocities
295
317
  end
296
318
  end
297
319
 
320
+ def pull
321
+ begin
322
+ quiet = !(['--log', '-l'].include? @subargs[0])
323
+
324
+ file = File.read @app_config_path
325
+ data = JSON.load file
326
+
327
+ last_pull_time = data["LAST_PULL"] ? data["LAST_PULL"]["time"] : nil
328
+ last_pull_loc = data["LAST_PULL"] ? data["LAST_PULL"]["loc"] : nil
329
+
330
+ Whirly.start spinner: ["😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿", "😾"], status: "Retrieving files for #{@pastel.bold @sitename}" if quiet
331
+ resp = @client.pull @sitename, last_pull_time, last_pull_loc, quiet
332
+
333
+ # write last pull data to file (not necessarily the best way to do this, but better than cloning every time)
334
+ data["LAST_PULL"] = {
335
+ "time": Time.now,
336
+ "loc": Dir.pwd
337
+ }
338
+
339
+ File.write @app_config_path, data.to_json
340
+ rescue StandardError => ex
341
+ Whirly.stop if quiet
342
+ puts @pastel.red.bold "\nA fatal error occurred :-("
343
+ puts @pastel.red ex
344
+ ensure
345
+ exit
346
+ end
347
+ end
348
+
349
+ def pizza
350
+ display_pizza_help_and_exit
351
+ end
352
+
298
353
  def display_pizza_help_and_exit
299
354
  puts "Sorry, we're fresh out of dough today. Try again tomorrow."
300
355
  exit
@@ -348,6 +403,16 @@ HERE
348
403
 
349
404
  #{@pastel.green '$ neocities upload -d images img.jpg'} Upload img.jpg to the 'images' directory on your site
350
405
 
406
+ HERE
407
+ exit
408
+ end
409
+
410
+ def display_pull_help_and_exit
411
+ display_banner
412
+
413
+ puts <<HERE
414
+ #{@pastel.magenta.bold 'pull'} - Get the most recent version of files from your site
415
+
351
416
  HERE
352
417
  exit
353
418
  end
@@ -367,7 +432,7 @@ HERE
367
432
  #{@pastel.green '$ neocities push --no-gitignore .'} Don't use .gitignore to exclude files
368
433
 
369
434
  #{@pastel.green '$ neocities push --dry-run .'} Just show what would be uploaded
370
-
435
+
371
436
  #{@pastel.green '$ neocities push --prune .'} Delete site files not in dir (be careful!)
372
437
 
373
438
  HERE
@@ -423,6 +488,7 @@ HERE
423
488
  info Information and stats for your site
424
489
  logout Remove the site api key from the config
425
490
  version Unceremoniously display version and self destruct
491
+ pull Get the most recent version of files from your site
426
492
  pizza Order a free pizza
427
493
 
428
494
  HERE
@@ -468,4 +534,4 @@ HERE
468
534
  end
469
535
  end
470
536
  end
471
- end
537
+ end
@@ -8,6 +8,10 @@ require 'pathname'
8
8
  require 'uri'
9
9
  require 'digest'
10
10
  require 'httpclient'
11
+ require 'pastel'
12
+ require 'date'
13
+
14
+ require 'whirly'
11
15
 
12
16
  module Neocities
13
17
  class Client
@@ -17,6 +21,7 @@ module Neocities
17
21
  @uri = URI.parse API_URI
18
22
  @http = HTTPClient.new force_basic_auth: true
19
23
  @opts = opts
24
+ @pastel = Pastel.new eachline: "\n"
20
25
 
21
26
  unless opts[:api_key] || (opts[:sitename] && opts[:password])
22
27
  raise ArgumentError, 'client requires a login (sitename/password) or an api_key'
@@ -27,13 +32,91 @@ module Neocities
27
32
  else
28
33
  @http.set_auth API_URI, opts[:sitename], opts[:password]
29
34
  end
30
-
31
35
  end
32
36
 
33
37
  def list(path=nil)
34
38
  get 'list', :path => path
35
39
  end
36
40
 
41
+ def pull(sitename, last_pull_time=nil, last_pull_loc=nil, quiet=true)
42
+ site_info = get 'info', sitename: sitename
43
+
44
+ if site_info[:result] == 'error'
45
+ raise ArgumentError, site_info[:message]
46
+ end
47
+
48
+ # handle custom domains for supporter accounts
49
+ if site_info[:info][:domain] && site_info[:info][:domain] != ""
50
+ domain = "https://#{site_info[:info][:domain]}/"
51
+ else
52
+ domain = "https://#{sitename}.neocities.org/"
53
+ end
54
+
55
+ # start stats
56
+ success_loaded = 0
57
+ start_time = Time.now
58
+ curr_dir = Dir.pwd
59
+
60
+ # get list of files
61
+ resp = get 'list'
62
+
63
+ if resp[:result] == 'error'
64
+ raise ArgumentError, resp[:message]
65
+ end
66
+
67
+ # fetch each file
68
+ resp[:files].each do |file|
69
+ if !file[:is_directory]
70
+ print @pastel.bold("Loading #{file[:path]} ... ") if !quiet
71
+
72
+ if
73
+ last_pull_time && \
74
+ last_pull_loc && \
75
+ Time.parse(file[:updated_at]) <= Time.parse(last_pull_time) && \
76
+ last_pull_loc == curr_dir && \
77
+ File.exist?(file[:path]) # case when user deletes file
78
+ # case when file hasn't been updated since last
79
+ print "#{@pastel.yellow.bold "NO NEW UPDATES"}\n" if !quiet
80
+ next
81
+ end
82
+
83
+ pathtotry = domain + file[:path]
84
+ fileconts = @http.get pathtotry
85
+
86
+ # follow redirects
87
+ while fileconts.status == 301
88
+ new_path = fileconts.header['location'][0]
89
+ print "\n#{@pastel.red "Fetch from #{pathtotry} failed."}\nTrying #{new_path} instead..." if !quiet
90
+
91
+ pathtotry = new_path
92
+ fileconts = @http.get pathtotry
93
+ end
94
+
95
+ if fileconts.ok?
96
+ print "#{@pastel.green.bold 'SUCCESS'}\n" if !quiet
97
+ success_loaded += 1
98
+
99
+ File.open("#{file[:path]}", "w") do |f|
100
+ f.write(fileconts.body)
101
+ end
102
+ else
103
+ print "#{@pastel.red.bold 'FAIL'}\n" if !quiet
104
+ end
105
+ else
106
+ FileUtils.mkdir_p "#{file[:path]}"
107
+ end
108
+ end
109
+
110
+ # calculate time command took
111
+ total_time = Time.now - start_time
112
+
113
+ # stop the spinner, if there is one
114
+ Whirly.stop if quiet
115
+
116
+ # display stats
117
+ puts @pastel.green "\nSuccessfully fetched #{success_loaded} files in #{total_time} seconds"
118
+ end
119
+
37
120
  def key
38
121
  get 'key'
39
122
  end
@@ -86,6 +169,7 @@ module Neocities
86
169
  uri = @uri+path
87
170
  uri.query = URI.encode_www_form params
88
171
  resp = @http.get uri
172
+
89
173
  JSON.parse resp.body, symbolize_names: true
90
174
  end
91
175
 
@@ -95,4 +179,4 @@ module Neocities
95
179
  JSON.parse resp.body, symbolize_names: true
96
180
  end
97
181
  end
98
- end
182
+ end
@@ -1,3 +1,3 @@
1
1
  module Neocities
2
- VERSION = '0.0.18'
2
+ VERSION = '0.0.19'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neocities
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.18
4
+ version: 0.0.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Drake
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-07 00:00:00.000000000 Z
11
+ date: 2024-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-table
@@ -148,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
148
  - !ruby/object:Gem::Version
149
149
  version: '0'
150
150
  requirements: []
151
- rubygems_version: 3.3.26
151
+ rubygems_version: 3.5.13
152
152
  signing_key:
153
153
  specification_version: 4
154
154
  summary: Neocities.org CLI and API client