rfd 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/rfd.rb +296 -31
- data/lib/rfd/commands.rb +65 -14
- data/lib/rfd/item.rb +44 -20
- data/lib/rfd/windows.rb +16 -1
- data/rfd.gemspec +4 -3
- data/spec/controller_spec.rb +142 -41
- data/spec/spec_helper.rb +0 -3
- data/spec/testdir/gz1.tar.gz +0 -0
- data/spec/testdir/zip1.zip +0 -0
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1dd36a7cd7b7b8a56e25d704089e6c390935e07
|
4
|
+
data.tar.gz: eb3055ac47eb7a6105fb9a1a0b093323bab1d23a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14187a51b3c5d3ea90b7f4fcbd363170c9cd391e0d4ea7f54f75045de1a34e6dd762c56f61964af537b4a4e83d28325198e794c99c4dd942f8a94645d41a0a92
|
7
|
+
data.tar.gz: d1beb6cd74c3c03c06c02140d2d2680727ab27ac8cb2dfcb93da8659e1f0c23f3c879437701e6f81b8eb90884a298ac23786a166eb4f949c59faa403524fd5e7
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# rfd (Ruby on Files
|
1
|
+
# rfd (Ruby on Files & Directories)
|
2
2
|
|
3
3
|
rfd is a terminal based File explorer, inpsired by the legendary freesoft MS-DOS filer, "FD".
|
4
4
|
|
@@ -14,7 +14,7 @@ rfd is a terminal based File explorer, inpsired by the legendary freesoft MS-DOS
|
|
14
14
|
|
15
15
|
## Tested environment
|
16
16
|
|
17
|
-
|
17
|
+
Mac OSX Snow Leopard
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
data/lib/rfd.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'ffi-ncurses'
|
2
|
+
require 'ffi-ncurses/mouse'
|
2
3
|
Curses = FFI::NCurses
|
3
4
|
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'rubygems/package'
|
7
|
+
require 'zip'
|
8
|
+
require 'zip/filesystem'
|
4
9
|
require_relative 'rfd/commands'
|
5
10
|
require_relative 'rfd/item'
|
6
11
|
require_relative 'rfd/windows'
|
@@ -20,6 +25,9 @@ module Rfd
|
|
20
25
|
[Curses::COLOR_WHITE, Curses::COLOR_CYAN, Curses::COLOR_MAGENTA, Curses::COLOR_GREEN, Curses::COLOR_RED].each do |c|
|
21
26
|
Curses.init_pair c, c, Curses::COLOR_BLACK
|
22
27
|
end
|
28
|
+
|
29
|
+
Curses.mousemask Curses::ALL_MOUSE_EVENTS | Curses::REPORT_MOUSE_POSITION, nil
|
30
|
+
Curses.extend FFI::NCurses::Mouse
|
23
31
|
end
|
24
32
|
|
25
33
|
# Start the app here!
|
@@ -37,7 +45,7 @@ module Rfd
|
|
37
45
|
class Controller
|
38
46
|
include Rfd::Commands
|
39
47
|
|
40
|
-
attr_reader :header_l, :header_r, :main, :command_line, :items, :displayed_items, :current_row, :current_page, :current_dir
|
48
|
+
attr_reader :header_l, :header_r, :main, :command_line, :items, :displayed_items, :current_row, :current_page, :current_dir, :current_zip
|
41
49
|
|
42
50
|
# :nodoc:
|
43
51
|
def initialize
|
@@ -45,10 +53,12 @@ module Rfd
|
|
45
53
|
@header_l = HeaderLeftWindow.new
|
46
54
|
@header_r = HeaderRightWindow.new
|
47
55
|
@command_line = CommandLineWindow.new
|
56
|
+
@direction, @dir_history = nil, []
|
48
57
|
end
|
49
58
|
|
50
59
|
# The main loop.
|
51
60
|
def run
|
61
|
+
mouse_event = Curses::MEVENT.new
|
52
62
|
loop do
|
53
63
|
begin
|
54
64
|
case (c = Curses.getch)
|
@@ -77,6 +87,14 @@ module Rfd
|
|
77
87
|
else
|
78
88
|
debug "key: #{c}" if ENV['DEBUG']
|
79
89
|
end
|
90
|
+
when Curses::KEY_MOUSE
|
91
|
+
if Curses.getmouse(mouse_event) == Curses::OK
|
92
|
+
if Curses.BUTTON_CLICK(mouse_event[:bstate], 1) > 0
|
93
|
+
click y: mouse_event[:y], x: mouse_event[:x]
|
94
|
+
elsif Curses.BUTTON_DOUBLE_CLICK(mouse_event[:bstate], 1) > 0
|
95
|
+
double_click y: mouse_event[:y], x: mouse_event[:x]
|
96
|
+
end
|
97
|
+
end
|
80
98
|
else
|
81
99
|
debug "key: #{c}" if ENV['DEBUG']
|
82
100
|
end
|
@@ -143,18 +161,29 @@ module Rfd
|
|
143
161
|
|
144
162
|
# Change the current directory.
|
145
163
|
def cd(dir, pushd: true)
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
164
|
+
if dir.is_a?(Item) && dir.zip?
|
165
|
+
cd_into_zip dir
|
166
|
+
else
|
167
|
+
target = expand_path dir
|
168
|
+
if File.readable? target
|
169
|
+
Dir.chdir target
|
170
|
+
@dir_history << current_dir if current_dir && pushd
|
171
|
+
@current_dir, @current_page, @current_row, @current_zip = target, 0, nil, nil
|
172
|
+
main.activate_pane 0
|
173
|
+
end
|
152
174
|
end
|
153
175
|
end
|
154
176
|
|
177
|
+
def cd_into_zip(zipfile)
|
178
|
+
@current_zip = zipfile
|
179
|
+
@dir_history << current_dir if current_dir
|
180
|
+
@current_dir, @current_page, @current_row = zipfile.path, 0, nil
|
181
|
+
main.activate_pane 0
|
182
|
+
end
|
183
|
+
|
155
184
|
# cd to the previous directory.
|
156
185
|
def popd
|
157
|
-
if
|
186
|
+
if @dir_history.any?
|
158
187
|
cd @dir_history.pop, pushd: false
|
159
188
|
ls
|
160
189
|
end
|
@@ -163,12 +192,12 @@ module Rfd
|
|
163
192
|
# Fetch files from current directory.
|
164
193
|
# Then update each windows reflecting the newest information.
|
165
194
|
def ls
|
166
|
-
|
195
|
+
fetch_items_from_filesystem_or_zip
|
167
196
|
sort_items_according_to_current_direction
|
168
197
|
|
169
198
|
@current_page ||= 0
|
170
199
|
draw_items
|
171
|
-
move_cursor current_row
|
200
|
+
move_cursor (current_row ? [current_row, items.size - 1].min : nil)
|
172
201
|
|
173
202
|
draw_marked_items
|
174
203
|
draw_total_items
|
@@ -200,18 +229,46 @@ module Rfd
|
|
200
229
|
# Change the file permission of the selected files and directories.
|
201
230
|
#
|
202
231
|
# ==== Parameters
|
203
|
-
# * +mode+ - Unix chmod string (e.g. +w, g-r)
|
204
|
-
#
|
205
|
-
#TODO: accept number forms such as 755, 0644
|
232
|
+
# * +mode+ - Unix chmod string (e.g. +w, g-r, 755, 0644)
|
206
233
|
def chmod(mode = nil)
|
207
234
|
return unless mode
|
235
|
+
begin
|
236
|
+
Integer mode
|
237
|
+
mode = Integer mode.size == 3 ? "0#{mode}" : mode
|
238
|
+
rescue ArgumentError
|
239
|
+
end
|
208
240
|
FileUtils.chmod mode, selected_items.map(&:path)
|
209
241
|
ls
|
210
242
|
end
|
211
243
|
|
212
|
-
#
|
213
|
-
|
214
|
-
|
244
|
+
# Change the file owner of the selected files and directories.
|
245
|
+
#
|
246
|
+
# ==== Parameters
|
247
|
+
# * +user_and_group+ - user name and group name separated by : (e.g. alice, nobody:nobody, :admin)
|
248
|
+
def chown(user_and_group)
|
249
|
+
return unless user_and_group
|
250
|
+
user, group = user_and_group.split(':').map {|s| s == '' ? nil : s}
|
251
|
+
FileUtils.chown user, group, selected_items.map(&:path)
|
252
|
+
ls
|
253
|
+
end
|
254
|
+
|
255
|
+
# Fetch files from current directory or current .zip file.
|
256
|
+
def fetch_items_from_filesystem_or_zip
|
257
|
+
unless in_zip?
|
258
|
+
@items = Dir.foreach(current_dir).map {|fn|
|
259
|
+
stat = File.lstat File.join(current_dir, fn)
|
260
|
+
Item.new dir: current_dir, name: fn, stat: stat, window_width: maxx
|
261
|
+
}.to_a
|
262
|
+
else
|
263
|
+
@items = [Item.new(dir: current_dir, name: '.', stat: File.stat(current_dir), window_width: maxx),
|
264
|
+
Item.new(dir: current_dir, name: '..', stat: File.stat(File.dirname(current_dir)), window_width: maxx)]
|
265
|
+
zf = Zip::File.new current_dir
|
266
|
+
zf.each {|entry|
|
267
|
+
next if entry.name_is_directory?
|
268
|
+
stat = zf.file.stat entry.name
|
269
|
+
@items << Item.new(dir: current_dir, name: entry.name, stat: stat, window_width: maxx)
|
270
|
+
}
|
271
|
+
end
|
215
272
|
end
|
216
273
|
|
217
274
|
# Focus at the first file or directory of which name starts with the given String.
|
@@ -223,7 +280,7 @@ module Rfd
|
|
223
280
|
# Focus at the last file or directory of which name starts with the given String.
|
224
281
|
def find_reverse(str)
|
225
282
|
index = items.reverse.index {|i| i.name.start_with? str}
|
226
|
-
move_cursor items.
|
283
|
+
move_cursor items.size - index - 1 if index
|
227
284
|
end
|
228
285
|
|
229
286
|
# Width of the currently active pane.
|
@@ -288,7 +345,7 @@ module Rfd
|
|
288
345
|
# .*\.pdf$ : Search PDF files
|
289
346
|
def grep(pattern = '.*')
|
290
347
|
regexp = Regexp.new(pattern)
|
291
|
-
|
348
|
+
fetch_items_from_filesystem_or_zip
|
292
349
|
@items = items.shift(2) + items.select {|i| i.name =~ regexp}
|
293
350
|
sort_items_according_to_current_direction
|
294
351
|
switch_page 0
|
@@ -299,15 +356,29 @@ module Rfd
|
|
299
356
|
|
300
357
|
# Copy selected files and directories to the destination.
|
301
358
|
def cp(dest)
|
302
|
-
|
303
|
-
|
359
|
+
unless in_zip?
|
360
|
+
src = (m = marked_items).any? ? m.map(&:path) : current_item.path
|
361
|
+
FileUtils.cp_r src, expand_path(dest)
|
362
|
+
else
|
363
|
+
raise 'cping multiple items in .zip is not supported.' if selected_items.size > 1
|
364
|
+
Zip::File.open(current_zip.path) do |zip|
|
365
|
+
entry = zip.find_entry(selected_items.first.name).dup
|
366
|
+
entry.name, entry.name_length = dest, dest.size
|
367
|
+
zip.instance_variable_get(:@entry_set) << entry
|
368
|
+
end
|
369
|
+
end
|
304
370
|
ls
|
305
371
|
end
|
306
372
|
|
307
373
|
# Move selected files and directories to the destination.
|
308
374
|
def mv(dest)
|
309
|
-
|
310
|
-
|
375
|
+
unless in_zip?
|
376
|
+
src = (m = marked_items).any? ? m.map(&:path) : current_item.path
|
377
|
+
FileUtils.mv src, expand_path(dest)
|
378
|
+
else
|
379
|
+
raise 'mving multiple items in .zip is not supported.' if selected_items.size > 1
|
380
|
+
rename "#{selected_items.first.name}/#{dest}"
|
381
|
+
end
|
311
382
|
ls
|
312
383
|
end
|
313
384
|
|
@@ -318,22 +389,165 @@ module Rfd
|
|
318
389
|
def rename(pattern)
|
319
390
|
from, to = pattern.split '/'
|
320
391
|
from = Regexp.new from
|
321
|
-
|
322
|
-
|
323
|
-
|
392
|
+
unless in_zip?
|
393
|
+
selected_items.each do |item|
|
394
|
+
name = item.name.gsub from, to
|
395
|
+
FileUtils.mv item.path, File.join(current_dir, name)
|
396
|
+
end
|
397
|
+
else
|
398
|
+
Zip::File.open(current_zip.path) do |zip|
|
399
|
+
selected_items.each do |item|
|
400
|
+
name = item.name.gsub from, to
|
401
|
+
zip.rename item.name, name
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
ls
|
406
|
+
end
|
407
|
+
|
408
|
+
# Soft delete selected files and directories.
|
409
|
+
#
|
410
|
+
# If the OS is not OSX, performs the same as `delete` command.
|
411
|
+
def trash
|
412
|
+
unless in_zip?
|
413
|
+
if osx?
|
414
|
+
FileUtils.mv selected_items.map(&:path), File.expand_path('~/.Trash/')
|
415
|
+
else
|
416
|
+
#TODO support other OS
|
417
|
+
FileUtils.rm_rf selected_items.map(&:path)
|
418
|
+
end
|
419
|
+
else
|
420
|
+
return unless ask %Q[Trashing zip entries is not supported. Actually the files will be deleted. Are you sure want to proceed? (y/n)]
|
421
|
+
delete
|
324
422
|
end
|
423
|
+
@current_row -= selected_items.count {|i| i.index <= current_row}
|
424
|
+
ls
|
425
|
+
end
|
426
|
+
|
427
|
+
# Delete selected files and directories.
|
428
|
+
def delete
|
429
|
+
unless in_zip?
|
430
|
+
FileUtils.rm_rf selected_items.map(&:path)
|
431
|
+
else
|
432
|
+
Zip::File.open(current_zip.path) do |zip|
|
433
|
+
zip.select {|e| selected_items.map(&:name).include? e.to_s}.each do |entry|
|
434
|
+
if entry.name_is_directory?
|
435
|
+
zip.dir.delete entry.to_s
|
436
|
+
else
|
437
|
+
zip.file.delete entry.to_s
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
@current_row -= selected_items.count {|i| i.index <= current_row}
|
325
443
|
ls
|
326
444
|
end
|
327
445
|
|
328
446
|
# Create a new directory.
|
329
447
|
def mkdir(dir)
|
330
|
-
|
448
|
+
unless in_zip?
|
449
|
+
FileUtils.mkdir_p File.join(current_dir, dir)
|
450
|
+
else
|
451
|
+
Zip::File.open(current_zip.path) do |zip|
|
452
|
+
zip.dir.mkdir dir
|
453
|
+
end
|
454
|
+
end
|
331
455
|
ls
|
332
456
|
end
|
333
457
|
|
334
458
|
# Create a new empty file.
|
335
459
|
def touch(filename)
|
336
|
-
|
460
|
+
unless in_zip?
|
461
|
+
FileUtils.touch File.join(current_dir, filename)
|
462
|
+
else
|
463
|
+
Zip::File.open(current_zip.path) do |zip|
|
464
|
+
# zip.file.open(filename, 'w') {|_f| } #HAXX this code creates an unneeded temporary file
|
465
|
+
zip.instance_variable_get(:@entry_set) << Zip::Entry.new(current_zip.path, filename)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
ls
|
469
|
+
end
|
470
|
+
|
471
|
+
# Create a symlink to the current file or directory.
|
472
|
+
def symlink(name)
|
473
|
+
FileUtils.ln_s current_item.path, name
|
474
|
+
ls
|
475
|
+
end
|
476
|
+
|
477
|
+
# Copy selected files and directories' path into clipboard on OSX.
|
478
|
+
def clipboard
|
479
|
+
IO.popen('pbcopy', 'w') {|f| f << selected_items.map(&:path).join(' ')} if osx?
|
480
|
+
end
|
481
|
+
|
482
|
+
# Archive selected files and directories into a .zip file.
|
483
|
+
def zip(zipfile_name)
|
484
|
+
return unless zipfile_name
|
485
|
+
zipfile_name << '.zip' unless zipfile_name.end_with? '.zip'
|
486
|
+
|
487
|
+
Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
|
488
|
+
selected_items.each do |item|
|
489
|
+
next if item.symlink?
|
490
|
+
if item.directory?
|
491
|
+
Dir[File.join(item.path, '**/**')].each do |file|
|
492
|
+
zipfile.add file.sub("#{current_dir}/", ''), file
|
493
|
+
end
|
494
|
+
else
|
495
|
+
zipfile.add item.name, item.path
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
ls
|
500
|
+
end
|
501
|
+
|
502
|
+
# Unarchive .zip and .tar.gz files within selected files and directories into current_directory.
|
503
|
+
def unarchive
|
504
|
+
unless in_zip?
|
505
|
+
zips, gzs = selected_items.partition(&:zip?).tap {|z, others| break [z, *others.partition(&:gz?)]}
|
506
|
+
zips.each do |item|
|
507
|
+
FileUtils.mkdir_p File.join(current_dir, item.basename)
|
508
|
+
Zip::File.open(item.path) do |zip|
|
509
|
+
zip.each do |entry|
|
510
|
+
FileUtils.mkdir_p File.join(File.join(item.basename, File.dirname(entry.to_s)))
|
511
|
+
zip.extract(entry, File.join(item.basename, entry.to_s)) { true }
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
gzs.each do |item|
|
516
|
+
Zlib::GzipReader.open(item.path) do |gz|
|
517
|
+
Gem::Package::TarReader.new(gz) do |tar|
|
518
|
+
dest_dir = File.join current_dir, (gz.orig_name || item.basename).sub(/\.tar$/, '')
|
519
|
+
tar.each do |entry|
|
520
|
+
dest = nil
|
521
|
+
if entry.full_name == '././@LongLink'
|
522
|
+
dest = File.join dest_dir, entry.read.strip
|
523
|
+
next
|
524
|
+
end
|
525
|
+
dest ||= File.join dest_dir, entry.full_name
|
526
|
+
if entry.directory?
|
527
|
+
FileUtils.mkdir_p dest, :mode => entry.header.mode
|
528
|
+
elsif entry.file?
|
529
|
+
FileUtils.mkdir_p dest_dir
|
530
|
+
File.open(dest, 'wb') {|f| f.print entry.read}
|
531
|
+
FileUtils.chmod entry.header.mode, dest
|
532
|
+
elsif entry.header.typeflag == '2' # symlink
|
533
|
+
File.symlink entry.header.linkname, dest
|
534
|
+
end
|
535
|
+
unless Dir.exist? dest_dir
|
536
|
+
FileUtils.mkdir_p dest_dir
|
537
|
+
File.open(File.join(dest_dir, gz.orig_name || item.basename), 'wb') {|f| f.print gz.read}
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
else
|
544
|
+
Zip::File.open(current_zip.path) do |zip|
|
545
|
+
zip.select {|e| selected_items.map(&:name).include? e.to_s}.each do |entry|
|
546
|
+
FileUtils.mkdir_p File.join(current_zip.dir, current_zip.basename, File.dirname(entry.to_s))
|
547
|
+
zip.extract(entry, File.join(current_zip.dir, current_zip.basename, entry.to_s)) { true }
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
337
551
|
ls
|
338
552
|
end
|
339
553
|
|
@@ -349,7 +563,7 @@ module Rfd
|
|
349
563
|
|
350
564
|
# Number of pages in the current directory.
|
351
565
|
def total_pages
|
352
|
-
items.
|
566
|
+
items.size / max_items + 1
|
353
567
|
end
|
354
568
|
|
355
569
|
# Move to the given page number.
|
@@ -426,15 +640,54 @@ module Rfd
|
|
426
640
|
def edit
|
427
641
|
execute_external_command do
|
428
642
|
editor = ENV['EDITOR'] || 'vim'
|
429
|
-
|
643
|
+
unless in_zip?
|
644
|
+
system %Q[#{editor} "#{current_item.path}"]
|
645
|
+
else
|
646
|
+
begin
|
647
|
+
tmpdir, tmpfile_name = nil
|
648
|
+
Zip::File.open(current_zip.path) do |zip|
|
649
|
+
tmpdir = Dir.mktmpdir
|
650
|
+
FileUtils.mkdir_p File.join(tmpdir, File.dirname(current_item.name))
|
651
|
+
tmpfile_name = File.join(tmpdir, current_item.name)
|
652
|
+
File.open(tmpfile_name, 'w') {|f| f.puts zip.file.read(current_item.name)}
|
653
|
+
system %Q[#{editor} "#{tmpfile_name}"]
|
654
|
+
zip.add(current_item.name, tmpfile_name) { true }
|
655
|
+
end
|
656
|
+
ls
|
657
|
+
ensure
|
658
|
+
FileUtils.remove_entry_secure tmpdir if tmpdir
|
659
|
+
end
|
660
|
+
end
|
430
661
|
end
|
431
662
|
end
|
432
663
|
|
433
664
|
# Open current file or directory with the viewer.
|
434
665
|
def view
|
666
|
+
pager = ENV['PAGER'] || 'less'
|
435
667
|
execute_external_command do
|
436
|
-
|
437
|
-
|
668
|
+
unless in_zip?
|
669
|
+
system %Q[#{pager} "#{current_item.path}"]
|
670
|
+
else
|
671
|
+
begin
|
672
|
+
tmpdir, tmpfile_name = nil
|
673
|
+
Zip::File.open(current_zip.path) do |zip|
|
674
|
+
tmpdir = Dir.mktmpdir
|
675
|
+
FileUtils.mkdir_p File.join(tmpdir, File.dirname(current_item.name))
|
676
|
+
tmpfile_name = File.join(tmpdir, current_item.name)
|
677
|
+
File.open(tmpfile_name, 'w') {|f| f.puts zip.file.read(current_item.name)}
|
678
|
+
end
|
679
|
+
system %Q[#{pager} "#{tmpfile_name}"]
|
680
|
+
ensure
|
681
|
+
FileUtils.remove_entry_secure tmpdir if tmpdir
|
682
|
+
end
|
683
|
+
end
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
def move_cursor_by_click(y: nil, x: nil)
|
688
|
+
if (idx = main.pane_index_at(y: y, x: x))
|
689
|
+
row = current_page * max_items + main.maxy * idx + y - main.begy
|
690
|
+
move_cursor row if (row >= 0) && (row < items.size)
|
438
691
|
end
|
439
692
|
end
|
440
693
|
|
@@ -449,6 +702,18 @@ module Rfd
|
|
449
702
|
Curses.refresh
|
450
703
|
end
|
451
704
|
|
705
|
+
def expand_path(path)
|
706
|
+
File.expand_path path.is_a?(Rfd::Item) ? path.path : path.start_with?('/') || path.start_with?('~') ? path : current_dir ? File.join(current_dir, path) : path
|
707
|
+
end
|
708
|
+
|
709
|
+
def osx?
|
710
|
+
@_osx ||= RbConfig::CONFIG['host_os'] =~ /darwin/
|
711
|
+
end
|
712
|
+
|
713
|
+
def in_zip?
|
714
|
+
@current_zip
|
715
|
+
end
|
716
|
+
|
452
717
|
def debug(str)
|
453
718
|
header_r.debug str
|
454
719
|
end
|
data/lib/rfd/commands.rb
CHANGED
@@ -10,13 +10,11 @@ module Rfd
|
|
10
10
|
process_command_line preset_command: 'cp'
|
11
11
|
end
|
12
12
|
|
13
|
-
# Soft "d"elete (actually mv to the trash folder) selected files and directories.
|
13
|
+
# Soft "d"elete (actually mv to the trash folder on OSX) selected files and directories.
|
14
14
|
def d
|
15
15
|
if selected_items.any?
|
16
|
-
if ask %Q[Are
|
17
|
-
|
18
|
-
@current_row -= selected_items.count {|i| i.index <= current_row}
|
19
|
-
ls
|
16
|
+
if ask %Q[Are you sure want to trash #{selected_items.one? ? selected_items.first.name : "these #{selected_items.size} files"}? (y/n)]
|
17
|
+
trash
|
20
18
|
end
|
21
19
|
end
|
22
20
|
end
|
@@ -26,7 +24,7 @@ module Rfd
|
|
26
24
|
edit
|
27
25
|
end
|
28
26
|
|
29
|
-
# "f"ind the first
|
27
|
+
# "f"ind the first file or directory of which name starts with the given String.
|
30
28
|
def f
|
31
29
|
process_command_line preset_command: 'find'
|
32
30
|
end
|
@@ -66,12 +64,16 @@ module Rfd
|
|
66
64
|
|
67
65
|
# "o"pen selected files and directories with the OS "open" command.
|
68
66
|
def o
|
69
|
-
|
67
|
+
if selected_items.any?
|
68
|
+
system "open #{selected_items.map {|i| %Q["#{i.path}"]}.join(' ')}"
|
69
|
+
elsif %w(. ..).include? current_item.name
|
70
|
+
system %Q[open "#{current_item.path}"]
|
71
|
+
end
|
70
72
|
end
|
71
73
|
|
72
74
|
# "q"uit the app.
|
73
75
|
def q
|
74
|
-
raise StopIteration if ask 'Are
|
76
|
+
raise StopIteration if ask 'Are you sure want to exit? (y/n)'
|
75
77
|
end
|
76
78
|
|
77
79
|
# "r"ename selected files and directories.
|
@@ -89,23 +91,41 @@ module Rfd
|
|
89
91
|
process_command_line preset_command: 'touch'
|
90
92
|
end
|
91
93
|
|
94
|
+
# "u"narchive .zip and .tar.gz files within selected files and directories into current_directory.
|
95
|
+
def u
|
96
|
+
unarchive
|
97
|
+
end
|
98
|
+
|
92
99
|
# "o"pen selected files and directories with the viewer.
|
93
100
|
def v
|
94
101
|
view
|
95
102
|
end
|
96
103
|
|
104
|
+
# Change o"w"ner of selected files and directories.
|
105
|
+
def w
|
106
|
+
process_command_line preset_command: 'chown'
|
107
|
+
end
|
108
|
+
|
109
|
+
# Archive selected files and directories into a "z"ip file.
|
110
|
+
def z
|
111
|
+
process_command_line preset_command: 'zip'
|
112
|
+
end
|
113
|
+
|
114
|
+
# "C"opy paths of selected files and directory to the "C"lipboard.
|
115
|
+
def C
|
116
|
+
clipboard
|
117
|
+
end
|
118
|
+
|
97
119
|
# Hard "d"elete selected files and directories.
|
98
120
|
def D
|
99
121
|
if selected_items.any?
|
100
|
-
if ask %Q[Are
|
101
|
-
|
102
|
-
@current_row -= selected_items.count {|i| i.index <= current_row}
|
103
|
-
ls
|
122
|
+
if ask %Q[Are you sure want to delete #{selected_items.one? ? selected_items.first.name : "these #{selected_items.size} files"}? (y/n)]
|
123
|
+
delete
|
104
124
|
end
|
105
125
|
end
|
106
126
|
end
|
107
127
|
|
108
|
-
# "f"ind the last
|
128
|
+
# "f"ind the last file or directory of which name starts with the given String.
|
109
129
|
def F
|
110
130
|
process_command_line preset_command: 'find_reverse'
|
111
131
|
end
|
@@ -130,6 +150,19 @@ module Rfd
|
|
130
150
|
move_cursor current_page * max_items + displayed_items.size / 2
|
131
151
|
end
|
132
152
|
|
153
|
+
# "O"pen terminal here.
|
154
|
+
def O
|
155
|
+
dir = current_item.directory? ? current_item.path : current_dir
|
156
|
+
system %Q[osascript -e 'tell app "Terminal"
|
157
|
+
do script "cd #{dir}"
|
158
|
+
end tell'] if osx?
|
159
|
+
end
|
160
|
+
|
161
|
+
# "S"ymlink the current file or directory
|
162
|
+
def S
|
163
|
+
process_command_line preset_command: 'symlink'
|
164
|
+
end
|
165
|
+
|
133
166
|
# Mark or unmark "a"ll files and directories.
|
134
167
|
def ctrl_a
|
135
168
|
mark = marked_items.size != (items.size - 2) # exclude . and ..
|
@@ -207,7 +240,13 @@ module Rfd
|
|
207
240
|
|
208
241
|
# cd into a directory, or view a file.
|
209
242
|
def enter
|
210
|
-
if current_item.
|
243
|
+
if current_item.name == '.' # do nothing
|
244
|
+
elsif current_item.name == '..'
|
245
|
+
cd '..'
|
246
|
+
ls
|
247
|
+
elsif in_zip?
|
248
|
+
v
|
249
|
+
elsif current_item.directory? || current_item.zip?
|
211
250
|
cd current_item
|
212
251
|
ls
|
213
252
|
else
|
@@ -230,5 +269,17 @@ module Rfd
|
|
230
269
|
ls
|
231
270
|
end
|
232
271
|
end
|
272
|
+
|
273
|
+
# Move cursor position by mouse click.
|
274
|
+
def click(y: nil, x: nil)
|
275
|
+
move_cursor_by_click y: y, x: x
|
276
|
+
end
|
277
|
+
|
278
|
+
# Move cursor position and enter
|
279
|
+
def double_click(y: nil, x: nil)
|
280
|
+
if move_cursor_by_click y: y, x: x
|
281
|
+
enter
|
282
|
+
end
|
283
|
+
end
|
233
284
|
end
|
234
285
|
end
|
data/lib/rfd/item.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Rfd
|
2
2
|
class Item
|
3
3
|
include Comparable
|
4
|
-
attr_reader :name
|
4
|
+
attr_reader :name, :dir, :stat
|
5
5
|
attr_accessor :index
|
6
6
|
|
7
|
-
def initialize(dir: nil, name: nil, window_width: nil)
|
8
|
-
@dir, @name, @window_width, @marked = dir, name, window_width, false
|
7
|
+
def initialize(dir: nil, name: nil, stat: nil, window_width: nil)
|
8
|
+
@dir, @name, @stat, @window_width, @marked = dir, name, stat, window_width, false
|
9
9
|
end
|
10
10
|
|
11
11
|
def path
|
@@ -35,20 +35,12 @@ module Rfd
|
|
35
35
|
if symlink?
|
36
36
|
mb_left n, @window_width - 16
|
37
37
|
else
|
38
|
-
"#{mb_left(basename, @window_width - 16 - extname.
|
38
|
+
"#{mb_left(basename, @window_width - 16 - extname.size)}…#{extname}"
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def lstat
|
45
|
-
@lstat ||= File.lstat path
|
46
|
-
end
|
47
|
-
|
48
|
-
def stat
|
49
|
-
@stat ||= File.stat path
|
50
|
-
end
|
51
|
-
|
52
44
|
def color
|
53
45
|
if symlink?
|
54
46
|
Curses::COLOR_MAGENTA
|
@@ -64,7 +56,7 @@ module Rfd
|
|
64
56
|
end
|
65
57
|
|
66
58
|
def size
|
67
|
-
directory? ? 0 :
|
59
|
+
directory? ? 0 : stat.size
|
68
60
|
end
|
69
61
|
|
70
62
|
def size_or_dir
|
@@ -72,20 +64,20 @@ module Rfd
|
|
72
64
|
end
|
73
65
|
|
74
66
|
def atime
|
75
|
-
|
67
|
+
stat.atime.strftime('%Y-%m-%d %H:%M:%S')
|
76
68
|
end
|
77
69
|
|
78
70
|
def ctime
|
79
|
-
|
71
|
+
stat.ctime.strftime('%Y-%m-%d %H:%M:%S')
|
80
72
|
end
|
81
73
|
|
82
74
|
def mtime
|
83
|
-
|
75
|
+
stat.mtime.strftime('%Y-%m-%d %H:%M:%S')
|
84
76
|
end
|
85
77
|
|
86
78
|
def mode
|
87
79
|
@mode ||= begin
|
88
|
-
m =
|
80
|
+
m = stat.mode
|
89
81
|
ret = directory? ? 'd' : symlink? ? 'l' : '-'
|
90
82
|
[(m & 0700) / 64, (m & 070) / 8, m & 07].inject(ret) do |str, s|
|
91
83
|
str << "#{s & 4 == 4 ? 'r' : '-'}#{s & 2 == 2 ? 'w' : '-'}#{s & 1 == 1 ? 'x' : '-'}"
|
@@ -104,11 +96,15 @@ module Rfd
|
|
104
96
|
end
|
105
97
|
|
106
98
|
def directory?
|
107
|
-
|
99
|
+
@directory ||= if symlink?
|
100
|
+
File.stat(path).directory?
|
101
|
+
else
|
102
|
+
stat.directory?
|
103
|
+
end
|
108
104
|
end
|
109
105
|
|
110
106
|
def symlink?
|
111
|
-
|
107
|
+
stat.symlink?
|
112
108
|
end
|
113
109
|
|
114
110
|
def hidden?
|
@@ -116,7 +112,35 @@ module Rfd
|
|
116
112
|
end
|
117
113
|
|
118
114
|
def executable?
|
119
|
-
|
115
|
+
stat.executable?
|
116
|
+
end
|
117
|
+
|
118
|
+
def zip?
|
119
|
+
@zip_ ||= begin
|
120
|
+
if directory?
|
121
|
+
false
|
122
|
+
elsif symlink?
|
123
|
+
File.binread(target, 4).unpack('V').first == 0x04034b50
|
124
|
+
else
|
125
|
+
File.binread(path, 4).unpack('V').first == 0x04034b50
|
126
|
+
end
|
127
|
+
rescue
|
128
|
+
false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def gz?
|
133
|
+
@gz_ ||= begin
|
134
|
+
if directory?
|
135
|
+
false
|
136
|
+
elsif symlink?
|
137
|
+
File.binread(target, 2).unpack('n').first == 0x1f8b
|
138
|
+
else
|
139
|
+
File.binread(path, 2).unpack('n').first == 0x1f8b
|
140
|
+
end
|
141
|
+
rescue
|
142
|
+
false
|
143
|
+
end
|
120
144
|
end
|
121
145
|
|
122
146
|
def target
|
data/lib/rfd/windows.rb
CHANGED
@@ -126,6 +126,13 @@ module Rfd
|
|
126
126
|
@current_index = index if index < @panes.size
|
127
127
|
end
|
128
128
|
|
129
|
+
def get_index_by_point(y: nil, x: nil)
|
130
|
+
@panes.each.with_index do |p, i|
|
131
|
+
return i if include_point? pane: p, y: y, x: x
|
132
|
+
end if y && x
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
129
136
|
def size
|
130
137
|
@panes.size
|
131
138
|
end
|
@@ -133,6 +140,10 @@ module Rfd
|
|
133
140
|
def close_all
|
134
141
|
@panes.each {|p| Curses.delwin p}
|
135
142
|
end
|
143
|
+
|
144
|
+
def include_point?(pane: pane, y: nil, x: nil)
|
145
|
+
(y >= Curses.getbegy(pane)) && (Curses.getbegy(pane) + Curses.getmaxy(pane) > y) && (x >= Curses.getbegx(pane)) && (Curses.getbegx(pane) + Curses.getmaxx(pane) > x)
|
146
|
+
end
|
136
147
|
end
|
137
148
|
|
138
149
|
def initialize(dir = '.')
|
@@ -155,6 +166,10 @@ module Rfd
|
|
155
166
|
@panes.activate num
|
156
167
|
end
|
157
168
|
|
169
|
+
def pane_index_at(y: nil, x: nil)
|
170
|
+
@panes.get_index_by_point y: y, x: x
|
171
|
+
end
|
172
|
+
|
158
173
|
def window
|
159
174
|
@panes.active
|
160
175
|
end
|
@@ -207,7 +222,7 @@ module Rfd
|
|
207
222
|
|
208
223
|
def get_command(prompt: nil)
|
209
224
|
Curses.echo
|
210
|
-
startx = prompt ? prompt.
|
225
|
+
startx = prompt ? prompt.size : 1
|
211
226
|
s = ' ' * 100
|
212
227
|
Curses.mvwgetstr window, 0, startx, s
|
213
228
|
"#{prompt[1..-1] if prompt}#{s.strip}"
|
data/rfd.gemspec
CHANGED
@@ -4,11 +4,11 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "rfd"
|
7
|
-
spec.version = '0.0
|
7
|
+
spec.version = '0.1.0'
|
8
8
|
spec.authors = ["Akira Matsuda"]
|
9
9
|
spec.email = ["ronnie@dio.jp"]
|
10
|
-
spec.description = 'Ruby on Files
|
11
|
-
spec.summary = 'Ruby on Files
|
10
|
+
spec.description = 'Ruby on Files & Directories'
|
11
|
+
spec.summary = 'Ruby on Files & Directories'
|
12
12
|
spec.homepage = 'https://github.com/amatsuda/rfd'
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
20
|
spec.add_dependency 'ffi-ncurses'
|
21
|
+
spec.add_dependency 'rubyzip', '>= 1.0.0'
|
21
22
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
23
|
spec.add_development_dependency "rake"
|
23
24
|
spec.add_development_dependency 'rspec'
|
data/spec/controller_spec.rb
CHANGED
@@ -6,12 +6,16 @@ describe Rfd::Controller do
|
|
6
6
|
|
7
7
|
around do |example|
|
8
8
|
@stdout = capture(:stdout) do
|
9
|
-
|
9
|
+
FileUtils.cp_r File.join(__dir__, 'testdir'), tmpdir
|
10
|
+
@rfd = Rfd.start tmpdir
|
10
11
|
def (@rfd.main).maxy
|
11
12
|
3
|
12
13
|
end
|
13
14
|
|
14
15
|
example.run
|
16
|
+
|
17
|
+
FileUtils.rm_r tmpdir
|
18
|
+
Dir.chdir __dir__
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
@@ -19,7 +23,7 @@ describe Rfd::Controller do
|
|
19
23
|
Curses.endwin
|
20
24
|
end
|
21
25
|
|
22
|
-
let(:
|
26
|
+
let(:tmpdir) { File.join __dir__, 'tmpdir' }
|
23
27
|
let!(:controller) { @rfd }
|
24
28
|
subject { controller }
|
25
29
|
let(:items) { controller.items }
|
@@ -102,13 +106,13 @@ describe Rfd::Controller do
|
|
102
106
|
before do
|
103
107
|
controller.cd 'dir1'
|
104
108
|
end
|
105
|
-
its(:current_dir) { should == File.join(
|
109
|
+
its(:current_dir) { should == File.join(tmpdir, 'dir1') }
|
106
110
|
|
107
111
|
describe '#popd' do
|
108
112
|
before do
|
109
113
|
controller.popd
|
110
114
|
end
|
111
|
-
its(:current_dir) { should ==
|
115
|
+
its(:current_dir) { should == tmpdir }
|
112
116
|
end
|
113
117
|
end
|
114
118
|
|
@@ -122,30 +126,76 @@ describe Rfd::Controller do
|
|
122
126
|
|
123
127
|
describe '#sort' do
|
124
128
|
let(:item) do
|
125
|
-
Dir.mkdir File.join
|
126
|
-
|
129
|
+
Dir.mkdir File.join(tmpdir, '.a')
|
130
|
+
stat = File.lstat File.join(tmpdir, '.a')
|
131
|
+
Rfd::Item.new dir: tmpdir, name: '.a', stat: stat, window_width: 100
|
127
132
|
end
|
128
133
|
before do
|
129
134
|
controller.items << item
|
130
135
|
controller.sort
|
131
136
|
end
|
132
|
-
after do
|
133
|
-
Dir.rmdir File.join testdir, '.a'
|
134
|
-
end
|
135
137
|
subject { item }
|
136
138
|
its(:index) { should == 2 } # . .. then next
|
137
139
|
end
|
138
140
|
|
139
141
|
describe '#chmod' do
|
140
142
|
let(:item) { controller.items.detect {|i| !i.directory?} }
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
143
|
+
|
144
|
+
context 'With an octet string' do
|
145
|
+
before do
|
146
|
+
item.toggle_mark
|
147
|
+
controller.chmod '666'
|
148
|
+
end
|
149
|
+
subject { controller.items.detect {|i| !i.directory?} }
|
150
|
+
its(:mode) { should == '-rw-rw-rw-' }
|
146
151
|
end
|
152
|
+
|
153
|
+
context 'With a decimal string' do
|
154
|
+
before do
|
155
|
+
item.toggle_mark
|
156
|
+
controller.chmod '0666'
|
157
|
+
end
|
158
|
+
subject { controller.items.detect {|i| !i.directory?} }
|
159
|
+
its(:mode) { should == '-rw-rw-rw-' }
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'With a non-numeric string' do
|
163
|
+
before do
|
164
|
+
item.toggle_mark
|
165
|
+
controller.chmod 'a+w'
|
166
|
+
end
|
167
|
+
subject { controller.items.detect {|i| !i.directory?} }
|
168
|
+
its(:mode) { should == '-rw-rw-rw-' }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe '#chown' do
|
173
|
+
let(:item) { controller.items.detect {|i| !i.directory?} }
|
147
174
|
subject { item }
|
148
|
-
|
175
|
+
|
176
|
+
context 'With user name only' do
|
177
|
+
before do
|
178
|
+
expect(FileUtils).to receive(:chown).with('alice', nil, Array(item.path))
|
179
|
+
item.toggle_mark
|
180
|
+
end
|
181
|
+
specify { controller.chown 'alice' }
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'With group name only' do
|
185
|
+
before do
|
186
|
+
expect(FileUtils).to receive(:chown).with(nil, 'admin', Array(item.path))
|
187
|
+
item.toggle_mark
|
188
|
+
end
|
189
|
+
specify { controller.chown ':admin' }
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'With both user name and group name' do
|
193
|
+
before do
|
194
|
+
expect(FileUtils).to receive(:chown).with('nobody', 'nobody', Array(item.path))
|
195
|
+
item.toggle_mark
|
196
|
+
end
|
197
|
+
specify { controller.chown 'nobody:nobody' }
|
198
|
+
end
|
149
199
|
end
|
150
200
|
|
151
201
|
describe '#find' do
|
@@ -178,11 +228,8 @@ describe Rfd::Controller do
|
|
178
228
|
controller.find 'file1'
|
179
229
|
controller.cp 'file4'
|
180
230
|
end
|
181
|
-
after do
|
182
|
-
File.delete File.join(testdir, 'file4')
|
183
|
-
end
|
184
231
|
it 'should be the same file as the copy source file' do
|
185
|
-
File.read(File.join(
|
232
|
+
File.read(File.join(tmpdir, 'file1')).should == File.read(File.join(tmpdir, 'file4'))
|
186
233
|
end
|
187
234
|
end
|
188
235
|
|
@@ -191,12 +238,8 @@ describe Rfd::Controller do
|
|
191
238
|
controller.find 'file3'
|
192
239
|
controller.mv 'dir2'
|
193
240
|
end
|
194
|
-
|
195
|
-
|
196
|
-
end
|
197
|
-
it 'should move current file to the specified directory' do
|
198
|
-
File.exist?(File.join(testdir, 'dir2/file3')).should == true
|
199
|
-
end
|
241
|
+
subject { File }
|
242
|
+
it { should be_exist File.join(tmpdir, 'dir2/file3') }
|
200
243
|
end
|
201
244
|
|
202
245
|
describe '#rename' do
|
@@ -207,13 +250,33 @@ describe Rfd::Controller do
|
|
207
250
|
controller.toggle_mark
|
208
251
|
controller.rename 'fi/faaai'
|
209
252
|
end
|
210
|
-
|
211
|
-
|
212
|
-
|
253
|
+
subject { File }
|
254
|
+
it { should be_exist File.join(tmpdir, '.faaaile2') }
|
255
|
+
it { should be_exist File.join(tmpdir, 'faaaile3') }
|
256
|
+
end
|
257
|
+
|
258
|
+
describe '#trash' do
|
259
|
+
before do
|
260
|
+
controller.find 'file3'
|
261
|
+
controller.toggle_mark
|
262
|
+
controller.trash
|
263
|
+
end
|
264
|
+
it 'should be properly deleted from the current directory' do
|
265
|
+
controller.items.should be_none {|i| i.name == 'file3'}
|
213
266
|
end
|
214
|
-
|
215
|
-
|
216
|
-
|
267
|
+
end
|
268
|
+
|
269
|
+
describe '#delete' do
|
270
|
+
before do
|
271
|
+
controller.find 'file3'
|
272
|
+
controller.toggle_mark
|
273
|
+
controller.find 'dir2'
|
274
|
+
controller.toggle_mark
|
275
|
+
controller.delete
|
276
|
+
end
|
277
|
+
it 'should be properly deleted from the current directory' do
|
278
|
+
controller.items.should be_none {|i| i.name == 'file3'}
|
279
|
+
controller.items.should be_none {|i| i.name == 'dir2'}
|
217
280
|
end
|
218
281
|
end
|
219
282
|
|
@@ -221,24 +284,62 @@ describe Rfd::Controller do
|
|
221
284
|
before do
|
222
285
|
controller.mkdir 'aho'
|
223
286
|
end
|
224
|
-
|
225
|
-
|
226
|
-
end
|
227
|
-
it 'should create a new directory' do
|
228
|
-
Dir.exist?(File.join(testdir, 'aho')).should == true
|
229
|
-
end
|
287
|
+
subject { Dir }
|
288
|
+
it { should be_exist File.join(tmpdir, 'aho') }
|
230
289
|
end
|
231
290
|
|
232
291
|
describe '#touch' do
|
233
292
|
before do
|
234
293
|
controller.touch 'fuga'
|
235
294
|
end
|
236
|
-
|
237
|
-
|
295
|
+
subject { File }
|
296
|
+
it { should be_exist File.join(tmpdir, 'fuga') }
|
297
|
+
end
|
298
|
+
|
299
|
+
describe '#symlink' do
|
300
|
+
before do
|
301
|
+
controller.find 'dir1'
|
302
|
+
controller.symlink 'aaa'
|
238
303
|
end
|
239
|
-
|
240
|
-
|
304
|
+
subject { File }
|
305
|
+
it { should be_symlink File.join(tmpdir, 'aaa') }
|
306
|
+
end
|
307
|
+
|
308
|
+
describe '#pbcopy' do
|
309
|
+
before do
|
310
|
+
controller.find '.file1'
|
311
|
+
controller.toggle_mark
|
312
|
+
controller.find 'dir3'
|
313
|
+
controller.toggle_mark
|
314
|
+
controller.clipboard
|
315
|
+
end
|
316
|
+
it 'copies the selected paths into clipboard' do
|
317
|
+
`pbpaste`.should == "#{File.join(tmpdir, 'dir3')} #{File.join(tmpdir, '.file1')}"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
describe '#zip' do
|
322
|
+
before do
|
323
|
+
controller.find 'dir1'
|
324
|
+
controller.zip 'archive1'
|
325
|
+
end
|
326
|
+
subject { File }
|
327
|
+
it { should be_exist File.join(tmpdir, 'archive1.zip') }
|
328
|
+
end
|
329
|
+
|
330
|
+
describe '#unarchive' do
|
331
|
+
before do
|
332
|
+
controller.find 'zip1'
|
333
|
+
controller.toggle_mark
|
334
|
+
controller.find 'gz1'
|
335
|
+
controller.toggle_mark
|
336
|
+
controller.unarchive
|
241
337
|
end
|
338
|
+
subject { File }
|
339
|
+
it { should be_exist File.join(tmpdir, 'zip1/zip_content1') }
|
340
|
+
it { should be_exist File.join(tmpdir, 'zip1/zip_content_dir1/zip_content1_1') }
|
341
|
+
it { should be_exist File.join(tmpdir, 'gz1/gz_content1') }
|
342
|
+
it { should be_exist File.join(tmpdir, 'gz1/gz_content_dir1/gz_content1_1') }
|
242
343
|
end
|
243
344
|
|
244
345
|
describe '#first_page? and #last_page?' do
|
data/spec/spec_helper.rb
CHANGED
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rfd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Akira Matsuda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi-ncurses
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubyzip
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,7 +80,7 @@ dependencies:
|
|
66
80
|
- - '>='
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '0'
|
69
|
-
description: Ruby on Files
|
83
|
+
description: Ruby on Files & Directories
|
70
84
|
email:
|
71
85
|
- ronnie@dio.jp
|
72
86
|
executables:
|
@@ -95,8 +109,10 @@ files:
|
|
95
109
|
- spec/testdir/file1
|
96
110
|
- spec/testdir/file2
|
97
111
|
- spec/testdir/file3
|
112
|
+
- spec/testdir/gz1.tar.gz
|
98
113
|
- spec/testdir/link1
|
99
114
|
- spec/testdir/link2
|
115
|
+
- spec/testdir/zip1.zip
|
100
116
|
homepage: https://github.com/amatsuda/rfd
|
101
117
|
licenses:
|
102
118
|
- MIT
|
@@ -120,7 +136,7 @@ rubyforge_project:
|
|
120
136
|
rubygems_version: 2.0.3
|
121
137
|
signing_key:
|
122
138
|
specification_version: 4
|
123
|
-
summary: Ruby on Files
|
139
|
+
summary: Ruby on Files & Directories
|
124
140
|
test_files:
|
125
141
|
- spec/controller_spec.rb
|
126
142
|
- spec/spec_helper.rb
|
@@ -132,6 +148,8 @@ test_files:
|
|
132
148
|
- spec/testdir/file1
|
133
149
|
- spec/testdir/file2
|
134
150
|
- spec/testdir/file3
|
151
|
+
- spec/testdir/gz1.tar.gz
|
135
152
|
- spec/testdir/link1
|
136
153
|
- spec/testdir/link2
|
154
|
+
- spec/testdir/zip1.zip
|
137
155
|
has_rdoc:
|