lib/ftpd/list_format/ls.rb
# frozen_string_literal: true
module Ftpd
module ListFormat
# Directory formatter that approximates the output of "ls -l"
class Ls
extend Forwardable
# Create a new formatter for a file object
# @param file_info [FileInfo]
def initialize(file_info)
@file_info = file_info
end
# Return the formatted directory entry, for example:
# -rw-r--r-- 1 user group Mar 3 08:38 foo
def to_s
'%s%s %d %-8s %-8s %8d %s %s' % [
file_type,
file_mode_letters,
@file_info.nlink,
@file_info.owner,
@file_info.group,
@file_info.size,
format_time(@file_info.mtime),
filename,
]
end
private
SIX_MONTHS = 180 * 24 * 60 * 60
def filename
File.basename(@file_info.path)
end
def file_type
FileType.letter(@file_info.ftype)
end
def file_mode_letters
FileMode.new(@file_info.mode).letters
end
def self.format_time(mtime)
age = Time.now - mtime
format = '%b %e ' + if age < 0 || age > SIX_MONTHS
' %Y'
else
'%H:%M'
end
mtime.strftime(format)
end
def_delegator self, :format_time
# Map file type strings to ls file type letters
class FileType
# Map a file type string to a file type letter.
# @param ftype [String] file type as returned by File::Stat#ftype
# @return [String] File type letter
def self.letter(ftype)
case ftype
when 'file'
'-'
when 'directory'
'd'
when 'characterSpecial'
'c'
when 'blockSpecial'
'b'
when 'fifo'
'p'
when 'link'
'l'
when 'socket'
's'
else # 'unknown', etc.
'?'
end
end
end
# Map file mode bits into ls style file mode letters
class FileMode
# @param mode [Integer] File mode bits, as returned by
# File::Stat#mode
def initialize(mode)
@mode = mode
end
# Return the mode bits as ls style letters.
# For example, "-rw-r--r--"
def letters
[
triad(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, SET_UID, 'Ss'),
triad(GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, SET_GID, 'Ss'),
triad(OTHER_READ, OTHER_WRITE, OTHER_EXECUTE, STICKY, 'Tt'),
].join
end
private
def bit(bit_number)
@mode >> bit_number & 1
end
def triad(read_bit, write_bit, execute_bit, special_bit, special_letters)
execute_chars = if bit(special_bit) != 0
special_letters
else
'-x'
end
[
pick_char('-r', read_bit),
pick_char('-w', write_bit),
pick_char(execute_chars, execute_bit),
]
end
def pick_char(s, bit_number)
s[bit(bit_number), 1]
end
OTHER_EXECUTE = 0
OTHER_WRITE = 1
OTHER_READ = 2
GROUP_EXECUTE = 3
GROUP_WRITE = 4
GROUP_READ = 5
OWNER_EXECUTE = 6
OWNER_WRITE = 7
OWNER_READ = 8
STICKY = 9
SET_GID = 10
SET_UID = 11
end
end
end
end