net-ftp-list 3.2.11 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +21 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +17 -0
- data/Gemfile +1 -5
- data/Gemfile.lock +47 -51
- data/Rakefile +10 -42
- data/lib/net/ftp/list.rb +3 -4
- data/lib/net/ftp/list/entry.rb +33 -53
- data/lib/net/ftp/list/microsoft.rb +14 -16
- data/lib/net/ftp/list/netware.rb +8 -10
- data/lib/net/ftp/list/parser.rb +35 -18
- data/lib/net/ftp/list/rumpus.rb +9 -10
- data/lib/net/ftp/list/unix.rb +31 -38
- data/lib/net/ftp/list/unknown.rb +3 -6
- data/net-ftp-list.gemspec +15 -57
- data/test/test_net_ftp_list.rb +2 -3
- data/test/test_net_ftp_list_entry.rb +20 -21
- data/test/test_net_ftp_list_microsoft.rb +6 -7
- data/test/test_net_ftp_list_netware.rb +6 -8
- data/test/test_net_ftp_list_rumpus.rb +5 -6
- data/test/test_net_ftp_list_unix.rb +49 -62
- metadata +46 -12
- data/.ruby-version +0 -1
- data/VERSION.yml +0 -5
data/lib/net/ftp/list/netware.rb
CHANGED
@@ -9,27 +9,25 @@ require 'time'
|
|
9
9
|
class Net::FTP::List::Netware < Net::FTP::List::Parser
|
10
10
|
# Stolen straight from the ASF's commons Java FTP LIST parser library.
|
11
11
|
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
|
12
|
-
REGEXP =
|
12
|
+
REGEXP = /^
|
13
13
|
(d|-){1}\s+
|
14
14
|
\[(.*?)\]\s+
|
15
15
|
(\S+)\s+(\d+)\s+
|
16
16
|
(\S+\s+\S+\s+((\d+:\d+)|(\d{4})))
|
17
17
|
\s+(.*)
|
18
|
-
|
18
|
+
$/x.freeze
|
19
19
|
|
20
20
|
# Parse a Netware like FTP LIST entries.
|
21
|
-
def self.parse(raw)
|
21
|
+
def self.parse(raw, timezone: :utc)
|
22
22
|
match = REGEXP.match(raw.strip) or return false
|
23
|
-
|
24
|
-
is_dir = match[1] == 'd'
|
23
|
+
type = match[1] == 'd' ? :dir : :file
|
25
24
|
|
26
25
|
emit_entry(
|
27
26
|
raw,
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:basename => match[9]
|
27
|
+
mtime: parse_time(match[5], timezone: timezone),
|
28
|
+
filesize: match[4].to_i,
|
29
|
+
type: type,
|
30
|
+
basename: match[9],
|
33
31
|
)
|
34
32
|
end
|
35
33
|
end
|
data/lib/net/ftp/list/parser.rb
CHANGED
@@ -1,28 +1,45 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
1
3
|
# Abstract FTP LIST parser. It really just defines and documents the interface.
|
2
4
|
class Net::FTP::List::Parser
|
5
|
+
class << self
|
6
|
+
# Returns registered parsers.
|
7
|
+
def parsers
|
8
|
+
@parsers ||= []
|
9
|
+
end
|
3
10
|
|
4
|
-
|
11
|
+
# Manuall register a parser.
|
12
|
+
def register(parser)
|
13
|
+
parsers.push(parser) unless parsers.include?(parser)
|
14
|
+
end
|
5
15
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
16
|
+
# Automatically add an inheriting parser to the list of known parsers.
|
17
|
+
def inherited(klass)
|
18
|
+
super
|
19
|
+
register(klass)
|
20
|
+
end
|
11
21
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
22
|
+
# The main parse method. Return false from it if parsing fails (this is cheaper than raising an exception)
|
23
|
+
def parse(_raw, **)
|
24
|
+
false
|
25
|
+
end
|
16
26
|
|
17
|
-
|
18
|
-
def self.parse(raw)
|
19
|
-
return false
|
20
|
-
end
|
27
|
+
private
|
21
28
|
|
22
|
-
|
29
|
+
# Automatically adds the name of the parser class to the server_type field
|
30
|
+
def emit_entry(raw, **extra)
|
31
|
+
Net::FTP::List::Entry.new raw, **extra, server_type: to_s.split('::').pop
|
32
|
+
end
|
23
33
|
|
24
|
-
|
25
|
-
|
26
|
-
|
34
|
+
def parse_time(str, timezone:, format: nil)
|
35
|
+
case timezone
|
36
|
+
when :utc
|
37
|
+
(format ? DateTime.strptime(str, format) : DateTime.parse(str)).to_time
|
38
|
+
when :local
|
39
|
+
format ? Time.strptime(str, format) : Time.parse(str)
|
40
|
+
else
|
41
|
+
raise ArgumentError, "invalid timezone #{timezone}, only :utc and :local are allowed"
|
42
|
+
end
|
43
|
+
end
|
27
44
|
end
|
28
45
|
end
|
data/lib/net/ftp/list/rumpus.rb
CHANGED
@@ -6,28 +6,27 @@ require 'net/ftp/list/parser'
|
|
6
6
|
# drwxr-xr-x folder 0 Nov 30 10:03 houdini
|
7
7
|
# -rw-r--r-- 0 101426 101426 Jun 7 2008 imap with spaces.rb
|
8
8
|
class Net::FTP::List::Rumpus < Net::FTP::List::Parser
|
9
|
-
|
10
|
-
|
11
|
-
([drwxr-]{10})\s+
|
9
|
+
REGEXP = /^
|
10
|
+
([drwx-]{10})\s+
|
12
11
|
(folder|0\s+\d+)\s+
|
13
12
|
(\d+)\s+
|
14
13
|
(\w+)\s+
|
15
14
|
(\d{1,2})\s+
|
16
15
|
(\d{2}:\d{2}|\d{4})\s+
|
17
16
|
(.+)
|
18
|
-
|
17
|
+
$/x.freeze
|
19
18
|
|
20
19
|
# Parse a Rumpus FTP LIST entry.
|
21
|
-
def self.parse(raw)
|
20
|
+
def self.parse(raw, timezone: :utc)
|
22
21
|
match = REGEXP.match(raw.strip) or return false
|
22
|
+
type = match[2] == 'folder' ? :dir : :file
|
23
23
|
|
24
24
|
emit_entry(
|
25
25
|
raw,
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:filesize => match[3].to_i
|
26
|
+
basename: match[7],
|
27
|
+
mtime: parse_time(match[4..6].join(' '), timezone: timezone),
|
28
|
+
type: type,
|
29
|
+
filesize: match[3].to_i,
|
31
30
|
)
|
32
31
|
end
|
33
32
|
end
|
data/lib/net/ftp/list/unix.rb
CHANGED
@@ -8,11 +8,10 @@ require 'net/ftp/list/parser'
|
|
8
8
|
# drwxr-xr-x 4 steve group 4096 Dec 10 20:23 etc
|
9
9
|
# -rw-r--r-- 1 root other 531 Jan 29 03:26 README.txt
|
10
10
|
class Net::FTP::List::Unix < Net::FTP::List::Parser
|
11
|
-
|
12
11
|
# Stolen straight from the ASF's commons Java FTP LIST parser library.
|
13
12
|
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
|
14
13
|
REGEXP = %r{
|
15
|
-
([
|
14
|
+
([pbcdlfmSs-])
|
16
15
|
(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\+?\s+
|
17
16
|
(?:(\d+)\s+)?
|
18
17
|
(\S+)?\s+
|
@@ -22,69 +21,63 @@ class Net::FTP::List::Unix < Net::FTP::List::Parser
|
|
22
21
|
((?:\d+[-/]\d+[-/]\d+)|(?:\S+\s+\S+))\s+
|
23
22
|
(\d+(?::\d+)?)\s+
|
24
23
|
(\S*)(\s*.*)
|
25
|
-
}x
|
24
|
+
}x.freeze
|
26
25
|
|
27
26
|
ONE_YEAR = (60 * 60 * 24 * 365)
|
28
27
|
|
29
28
|
# Parse a Unix like FTP LIST entries.
|
30
|
-
def self.parse(raw)
|
29
|
+
def self.parse(raw, timezone: :utc)
|
31
30
|
match = REGEXP.match(raw.strip) or return false
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
return false unless dir or symlink or file or device
|
31
|
+
type = case match[1]
|
32
|
+
when /d/ then :dir
|
33
|
+
when /l/ then :symlink
|
34
|
+
when /[f-]/ then :file
|
35
|
+
when /[psbc]/ then :device
|
36
|
+
end
|
37
|
+
return false if type.nil?
|
41
38
|
|
42
39
|
# Don't match on rumpus (which looks very similar to unix)
|
43
|
-
return false if match[17].nil?
|
40
|
+
return false if match[17].nil? && ((match[15].nil? && (match[16].to_s == 'folder')) || (match[15].to_s == '0'))
|
44
41
|
|
45
42
|
# TODO: Permissions, users, groups, date/time.
|
46
43
|
filesize = match[18].to_i
|
47
|
-
|
48
44
|
mtime_month_and_day = match[19]
|
49
45
|
mtime_time_or_year = match[20]
|
50
46
|
|
51
47
|
# Unix mtimes specify a 4 digit year unless the data is within the past 180
|
52
|
-
# days or so. Future dates always specify a 4 digit year.
|
48
|
+
# days or so. Future dates always specify a 4 digit year.
|
53
49
|
# If the parsed date, with today's year, could be in the future, then
|
54
50
|
# the date must be for the previous year
|
55
|
-
mtime_string =
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
mtime = Time.parse(mtime_string)
|
51
|
+
mtime_string = case mtime_time_or_year
|
52
|
+
when /^[0-9]{1,2}:[0-9]{2}$/
|
53
|
+
if parse_time("#{mtime_month_and_day} #{Time.now.year}", timezone: timezone) > Time.now
|
54
|
+
"#{mtime_month_and_day} #{mtime_time_or_year} #{Time.now.year - 1}"
|
55
|
+
else
|
56
|
+
"#{mtime_month_and_day} #{mtime_time_or_year} #{Time.now.year}"
|
57
|
+
end
|
58
|
+
when /^[0-9]{4}$/
|
59
|
+
"#{mtime_month_and_day} #{mtime_time_or_year}"
|
60
|
+
end
|
66
61
|
|
62
|
+
mtime = parse_time(mtime_string, timezone: timezone)
|
67
63
|
basename = match[21].strip
|
68
64
|
|
69
65
|
# filenames with spaces will end up in the last match
|
70
66
|
basename += match[22] unless match[22].nil?
|
71
67
|
|
72
68
|
# strip the symlink stuff we don't care about
|
73
|
-
if symlink
|
74
|
-
basename.sub!(/\s
|
75
|
-
symlink_destination =
|
69
|
+
if type == :symlink
|
70
|
+
basename.sub!(/\s+->(.+)$/, '')
|
71
|
+
symlink_destination = Regexp.last_match(1).strip if Regexp.last_match(1)
|
76
72
|
end
|
77
73
|
|
78
74
|
emit_entry(
|
79
75
|
raw,
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
83
|
-
:
|
84
|
-
:
|
85
|
-
:basename => basename,
|
86
|
-
:symlink_destination => symlink_destination,
|
87
|
-
:mtime => mtime
|
76
|
+
type: type,
|
77
|
+
filesize: filesize,
|
78
|
+
basename: basename,
|
79
|
+
symlink_destination: symlink_destination,
|
80
|
+
mtime: mtime,
|
88
81
|
)
|
89
82
|
end
|
90
83
|
end
|
data/lib/net/ftp/list/unknown.rb
CHANGED
@@ -3,12 +3,9 @@ require 'net/ftp/list/parser'
|
|
3
3
|
# If all other attempts to parse the entry fail this is the parser that is going to be used.
|
4
4
|
# It might be a good idea to fail loudly.
|
5
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
|
6
|
+
def self.parse(raw, **)
|
7
|
+
raise Net::FTP::List::ParseError, "Could not parse #{raw} since none of the parsers was up to the task" if Net::FTP::List.raise_on_failed_server_detection
|
10
8
|
|
11
|
-
emit_entry(raw
|
9
|
+
emit_entry(raw)
|
12
10
|
end
|
13
11
|
end
|
14
|
-
|
data/net-ftp-list.gemspec
CHANGED
@@ -1,61 +1,19 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: net-ftp-list 3.2.11 ruby lib
|
6
|
-
|
7
1
|
Gem::Specification.new do |s|
|
8
|
-
s.name =
|
9
|
-
s.version =
|
10
|
-
|
11
|
-
s.
|
12
|
-
s.
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
s.email = "enquiries@statelesssystems.com".freeze
|
16
|
-
s.extra_rdoc_files = [
|
17
|
-
"README.txt"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".ruby-version",
|
21
|
-
"Gemfile",
|
22
|
-
"Gemfile.lock",
|
23
|
-
"README.txt",
|
24
|
-
"Rakefile",
|
25
|
-
"VERSION.yml",
|
26
|
-
"lib/net/ftp/list.rb",
|
27
|
-
"lib/net/ftp/list/entry.rb",
|
28
|
-
"lib/net/ftp/list/microsoft.rb",
|
29
|
-
"lib/net/ftp/list/netware.rb",
|
30
|
-
"lib/net/ftp/list/parser.rb",
|
31
|
-
"lib/net/ftp/list/rumpus.rb",
|
32
|
-
"lib/net/ftp/list/unix.rb",
|
33
|
-
"lib/net/ftp/list/unknown.rb",
|
34
|
-
"net-ftp-list.gemspec",
|
35
|
-
"test/test_net_ftp_list.rb",
|
36
|
-
"test/test_net_ftp_list_entry.rb",
|
37
|
-
"test/test_net_ftp_list_microsoft.rb",
|
38
|
-
"test/test_net_ftp_list_netware.rb",
|
39
|
-
"test/test_net_ftp_list_rumpus.rb",
|
40
|
-
"test/test_net_ftp_list_unix.rb"
|
41
|
-
]
|
42
|
-
s.homepage = "http://github.com/stateless-systems/net-ftp-list".freeze
|
43
|
-
s.rubygems_version = "2.7.6".freeze
|
44
|
-
s.summary = "Parse FTP LIST command output.".freeze
|
2
|
+
s.name = 'net-ftp-list'
|
3
|
+
s.version = '3.3.0'
|
4
|
+
s.authors = ['Stateless Systems']
|
5
|
+
s.email = 'enquiries@statelesssystems.com'
|
6
|
+
s.summary = 'Parse FTP LIST command output.'
|
7
|
+
s.homepage = 'http://github.com/stateless-systems/net-ftp-list'
|
8
|
+
s.license = 'MIT'
|
45
9
|
|
46
|
-
|
47
|
-
|
10
|
+
s.files = `git ls-files -z`.split("\x0").reject {|f| f.start_with?('test/') }
|
11
|
+
s.test_files = `git ls-files -z -- test/*`.split("\x0")
|
12
|
+
s.require_paths = ['lib']
|
13
|
+
s.required_ruby_version = '>= 2.5'
|
48
14
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
54
|
-
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
55
|
-
end
|
56
|
-
else
|
57
|
-
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
58
|
-
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
59
|
-
end
|
15
|
+
s.add_development_dependency 'rake'
|
16
|
+
s.add_development_dependency 'rubocop-bsm'
|
17
|
+
s.add_development_dependency 'test-unit'
|
18
|
+
s.add_development_dependency 'timecop'
|
60
19
|
end
|
61
|
-
|
data/test/test_net_ftp_list.rb
CHANGED
@@ -2,14 +2,13 @@ require 'test/unit'
|
|
2
2
|
require 'net/ftp/list'
|
3
3
|
|
4
4
|
class TestNetFTPList < Test::Unit::TestCase
|
5
|
-
|
6
5
|
def test_all
|
7
6
|
one = Net::FTP::List.parse('drwxr-xr-x 4 user group 4096 Dec 10 20:23 etc')
|
8
7
|
assert_kind_of Net::FTP::List::Entry, one
|
9
8
|
assert one.dir?
|
10
9
|
assert !one.file?
|
11
10
|
|
12
|
-
two = Net::FTP::List.parse(
|
11
|
+
two = Net::FTP::List.parse('++ unknown garbage +++')
|
13
12
|
assert_kind_of Net::FTP::List::Entry, two
|
14
13
|
assert two.unknown?
|
15
14
|
assert_equal '', two.basename
|
@@ -18,7 +17,7 @@ class TestNetFTPList < Test::Unit::TestCase
|
|
18
17
|
def test_raise_when_flag_set
|
19
18
|
Net::FTP::List.raise_on_failed_server_detection = true
|
20
19
|
assert_raise(Net::FTP::List::ParseError) do
|
21
|
-
Net::FTP::List.parse(
|
20
|
+
Net::FTP::List.parse('++ unknown garbage +++')
|
22
21
|
end
|
23
22
|
ensure
|
24
23
|
Net::FTP::List.raise_on_failed_server_detection = false
|
@@ -1,18 +1,18 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'net/ftp/list'
|
3
|
+
require 'bigdecimal'
|
3
4
|
|
4
5
|
class TestNetFTPEntry < Test::Unit::TestCase
|
5
|
-
|
6
6
|
def test_equality
|
7
|
-
a = Net::FTP::List::Entry.new('foo1',
|
8
|
-
b = Net::FTP::List::Entry.new('foo2',
|
9
|
-
c = Net::FTP::List::Entry.new('foo1',
|
7
|
+
a = Net::FTP::List::Entry.new('foo1', basename: 'foo1')
|
8
|
+
b = Net::FTP::List::Entry.new('foo2', basename: 'foo2')
|
9
|
+
c = Net::FTP::List::Entry.new('foo1', basename: 'foo1')
|
10
10
|
|
11
|
-
assert a.eql?
|
11
|
+
assert a.eql?(c)
|
12
12
|
assert a.eql? a
|
13
13
|
assert !a.eql?(b)
|
14
14
|
assert !a.eql?(1)
|
15
|
-
assert !a.eql?(1.0)
|
15
|
+
assert !a.eql?(BigDecimal('1.0'))
|
16
16
|
end
|
17
17
|
|
18
18
|
def test_comparison
|
@@ -26,24 +26,24 @@ class TestNetFTPEntry < Test::Unit::TestCase
|
|
26
26
|
|
27
27
|
assert file2 > file1
|
28
28
|
assert file2 >= file1
|
29
|
-
assert file2 ==
|
29
|
+
assert file2 == Net::FTP::List.parse(raw2)
|
30
|
+
assert file2 <= file3
|
31
|
+
assert file2 < file3
|
32
|
+
assert file2 >= file1
|
33
|
+
assert file2 > file1
|
34
|
+
assert file2 != file1
|
35
|
+
assert file2 != file3
|
30
36
|
assert file2 <= file3
|
31
37
|
assert file2 < file3
|
32
|
-
assert !(file2 < file1)
|
33
|
-
assert !(file2 <= file1)
|
34
|
-
assert !(file2 == file1)
|
35
|
-
assert !(file2 == file3)
|
36
|
-
assert !(file2 > file3)
|
37
|
-
assert !(file2 >= file3)
|
38
38
|
|
39
|
-
assert
|
40
|
-
assert
|
39
|
+
assert file2 >= 2
|
40
|
+
assert file2 >= 2.0
|
41
41
|
assert file2 <= 2
|
42
42
|
assert file2 <= 2.0
|
43
43
|
assert file2 == 2
|
44
|
-
assert file2 == 2.0
|
45
|
-
assert
|
46
|
-
assert
|
44
|
+
assert file2 == BigDecimal('2.0')
|
45
|
+
assert file2 <= 2
|
46
|
+
assert file2 <= 2.0
|
47
47
|
assert file2 >= 2
|
48
48
|
assert file2 >= 2.0
|
49
49
|
|
@@ -52,7 +52,7 @@ class TestNetFTPEntry < Test::Unit::TestCase
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def test_raise_on_unknown_options
|
55
|
-
assert_raise(ArgumentError) { Net::FTP::List::Entry.new(
|
55
|
+
assert_raise(ArgumentError) { Net::FTP::List::Entry.new('foo', bar: 'baz') }
|
56
56
|
end
|
57
57
|
|
58
58
|
def test_default_values
|
@@ -67,7 +67,6 @@ class TestNetFTPEntry < Test::Unit::TestCase
|
|
67
67
|
assert !e.file?
|
68
68
|
assert !e.symlink?
|
69
69
|
assert_kind_of Time, e.mtime
|
70
|
-
assert_equal
|
70
|
+
assert_equal 'Unknown', e.server_type
|
71
71
|
end
|
72
|
-
|
73
72
|
end
|