neocities 0.0.1 → 0.0.20

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 66e57f453b9ea7929315daf45943965369a7d9ee
4
- data.tar.gz: 1329416d9d7ffde417ba98ba67c0e050fff11fec
2
+ SHA256:
3
+ metadata.gz: 5031613f2ead49cb8b0d897ba928dca50121afbc65b0f624afd89038c6f97c3e
4
+ data.tar.gz: 3f6a7813f75c2d0d667700258a5f2a9a60b74176b69cee5c1d26d95892970a91
5
5
  SHA512:
6
- metadata.gz: c3510f9cb980ffbb3c163ff2698421f2a97318b3dbcc07fb50db7a3774dd5d8b4192aac0db600684852fe234dc0335551f34c1f79d8b74104f486ccbec0f0998
7
- data.tar.gz: 67772ce43a82756e3a9ebf91929bf41e9a154885d5f4ccf4bd20a7821fbdca0aa03acd2d0d061a74bb2416ad9261a9f9c86cbedb5f67bcfcdd863ecd4d133afc
6
+ metadata.gz: b1ae46e13488bbdd244da5e1f3eb005002f6a2e9a687c9682fc2055ccaf054c76de1f9386ed578b7462745acf1c53d7fac81957b41b474b2f0422ab5823d3f09
7
+ data.tar.gz: a72a64b9bc2ea15a3c7c6ad01f8f405802c5476e96b8975081d3e7dbef531543838eb029fcba108c7cc7a96e89725fac8855500c0c8ffd8922235f05a86fb9b9
data/.gitignore CHANGED
@@ -1,17 +1,3 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
1
  Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
2
+ test.rb
3
+ *.gem
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in neocities-ruby.gemspec
4
3
  gemspec
data/README.md CHANGED
@@ -1,29 +1,27 @@
1
- # Neocities
1
+ # The Neocities Gem
2
2
 
3
- TODO: Write a gem description
3
+ A CLI and library for using the Neocities API. Makes it easy to quickly upload, push, delete, and list your Neocities site.
4
4
 
5
5
  ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
7
+ ### Mac OSX
8
8
 
9
- gem 'neocities'
9
+ If you're using OSX (or already have ruby installed):
10
10
 
11
- And then execute:
11
+ gem install neocities
12
12
 
13
- $ bundle
13
+ ### Ubuntu/Debian
14
14
 
15
- Or install it yourself as:
15
+ sudo apt-get install ruby
16
16
 
17
- $ gem install neocities
17
+ ### Windows
18
18
 
19
- ## Usage
19
+ Install ruby with [RubyInstaller](https://rubyinstaller.org/). Then you can install the gem with the above command.
20
20
 
21
- TODO: Write usage instructions here
21
+ ### Running
22
22
 
23
- ## Contributing
23
+ After that, you are all set! Run `neocities` in a command line to see the options and get started.
24
24
 
25
- 1. Fork it ( http://github.com/<my-github-username>/neocities/fork )
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
25
+ ## Neocities::Client
26
+
27
+ This gem also ships with Neocities::Client, which you can use to write code that interfaces with the Neocities API.
data/bin/neocities ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'neocities')
3
+ cli = Neocities::CLI.new ARGV
4
+ cli.run
data/ext/mkrf_conf.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'rubygems/command'
3
+ require 'rubygems/dependency_installer'
4
+ begin
5
+ Gem::Command.build_args = ARGV
6
+ rescue NoMethodError
7
+ end
8
+ inst = Gem::DependencyInstaller.new
9
+ begin
10
+ if Gem.win_platform?
11
+ inst.install 'openssl-win-root', '~> 1.1'
12
+ end
13
+ rescue
14
+ exit(1)
15
+ end
16
+
17
+ f = File.open(File.join(File.dirname(__FILE__), "Rakefile"), "w") # create dummy rakefile to indicate success
18
+ f.write("task :default\n")
19
+ f.close
@@ -0,0 +1,542 @@
1
+ require 'pathname'
2
+ require 'pastel'
3
+ require 'tty/table'
4
+ require 'tty/prompt'
5
+ require 'fileutils'
6
+ require 'json' # for reading configs
7
+ require 'whirly' # for loader spinner
8
+
9
+ require File.join(File.dirname(__FILE__), 'client')
10
+
11
+ module Neocities
12
+ class CLI
13
+ SUBCOMMANDS = %w{upload delete list info push logout pizza pull}
14
+ HELP_SUBCOMMANDS = ['-h', '--help', 'help']
15
+ PENELOPE_MOUTHS = %w{^ o ~ - v U}
16
+ PENELOPE_EYES = %w{o ~ O}
17
+
18
+ def initialize(argv)
19
+ @argv = argv.dup
20
+ @pastel = Pastel.new eachline: "\n"
21
+ @subcmd = @argv.first
22
+ @subargs = @argv[1..@argv.length]
23
+ @prompt = TTY::Prompt.new
24
+ @api_key = ENV['NEOCITIES_API_KEY'] || nil
25
+ @app_config_path = File.join self.class.app_config_path('neocities'), 'config.json' # added json extension
26
+ end
27
+
28
+ def display_response(resp)
29
+ if resp[:result] == 'success'
30
+ puts "#{@pastel.green.bold 'SUCCESS:'} #{resp[:message]}"
31
+ elsif resp[:result] == 'error' && resp[:error_type] == 'file_exists'
32
+ out = "#{@pastel.yellow.bold 'EXISTS:'} #{resp[:message]}"
33
+ out += " (#{resp[:error_type]})" if resp[:error_type]
34
+ puts out
35
+ else
36
+ out = "#{@pastel.red.bold 'ERROR:'} #{resp[:message]}"
37
+ out += " (#{resp[:error_type]})" if resp[:error_type]
38
+ puts out
39
+ end
40
+ end
41
+
42
+ def run
43
+ if @argv[0] == 'version'
44
+ puts Neocities::VERSION
45
+ exit
46
+ end
47
+
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
55
+
56
+ if !@api_key
57
+ begin
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
66
+ rescue Errno::ENOENT
67
+ @api_key = nil
68
+ end
69
+ end
70
+
71
+ if @api_key.nil?
72
+ puts "Please login to get your API key:"
73
+
74
+ if !@sitename && !@password
75
+ @sitename = @prompt.ask('sitename:', default: ENV['NEOCITIES_SITENAME'])
76
+ @password = @prompt.mask('password:', default: ENV['NEOCITIES_PASSWORD'])
77
+ end
78
+
79
+ @client = Neocities::Client.new sitename: @sitename, password: @password
80
+
81
+ resp = @client.key
82
+ if resp[:api_key]
83
+ conf = {
84
+ "API_KEY": resp[:api_key],
85
+ "SITENAME": @sitename,
86
+ }
87
+
88
+ FileUtils.mkdir_p Pathname(@app_config_path).dirname
89
+ File.write @app_config_path, conf.to_json
90
+
91
+ puts "The api key for #{@pastel.bold @sitename} has been stored in #{@pastel.bold @app_config_path}."
92
+ else
93
+ display_response resp
94
+ exit
95
+ end
96
+ else
97
+ @client = Neocities::Client.new api_key: @api_key
98
+ end
99
+
100
+ send @subcmd
101
+ end
102
+
103
+ def delete
104
+ display_delete_help_and_exit if @subargs.empty?
105
+ @subargs.each do |file|
106
+ puts @pastel.bold("Deleting #{file} ...")
107
+ resp = @client.delete file
108
+
109
+ display_response resp
110
+ end
111
+ end
112
+
113
+ def logout
114
+ confirmed = false
115
+ loop do
116
+ case @subargs[0]
117
+ when '-y' then @subargs.shift; confirmed = true
118
+ when /^-/ then puts(@pastel.red.bold("Unknown option: #{@subargs[0].inspect}")); break
119
+ else break
120
+ end
121
+ end
122
+ if confirmed
123
+ FileUtils.rm @app_config_path
124
+ puts @pastel.bold("Your api key has been removed.")
125
+ else
126
+ display_logout_help_and_exit
127
+ end
128
+ end
129
+
130
+ def info
131
+ resp = @client.info(@subargs[0] || @sitename)
132
+
133
+ if resp[:result] == 'error'
134
+ display_response resp
135
+ exit
136
+ end
137
+
138
+ out = []
139
+
140
+ resp[:info].each do |k, v|
141
+ v = Time.parse(v).localtime if v && (k == :created_at || k == :last_updated)
142
+ out.push [@pastel.bold(k), v]
143
+ end
144
+
145
+ puts TTY::Table.new(out).to_s
146
+ exit
147
+ end
148
+
149
+ def list
150
+ display_list_help_and_exit if @subargs.empty?
151
+ if @subargs.delete('-d') == '-d'
152
+ @detail = true
153
+ end
154
+
155
+ if @subargs.delete('-a')
156
+ @subargs[0] = nil
157
+ end
158
+
159
+ resp = @client.list @subargs[0]
160
+
161
+ if resp[:result] == 'error'
162
+ display_response resp
163
+ exit
164
+ end
165
+
166
+ if @detail
167
+ out = [
168
+ [@pastel.bold('Path'), @pastel.bold('Size'), @pastel.bold('Updated')]
169
+ ]
170
+ resp[:files].each do |file|
171
+ out.push([
172
+ @pastel.send(file[:is_directory] ? :blue : :green).bold(file[:path]),
173
+ file[:size] || '',
174
+ Time.parse(file[:updated_at]).localtime
175
+ ])
176
+ end
177
+ puts TTY::Table.new(out).to_s
178
+ exit
179
+ end
180
+
181
+ resp[:files].each do |file|
182
+ puts @pastel.send(file[:is_directory] ? :blue : :green).bold(file[:path])
183
+ end
184
+ end
185
+
186
+ def push
187
+ display_push_help_and_exit if @subargs.empty?
188
+ @no_gitignore = false
189
+ @excluded_files = []
190
+ @dry_run = false
191
+ @prune = false
192
+ loop do
193
+ case @subargs[0]
194
+ when '--no-gitignore' then @subargs.shift; @no_gitignore = true
195
+ when '-e' then @subargs.shift; @excluded_files.push(@subargs.shift)
196
+ when '--dry-run' then @subargs.shift; @dry_run = true
197
+ when '--prune' then @subargs.shift; @prune = true
198
+ when /^-/ then puts(@pastel.red.bold("Unknown option: #{@subargs[0].inspect}")); display_push_help_and_exit
199
+ else break
200
+ end
201
+ end
202
+
203
+ if @subargs[0].nil?
204
+ display_response result: 'error', message: "no local path provided"
205
+ display_push_help_and_exit
206
+ end
207
+
208
+ root_path = Pathname @subargs[0]
209
+
210
+ if !root_path.exist?
211
+ display_response result: 'error', message: "path #{root_path} does not exist"
212
+ display_push_help_and_exit
213
+ end
214
+
215
+ if !root_path.directory?
216
+ display_response result: 'error', message: 'provided path is not a directory'
217
+ display_push_help_and_exit
218
+ end
219
+
220
+ if @dry_run
221
+ puts @pastel.green.bold("Doing a dry run, not actually pushing anything")
222
+ end
223
+
224
+ if @prune
225
+ pruned_dirs = []
226
+ resp = @client.list
227
+ resp[:files].each do |file|
228
+ path = Pathname(File.join(@subargs[0], file[:path]))
229
+
230
+ pruned_dirs << path if !path.exist? && (file[:is_directory])
231
+
232
+ if !path.exist? && !pruned_dirs.include?(path.dirname)
233
+ print @pastel.bold("Deleting #{file[:path]} ... ")
234
+ resp = @client.delete_wrapper_with_dry_run file[:path], @dry_run
235
+
236
+ if resp[:result] == 'success'
237
+ print @pastel.green.bold("SUCCESS") + "\n"
238
+ else
239
+ print "\n"
240
+ display_response resp
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ Dir.chdir(root_path) do
247
+ paths = Dir.glob(File.join('**', '*'), File::FNM_DOTMATCH)
248
+
249
+ if @no_gitignore == false
250
+ begin
251
+ ignores = File.readlines('.gitignore').collect! do |ignore|
252
+ ignore.strip!
253
+ File.directory?(ignore) ? "#{ignore}**" : ignore
254
+ end
255
+ paths.select! do |path|
256
+ res = true
257
+ ignores.each do |ignore|
258
+ if File.fnmatch?(ignore.strip, path)
259
+ res = false
260
+ break
261
+ end
262
+ end
263
+ end
264
+ puts "Not pushing .gitignore entries (--no-gitignore to disable)"
265
+ rescue Errno::ENOENT
266
+ end
267
+ end
268
+
269
+ paths.select! { |p| !@excluded_files.include?(p) }
270
+
271
+ paths.select! { |p| !@excluded_files.include?(Pathname.new(p).dirname.to_s) }
272
+
273
+ paths.collect! { |path| Pathname path }
274
+
275
+ paths.each do |path|
276
+ next if path.directory?
277
+ print @pastel.bold("Uploading #{path} ... ")
278
+ resp = @client.upload path, path, @dry_run
279
+
280
+ if resp[:result] == 'error' && resp[:error_type] == 'file_exists'
281
+ print @pastel.yellow.bold("EXISTS") + "\n"
282
+ elsif resp[:result] == 'success'
283
+ print @pastel.green.bold("SUCCESS") + "\n"
284
+ else
285
+ print "\n"
286
+ display_response resp
287
+ end
288
+ end
289
+ end
290
+ end
291
+
292
+ def upload
293
+ display_upload_help_and_exit if @subargs.empty?
294
+ @dir = ''
295
+
296
+ loop do
297
+ case @subargs[0]
298
+ when '-d' then @subargs.shift; @dir = @subargs.shift
299
+ when /^-/ then puts(@pastel.red.bold("Unknown option: #{@subargs[0].inspect}")); display_upload_help_and_exit
300
+ else break
301
+ end
302
+ end
303
+
304
+ @subargs.each do |path|
305
+ path = Pathname path
306
+
307
+ if !path.exist?
308
+ display_response result: 'error', message: "#{path} does not exist locally."
309
+ next
310
+ end
311
+
312
+ if path.directory?
313
+ puts "#{path} is a directory, skipping (see the push command)"
314
+ next
315
+ end
316
+
317
+ remote_path = ['/', @dir, path.basename.to_s].join('/').gsub %r{/+}, '/'
318
+
319
+ puts @pastel.bold("Uploading #{path} to #{remote_path} ...")
320
+ resp = @client.upload path, remote_path
321
+ display_response resp
322
+ end
323
+ end
324
+
325
+ def pull
326
+ begin
327
+ quiet = !(['--log', '-l'].include? @subargs[0])
328
+
329
+ file = File.read @app_config_path
330
+ data = JSON.load file
331
+
332
+ last_pull_time = data["LAST_PULL"] ? data["LAST_PULL"]["time"] : nil
333
+ last_pull_loc = data["LAST_PULL"] ? data["LAST_PULL"]["loc"] : nil
334
+
335
+ Whirly.start spinner: ["😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿", "😾"], status: "Retrieving files for #{@pastel.bold @sitename}" if quiet
336
+ resp = @client.pull @sitename, last_pull_time, last_pull_loc, quiet
337
+
338
+ # write last pull data to file (not necessarily the best way to do this, but better than cloning every time)
339
+ data["LAST_PULL"] = {
340
+ "time": Time.now,
341
+ "loc": Dir.pwd
342
+ }
343
+
344
+ File.write @app_config_path, data.to_json
345
+ rescue StandardError => ex
346
+ Whirly.stop if quiet
347
+ puts @pastel.red.bold "\nA fatal error occurred :-("
348
+ puts @pastel.red ex
349
+ ensure
350
+ exit
351
+ end
352
+ end
353
+
354
+ def pizza
355
+ display_pizza_help_and_exit
356
+ end
357
+
358
+ def display_pizza_help_and_exit
359
+ puts "Sorry, we're fresh out of dough today. Try again tomorrow."
360
+ exit
361
+ end
362
+
363
+ def display_list_help_and_exit
364
+ display_banner
365
+
366
+ puts <<HERE
367
+ #{@pastel.green.bold 'list'} - List files on your Neocities site
368
+
369
+ #{@pastel.dim 'Examples:'}
370
+
371
+ #{@pastel.green '$ neocities list /'} List files in your root directory
372
+
373
+ #{@pastel.green '$ neocities list -a'} Recursively display all files and directories
374
+
375
+ #{@pastel.green '$ neocities list -d /mydir'} Show detailed information on /mydir
376
+
377
+ HERE
378
+ exit
379
+ end
380
+
381
+ def display_delete_help_and_exit
382
+ display_banner
383
+
384
+ puts <<HERE
385
+ #{@pastel.green.bold 'delete'} - Delete files on your Neocities site
386
+
387
+ #{@pastel.dim 'Examples:'}
388
+
389
+ #{@pastel.green '$ neocities delete myfile.jpg'} Delete myfile.jpg
390
+
391
+ #{@pastel.green '$ neocities delete myfile.jpg myfile2.jpg'} Delete myfile.jpg and myfile2.jpg
392
+
393
+ #{@pastel.green '$ neocities delete mydir'} Deletes mydir and everything inside it (be careful!)
394
+
395
+ HERE
396
+ exit
397
+ end
398
+
399
+ def display_upload_help_and_exit
400
+ display_banner
401
+
402
+ puts <<HERE
403
+ #{@pastel.green.bold 'upload'} - Upload individual files to your Neocities site
404
+
405
+ #{@pastel.dim 'Examples:'}
406
+
407
+ #{@pastel.green '$ neocities upload img.jpg img2.jpg'} Upload images to the root of your site
408
+
409
+ #{@pastel.green '$ neocities upload -d images img.jpg'} Upload img.jpg to the 'images' directory on your site
410
+
411
+ HERE
412
+ exit
413
+ end
414
+
415
+ def display_pull_help_and_exit
416
+ display_banner
417
+
418
+ puts <<HERE
419
+ #{@pastel.magenta.bold 'pull'} - Get the most recent version of files from your site
420
+
421
+ HERE
422
+ exit
423
+ end
424
+
425
+ def display_push_help_and_exit
426
+ display_banner
427
+
428
+ puts <<HERE
429
+ #{@pastel.green.bold 'push'} - Recursively upload a local directory to your Neocities site
430
+
431
+ #{@pastel.dim 'Examples:'}
432
+
433
+ #{@pastel.green '$ neocities push .'} Recursively upload current directory.
434
+
435
+ #{@pastel.green '$ neocities push -e node_modules -e secret.txt .'} Exclude certain files from push
436
+
437
+ #{@pastel.green '$ neocities push --no-gitignore .'} Don't use .gitignore to exclude files
438
+
439
+ #{@pastel.green '$ neocities push --dry-run .'} Just show what would be uploaded
440
+
441
+ #{@pastel.green '$ neocities push --prune .'} Delete site files not in dir (be careful!)
442
+
443
+ HERE
444
+ exit
445
+ end
446
+
447
+ def display_info_help_and_exit
448
+ display_banner
449
+
450
+ puts <<HERE
451
+ #{@pastel.green.bold 'info'} - Get site info
452
+
453
+ #{@pastel.dim 'Examples:'}
454
+
455
+ #{@pastel.green '$ neocities info fauux'} Gets info for 'fauux' site
456
+
457
+ HERE
458
+ exit
459
+ end
460
+
461
+ def display_logout_help_and_exit
462
+ display_banner
463
+
464
+ puts <<HERE
465
+ #{@pastel.green.bold 'logout'} - Remove the site api key from the config
466
+
467
+ #{@pastel.dim 'Examples:'}
468
+
469
+ #{@pastel.green '$ neocities logout -y'}
470
+
471
+ HERE
472
+ exit
473
+ end
474
+
475
+ def display_banner
476
+ puts <<HERE
477
+
478
+ |\\---/|
479
+ | #{PENELOPE_EYES.sample}_#{PENELOPE_EYES.sample} | #{@pastel.on_cyan.bold ' Neocities '}
480
+ \\_#{PENELOPE_MOUTHS.sample}_/
481
+
482
+ HERE
483
+ end
484
+
485
+ def display_help_and_exit
486
+ display_banner
487
+ puts <<HERE
488
+ #{@pastel.dim 'Subcommands:'}
489
+ push Recursively upload a local directory to your site
490
+ upload Upload individual files to your Neocities site
491
+ delete Delete files from your Neocities site
492
+ list List files from your Neocities site
493
+ info Information and stats for your site
494
+ logout Remove the site api key from the config
495
+ version Unceremoniously display version and self destruct
496
+ pull Get the most recent version of files from your site
497
+ pizza Order a free pizza
498
+
499
+ HERE
500
+ exit
501
+ end
502
+
503
+ def self.app_config_path(name)
504
+ platform = if RUBY_PLATFORM =~ /win32/
505
+ :win32
506
+ elsif RUBY_PLATFORM =~ /darwin/
507
+ :darwin
508
+ elsif RUBY_PLATFORM =~ /linux/
509
+ :linux
510
+ else
511
+ :unknown
512
+ end
513
+
514
+ case platform
515
+ when :linux
516
+ if ENV['XDG_CONFIG_HOME']
517
+ return File.join(ENV['XDG_CONFIG_HOME'], name)
518
+ end
519
+
520
+ if ENV['HOME']
521
+ return File.join(ENV['HOME'], '.config', name)
522
+ end
523
+ when :darwin
524
+ return File.join(ENV['HOME'], 'Library', 'Application Support', name)
525
+ else
526
+ # Windows platform detection is weird, just look for the env variables
527
+ if ENV['LOCALAPPDATA']
528
+ return File.join(ENV['LOCALAPPDATA'], name)
529
+ end
530
+
531
+ if ENV['USERPROFILE']
532
+ return File.join(ENV['USERPROFILE'], 'Local Settings', 'Application Data', name)
533
+ end
534
+
535
+ # Should work for the BSDs
536
+ if ENV['HOME']
537
+ return File.join(ENV['HOME'], '.'+name)
538
+ end
539
+ end
540
+ end
541
+ end
542
+ end
@@ -0,0 +1,183 @@
1
+ begin
2
+ require 'openssl/win/root' if Gem.win_platform?
3
+ rescue
4
+ end
5
+
6
+ require 'json'
7
+ require 'pathname'
8
+ require 'uri'
9
+ require 'digest'
10
+ require 'httpclient'
11
+ require 'pastel'
12
+ require 'date'
13
+
14
+ require 'whirly'
15
+
16
+ module Neocities
17
+ class Client
18
+ API_URI = 'https://neocities.org/api/'
19
+
20
+ def initialize(opts={})
21
+ @uri = URI.parse API_URI
22
+ @http = HTTPClient.new force_basic_auth: true
23
+ @opts = opts
24
+ @pastel = Pastel.new eachline: "\n"
25
+
26
+ unless opts[:api_key] || (opts[:sitename] && opts[:password])
27
+ raise ArgumentError, 'client requires a login (sitename/password) or an api_key'
28
+ end
29
+
30
+ if opts[:api_key]
31
+ @http.default_header = {'Authorization' => "Bearer #{opts[:api_key]}"}
32
+ else
33
+ @http.set_auth API_URI, opts[:sitename], opts[:password]
34
+ end
35
+ end
36
+
37
+ def list(path=nil)
38
+ get 'list', :path => path
39
+ end
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
+ uri_parser = URI::Parser.new
69
+ resp[:files].each do |file|
70
+ if !file[:is_directory]
71
+ print @pastel.bold("Loading #{file[:path]} ... ") if !quiet
72
+
73
+ if
74
+ last_pull_time && \
75
+ last_pull_loc && \
76
+ Time.parse(file[:updated_at]) <= Time.parse(last_pull_time) && \
77
+ last_pull_loc == curr_dir && \
78
+ File.exist?(file[:path]) # case when user deletes file
79
+ # case when file hasn't been updated since last
80
+ print "#{@pastel.yellow.bold "NO NEW UPDATES"}\n" if !quiet
81
+ next
82
+ end
83
+
84
+ pathtotry = uri_parser.escape(domain + file[:path])
85
+ fileconts = @http.get pathtotry
86
+
87
+ # follow redirects
88
+ while fileconts.status == 301
89
+ new_path = fileconts.header['location'][0]
90
+ print "\n#{@pastel.red "Fetch from #{pathtotry} failed."}\nTrying #{new_path} instead..." if !quiet
91
+
92
+ pathtotry = new_path
93
+ fileconts = @http.get pathtotry
94
+ end
95
+
96
+ if fileconts.ok?
97
+ print "#{@pastel.green.bold 'SUCCESS'}\n" if !quiet
98
+ success_loaded += 1
99
+
100
+ File.open("#{file[:path]}", "w") do |f|
101
+ f.write(fileconts.body)
102
+ end
103
+ else
104
+ print "#{@pastel.red.bold 'FAIL'}\n" if !quiet
105
+ end
106
+ else
107
+ FileUtils.mkdir_p "#{file[:path]}"
108
+ end
109
+ end
110
+
111
+ # calculate time command took
112
+ total_time = Time.now - start_time
113
+
114
+ # stop the spinner, if there is one
115
+ Whirly.stop if quiet
116
+
117
+ # display stats
118
+ puts @pastel.green "\nSuccessfully fetched #{success_loaded} files in #{total_time} seconds"
119
+ end
120
+
121
+ def key
122
+ get 'key'
123
+ end
124
+
125
+ def upload_hash(remote_path, sha1_hash)
126
+ post 'upload_hash', remote_path => sha1_hash
127
+ end
128
+
129
+ def upload(path, remote_path=nil, dry_run=false)
130
+ path = Pathname path
131
+
132
+ unless path.exist?
133
+ raise ArgumentError, "#{path.to_s} does not exist."
134
+ end
135
+
136
+ rpath = (remote_path || path.basename)
137
+
138
+ res = upload_hash rpath, Digest::SHA1.file(path.to_s).hexdigest
139
+
140
+ if res[:files] && res[:files][remote_path.to_s.to_sym] == true
141
+ return {result: 'error', error_type: 'file_exists', message: 'file already exists and matches local file, not uploading'}
142
+ else
143
+ if dry_run
144
+ return {result: 'success'}
145
+ else
146
+ File.open(path.to_s) do |file|
147
+ post 'upload', rpath => file
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ def delete_wrapper_with_dry_run(paths, dry_run=false)
154
+ if dry_run
155
+ return {result: 'success'}
156
+ else
157
+ delete(paths)
158
+ end
159
+ end
160
+
161
+ def delete(*paths)
162
+ post 'delete', 'filenames[]' => paths
163
+ end
164
+
165
+ def info(sitename)
166
+ get 'info', sitename: sitename
167
+ end
168
+
169
+ def get(path, params={})
170
+ uri = @uri+path
171
+ uri.query = URI.encode_www_form params
172
+ resp = @http.get uri
173
+
174
+ JSON.parse resp.body, symbolize_names: true
175
+ end
176
+
177
+ def post(path, args={})
178
+ uri = @uri+path
179
+ resp = @http.post uri, args
180
+ JSON.parse resp.body, symbolize_names: true
181
+ end
182
+ end
183
+ end
@@ -1,3 +1,3 @@
1
1
  module Neocities
2
- VERSION = "0.0.1"
2
+ VERSION = '0.0.20'
3
3
  end
data/lib/neocities.rb ADDED
@@ -0,0 +1,3 @@
1
+ require File.join(File.dirname(__FILE__), 'neocities', 'version')
2
+ require File.join(File.dirname(__FILE__), 'neocities', 'client')
3
+ require File.join(File.dirname(__FILE__), 'neocities', 'cli')
data/neocities.gemspec CHANGED
@@ -7,15 +7,20 @@ Gem::Specification.new do |spec|
7
7
  spec.name = "neocities"
8
8
  spec.version = Neocities::VERSION
9
9
  spec.authors = ["Kyle Drake"]
10
- spec.email = ["kyle@kyledrake.net"]
11
- spec.summary = %q{Library for the NeoCities.org API}
10
+ spec.email = ["contact@neocities.org"]
11
+ spec.summary = %q{Neocities.org CLI and API client}
12
12
  spec.homepage = "https://neocities.org"
13
13
  spec.license = "MIT"
14
- spec.files = `git ls-files -z`.split("\x0")
15
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
14
+ spec.files = `git ls-files | grep -Ev '^(test)'`.split("\n")
15
+ spec.executables = ['neocities']
16
+ spec.test_files = spec.files.grep(%r{^(tests)/})
17
17
  spec.require_paths = ["lib"]
18
+ spec.extensions = ['ext/mkrf_conf.rb']
18
19
 
19
- spec.add_development_dependency "bundler", "~> 1.5"
20
- spec.add_development_dependency "rake"
20
+ spec.add_dependency 'tty-table', '~> 0.10', '= 0.10.0'
21
+ spec.add_dependency 'tty-prompt', '~> 0.12', '= 0.12.0'
22
+ spec.add_dependency 'pastel', '~> 0.7', '= 0.7.2'
23
+ spec.add_dependency 'httpclient-fixcerts', '~> 2.8', '>= 2.8.5'
24
+ spec.add_dependency 'rake', '~> 12.3', '>= 12.3.1'
25
+ spec.add_dependency 'whirly', '~> 0.3', '>= 0.3.0'
21
26
  end
metadata CHANGED
@@ -1,56 +1,152 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neocities
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Drake
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-11 00:00:00.000000000 Z
11
+ date: 2024-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: tty-table
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.5'
20
- type: :development
19
+ version: '0.10'
20
+ - - '='
21
+ - !ruby/object:Gem::Version
22
+ version: 0.10.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.10'
30
+ - - '='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.10.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: tty-prompt
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.12'
40
+ - - '='
41
+ - !ruby/object:Gem::Version
42
+ version: 0.12.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.12'
50
+ - - '='
51
+ - !ruby/object:Gem::Version
52
+ version: 0.12.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: pastel
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.7'
60
+ - - '='
61
+ - !ruby/object:Gem::Version
62
+ version: 0.7.2
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0.7'
70
+ - - '='
71
+ - !ruby/object:Gem::Version
72
+ version: 0.7.2
73
+ - !ruby/object:Gem::Dependency
74
+ name: httpclient-fixcerts
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '2.8'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 2.8.5
83
+ type: :runtime
21
84
  prerelease: false
22
85
  version_requirements: !ruby/object:Gem::Requirement
23
86
  requirements:
24
87
  - - "~>"
25
88
  - !ruby/object:Gem::Version
26
- version: '1.5'
89
+ version: '2.8'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 2.8.5
27
93
  - !ruby/object:Gem::Dependency
28
94
  name: rake
29
95
  requirement: !ruby/object:Gem::Requirement
30
96
  requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '12.3'
31
100
  - - ">="
32
101
  - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
102
+ version: 12.3.1
103
+ type: :runtime
35
104
  prerelease: false
36
105
  version_requirements: !ruby/object:Gem::Requirement
37
106
  requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '12.3'
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 12.3.1
113
+ - !ruby/object:Gem::Dependency
114
+ name: whirly
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '0.3'
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 0.3.0
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '0.3'
38
130
  - - ">="
39
131
  - !ruby/object:Gem::Version
40
- version: '0'
132
+ version: 0.3.0
41
133
  description:
42
134
  email:
43
- - kyle@kyledrake.net
44
- executables: []
45
- extensions: []
135
+ - contact@neocities.org
136
+ executables:
137
+ - neocities
138
+ extensions:
139
+ - ext/mkrf_conf.rb
46
140
  extra_rdoc_files: []
47
141
  files:
48
142
  - ".gitignore"
49
143
  - Gemfile
50
- - LICENSE.txt
51
144
  - README.md
52
- - Rakefile
53
- - lib/neocities/neocities.rb
145
+ - bin/neocities
146
+ - ext/mkrf_conf.rb
147
+ - lib/neocities.rb
148
+ - lib/neocities/cli.rb
149
+ - lib/neocities/client.rb
54
150
  - lib/neocities/version.rb
55
151
  - neocities.gemspec
56
152
  homepage: https://neocities.org
@@ -72,9 +168,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
168
  - !ruby/object:Gem::Version
73
169
  version: '0'
74
170
  requirements: []
75
- rubyforge_project:
76
- rubygems_version: 2.2.2
171
+ rubygems_version: 3.5.13
77
172
  signing_key:
78
173
  specification_version: 4
79
- summary: Library for the NeoCities.org API
174
+ summary: Neocities.org CLI and API client
80
175
  test_files: []
data/LICENSE.txt DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2014 Kyle Drake
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile DELETED
@@ -1 +0,0 @@
1
- require "bundler/gem_tasks"
@@ -1,4 +0,0 @@
1
- require "neocities/version"
2
-
3
- module Neocities
4
- end