diff --git a/CHANGELOG.md b/CHANGELOG.md index cadc339..e37412b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. - almost all option (-A) - full path option (-F) - add relative time option (-T) += add mime type (-M) ## [2024.6] ### Added diff --git a/lsv/entry.v b/lsv/entry.v index bd85aae..fd04e70 100644 --- a/lsv/entry.v +++ b/lsv/entry.v @@ -4,6 +4,7 @@ import crypto.sha1 import crypto.sha256 import crypto.sha512 import crypto.blake2b +import net.http.mime import math struct Entry { @@ -26,6 +27,7 @@ struct Entry { size_ki string size_kb string checksum string + mime_type string invalid bool // lstat could not access } @@ -79,6 +81,9 @@ fn make_entry(file string, dir_name string, options Options) Entry { is_exe := !is_dir && is_executable(stat) is_file := filetype == .regular indicator := if is_dir && options.dir_indicator { '/' } else { '' } + if options.mime_type { + os.file_ext(file).trim_left('.') + } name := if options.full_path { os.real_path(path) + indicator } else { file + indicator } return Entry{ @@ -101,6 +106,7 @@ fn make_entry(file string, dir_name string, options Options) Entry { size_ki: if options.size_ki { readable_size(size, true) } else { '' } size_kb: if options.size_kb { readable_size(size, false) } else { '' } checksum: if is_file { checksum(file, dir_name, options) } else { '' } + mime_type: get_mime_type(file, filetype, is_exe) invalid: invalid } } @@ -155,6 +161,18 @@ fn checksum(name string, dir_name string, options Options) string { } } +fn get_mime_type(file string, file_type os.FileType, is_exe bool) string { + if is_exe { + return 'application/octet-stream' + } + if file_type == .block_device || file_type == .character_device || file_type == .directory + || file_type == .fifo || file_type == .symbolic_link || file_type == .socket { + return '' + } + mt := mime.get_mime_type(os.file_ext(file).trim_left('.')) + return mt +} + @[inline] fn is_executable(stat os.Stat) bool { return stat.get_mode().bitmask() & 0b001001001 > 0 diff --git a/lsv/format_long.v b/lsv/format_long.v index 5c0c55e..5cfb2ef 100644 --- a/lsv/format_long.v +++ b/lsv/format_long.v @@ -15,6 +15,7 @@ const date_modified_title = 'Modified' const date_accessed_title = 'Accessed' const date_status_title = 'Status Change' const name_title = 'Name' +const mime_type_title = 'Mime Type' const unknown = '?' const block_size = 5 const space = ' ' @@ -34,6 +35,7 @@ struct Longest { mtime int atime int ctime int + mime_type int } enum StatTime { @@ -69,10 +71,16 @@ fn format_long_listing(entries []Entry, options Options) { print(table_border_pad_left) } + // mime type + if options.mime_type { + print(format_cell(entry.mime_type, longest.mime_type, .right, no_style, options)) + print_space() + } + // inode if options.inode { content := if entry.invalid { unknown } else { entry.stat.inode.str() } - print(format_cell(content, longest.inode, Align.right, no_style, options)) + print(format_cell(content, longest.inode, .right, no_style, options)) print_space() } @@ -200,44 +208,19 @@ fn format_long_listing(entries []Entry, options Options) { fn longest_entries(entries []Entry, options Options) Longest { return Longest{ + // vfmt off inode: if options.inode { longest_inode_len(entries, inode_title, options) } else { 0 } - nlink: if !options.no_hard_links { - longest_nlink_len(entries, links_title, options) - } else { - 0 - } - owner_name: if !options.no_owner_name { - longest_owner_name_len(entries, owner_title, options) - } else { - 0 - } - group_name: if !options.no_group_name { - longest_group_name_len(entries, group_title, options) - } else { - 0 - } + nlink: if !options.no_hard_links { longest_nlink_len(entries, links_title, options) } else { 0 } + owner_name: if !options.no_owner_name { longest_owner_name_len(entries, owner_title, options) } else { 0 } + group_name: if !options.no_group_name { longest_group_name_len(entries, group_title, options) } else { 0 } size: if !options.no_size { longest_size_len(entries, size_title, options) } else { 0 } - checksum: if options.checksum.len == 0 { - longest_checksum_len(entries, options.checksum, options) - } else { - 0 - } + checksum: if options.checksum.len == 0 { longest_checksum_len(entries, options.checksum, options) } else { 0 } file: longest_file_name_len(entries, name_title, options) - mtime: if !options.no_date { - longest_time(entries, .modified, date_modified_title, options) - } else { - 0 - } - atime: if options.accessed_date { - longest_time(entries, .accessed, date_accessed_title, options) - } else { - 0 - } - ctime: if options.changed_date { - longest_time(entries, .changed, date_status_title, options) - } else { - 0 - } + mtime: if !options.no_date { longest_time(entries, .modified, date_modified_title, options) } else { 0 } + atime: if options.accessed_date { longest_time(entries, .accessed, date_accessed_title, options) } else { 0 } + ctime: if options.changed_date { longest_time(entries, .changed, date_status_title, options) } else { 0 } + mime_type: if options.mime_type { longest_mime_type(entries, mime_type_title, options) } else { 0 } + // vfmt on } } @@ -259,6 +242,11 @@ fn format_header(options Options, longest Longest) (string, []int) { if options.table_format { buffer += table_border_pad_left } + if options.mime_type { + title := if options.header { mime_type_title } else { '' } + buffer += right_pad(title, longest.mime_type) + table_pad + cols << real_length(buffer) - 1 + } if options.inode { title := if options.header { inode_title } else { '' } buffer += left_pad(title, longest.inode) + table_pad @@ -439,7 +427,7 @@ fn format_time(entry Entry, stat_time StatTime, options Options) string { date := if options.time_relative { local.relative_short() } else { - mut dt := local.custom_format(time_format(options)) + dt := local.custom_format(time_format(options)) if dt.starts_with('0') { ' ' + dt[1..] } else { @@ -507,3 +495,9 @@ fn longest_time(entries []Entry, stat_time StatTime, title string, options Optio max := arrays.max(lengths) or { 0 } return if options.header { max(real_length(title), max) } else { max } } + +fn longest_mime_type(entries []Entry, title string, options Options) int { + mime_types := entries.map(it.mime_type.len) + max := arrays.max(mime_types) or { 0 } + return if options.header { max(real_length(title), max) } else { max } +} diff --git a/lsv/options.v b/lsv/options.v index 7bd2747..28e6c3d 100644 --- a/lsv/options.v +++ b/lsv/options.v @@ -61,6 +61,7 @@ struct Options { time_compact bool time_compact_with_day bool time_relative bool + mime_type bool checksum string // // from ls colors @@ -127,6 +128,7 @@ fn parse_args(args []string) Options { time_compact := fp.bool('', `J`, false, 'show time in compact format') time_compact_with_day := fp.bool('', `L`, false, 'show time in compact format with week day') time_relative := fp.bool('', `T`, false, 'show relative time') + mime_type := fp.bool('', `M`, false, 'show mime type') inode := fp.bool('', `N`, false, 'show inodes') no_wrap := fp.bool('', `Z`, false, 'do not wrap long lines\n') @@ -166,6 +168,7 @@ fn parse_args(args []string) Options { inode: inode list_by_lines: list_by_lines long_format: long_format + mime_type: mime_type no_count: no_count no_date: no_date no_dim: no_dim