6 Commits
9 changed files with 312 additions and 31 deletions
+2
View File
@@ -5,3 +5,5 @@ echo -e "\`\`\`txt" > HELP.md
go run . -? >> HELP.md
echo -e "\`\`\`" >> HELP.md
exec git add HELP.md
exit 0
+43
View File
@@ -0,0 +1,43 @@
#!/bin/sh
R_NAME=$1
R_URL=$2
error_exit()
{
if [ "$?" != "0" ]; then
nl=$'\n'
echo "$1""$nl""$2""$nl""$nl""Aborting push to ""$R_NAME"" (""$R_URL"")" 1>&2
exit 1
fi
}
# Build binary for variety of arch
# linux amd64
ERROR=$(go env -w GOOS=linux GOARCH=amd64 2>&1 > /dev/null)
error_exit "Cannot set GOOS=linux and GOARCH=amd64:" "$ERROR"
ERROR=$(go build -o build/logo-ls-linux-amd64 2>&1 > /dev/null)
error_exit "Cannot build logo-ls-linux-amd64:" "$ERROR"
# linux arm64
ERROR=$(go env -w GOOS=linux GOARCH=arm64 2>&1 > /dev/null)
error_exit "Cannot set GOOS=linux and GOARCH=arm64:" "$ERROR"
ERROR=$(go build -o build/logo-ls-linux-arm64 2>&1 > /dev/null)
error_exit "Cannot build logo-ls-linux-arm64:" "$ERROR"
# linux 386
ERROR=$(go env -w GOOS=linux GOARCH=386 2>&1 > /dev/null)
error_exit "Cannot set GOOS=linux and GOARCH=386:" "$ERROR"
ERROR=$(go build -o build/logo-ls-linux-386 2>&1 > /dev/null)
error_exit "Cannot build logo-ls-linux-386:" "$ERROR"
# darwin amd64
ERROR=$(go env -w GOOS=darwin GOARCH=amd64 2>&1 > /dev/null)
error_exit "Cannot set GOOS=darwin and GOARCH=amd64:" "$ERROR"
ERROR=$(go build -o build/logo-ls-darwin-amd64 2>&1 > /dev/null)
error_exit "Cannot build logo-ls-darwin-amd64:" "$ERROR"
# reset env to default
go env -u GOOS GOARCH
exit 0
+1
View File
@@ -16,3 +16,4 @@
# Do not commit the executable
logo-ls
build/
+106
View File
@@ -0,0 +1,106 @@
// custom/ in-house implementation of go's tab-writer
// because ls shows table in column major fation and according to screen size
package ctw
import (
"bytes"
"fmt"
"math"
)
func widthsSum(w []int, p int) int {
s := 0
for _, v := range w {
s += v + p
}
s -= p
return s
}
func findMax(arr []int) int {
max := 0
for _, v := range arr {
if v >= max {
max = v
}
}
return max
}
//Ctw has complexity of no. of columns x no. of files
// termW is width of the terminal
func Ctw(buf *bytes.Buffer, data []string, termW int) {
dn := len(data) // length of data slice
if dn == 0 {
return
}
const pad = 2 // padding b/w columns
var lens []int // slice of lengths of each element in the data slice
widths := make([]int, 0) // slice of widths of each column (don't use because it over run once)
var prevWidths []int // accurate widths value
for _, v := range data {
lens = append(lens, len(v))
}
prevj := 0 // prevj is previous jump value (row value if you may)
// for loop runs for all possible nos. of column (1,2,3,4....)
for {
cols := len(widths) + 1
widths = append(widths, 0)
j := int(math.Ceil(float64(dn) / float64(cols))) // jump value corresponding to cols
if prevj == j { // removes redundant calculations
continue
}
b := 0 // begining of column
e := j // end of column
// find optimal widths (width of each column and total no of columns)
for i := 0; i < cols && e <= dn; i++ {
widths[i] = findMax(lens[b:e])
b, e = e, e+j
}
// for last column if last column is not complete
if e-j < dn {
widths[cols-1] = findMax(lens[e-j : dn])
}
prevj = j
totW := widthsSum(widths, pad) //total width of the ls block
if totW > termW {
break
} else if totW > termW/2 { // if total width of the ls block is more than half of terminal
// copy widths to prevWidths
prevWidths = make([]int, len(widths))
for i := range widths {
prevWidths[i] = widths[i]
}
}
if cols == dn { // if content comes in one line of terminal
// copy widths to prevWidths
prevWidths = make([]int, len(widths))
for i := range widths {
prevWidths[i] = widths[i]
}
break
}
}
// total no of rows
rows := int(math.Ceil(float64(dn) / float64(len(prevWidths))))
// loop to write entire ls block to buffer
for i := 0; i < rows; i++ {
for j := 0; j < len(prevWidths); j++ {
if i+j*rows >= dn { // checks for last column if incomplete
continue
}
fmt.Fprintf(buf, "%-*s", prevWidths[j]+pad, data[i+j*rows])
}
fmt.Fprintf(buf, "\n")
}
}
+70 -11
View File
@@ -11,6 +11,8 @@ import (
"syscall"
"text/tabwriter"
"time"
"github.com/Yash-Handa/logo-ls/ctw"
)
type file struct {
@@ -24,10 +26,11 @@ type file struct {
}
type dir struct {
info *file
files []*file // all child files and dirs
dirs []*file // for recursion contain only child dirs
less func(int, int) bool
info *file
parent *file
files []*file // all child files and dirs
dirs []*file // for recursion contain only child dirs
less func(int, int) bool
}
// define methods on *dir type only not on file type
@@ -40,8 +43,7 @@ func newDir(d *os.File) (*dir, error) {
// filing current dir info
t.info = new(file)
t.info.name = d.Name()
t.info.ext = ""
t.info.name = "."
if curDir {
ds, err := d.Stat()
if err != nil {
@@ -96,8 +98,35 @@ func newDir(d *os.File) (*dir, error) {
}
}
// if -a flag is passed then only eval parent dir and append to files
if flagVector&flag_a > 0 {
t.files = append(t.files, t.info)
p, err := filepath.Abs(d.Name())
if err != nil {
// partial *dir (without parent dir) and error
return t, err
}
pp := filepath.Dir(p)
pds, err := os.Lstat(pp)
if err != nil {
// partial *dir (without parent dir) and error
return t, err
}
t.parent = new(file)
t.parent.name = ".."
t.parent.size = pds.Size()
t.parent.modTime = pds.ModTime()
if long {
t.parent.mode = pds.Mode().String()
t.parent.modeBits = uint32(pds.Mode())
t.parent.owner, t.parent.group = getOwnerGroupInfo(pds)
}
if flagVector&flag_s > 0 {
if s, ok := pds.Sys().(*syscall.Stat_t); ok {
t.parent.blocks = s.Blocks
}
}
t.files = append(t.files, t.parent)
}
// return *dir with no error
@@ -105,6 +134,34 @@ func newDir(d *os.File) (*dir, error) {
return t, err
}
func newDir_ArgFiles(files []os.FileInfo) *dir {
var long bool = flagVector&(flag_l|flag_o|flag_g) > 0
t := new(dir)
for _, v := range files {
name := v.Name()
f := new(file)
f.ext = filepath.Ext(name)
f.name = name[0 : len(name)-len(f.ext)]
f.indicator = getIndicator(v.Mode())
f.size = v.Size()
f.modTime = v.ModTime()
if long {
f.mode = v.Mode().String()
f.modeBits = uint32(v.Mode())
f.owner, f.group = getOwnerGroupInfo(v)
}
if flagVector&flag_s > 0 {
if s, ok := v.Sys().(*syscall.Stat_t); ok {
f.blocks = s.Blocks
}
}
t.files = append(t.files, f)
}
return t
}
func (d *dir) print() *bytes.Buffer {
// take care of printing, extending symbolic links in long forms
@@ -128,6 +185,7 @@ func (d *dir) print() *bytes.Buffer {
}
fmt.Fprintf(w, fmtStr, v.mode, v.owner, v.group, getSizeInFormate(v.size), v.modTime.Format(time.Stamp), v.name+v.ext+v.indicator)
}
w.Flush()
case flagVector&flag_1 > 0:
w = tabwriter.NewWriter(buf, 0, 0, 1, ' ', tabwriter.DiscardEmptyColumns)
for _, v := range d.files {
@@ -136,18 +194,19 @@ func (d *dir) print() *bytes.Buffer {
}
fmt.Fprintf(w, "%s\t\n", v.name+v.ext+v.indicator)
}
w.Flush()
default:
w = tabwriter.NewWriter(buf, 0, 0, 2, ' ', tabwriter.DiscardEmptyColumns)
for _, v := range d.files {
// w = tabwriter.NewWriter(buf, 0, 0, 2, ' ', tabwriter.DiscardEmptyColumns)
temp := make([]string, len(d.files))
for i, v := range d.files {
s := ""
if flagVector&flag_s > 0 {
s = getSizeInFormate(v.blocks*512) + " "
}
fmt.Fprintf(w, "%s\t", s+v.name+v.ext+v.indicator)
temp[i] = s + v.name + v.ext + v.indicator
}
fmt.Fprintln(w)
ctw.Ctw(buf, temp, terminalWidth)
}
w.Flush()
return buf
}
+12 -2
View File
@@ -10,6 +10,16 @@ import (
)
func mainSort(a, b string) bool {
switch a {
case ".", "..":
default:
a = strings.TrimPrefix(a, ".")
}
switch b {
case ".", "..":
default:
b = strings.TrimPrefix(b, ".")
}
return strings.ToLower(a) < strings.ToLower(b)
}
@@ -107,14 +117,14 @@ func getIndicator(modebit os.FileMode) (i string) {
switch {
case modebit&os.ModeDir > 0:
i = "/"
case modebit&1000000 > 0:
i = "*"
case modebit&os.ModeNamedPipe > 0:
i = "|"
case modebit&os.ModeSymlink > 0:
i = "@"
case modebit&os.ModeSocket > 0:
i = "="
case modebit&1000000 > 0:
i = "*"
}
return i
}
+4 -1
View File
@@ -2,4 +2,7 @@ module github.com/Yash-Handa/logo-ls
go 1.15
require github.com/pborman/getopt/v2 v2.0.0
require (
github.com/pborman/getopt/v2 v2.0.0
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
)
+8
View File
@@ -1,2 +1,10 @@
github.com/pborman/getopt/v2 v2.0.0 h1:Tn8XVmhb93Wbc346Tk4P6KutfpMVp+iztUzkZrTSyB4=
github.com/pborman/getopt/v2 v2.0.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+66 -17
View File
@@ -5,8 +5,10 @@ import (
"io"
"log"
"os"
"sort"
"github.com/pborman/getopt/v2"
"golang.org/x/crypto/ssh/terminal"
)
// flags with corresponding bit values
@@ -35,6 +37,9 @@ const (
// flagVector has all the options set in it. Each bit represent an option.
var flagVector uint
// terminal width for formatting
var terminalWidth int
func main() {
// content flags
f_a := getopt.BoolLong("all", 'a', "do not ignore entries starting with .")
@@ -144,32 +149,78 @@ func main() {
flagVector |= flag_g
case *f_l:
flagVector |= flag_l
case *f_1:
default:
// screen width for custom tw
var e error = nil
terminalWidth, _, e = terminal.GetSize(int(os.Stdout.Fd()))
if e != nil {
terminalWidth = 80
}
}
// extract files/dir from arguments
dirs := getopt.Args()
if len(dirs) == 0 {
// use pwd
pwd, err := os.Open(".")
dirs = append(dirs, ".")
}
sort.Strings(dirs)
args := struct {
files []os.FileInfo
dirs []*os.File
}{}
// segregate args in files and dirs, and print error for those which cannot be opened
for _, v := range dirs {
d, err := os.Open(v)
if err != nil {
log.Printf("cannot access \".\": %v\n", err)
pwd.Close()
os.Exit(2)
}
d, err := newDir(pwd)
pwd.Close()
if err != nil {
log.Printf("partial access to \".\": %v\n", err)
log.Printf("cannot access %q: %v\n", v, err)
d.Close()
defer os.Exit(2)
continue
}
// print the info of the files of pwd
io.Copy(os.Stdout, d.print())
} else {
for _, v := range dirs {
// use this list containing both dirs and files
_ = v
ds, err := d.Stat()
if err != nil {
log.Printf("cannot access %q: %v\n", v, err)
d.Close()
defer os.Exit(2)
continue
}
if ds.IsDir() {
args.dirs = append(args.dirs, d)
} else {
args.files = append(args.files, ds)
}
}
// process and display all files
io.Copy(os.Stdout, newDir_ArgFiles(args.files).print())
if len(args.files) > 0 && len(args.dirs) > 0 {
fmt.Println()
}
// process and display all the dirs in arg
pName := len(dirs) > 1
for i, v := range args.dirs {
if pName {
fmt.Printf("%s:\n", v.Name())
}
d, err := newDir(v)
v.Close()
if err != nil {
log.Printf("partial access to %q: %v\n", v.Name(), err)
defer os.Exit(2)
}
// print the info of the files of the directory
io.Copy(os.Stdout, d.print())
if i < len(args.dirs)-1 {
fmt.Println()
}
}
}
func init() {
@@ -177,5 +228,3 @@ func init() {
log.SetPrefix("logo-ls: ")
log.SetFlags(0)
}
// todo: i. multiple dir/ file handle