diff --git a/README.md b/README.md index 8c8ca23..f78d290 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,7 @@ paths like missing support for symbolic links, mismatched path separators, inability to handle spaces in directory names, and resolves C-drive paths to their normal DOS form. +## `qdls` + +A DOS clone of `ls` that hides Windows hidden files. + diff --git a/qdls/.gitignore b/qdls/.gitignore new file mode 100644 index 0000000..f57e698 --- /dev/null +++ b/qdls/.gitignore @@ -0,0 +1,4 @@ +obj/ +bin/ +.idea/ + diff --git a/qdls/Program.cs b/qdls/Program.cs new file mode 100644 index 0000000..9ec5455 --- /dev/null +++ b/qdls/Program.cs @@ -0,0 +1,66 @@ +using Qdls; + +// collect args and consume flags +List arguments = args.ToList(); + +bool showHidden = Util.CheckFlag(arguments, "-a", "--all", "-A"); + +// default to "." if no target args +if(arguments.Count == 0) + arguments = ["."]; + +// run on targets +foreach(var arg in arguments) { + // don't try to list nonexistent entries or files + if(!Path.Exists(arg)) { + Console.WriteLine($"'{arg}': does not exist"); + continue; + } + if(File.Exists(arg)) { + Console.WriteLine($"'{arg}': is file"); + continue; + } + + // fetch children + var children = Directory.GetFileSystemEntries(arg); + // exclude hidden files where applicable + if(!showHidden) { children = children.Where(f => !Util.IsHidden(f)).ToArray(); } + + // state vars + var buffer = ""; + var longest = Path.GetFileName(children.OrderByDescending(s => s.Length).First()).Length; + var columns = Console.WindowWidth / (longest + 2); + var multiline = columns < children.Count(); + var position = 0; + + // print names if more than one target + if(args.Length > 1) + Console.WriteLine($"{arg}:"); + // iterate over children; build buffer and output when full + foreach(var child in children.OrderBy(f => (int)(Path.GetFileName(f)[0]))) { + var name = Path.GetFileName(child); + + // format hidden files + if(Util.IsHidden(child)) + buffer += Format.Hidden; + + // format directories + if(Directory.Exists(child)) + buffer += Format.Directory; + + // if we pass max width, write buffer and clear + if( (++position > columns) && multiline ) { + position = 1; + Console.WriteLine(buffer.TrimEnd()); + buffer = ""; + } + if(multiline) + buffer += $"{name}{Format.Reset}".PadRight(longest + 6); // magic number 6 is +2 ignoring ansi reset sequence + else + buffer += $"{name}{Format.Reset} "; + + } + + // print last line + Console.WriteLine(buffer); +} diff --git a/qdls/Util.cs b/qdls/Util.cs new file mode 100644 index 0000000..103f34c --- /dev/null +++ b/qdls/Util.cs @@ -0,0 +1,29 @@ +namespace Qdls; + +using System.IO; + +public static class Util { + + public static bool IsHidden(string path) { + var name = Path.GetFileName(path); + return + name.StartsWith('.') || + ( File.GetAttributes(path) & FileAttributes.Hidden ) == FileAttributes.Hidden; + } + + public static bool CheckFlag(List args, params string[] forms) { + var found = args.RemoveAll(forms.Contains); + return found > 0; + } + +} + +public static class Format { + + public const string Reset = "\x1b[0m"; // reset + public const string Hidden = "\x1b[2;3m"; // faint + italic + public const string Executable = "\x1b[1;32m"; // bold + green + public const string Directory = "\x1b[1;34m"; // bold + blue + +} + diff --git a/qdls/qdls.csproj b/qdls/qdls.csproj new file mode 100644 index 0000000..206b89a --- /dev/null +++ b/qdls/qdls.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + +