net-ftp-list 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ = Net::Ftp::List
2
+
3
+ == DESCRIPTION
4
+
5
+ Ruby lib to parse FTP LIST responses.
6
+
7
+ According to the FTP RFC the LIST command "information on a file may vary widely from system to system, this
8
+ information may be hard to use automatically in a program, but may be quite useful to a human user".
9
+ Unfortunately the NLST command "intended to return information that can be used by a program to further process
10
+ the files automatically" only returns the filename and no other information. If you want to know to know even
11
+ simple things like 'is the NLST entry a directory or file' you are left with the choice of attempting to CWD to
12
+ (and back from) each entry or parsing the LIST command. This extension is an attempt at parsing the LIST command
13
+ and as such is subject to all the variability that results from such an undertaking, take responses with a grain
14
+ of salt and expect failures.
15
+
16
+ See the RFC for more guff on LIST and NLST: http://www.ietf.org/rfc/rfc0959.txt
17
+
18
+ == TODO & PROBLEMS
19
+
20
+ * I'm new to Ruby, I'm sure some exist :)
21
+ * The factory and abstract base class for parsers are one and the same. OO geeks will cry.
22
+ * More OS's and server types. Only servers that return Unix like LIST responses will work at the moment.
23
+ * Calling <tt>if entry.file? or entry.dir?</tt> is hard work when you really mean <tt>unless entry.unknown?</tt>
24
+ * I'm not sure about overwriting Net::FTP's +list+, +ls+ and +dir+. It's a base lib after all and people will be
25
+ expecting String. Perhaps I'd be better to <tt>class Parser < String</tt> for the abstract parser.
26
+ * The block handling for +list+, +ls+ and +dir+ has a nasty +map+ that's essentially building up an unused Array.
27
+
28
+ == SYNOPSIS
29
+
30
+ By requiring Net::FTP::List instances are created by calling any of Net::FTP's <tt>list</tt>, <tt>ls</tt> or
31
+ <tt>dir</tt> metods. The <tt>to_s</tt> method still returns the raw line so for the most part this should be
32
+ transparent.
33
+
34
+ require 'net/ftp' # Not really required but I like to list dependencies sometimes.
35
+ require 'net/ftp/list'
36
+
37
+ ftp = Net::FTP.open('somehost.com', 'user', 'pass')
38
+ ftp.list('/some/path') do |entry|
39
+ # Ignore everything that's not a file (so symlinks, directories and devices etc.)
40
+ next unless entry.file?
41
+
42
+ # 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
43
+ # same name as ftp.nlist('/some/path') would have returned.
44
+ puts entry.basename
45
+ end
46
+
47
+ == CREDITS
48
+
49
+ * ASF, Commons. http://commons.apache.org/
50
+ * The good people at Stateless Systems who let me write Ruby and give it away.
51
+
52
+ == LICENSE
53
+
54
+ (The MIT License, go nuts)
55
+
56
+ Copyright (c) 2007 Shane Hanna
57
+ Stateless Systems
58
+ http://statelesssystems.com
59
+
60
+ Permission is hereby granted, free of charge, to any person obtaining
61
+ a copy of this software and associated documentation files (the
62
+ 'Software'), to deal in the Software without restriction, including
63
+ without limitation the rights to use, copy, modify, merge, publish,
64
+ distribute, sublicense, and/or sell copies of the Software, and to
65
+ permit persons to whom the Software is furnished to do so, subject to
66
+ the following conditions:
67
+
68
+ The above copyright notice and this permission notice shall be
69
+ included in all copies or substantial portions of the Software.
70
+
71
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
72
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
73
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
74
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
75
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
76
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
77
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ # -*- ruby -*-
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/testtask'
6
+
7
+ NAME = 'net-ftp-list'
8
+ VERS = '0.3'
9
+
10
+ CLEAN.include ['**/*.log', '*.gem']
11
+ CLOBBER.include ['**/*.log']
12
+
13
+ spec = Gem::Specification.new do |s|
14
+ s.name = NAME
15
+ s.version = VERS
16
+ s.platform = Gem::Platform::RUBY
17
+ s.has_rdoc = true
18
+ s.extra_rdoc_files = ["README.txt"]
19
+ s.summary = 'Parse FTP LIST command output.'
20
+ s.description = s.summary
21
+ s.author = 'Shane Hanna'
22
+ s.email = 'shane@statelesssystems.com'
23
+ s.homepage = 'http://statelesssystems.com'
24
+
25
+ s.files = FileList['Rakefile', '**/*.{rb,txt}'].to_a
26
+ s.test_files = FileList['tests/*.rb'].to_a
27
+ end
28
+
29
+ desc 'Default: Run unit tests.'
30
+ task :default => :test
31
+
32
+ Rake::GemPackageTask.new(spec) do |p|
33
+ p.need_tar = true
34
+ p.gem_spec = spec
35
+ end
36
+
37
+ desc 'Run the unit tests.'
38
+ Rake::TestTask.new do |t|
39
+ t.verbose = true
40
+ end
41
+
42
+ desc 'Package and install as gem.'
43
+ task :install do
44
+ sh %{rake package}
45
+ sh %{sudo gem install pkg/net-ftp-list-#{VERS}}
46
+ end
47
+
48
+ desc 'Uninstall the gem.'
49
+ task :uninstall => [:clean] do
50
+ sh %{sudo gem uninstall #{NAME}}
51
+ end
@@ -0,0 +1,67 @@
1
+ require 'net/ftp'
2
+ require 'net/ftp/list/parser'
3
+
4
+ # The order here is important for the time being. Corse grained parsers should appear before specializations because
5
+ # the whole thing is searched in reverse order.
6
+ require 'net/ftp/list/unix'
7
+
8
+ module Net #:nodoc:
9
+ class FTP #:nodoc:
10
+
11
+ alias_method :raw_list, :list
12
+ def list(*args, &block)
13
+ # TODO: Map in void context when you pass a block.
14
+ raw_list(*args).map do |raw|
15
+ entry = Net::FTP::List.parse(raw)
16
+ block ? yield(entry) : entry
17
+ end
18
+ end
19
+
20
+ # Parse FTP LIST responses.
21
+ #
22
+ # Simply require the library and each entry from Net::FTP +list+, +ls+ or +dir+ methods will
23
+ # be parsed by the LIST parse as best it can.
24
+ #
25
+ # == Creation
26
+ #
27
+ # By requiring Net::FTP::List instances are created by calling any of Net::FTP's +list+, +ls+ or
28
+ # +dir+ metods. The +to_s+ method still returns the raw line so for the most part this should be
29
+ # transparent.
30
+ #
31
+ # require 'net/ftp' # Not really required but I like to list dependencies sometimes.
32
+ # require 'net/ftp/list'
33
+ #
34
+ # ftp = Net::FTP.open('somehost.com', 'user', 'pass')
35
+ # ftp.list('/some/path') do |entry|
36
+ # # Ignore everything that's not a file (so symlinks, directories and devices etc.)
37
+ # next unless entry.file?
38
+ #
39
+ # # 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
40
+ # # same name as ftp.nlist('/some/path') would have returned.
41
+ # puts entry.basename
42
+ # end
43
+ #
44
+ # == Exceptions
45
+ #
46
+ # None at this time. At worst you'll end up with an Net::FTP::List::Unknown instance which won't have any extra
47
+ # useful information. Methods like <tt>dir?</tt>, <tt>file?</tt> and <tt>symlink?</tt> will all return +false+.
48
+ module List
49
+ class << self
50
+
51
+ # Parse a raw FTP LIST line.
52
+ #
53
+ # Net::FTP::List.parse(raw_list_string) # => Net::FTP::List::Parser instance.
54
+ def parse(raw)
55
+ Parser.parse(raw)
56
+ end
57
+
58
+ # Parse a raw FTP LIST line.
59
+ #
60
+ # An alias for +parse+.
61
+ alias_method :new, :parse
62
+
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,103 @@
1
+ module Net
2
+ class FTP
3
+ module List
4
+
5
+ # ParserError
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
13
+
14
+ # Abstract FTP LIST parser.
15
+ #
16
+ # It really just defines and documents the interface.
17
+ #
18
+ # == Exceptions
19
+ #
20
+ # +ParserError+ -- Raw entry could not be parsed.
21
+ class Parser
22
+ @@parsers = []
23
+
24
+ # The raw list entry string.
25
+ attr_reader :raw
26
+
27
+ # The items basename (filename).
28
+ attr_reader :basename
29
+
30
+ # Looks like a directory, try CWD.
31
+ def dir?
32
+ !!self.dir
33
+ end
34
+
35
+ # Looks like a file, try RETR.
36
+ def file?
37
+ self.file
38
+ end
39
+
40
+ # Looks like a symbolic link.
41
+ def symlink?
42
+ !!self.symlink
43
+ end
44
+
45
+ # Parse a raw FTP LIST line.
46
+ #
47
+ # By default just takes and set the raw list entry.
48
+ #
49
+ # Net::FTP::List.parse(raw_list_string) # => Net::FTP::List::Parser instance.
50
+ def initialize(raw)
51
+ self.raw = raw
52
+ end
53
+
54
+ # Stringify.
55
+ def to_s
56
+ raw
57
+ end
58
+ alias_method :raw, :to_s
59
+
60
+ class << self
61
+
62
+ # Acts as a factory.
63
+ #
64
+ # TODO: Having a class be both factory and abstract implementation seems a little nutty to me. If it ends up
65
+ # too confusing or gives anyone the shits I'll move it.
66
+ def inherited(klass)
67
+ @@parsers << klass
68
+ end
69
+
70
+ # Factory method.
71
+ #
72
+ # Attempt to find and parse a list item with the a parser.
73
+ def parse(raw)
74
+ @@parsers.reverse.each do |parser|
75
+ begin
76
+ return parser.new(raw)
77
+ rescue ParserError
78
+ next
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ protected
85
+ # Protected boolean.
86
+ attr_accessor :file, :dir, :symlink
87
+
88
+ # Protected raw list entry string.
89
+ attr_writer :raw
90
+
91
+ # Protected item basename (filename).
92
+ attr_writer :basename
93
+ end
94
+
95
+ # Unknown parser.
96
+ #
97
+ # If all other attempts to parse the entry fail this class will be returned. Only the +raw+ and +to_s+
98
+ # methods will return anything useful.
99
+ class Unknown < Parser
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,56 @@
1
+ require 'net/ftp/list/parser'
2
+
3
+ module Net
4
+ class FTP
5
+ module List
6
+
7
+ # Parse Unix like FTP LIST entries.
8
+ #
9
+ # == MATCHES
10
+ #
11
+ # drwxr-xr-x 4 steve group 4096 Dec 10 20:23 etc
12
+ # -rw-r--r-- 1 root other 531 Jan 29 03:26 README.txt
13
+ #
14
+ # == SYNOPSIS
15
+ #
16
+ # entry = Net::FTP::List::Unix.new('drwxr-xr-x 4 steve group 4096 Dec 10 20:23 etc')
17
+ # entry.dir? # => true
18
+ # entry.basename # => 'etc'
19
+ class Unix < Parser
20
+
21
+ # Stolen straight from the ASF's commons Java FTP LIST parser library.
22
+ # http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
23
+ REGEXP = %r{
24
+ ([bcdlfmpSs-])
25
+ (((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\+?\s+
26
+ (\d+)\s+
27
+ (\S+)\s+
28
+ (?:(\S+(?:\s\S+)*)\s+)?
29
+ (\d+)\s+
30
+ ((?:\d+[-/]\d+[-/]\d+)|(?:\S+\s+\S+))\s+
31
+ (\d+(?::\d+)?)\s+
32
+ (\S*)(\s*.*)
33
+ }x
34
+
35
+ # Parse a Unix like FTP LIST entries.
36
+ def initialize(raw)
37
+ super(raw)
38
+ match = REGEXP.match(raw.strip) or raise ParserError
39
+
40
+ case match[1]
41
+ when /d/ then self.dir = true
42
+ when /l/ then self.symlink = true
43
+ when /[f-]/ then self.file = true
44
+ when /[bc]/ then # Do nothing with devices for now.
45
+ else ParserError 'Unknown LIST entry type.'
46
+ end
47
+
48
+ # TODO: Permissions, users, groups, date/time.
49
+
50
+ self.basename = match[21].match(/^(.+)(?:\s+\->.+)?$/)[0].strip
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ require 'test/unit'
2
+ require 'net/ftp/list'
3
+
4
+ class TestNetFTPList < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @dir = Net::FTP::List.new('drwxr-xr-x 4 user group 4096 Dec 10 20:23 etc')
8
+ @file = Net::FTP::List.new('-rw-r--r-- 1 root other 531 Jan 29 03:26 README')
9
+ end
10
+
11
+ def test_parse_new
12
+ assert_instance_of Net::FTP::List::Unix, @dir, 'LIST unixish directory'
13
+ assert_instance_of Net::FTP::List::Unix, @file, 'LIST unixish file'
14
+ end
15
+
16
+ def test_rubbish_lines
17
+ assert_instance_of Net::FTP::List::Unknown, Net::FTP::List.new("++ bah! ++")
18
+ end
19
+
20
+ def test_ruby_unix_like_dir
21
+ assert_equal 'etc', @dir.basename
22
+ assert @dir.dir?
23
+ assert !@dir.file?
24
+ end
25
+
26
+ def test_ruby_unix_like_file
27
+ assert_equal 'README', @file.basename
28
+ assert @file.file?
29
+ assert !@file.dir?
30
+ end
31
+
32
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: net-ftp-list
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.3"
7
+ date: 2008-01-13 00:00:00 +11:00
8
+ summary: Parse FTP LIST command output.
9
+ require_paths:
10
+ - lib
11
+ email: shane@statelesssystems.com
12
+ homepage: http://statelesssystems.com
13
+ rubyforge_project:
14
+ description: Parse FTP LIST command output.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Shane Hanna
31
+ files:
32
+ - Rakefile
33
+ - lib/net/ftp/list/parser.rb
34
+ - lib/net/ftp/list/unix.rb
35
+ - lib/net/ftp/list.rb
36
+ - test/test_net_ftp_list.rb
37
+ - README.txt
38
+ test_files: []
39
+
40
+ rdoc_options: []
41
+
42
+ extra_rdoc_files:
43
+ - README.txt
44
+ executables: []
45
+
46
+ extensions: []
47
+
48
+ requirements: []
49
+
50
+ dependencies: []
51
+