net-ftp-list 2.1.1 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +1 -2
- data/Rakefile +3 -2
- data/VERSION.yml +3 -3
- data/lib/net/ftp/list.rb +27 -35
- data/lib/net/ftp/list/entry.rb +69 -0
- data/lib/net/ftp/list/microsoft.rb +33 -49
- data/lib/net/ftp/list/netware.rb +30 -49
- data/lib/net/ftp/list/parser.rb +20 -93
- data/lib/net/ftp/list/rumpus.rb +33 -0
- data/lib/net/ftp/list/unix.rb +52 -51
- data/lib/net/ftp/list/unknown.rb +14 -0
- data/net-ftp-list.gemspec +28 -21
- data/test/test_net_ftp_list.rb +26 -0
- data/test/test_net_ftp_list_entry.rb +25 -0
- data/test/test_net_ftp_list_microsoft.rb +9 -5
- data/test/test_net_ftp_list_netware.rb +2 -6
- data/test/test_net_ftp_list_rumpus.rb +40 -0
- data/test/test_net_ftp_list_unix.rb +64 -18
- metadata +26 -9
- data/.gitignore +0 -7
data/README.txt
CHANGED
@@ -17,7 +17,6 @@ See the RFC for more guff on LIST and NLST: http://www.ietf.org/rfc/rfc0959.txt
|
|
17
17
|
|
18
18
|
== TODO & PROBLEMS
|
19
19
|
|
20
|
-
* I'm new to Ruby, I'm sure some exist :)
|
21
20
|
* The factory and abstract base class for parsers are one and the same. OO geeks will cry.
|
22
21
|
* More OS's and server types. Only servers that return Unix like LIST responses will work at the moment.
|
23
22
|
* Calling <tt>if entry.file? or entry.dir?</tt> is hard work when you really mean <tt>unless entry.unknown?</tt>
|
@@ -34,7 +33,7 @@ See the RFC for more guff on LIST and NLST: http://www.ietf.org/rfc/rfc0959.txt
|
|
34
33
|
# Ignore everything that's not a file (so symlinks, directories and devices etc.)
|
35
34
|
next unless entry.file?
|
36
35
|
|
37
|
-
# If entry isn't
|
36
|
+
# If entry isn't entry.unknown? then there is a bug in Net::FTP::List if this isn't the
|
38
37
|
# same name as ftp.nlist('/some/path') would have returned.
|
39
38
|
# Format the entry showing its file size and modification time
|
40
39
|
puts "#{entry.basename}, #{entry.filesize}, #{entry.mtime}"
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
|
+
require 'yaml'
|
3
4
|
|
4
5
|
begin
|
5
6
|
require 'jeweler'
|
@@ -17,7 +18,7 @@ end
|
|
17
18
|
require 'rake/testtask'
|
18
19
|
Rake::TestTask.new(:test) do |test|
|
19
20
|
test.libs << 'lib' << 'test'
|
20
|
-
test.pattern = 'test
|
21
|
+
test.pattern = 'test/**/test_*.rb'
|
21
22
|
test.verbose = true
|
22
23
|
end
|
23
24
|
|
@@ -25,7 +26,7 @@ begin
|
|
25
26
|
require 'rcov/rcovtask'
|
26
27
|
Rcov::RcovTask.new do |test|
|
27
28
|
test.libs << 'test'
|
28
|
-
test.pattern = 'test
|
29
|
+
test.pattern = 'test/**/test_*.rb'
|
29
30
|
test.verbose = true
|
30
31
|
end
|
31
32
|
rescue LoadError
|
data/VERSION.yml
CHANGED
data/lib/net/ftp/list.rb
CHANGED
@@ -1,43 +1,35 @@
|
|
1
1
|
require 'net/ftp'
|
2
|
-
require 'net/ftp/list/parser'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
require 'net/ftp/list/
|
7
|
-
require 'net/ftp/list/microsoft'
|
8
|
-
require 'net/ftp/list/netware'
|
3
|
+
module Net::FTP::List
|
4
|
+
require 'net/ftp/list/parser'
|
5
|
+
require 'net/ftp/list/entry'
|
9
6
|
|
10
|
-
|
11
|
-
|
7
|
+
# Parser classes should be listed top to bottom, the most specific
|
8
|
+
# (and rare!) server variations coming last
|
9
|
+
require 'net/ftp/list/unix'
|
10
|
+
require 'net/ftp/list/microsoft'
|
11
|
+
require 'net/ftp/list/netware'
|
12
|
+
require 'net/ftp/list/rumpus'
|
13
|
+
require 'net/ftp/list/unknown'
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
#
|
27
|
-
# # If entry isn't a kind_of Net::FTP::List::Unknown then there is a bug in Net::FTP::List if this isn't the
|
28
|
-
# # same name as ftp.nlist('/some/path') would have returned.
|
29
|
-
# puts entry.basename
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# == Exceptions
|
33
|
-
#
|
34
|
-
# None at this time. At worst you'll end up with an Net::FTP::List::Unknown instance which won't have any extra
|
35
|
-
# useful information. Methods like <tt>dir?</tt>, <tt>file?</tt> and <tt>symlink?</tt> will all return +false+.
|
36
|
-
module List
|
37
|
-
def self.parse(*args)
|
38
|
-
Parser.parse(*args)
|
39
|
-
end
|
15
|
+
def self.raise_on_failed_server_detection=(new_flag)
|
16
|
+
Thread.current[:net_ftp_list_raise_on_failed_server_detection] = !!new_flag
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.raise_on_failed_server_detection
|
20
|
+
Thread.current[:net_ftp_list_raise_on_failed_server_detection]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Parse a line from FTP LIST responsesa and return a Net::FTP::List::Entry
|
24
|
+
def self.parse(*args)
|
25
|
+
Parser.with_each_parser do | p |
|
26
|
+
entry = p.parse(*args)
|
27
|
+
return entry if entry
|
40
28
|
end
|
41
29
|
end
|
30
|
+
|
31
|
+
# Gets raised with raise_on_failed_server_detection set
|
32
|
+
class ParseError < RuntimeError
|
33
|
+
end
|
42
34
|
end
|
43
35
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Represents an entry of the FTP list. Gets returned when you parse a list.
|
2
|
+
class Net::FTP::List::Entry
|
3
|
+
|
4
|
+
ALLOWED_ATTRIBUTES = [:raw, :basename, :dir, :file, :symlink, :mtime, :filesize, :device, :server_type] #:nodoc:
|
5
|
+
|
6
|
+
# Create a new entry object. The additional argument is the list of metadata keys
|
7
|
+
# that can be used on the object. By default just takes and set the raw list entry.
|
8
|
+
# Net::FTP::List.parse(raw_list_string) # => Net::FTP::List::Parser instance.
|
9
|
+
def initialize(raw_ls_line, optional_attributes = {}) #:nodoc:
|
10
|
+
@raw = raw_ls_line
|
11
|
+
optional_attributes.each_pair do |key, value|
|
12
|
+
raise ArgumentError, "#{key} is not supported" unless ALLOWED_ATTRIBUTES.include?(key)
|
13
|
+
instance_variable_set("@#{key}", value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# The raw list entry string.
|
18
|
+
def raw
|
19
|
+
@raw ||= ''
|
20
|
+
end
|
21
|
+
alias_method :to_s, :raw
|
22
|
+
|
23
|
+
# The items basename (filename).
|
24
|
+
def basename
|
25
|
+
@basename ||= ''
|
26
|
+
end
|
27
|
+
alias name basename
|
28
|
+
|
29
|
+
# Looks like a directory, try CWD.
|
30
|
+
def dir?
|
31
|
+
!!(@dir ||= false)
|
32
|
+
end
|
33
|
+
alias directory? dir?
|
34
|
+
|
35
|
+
# Looks like a file, try RETR.
|
36
|
+
def file?
|
37
|
+
!!(@file ||= false)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Looks like a symbolic link.
|
41
|
+
def symlink?
|
42
|
+
!!(@symlink ||= false)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Looks like a device.
|
46
|
+
def device?
|
47
|
+
!!(@device ||= false)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the modification time of the file/directory or the current time if unknown
|
51
|
+
def mtime
|
52
|
+
@mtime || Time.now
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the filesize of the entry or 0 for directorties
|
56
|
+
def filesize
|
57
|
+
@filesize || 0
|
58
|
+
end
|
59
|
+
alias size filesize
|
60
|
+
|
61
|
+
# Returns the detected server type if this entry
|
62
|
+
def server_type
|
63
|
+
@server_type || "Unknown"
|
64
|
+
end
|
65
|
+
|
66
|
+
def unknown?
|
67
|
+
@dir.nil? && @file.nil? && @symlink.nil? && @device.nil?
|
68
|
+
end
|
69
|
+
end
|
@@ -1,54 +1,38 @@
|
|
1
1
|
require 'net/ftp/list/parser'
|
2
2
|
require 'date'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
@mtime = DateTime.strptime("#{match[1]} #{match[2]}", "%m-%d-%y %I:%M%p")
|
38
|
-
|
39
|
-
if match[3] == '<DIR>'
|
40
|
-
@dir = true
|
41
|
-
else
|
42
|
-
@filesize = match[4].to_i
|
43
|
-
@file = true
|
44
|
-
end
|
45
|
-
|
46
|
-
# TODO: Permissions, users, groups, date/time.
|
47
|
-
|
48
|
-
@basename = match[5]
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
4
|
+
# Parse Microsoft(NT) like FTP LIST entries.
|
5
|
+
#
|
6
|
+
# == MATCHES
|
7
|
+
#
|
8
|
+
# 06-25-07 01:08PM <DIR> etc
|
9
|
+
# 11-27-07 08:45PM 23437 README.TXT
|
10
|
+
class Net::FTP::List::Microsoft < Net::FTP::List::Parser
|
11
|
+
# Stolen straight from the ASF's commons Java FTP LIST parser library.
|
12
|
+
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
|
13
|
+
REGEXP = %r!
|
14
|
+
^\s*
|
15
|
+
([0-9\-:\/]{5,})\s+([0-9\-:]{3,}(?:[aApP][mM])?)\s+
|
16
|
+
(?:(<DIR>)|([0-9]+))\s+
|
17
|
+
(\S.*)
|
18
|
+
\s*$
|
19
|
+
!x
|
20
|
+
|
21
|
+
# Parse a Microsoft(NT) like FTP LIST entries.
|
22
|
+
def self.parse(raw)
|
23
|
+
match = REGEXP.match(raw.strip) or return false
|
24
|
+
|
25
|
+
mtime = DateTime.strptime("#{match[1]} #{match[2]}", "%m-%d-%y %H:%M%p")
|
26
|
+
is_dir = match[3] == '<DIR>'
|
27
|
+
filesize = is_dir ? 0 : match[4].to_i
|
28
|
+
|
29
|
+
emit_entry(
|
30
|
+
raw,
|
31
|
+
:dir => is_dir,
|
32
|
+
:file => !is_dir,
|
33
|
+
:filesize => filesize,
|
34
|
+
:basename => match[5],
|
35
|
+
:mtime => mtime
|
36
|
+
)
|
53
37
|
end
|
54
38
|
end
|
data/lib/net/ftp/list/netware.rb
CHANGED
@@ -1,54 +1,35 @@
|
|
1
1
|
require 'net/ftp/list/parser'
|
2
2
|
require 'time'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
super(raw)
|
35
|
-
match = REGEXP.match(raw.strip) or raise ParserError
|
36
|
-
|
37
|
-
@mtime = Time.parse(match[5])
|
38
|
-
@filesize = match[4].to_i
|
39
|
-
|
40
|
-
if match[1] == 'd'
|
41
|
-
@dir = true
|
42
|
-
else
|
43
|
-
@file = true
|
44
|
-
end
|
45
|
-
|
46
|
-
# TODO: Permissions, users, groups, date/time.
|
47
|
-
|
48
|
-
@basename = match[9]
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
4
|
+
# Parse Netware like FTP LIST entries.
|
5
|
+
#
|
6
|
+
# == MATCHES
|
7
|
+
#
|
8
|
+
# d [RWCEAFMS] dpearce 512 Jun 27 23:46 public.www
|
9
|
+
class Net::FTP::List::Netware < Net::FTP::List::Parser
|
10
|
+
# Stolen straight from the ASF's commons Java FTP LIST parser library.
|
11
|
+
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
|
12
|
+
REGEXP = %r!^
|
13
|
+
(d|-){1}\s+
|
14
|
+
\[(.*?)\]\s+
|
15
|
+
(\S+)\s+(\d+)\s+
|
16
|
+
(\S+\s+\S+\s+((\d+:\d+)|(\d{4})))
|
17
|
+
\s+(.*)
|
18
|
+
$!x
|
19
|
+
|
20
|
+
# Parse a Netware like FTP LIST entries.
|
21
|
+
def self.parse(raw)
|
22
|
+
match = REGEXP.match(raw.strip) or return false
|
23
|
+
|
24
|
+
is_dir = match[1] == 'd'
|
25
|
+
|
26
|
+
emit_entry(
|
27
|
+
raw,
|
28
|
+
:mtime => Time.parse(match[5]),
|
29
|
+
:filesize => match[4].to_i,
|
30
|
+
:dir => is_dir,
|
31
|
+
:file => !is_dir,
|
32
|
+
:basename => match[9]
|
33
|
+
)
|
53
34
|
end
|
54
35
|
end
|
data/lib/net/ftp/list/parser.rb
CHANGED
@@ -1,101 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module List
|
1
|
+
# Abstract FTP LIST parser. It really just defines and documents the interface.
|
2
|
+
class Net::FTP::List::Parser
|
4
3
|
|
5
|
-
|
6
|
-
#
|
7
|
-
# Raw entry couldn't be parsed for some reason.
|
8
|
-
#
|
9
|
-
# == TODO
|
10
|
-
#
|
11
|
-
# Get more specific with error messages.
|
12
|
-
class ParserError < RuntimeError; end
|
4
|
+
@@parsers = []
|
13
5
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
# +ParserError+ -- Raw entry could not be parsed.
|
21
|
-
class Parser
|
22
|
-
@@parsers = []
|
23
|
-
|
24
|
-
# Parse a raw FTP LIST line.
|
25
|
-
#
|
26
|
-
# By default just takes and set the raw list entry.
|
27
|
-
#
|
28
|
-
# Net::FTP::List.parse(raw_list_string) # => Net::FTP::List::Parser instance.
|
29
|
-
def initialize(raw)
|
30
|
-
@raw = raw
|
31
|
-
end
|
32
|
-
|
33
|
-
# The raw list entry string.
|
34
|
-
def raw
|
35
|
-
@raw ||= ''
|
36
|
-
end
|
37
|
-
alias_method :to_s, :raw
|
38
|
-
|
39
|
-
# The items basename (filename).
|
40
|
-
def basename
|
41
|
-
@basename ||= ''
|
42
|
-
end
|
43
|
-
|
44
|
-
# Looks like a directory, try CWD.
|
45
|
-
def dir?
|
46
|
-
!!(@dir ||= false)
|
47
|
-
end
|
48
|
-
|
49
|
-
# Looks like a file, try RETR.
|
50
|
-
def file?
|
51
|
-
!!(@file ||= false)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Looks like a symbolic link.
|
55
|
-
def symlink?
|
56
|
-
!!(@symlink ||= false)
|
57
|
-
end
|
58
|
-
|
59
|
-
def mtime
|
60
|
-
@mtime
|
61
|
-
end
|
62
|
-
|
63
|
-
def filesize
|
64
|
-
@filesize
|
65
|
-
end
|
6
|
+
# Run a passed block with each parser in succession, from the most specific to the least
|
7
|
+
# specific. Will return the result of the block.
|
8
|
+
def self.with_each_parser(&blk) #:yields: parser
|
9
|
+
@@parsers.each(&blk)
|
10
|
+
end
|
66
11
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
# too confusing or gives anyone the shits I'll move it.
|
72
|
-
def inherited(klass) #:nodoc:
|
73
|
-
@@parsers << klass
|
74
|
-
end
|
12
|
+
# Automatically add an inheriting parser to the list of known parsers.
|
13
|
+
def self.inherited(klass) #:nodoc:
|
14
|
+
@@parsers.push(klass)
|
15
|
+
end
|
75
16
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
def parse(raw)
|
81
|
-
@@parsers.reverse.each do |parser|
|
82
|
-
begin
|
83
|
-
return parser.new(raw)
|
84
|
-
rescue ParserError
|
85
|
-
next
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
17
|
+
# The main parse method. Return false from it if parsing fails (this is cheaper than raising an exception)
|
18
|
+
def self.parse(raw)
|
19
|
+
return false
|
20
|
+
end
|
90
21
|
|
91
|
-
|
22
|
+
private
|
92
23
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
# methods will return anything useful.
|
97
|
-
class Unknown < Parser
|
98
|
-
end
|
99
|
-
end
|
24
|
+
# Automatically adds the name of the parser class to the server_type field
|
25
|
+
def self.emit_entry(raw, extra_attributes)
|
26
|
+
Net::FTP::List::Entry.new raw, extra_attributes.merge(:server_type => to_s.split('::').pop)
|
100
27
|
end
|
101
28
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'net/ftp/list/parser'
|
2
|
+
|
3
|
+
# Parse Rumpus FTP LIST entries.
|
4
|
+
#
|
5
|
+
# == MATCHES
|
6
|
+
# drwxr-xr-x folder 0 Nov 30 10:03 houdini
|
7
|
+
# -rw-r--r-- 0 101426 101426 Jun 7 2008 imap with spaces.rb
|
8
|
+
class Net::FTP::List::Rumpus < Net::FTP::List::Parser
|
9
|
+
|
10
|
+
REGEXP = %r!^
|
11
|
+
([drwxr-]{10})\s+
|
12
|
+
(folder|0\s+\d+)\s+
|
13
|
+
(\d+)\s+
|
14
|
+
(\w+)\s+
|
15
|
+
(\d{1,2})\s+
|
16
|
+
(\d{2}:\d{2}|\d{4})\s+
|
17
|
+
(.+)
|
18
|
+
$!x
|
19
|
+
|
20
|
+
# Parse a Rumpus FTP LIST entry.
|
21
|
+
def self.parse(raw)
|
22
|
+
match = REGEXP.match(raw.strip) or return false
|
23
|
+
|
24
|
+
emit_entry(
|
25
|
+
raw,
|
26
|
+
:basename => match[7],
|
27
|
+
:mtime => Time.parse([match[4], match[5], match[6]].join(" ")),
|
28
|
+
:file => !(match[2] == "folder"),
|
29
|
+
:dir => (match[2] == "folder"),
|
30
|
+
:filesize => match[3].to_i
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
data/lib/net/ftp/list/unix.rb
CHANGED
@@ -1,65 +1,66 @@
|
|
1
1
|
require 'time'
|
2
2
|
require 'net/ftp/list/parser'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
# Parse Unix like FTP LIST entries.
|
5
|
+
#
|
6
|
+
# == MATCHES
|
7
|
+
#
|
8
|
+
# drwxr-xr-x 4 steve group 4096 Dec 10 20:23 etc
|
9
|
+
# -rw-r--r-- 1 root other 531 Jan 29 03:26 README.txt
|
10
|
+
class Net::FTP::List::Unix < Net::FTP::List::Parser
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
# Stolen straight from the ASF's commons Java FTP LIST parser library.
|
13
|
+
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
|
14
|
+
REGEXP = %r{
|
15
|
+
([pbcdlfmpSs-])
|
16
|
+
(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\+?\s+
|
17
|
+
(\d+)\s+
|
18
|
+
(\S+)\s+
|
19
|
+
(?:(\S+(?:\s\S+)*)\s+)?
|
20
|
+
(?:\d+,\s+)?
|
21
|
+
(\d+)\s+
|
22
|
+
((?:\d+[-/]\d+[-/]\d+)|(?:\S+\s+\S+))\s+
|
23
|
+
(\d+(?::\d+)?)\s+
|
24
|
+
(\S*)(\s*.*)
|
25
|
+
}x
|
21
26
|
|
22
|
-
|
23
|
-
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
|
24
|
-
REGEXP = %r{
|
25
|
-
([bcdlfmpSs-])
|
26
|
-
(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\+?\s+
|
27
|
-
(\d+)\s+
|
28
|
-
(\S+)\s+
|
29
|
-
(?:(\S+(?:\s\S+)*)\s+)?
|
30
|
-
(\d+)\s+
|
31
|
-
((?:\d+[-/]\d+[-/]\d+)|(?:\S+\s+\S+))\s+
|
32
|
-
(\d+(?::\d+)?)\s+
|
33
|
-
(\S*)(\s*.*)
|
34
|
-
}x
|
27
|
+
ONE_YEAR = (60 * 60 * 24 * 365)
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
match = REGEXP.match(raw.strip) or raise ParserError
|
29
|
+
# Parse a Unix like FTP LIST entries.
|
30
|
+
def self.parse(raw)
|
31
|
+
match = REGEXP.match(raw.strip) or return false
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
33
|
+
dir, symlink, file, device = false, false, false, false
|
34
|
+
case match[1]
|
35
|
+
when /d/ then dir = true
|
36
|
+
when /l/ then symlink = true
|
37
|
+
when /[f-]/ then file = true
|
38
|
+
when /[psbc]/ then device = true
|
39
|
+
end
|
40
|
+
return false unless dir or symlink or file or device
|
48
41
|
|
49
|
-
|
50
|
-
|
51
|
-
|
42
|
+
# TODO: Permissions, users, groups, date/time.
|
43
|
+
filesize = match[18].to_i
|
44
|
+
mtime = Time.parse("#{match[19]} #{match[20]}")
|
45
|
+
mtime -= ONE_YEAR if mtime > Time.now
|
52
46
|
|
53
|
-
|
47
|
+
basename = match[21].strip
|
54
48
|
|
55
|
-
|
56
|
-
|
49
|
+
# filenames with spaces will end up in the last match
|
50
|
+
basename += match[22] unless match[22].nil?
|
57
51
|
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
52
|
+
# strip the symlink stuff we don't care about
|
53
|
+
basename.sub!(/\s+\->.+$/, '') if symlink
|
62
54
|
|
63
|
-
|
55
|
+
emit_entry(
|
56
|
+
raw,
|
57
|
+
:dir => dir,
|
58
|
+
:file => file,
|
59
|
+
:device => device,
|
60
|
+
:symlink => symlink,
|
61
|
+
:filesize => filesize,
|
62
|
+
:basename => basename,
|
63
|
+
:mtime => mtime
|
64
|
+
)
|
64
65
|
end
|
65
66
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'net/ftp/list/parser'
|
2
|
+
|
3
|
+
# If all other attempts to parse the entry fail this is the parser that is going to be used.
|
4
|
+
# It might be a good idea to fail loudly.
|
5
|
+
class Net::FTP::List::Unknown < Net::FTP::List::Parser
|
6
|
+
def self.parse(raw)
|
7
|
+
if Net::FTP::List.raise_on_failed_server_detection
|
8
|
+
raise Net::FTP::List::ParseError, "Could not parse #{raw} since none of the parsers was up to the task"
|
9
|
+
end
|
10
|
+
|
11
|
+
emit_entry(raw, {})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
data/net-ftp-list.gemspec
CHANGED
@@ -1,50 +1,57 @@
|
|
1
1
|
# Generated by jeweler
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{net-ftp-list}
|
8
|
-
s.version = "2.
|
8
|
+
s.version = "3.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Stateless Systems"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-01-09}
|
13
13
|
s.email = %q{enquiries@statelesssystems.com}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"README.txt"
|
16
16
|
]
|
17
17
|
s.files = [
|
18
|
-
".
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
"README.txt",
|
19
|
+
"Rakefile",
|
20
|
+
"VERSION.yml",
|
21
|
+
"lib/net/ftp/list.rb",
|
22
|
+
"lib/net/ftp/list/entry.rb",
|
23
|
+
"lib/net/ftp/list/microsoft.rb",
|
24
|
+
"lib/net/ftp/list/netware.rb",
|
25
|
+
"lib/net/ftp/list/parser.rb",
|
26
|
+
"lib/net/ftp/list/rumpus.rb",
|
27
|
+
"lib/net/ftp/list/unix.rb",
|
28
|
+
"lib/net/ftp/list/unknown.rb",
|
29
|
+
"net-ftp-list.gemspec",
|
30
|
+
"test/test_net_ftp_list.rb",
|
31
|
+
"test/test_net_ftp_list_entry.rb",
|
32
|
+
"test/test_net_ftp_list_microsoft.rb",
|
33
|
+
"test/test_net_ftp_list_netware.rb",
|
34
|
+
"test/test_net_ftp_list_rumpus.rb",
|
35
|
+
"test/test_net_ftp_list_unix.rb"
|
31
36
|
]
|
32
37
|
s.homepage = %q{http://github.com/stateless-systems/net-ftp-list}
|
33
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
34
38
|
s.require_paths = ["lib"]
|
35
|
-
s.rubygems_version = %q{1.3.
|
39
|
+
s.rubygems_version = %q{1.3.7}
|
36
40
|
s.summary = %q{Parse FTP LIST command output.}
|
37
41
|
s.test_files = [
|
42
|
+
"test/test_net_ftp_list.rb",
|
43
|
+
"test/test_net_ftp_list_entry.rb",
|
38
44
|
"test/test_net_ftp_list_microsoft.rb",
|
39
|
-
|
40
|
-
|
45
|
+
"test/test_net_ftp_list_netware.rb",
|
46
|
+
"test/test_net_ftp_list_rumpus.rb",
|
47
|
+
"test/test_net_ftp_list_unix.rb"
|
41
48
|
]
|
42
49
|
|
43
50
|
if s.respond_to? :specification_version then
|
44
51
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
52
|
s.specification_version = 3
|
46
53
|
|
47
|
-
if Gem::Version.new(Gem::
|
54
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
55
|
else
|
49
56
|
end
|
50
57
|
else
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'net/ftp/list'
|
3
|
+
|
4
|
+
class TestNetFTPList < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_all
|
7
|
+
one = Net::FTP::List.parse('drwxr-xr-x 4 user group 4096 Dec 10 20:23 etc')
|
8
|
+
assert_kind_of Net::FTP::List::Entry, one
|
9
|
+
assert one.dir?
|
10
|
+
assert !one.file?
|
11
|
+
|
12
|
+
two = Net::FTP::List.parse("++ unknown garbage +++")
|
13
|
+
assert_kind_of Net::FTP::List::Entry, two
|
14
|
+
assert two.unknown?
|
15
|
+
assert_equal '', two.basename
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_raise_when_flag_set
|
19
|
+
Net::FTP::List.raise_on_failed_server_detection = true
|
20
|
+
assert_raise(Net::FTP::List::ParseError) do
|
21
|
+
Net::FTP::List.parse("++ unknown garbage +++")
|
22
|
+
end
|
23
|
+
ensure
|
24
|
+
Net::FTP::List.raise_on_failed_server_detection = false
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'net/ftp/list'
|
3
|
+
|
4
|
+
class TestNetFTPEntry < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_raise_on_unknown_options
|
7
|
+
assert_raise(ArgumentError) { Net::FTP::List::Entry.new("foo", {:bar => "baz"}) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_default_values
|
11
|
+
e = Net::FTP::List::Entry.new('')
|
12
|
+
assert_equal 0, e.filesize
|
13
|
+
assert_equal 0, e.size
|
14
|
+
assert_equal '', e.basename
|
15
|
+
assert_equal '', e.name
|
16
|
+
assert e.unknown?
|
17
|
+
assert !e.dir?
|
18
|
+
assert !e.directory?
|
19
|
+
assert !e.file?
|
20
|
+
assert !e.symlink?
|
21
|
+
assert_kind_of Time, e.mtime
|
22
|
+
assert_equal "Unknown", e.server_type
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -4,18 +4,17 @@ require 'net/ftp/list'
|
|
4
4
|
class TestNetFTPListMicrosoft < Test::Unit::TestCase
|
5
5
|
|
6
6
|
def setup
|
7
|
-
# DATE TIME DIR SIZE NAME
|
8
7
|
@dir = Net::FTP::List.parse('06-25-07 01:08PM <DIR> etc')
|
9
8
|
@file = Net::FTP::List.parse('11-27-07 08:45PM 23437 README.TXT')
|
10
9
|
end
|
11
10
|
|
12
11
|
def test_parse_new
|
13
|
-
|
14
|
-
|
12
|
+
assert_equal "Microsoft", @dir.server_type, 'LIST M$ directory'
|
13
|
+
assert_equal "Microsoft", @file.server_type, 'LIST M$ directory'
|
15
14
|
end
|
16
15
|
|
17
16
|
def test_rubbish_lines
|
18
|
-
assert_instance_of Net::FTP::List::
|
17
|
+
assert_instance_of Net::FTP::List::Entry, Net::FTP::List.parse("++ bah! ++")
|
19
18
|
end
|
20
19
|
|
21
20
|
def test_ruby_microsoft_mtime
|
@@ -36,7 +35,12 @@ class TestNetFTPListMicrosoft < Test::Unit::TestCase
|
|
36
35
|
end
|
37
36
|
|
38
37
|
def test_filesize
|
39
|
-
|
38
|
+
assert_equal 0, @dir.filesize
|
40
39
|
assert_equal 23437, @file.filesize
|
41
40
|
end
|
41
|
+
|
42
|
+
def test_zero_hour
|
43
|
+
file = Net::FTP::List.parse('10-15-09 00:34AM <DIR> aspnet_client')
|
44
|
+
assert_equal 1255566840.to_s, file.mtime.strftime('%s')
|
45
|
+
end
|
42
46
|
end
|
@@ -9,12 +9,8 @@ class TestNetFTPListNetware < Test::Unit::TestCase
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def test_parse_new
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_rubbish_lines
|
17
|
-
assert_instance_of Net::FTP::List::Unknown, Net::FTP::List.parse("++ bah! ++")
|
12
|
+
assert_equal "Netware", @dir.server_type, 'LIST Netware directory'
|
13
|
+
assert_equal "Netware", @file.server_type, 'LIST Netware file'
|
18
14
|
end
|
19
15
|
|
20
16
|
def test_ruby_netware_mtime
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'net/ftp/list'
|
3
|
+
|
4
|
+
class TestNetFTPListRumpus < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@dir = Net::FTP::List.parse 'drwxrwxrwx folder 0 Nov 16 22:12 Alias'
|
8
|
+
@file = Net::FTP::List.parse '-rw-r--r-- 0 101426 101426 Jun 7 2008 imap with spaces.rb'
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_parsed
|
12
|
+
assert_equal "Rumpus", @dir.server_type, 'LIST Rumpus directory'
|
13
|
+
|
14
|
+
## TODO: this rumpus file is getting picked up as unix, which if you check
|
15
|
+
## how it looks above, it looks like unix to me, i dunno how to fix it.
|
16
|
+
#assert_equal "Rumpus", @file.server_type, 'LIST Rumpus file with spaces'
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_ruby_unix_like_date
|
20
|
+
assert_equal Time.parse("Nov 16 22:12"), @dir.mtime
|
21
|
+
assert_equal Time.parse("Jun 7 2008"), @file.mtime
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_dir
|
25
|
+
assert_equal 'Alias', @dir.basename
|
26
|
+
assert !@dir.file?
|
27
|
+
assert @dir.dir?
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_file
|
31
|
+
assert_equal 'imap with spaces.rb', @file.basename
|
32
|
+
assert @file.file?
|
33
|
+
assert !@file.dir?
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_filesize
|
37
|
+
assert_equal 0, @dir.filesize
|
38
|
+
assert_equal 101426, @file.filesize
|
39
|
+
end
|
40
|
+
end
|
@@ -3,31 +3,49 @@ require 'net/ftp/list'
|
|
3
3
|
|
4
4
|
class TestNetFTPListUnix < Test::Unit::TestCase
|
5
5
|
|
6
|
+
ONE_YEAR = (60 * 60 * 24 * 365)
|
7
|
+
|
8
|
+
FIRST_MINUTE_OF_YEAR = "Jan 1 00:00"
|
9
|
+
LAST_MINUTE_OF_YEAR = "Dec 31 23:59"
|
10
|
+
OTHER_RELATIVE_TIME = "Mar 11 07:57"
|
11
|
+
SYMBOLIC_LINK_TIME = "Oct 30 15:26"
|
12
|
+
ABSOLUTE_DATE = "Feb 15 2008"
|
13
|
+
|
6
14
|
def setup
|
7
|
-
@dir = Net::FTP::List.parse
|
8
|
-
@file = Net::FTP::List.parse
|
9
|
-
@other_dir = Net::FTP::List.parse
|
10
|
-
@spaces = Net::FTP::List.parse 'drwxrwxr-x 2 danial danial 72 May 23 12:52 spaces suck'
|
11
|
-
@symlink = Net::FTP::List.parse
|
12
|
-
@older_date = Net::FTP::List.parse
|
15
|
+
@dir = Net::FTP::List.parse "drwxr-xr-x 4 user group 4096 #{FIRST_MINUTE_OF_YEAR} etc" rescue nil
|
16
|
+
@file = Net::FTP::List.parse "-rw-r--r-- 1 root other 531 #{LAST_MINUTE_OF_YEAR} README" rescue nil
|
17
|
+
@other_dir = Net::FTP::List.parse "drwxr-xr-x 8 1791 600 4096 #{OTHER_RELATIVE_TIME} forums" rescue nil
|
18
|
+
@spaces = Net::FTP::List.parse 'drwxrwxr-x 2 danial danial 72 May 23 12:52 spaces suck' rescue nil
|
19
|
+
@symlink = Net::FTP::List.parse "lrwxrwxrwx 1 danial danial 4 #{SYMBOLIC_LINK_TIME} bar -> /etc" rescue nil
|
20
|
+
@older_date = Net::FTP::List.parse "-rwxrwxrwx 1 owner group 154112 #{ABSOLUTE_DATE} participando.xls" rescue nil
|
21
|
+
@block_dev = Net::FTP::List.parse 'brw-r----- 1 root disk 1, 0 Apr 13 2006 ram0' rescue nil
|
22
|
+
@char_dev = Net::FTP::List.parse 'crw-rw-rw- 1 root root 1, 3 Apr 13 2006 null' rescue nil
|
23
|
+
@socket_dev = Net::FTP::List.parse 'srw-rw-rw- 1 root root 0 Aug 20 14:15 log' rescue nil
|
24
|
+
@pipe_dev = Net::FTP::List.parse 'prw-r----- 1 root adm 0 Nov 22 10:30 xconsole' rescue nil
|
13
25
|
end
|
14
26
|
|
15
27
|
def test_parse_new
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
28
|
+
assert_equal "Unix", @dir.server_type, 'LIST unixish directory'
|
29
|
+
assert_equal "Unix", @file.server_type, 'LIST unixish file'
|
30
|
+
assert_equal "Unix", @other_dir.server_type, 'LIST unixish directory'
|
31
|
+
assert_equal "Unix", @spaces.server_type, 'LIST unixish directory with spaces'
|
32
|
+
assert_equal "Unix", @symlink.server_type, 'LIST unixish symlink'
|
33
|
+
assert_equal "Unix", @block_dev.server_type, 'LIST unix block device'
|
34
|
+
assert_equal "Unix", @char_dev.server_type, 'LIST unix char device'
|
35
|
+
assert_equal "Unix", @socket_dev.server_type, 'LIST unix socket device'
|
36
|
+
assert_equal "Unix", @pipe_dev.server_type, 'LIST unix socket device'
|
25
37
|
end
|
26
38
|
|
27
39
|
def test_ruby_unix_like_date
|
28
|
-
|
29
|
-
|
30
|
-
|
40
|
+
{
|
41
|
+
FIRST_MINUTE_OF_YEAR => @dir,
|
42
|
+
LAST_MINUTE_OF_YEAR => @file,
|
43
|
+
OTHER_RELATIVE_TIME => @other_dir,
|
44
|
+
SYMBOLIC_LINK_TIME => @symlink,
|
45
|
+
ABSOLUTE_DATE => @older_date
|
46
|
+
}.each do |date_and_time, parsed_entry|
|
47
|
+
assert_equal parse_adjust_date_and_time(date_and_time), parsed_entry.send(:mtime)
|
48
|
+
end
|
31
49
|
end
|
32
50
|
|
33
51
|
def test_ruby_unix_like_dir
|
@@ -67,4 +85,32 @@ class TestNetFTPListUnix < Test::Unit::TestCase
|
|
67
85
|
assert_equal 4, @symlink.filesize
|
68
86
|
assert_equal 154112, @older_date.filesize
|
69
87
|
end
|
88
|
+
|
89
|
+
def test_unix_block_device
|
90
|
+
assert_equal 'ram0', @block_dev.basename
|
91
|
+
assert @block_dev.device?
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_unix_char_device
|
95
|
+
assert_equal 'null', @char_dev.basename
|
96
|
+
assert @char_dev.device?
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_unix_socket_device
|
100
|
+
assert_equal 'log', @socket_dev.basename
|
101
|
+
assert @socket_dev.device?
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_unix_pipe_device
|
105
|
+
assert_equal 'xconsole', @pipe_dev.basename
|
106
|
+
assert @pipe_dev.device?
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
private
|
111
|
+
def parse_adjust_date_and_time(date_and_time)
|
112
|
+
parsed_time = Time.parse(date_and_time)
|
113
|
+
parsed_time -= ONE_YEAR if parsed_time > Time.now
|
114
|
+
parsed_time
|
115
|
+
end
|
70
116
|
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-ftp-list
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 3
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 3.2.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Stateless Systems
|
@@ -9,7 +14,7 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date:
|
17
|
+
date: 2011-01-09 00:00:00 +11:00
|
13
18
|
default_executable:
|
14
19
|
dependencies: []
|
15
20
|
|
@@ -22,48 +27,60 @@ extensions: []
|
|
22
27
|
extra_rdoc_files:
|
23
28
|
- README.txt
|
24
29
|
files:
|
25
|
-
- .gitignore
|
26
30
|
- README.txt
|
27
31
|
- Rakefile
|
28
32
|
- VERSION.yml
|
29
33
|
- lib/net/ftp/list.rb
|
34
|
+
- lib/net/ftp/list/entry.rb
|
30
35
|
- lib/net/ftp/list/microsoft.rb
|
31
36
|
- lib/net/ftp/list/netware.rb
|
32
37
|
- lib/net/ftp/list/parser.rb
|
38
|
+
- lib/net/ftp/list/rumpus.rb
|
33
39
|
- lib/net/ftp/list/unix.rb
|
40
|
+
- lib/net/ftp/list/unknown.rb
|
34
41
|
- net-ftp-list.gemspec
|
42
|
+
- test/test_net_ftp_list.rb
|
43
|
+
- test/test_net_ftp_list_entry.rb
|
35
44
|
- test/test_net_ftp_list_microsoft.rb
|
36
45
|
- test/test_net_ftp_list_netware.rb
|
46
|
+
- test/test_net_ftp_list_rumpus.rb
|
37
47
|
- test/test_net_ftp_list_unix.rb
|
38
48
|
has_rdoc: true
|
39
49
|
homepage: http://github.com/stateless-systems/net-ftp-list
|
40
50
|
licenses: []
|
41
51
|
|
42
52
|
post_install_message:
|
43
|
-
rdoc_options:
|
44
|
-
|
53
|
+
rdoc_options: []
|
54
|
+
|
45
55
|
require_paths:
|
46
56
|
- lib
|
47
57
|
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
48
59
|
requirements:
|
49
60
|
- - ">="
|
50
61
|
- !ruby/object:Gem::Version
|
62
|
+
segments:
|
63
|
+
- 0
|
51
64
|
version: "0"
|
52
|
-
version:
|
53
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
54
67
|
requirements:
|
55
68
|
- - ">="
|
56
69
|
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
57
72
|
version: "0"
|
58
|
-
version:
|
59
73
|
requirements: []
|
60
74
|
|
61
75
|
rubyforge_project:
|
62
|
-
rubygems_version: 1.3.
|
76
|
+
rubygems_version: 1.3.7
|
63
77
|
signing_key:
|
64
78
|
specification_version: 3
|
65
79
|
summary: Parse FTP LIST command output.
|
66
80
|
test_files:
|
81
|
+
- test/test_net_ftp_list.rb
|
82
|
+
- test/test_net_ftp_list_entry.rb
|
67
83
|
- test/test_net_ftp_list_microsoft.rb
|
68
|
-
- test/test_net_ftp_list_unix.rb
|
69
84
|
- test/test_net_ftp_list_netware.rb
|
85
|
+
- test/test_net_ftp_list_rumpus.rb
|
86
|
+
- test/test_net_ftp_list_unix.rb
|