net-ftp-list 3.2.11 → 3.3.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/.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
|