Skip to content

Commit

Permalink
Merge branch 'master' of github.com:stateless-systems/net-ftp-list
Browse files Browse the repository at this point in the history
Conflicts:
	lib/net/ftp/list/microsoft.rb
	lib/net/ftp/list/netware.rb
	lib/net/ftp/list/parser.rb
	lib/net/ftp/list/unix.rb
	test/test_net_ftp_list_microsoft.rb
	test/test_net_ftp_list_unix.rb
  • Loading branch information
norbertwinklareth committed Jan 3, 2011
2 parents 3bf6ebb + 891a66a commit c5cca44
Show file tree
Hide file tree
Showing 18 changed files with 436 additions and 321 deletions.
3 changes: 1 addition & 2 deletions README.txt
Expand Up @@ -17,7 +17,6 @@ See the RFC for more guff on LIST and NLST: http://www.ietf.org/rfc/rfc0959.txt

== TODO & PROBLEMS

* I'm new to Ruby, I'm sure some exist :)
* The factory and abstract base class for parsers are one and the same. OO geeks will cry.
* More OS's and server types. Only servers that return Unix like LIST responses will work at the moment.
* Calling <tt>if entry.file? or entry.dir?</tt> is hard work when you really mean <tt>unless entry.unknown?</tt>
Expand All @@ -34,7 +33,7 @@ See the RFC for more guff on LIST and NLST: http://www.ietf.org/rfc/rfc0959.txt
# Ignore everything that's not a file (so symlinks, directories and devices etc.)
next unless entry.file?

# 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
# If entry isn't entry.unknown? then there is a bug in Net::FTP::List if this isn't the
# same name as ftp.nlist('/some/path') would have returned.
# Format the entry showing its file size and modification time
puts "#{entry.basename}, #{entry.filesize}, #{entry.mtime}"
Expand Down
4 changes: 2 additions & 2 deletions Rakefile
Expand Up @@ -17,15 +17,15 @@ end
require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test'
test.pattern = 'test/**/*_test.rb'
test.pattern = 'test/**/test_*.rb'
test.verbose = true
end

begin
require 'rcov/rcovtask'
Rcov::RcovTask.new do |test|
test.libs << 'test'
test.pattern = 'test/**/*_test.rb'
test.pattern = 'test/**/test_*.rb'
test.verbose = true
end
rescue LoadError
Expand Down
4 changes: 2 additions & 2 deletions VERSION.yml
@@ -1,5 +1,5 @@
---
:major: 2
:major: 3
:minor: 1
:patch: 0
:patch: 1
:build:
62 changes: 27 additions & 35 deletions lib/net/ftp/list.rb
@@ -1,43 +1,35 @@
require 'net/ftp'
require 'net/ftp/list/parser'

# The order here is important for the time being. Corse grained parsers should appear before specializations because
# the whole thing is searched in reverse order.
require 'net/ftp/list/unix'
require 'net/ftp/list/microsoft'
require 'net/ftp/list/netware'
module Net::FTP::List
require 'net/ftp/list/parser'
require 'net/ftp/list/entry'

module Net #:nodoc:
class FTP #:nodoc:
# Parser classes should be listed top to bottom, the most specific
# (and rare!) server variations coming last
require 'net/ftp/list/unix'
require 'net/ftp/list/microsoft'
require 'net/ftp/list/netware'
require 'net/ftp/list/rumpus'
require 'net/ftp/list/unknown'

# Parse FTP LIST responses.
#
# == Creation
#
# require 'net/ftp' # Not really required but I like to list dependencies sometimes.
# require 'net/ftp/list'
#
# ftp = Net::FTP.open('somehost.com', 'user', 'pass')
# ftp.list('/some/path') do |e|
# entry = Net::FTP::List.parse(e)
#
# # Ignore everything that's not a file (so symlinks, directories and devices etc.)
# next unless entry.file?
#
# # 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
# # same name as ftp.nlist('/some/path') would have returned.
# puts entry.basename
# end
#
# == Exceptions
#
# None at this time. At worst you'll end up with an Net::FTP::List::Unknown instance which won't have any extra
# useful information. Methods like <tt>dir?</tt>, <tt>file?</tt> and <tt>symlink?</tt> will all return +false+.
module List
def self.parse(*args)
Parser.parse(*args)
end
def self.raise_on_failed_server_detection=(new_flag)
Thread.current[:net_ftp_list_raise_on_failed_server_detection] = !!new_flag
end

def self.raise_on_failed_server_detection
Thread.current[:net_ftp_list_raise_on_failed_server_detection]
end

# Parse a line from FTP LIST responsesa and return a Net::FTP::List::Entry
def self.parse(*args)
Parser.with_each_parser do | p |
entry = p.parse(*args)
return entry if entry
end
end

# Gets raised with raise_on_failed_server_detection set
class ParseError < RuntimeError
end
end

66 changes: 66 additions & 0 deletions lib/net/ftp/list/entry.rb
@@ -0,0 +1,66 @@
# Represents an entry of the FTP list. Gets returned when you parse a list.
class Net::FTP::List::Entry

ALLOWED_ATTRIBUTES = [:raw, :basename, :dir, :file, :symlink, :mtime, :filesize, :device, :server_type] #:nodoc:

# Create a new entry object. The additional argument is the list of metadata keys
# that can be used on the object. By default just takes and set the raw list entry.
# Net::FTP::List.parse(raw_list_string) # => Net::FTP::List::Parser instance.
def initialize(raw_ls_line, optional_attributes = {}) #:nodoc:
@raw = raw_ls_line
optional_attributes.each_pair do |key, value|
raise ArgumentError, "#{key} is not supported" unless ALLOWED_ATTRIBUTES.include?(key)
instance_variable_set("@#{key}", value)
end
end

# The raw list entry string.
def raw
@raw ||= ''
end
alias_method :to_s, :raw

# The items basename (filename).
def basename
@basename ||= ''
end

# Looks like a directory, try CWD.
def dir?
!!(@dir ||= false)
end

# Looks like a file, try RETR.
def file?
!!(@file ||= false)
end

# Looks like a symbolic link.
def symlink?
!!(@symlink ||= false)
end

# Looks like a device.
def device?
!!(@device ||= false)
end

# Returns the modification time of the file/directory or the current time if unknown
def mtime
@mtime || Time.now
end

# Returns the filesize of the entry or 0 for directorties
def filesize
@filesize || 0
end

# Returns the detected server type if this entry
def server_type
@server_type || "Unknown"
end

def unknown?
@dir.nil? && @file.nil? && @symlink.nil? && @device.nil?
end
end
82 changes: 33 additions & 49 deletions lib/net/ftp/list/microsoft.rb
@@ -1,54 +1,38 @@
require 'net/ftp/list/parser'
require 'date'

module Net
class FTP
module List

# Parse Microsoft(NT) like FTP LIST entries.
#
# == MATCHES
#
# 06-25-07 01:08PM <DIR> etc
# 11-27-07 08:45PM 23437 README.TXT
#
# == SYNOPSIS
#
# entry = Net::FTP::List::Microsoft.new('06-25-07 01:08PM <DIR> etc')
# entry.dir? # => true
# entry.basename # => 'etc'
class Microsoft < Parser

# Stolen straight from the ASF's commons Java FTP LIST parser library.
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
REGEXP = %r!
^\s*
([0-9\-:\/]{5,})\s+([0-9\-:]{3,}(?:[aApP][mM])?)\s+
(?:(<DIR>)|([0-9]+))\s+
(\S.*)
\s*$
!x

# Parse a Microsoft(NT) like FTP LIST entries.
def initialize(raw)
super(raw)
match = REGEXP.match(raw.strip) or raise ParserError

@mtime = DateTime.strptime("#{match[1]} #{match[2]}", "%m-%d-%y %I:%M%p")

if match[3] == '<DIR>'
@dir = true
else
@filesize = match[4].to_i
@file = true
end

# TODO: Permissions, users, groups, date/time.

@basename = match[5]
end
end

end
# Parse Microsoft(NT) like FTP LIST entries.
#
# == MATCHES
#
# 06-25-07 01:08PM <DIR> etc
# 11-27-07 08:45PM 23437 README.TXT
class Net::FTP::List::Microsoft < Net::FTP::List::Parser
# Stolen straight from the ASF's commons Java FTP LIST parser library.
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
REGEXP = %r!
^\s*
([0-9\-:\/]{5,})\s+([0-9\-:]{3,}(?:[aApP][mM])?)\s+
(?:(<DIR>)|([0-9]+))\s+
(\S.*)
\s*$
!x

# Parse a Microsoft(NT) like FTP LIST entries.
def self.parse(raw)
match = REGEXP.match(raw.strip) or return false

mtime = DateTime.strptime("#{match[1]} #{match[2]}", "%m-%d-%y %H:%M%p")
is_dir = match[3] == '<DIR>'
filesize = is_dir ? 0 : match[4].to_i

emit_entry(
raw,
:dir => is_dir,
:file => !is_dir,
:filesize => filesize,
:basename => match[5],
:mtime => mtime
)
end
end
79 changes: 30 additions & 49 deletions lib/net/ftp/list/netware.rb
@@ -1,54 +1,35 @@
require 'net/ftp/list/parser'
require 'time'

module Net
class FTP
module List

# Parse Netware like FTP LIST entries.
#
# == MATCHES
#
# d [RWCEAFMS] dpearce 512 Jun 27 23:46 public.www
#
# == SYNOPSIS
#
# entry = Net::FTP::List::Netware.new('d [RWCEAFMS] dpearce 512 Jun 27 23:46 public.www')
# entry.dir? # => true
# entry.basename # => 'public.www'
class Netware < Parser

# Stolen straight from the ASF's commons Java FTP LIST parser library.
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/

REGEXP = %r!^
(d|-){1}\s+
\[(.*?)\]\s+
(\S+)\s+(\d+)\s+
(\S+\s+\S+\s+((\d+:\d+)|(\d{4})))
\s+(.*)
$!x

# Parse a Netware like FTP LIST entries.
def initialize(raw)
super(raw)
match = REGEXP.match(raw.strip) or raise ParserError

@mtime = Time.parse(match[5])
@filesize = match[4].to_i

if match[1] == 'd'
@dir = true
else
@file = true
end

# TODO: Permissions, users, groups, date/time.

@basename = match[9]
end
end

end
# Parse Netware like FTP LIST entries.
#
# == MATCHES
#
# d [RWCEAFMS] dpearce 512 Jun 27 23:46 public.www
class Net::FTP::List::Netware < Net::FTP::List::Parser
# Stolen straight from the ASF's commons Java FTP LIST parser library.
# http://svn.apache.org/repos/asf/commons/proper/net/trunk/src/java/org/apache/commons/net/ftp/
REGEXP = %r!^
(d|-){1}\s+
\[(.*?)\]\s+
(\S+)\s+(\d+)\s+
(\S+\s+\S+\s+((\d+:\d+)|(\d{4})))
\s+(.*)
$!x

# Parse a Netware like FTP LIST entries.
def self.parse(raw)
match = REGEXP.match(raw.strip) or return false

is_dir = match[1] == 'd'

emit_entry(
raw,
:mtime => Time.parse(match[5]),
:filesize => match[4].to_i,
:dir => is_dir,
:file => !is_dir,
:basename => match[9]
)
end
end

0 comments on commit c5cca44

Please sign in to comment.