Filter by size limits

This commit is contained in:
Blallo 2020-03-19 00:11:11 +01:00
parent 644c71f7d9
commit 7d1e791433
No known key found for this signature in database
GPG key ID: 0CBE577C9B72DC3F
3 changed files with 156 additions and 29 deletions

View file

@ -44,6 +44,30 @@ func (u *UnitValue) Set(value string) error {
} }
} }
type LimitValue struct {
limit int64
input string
}
func (l *LimitValue) String() string {
return fmt.Sprintf("%d", l.limit)
}
func (l *LimitValue) Set(size string) error {
value, err := tree.ParseSize(size)
l.limit = value
l.input = size
return err
}
func validateLimits(min, max *LimitValue) bool {
if max.limit != 0 && min.limit > max.limit {
fmt.Fprintf(os.Stderr, "Given min-limit (%s) is above max-limit (%s)\n", min.input, max.input)
return false
}
return true
}
func min(a, b int) int { func min(a, b int) int {
if a < b { if a < b {
return a return a
@ -57,9 +81,13 @@ func main() {
var root tree.Node var root tree.Node
var interval time.Duration var interval time.Duration
var unit = &UnitValue{unit: "KB"} var unit = &UnitValue{unit: "KB"}
var minLimit = &LimitValue{limit: 0}
var maxLimit = &LimitValue{limit: 0}
cli := flag.NewFlagSet(os.Args[0], flag.ExitOnError) cli := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
cli.IntVar(&depth, "depth", 0, "Depth to display") cli.IntVar(&depth, "depth", 0, "Depth to display")
cli.Var(unit, "unit", "Unit in which to report size") cli.Var(unit, "unit", "Unit in which to report size")
cli.Var(minLimit, "limit-min", "Mininmum size of directories to display, with unit (0 means no limit)")
cli.Var(maxLimit, "limit-max", "Maximum size of directories to display, with unit (0 means no limit)")
cli.DurationVar(&interval, "interval", 100*time.Millisecond, "The update interval") cli.DurationVar(&interval, "interval", 100*time.Millisecond, "The update interval")
cli.Usage = func() { cli.Usage = func() {
fmt.Fprintf(cli.Output(), "Usage:\n%s [opts] [PATH]\n\n PATH: the root path to start from. Defaults to $PWD.\n\nopts:\n", os.Args[0]) fmt.Fprintf(cli.Output(), "Usage:\n%s [opts] [PATH]\n\n PATH: the root path to start from. Defaults to $PWD.\n\nopts:\n", os.Args[0])
@ -67,6 +95,10 @@ func main() {
} }
cli.Parse(os.Args[1:]) cli.Parse(os.Args[1:])
if !validateLimits(minLimit, maxLimit) {
os.Exit(-1)
}
switch narg := cli.NArg(); narg { switch narg := cli.NArg(); narg {
case 0: case 0:
path = "." path = "."
@ -83,6 +115,7 @@ func main() {
root = tree.NewTop(path) root = tree.NewTop(path)
} }
root.SetUnit(unit.String()) root.SetUnit(unit.String())
root.SetLimits(minLimit.limit, maxLimit.limit)
out := log.New(os.Stdout, "", 0) out := log.New(os.Stdout, "", 0)
go root.Spawn(depth) go root.Spawn(depth)
@ -92,18 +125,24 @@ func main() {
os.Exit(1) os.Exit(1)
} }
firstRound := true treeDepth := 0
for { for {
select { select {
case <-time.After(interval): case <-time.After(interval):
if !firstRound { if treeDepth != 0 {
depth := min(root.Depth()+2, height) out.Printf("\r\033[%dA", treeDepth)
out.Printf("\r\033[%dA", depth)
} else { } else {
out.Print("\r\033[A") out.Print("\r\033[A")
} }
newTreeDepth := min(root.Depth()+2, height)
out.Print(root) out.Print(root)
firstRound = false if newTreeDepth <= treeDepth {
//for i := treeDepth; i < newTreeDepth; i++ {
// out.Print("\n\033[2K")
//}
out.Print("\033[J\033[A")
}
treeDepth = newTreeDepth
if root.Complete() { if root.Complete() {
return return
} }

View file

@ -23,6 +23,7 @@ var (
NodeT = "├──" NodeT = "├──"
NodeL = "└──" NodeL = "└──"
NodePad = " " NodePad = " "
CloseLine = "\033[K\n"
) )
type Node interface { type Node interface {
@ -32,6 +33,7 @@ type Node interface {
Spawn(int) error Spawn(int) error
Collect() error Collect() error
AddCollector(chan int64) AddCollector(chan int64)
SetLimits(int64, int64)
Level() int Level() int
Name() string Name() string
Complete() bool Complete() bool
@ -48,6 +50,8 @@ type Top struct {
unit ByteSize unit ByteSize
tree []Node tree []Node
collect []<-chan int64 collect []<-chan int64
minLimit int64
maxLimit int64
level int level int
complete bool complete bool
} }
@ -58,7 +62,7 @@ func NewTop(path string) *Top {
} }
func (t *Top) SetUnit(unit string) { func (t *Top) SetUnit(unit string) {
t.unit = setUnit(unit) t.unit = parseUnit(unit)
} }
func (t *Top) GetUnit() ByteSize { func (t *Top) GetUnit() ByteSize {
@ -112,6 +116,11 @@ func (t *Top) AddCollector(collect chan int64) {
t.collect = append(t.collect, collect) t.collect = append(t.collect, collect)
} }
func (t *Top) SetLimits(min, max int64) {
t.minLimit = min
t.maxLimit = max
}
func (t *Top) Level() int { func (t *Top) Level() int {
return t.level return t.level
} }
@ -132,30 +141,35 @@ func (t *Top) Complete() bool {
func (t *Top) Depth() int { func (t *Top) Depth() int {
var depth int var depth int
for _, child := range t.tree { for _, child := range t.tree {
if isIntoLimits(child.Size(), t.minLimit, t.maxLimit) {
depth += child.Depth() depth += child.Depth()
} }
}
return depth return depth
} }
func (t *Top) String() string { func (t *Top) String() string {
var out string var out string
var lines []string var lines []string
out += fmt.Sprintf("(%s) %s\n", fmtSize(t.Size(), t.GetUnit()), t.path) out += fmt.Sprintf("(%s) %s%s", fmtSize(t.Size(), t.GetUnit()), t.path, CloseLine)
treeSize := len(t.tree) treeSize := 0
for _, child := range t.tree { for _, child := range t.tree {
if isIntoLimits(child.Size(), t.minLimit, t.maxLimit) {
lines = append(lines, fmt.Sprintf("%s", child)) lines = append(lines, fmt.Sprintf("%s", child))
treeSize += 1
}
} }
for a := 0; a < treeSize-1; a++ { for a := 0; a < treeSize-1; a++ {
childLines := strings.Split(lines[a], "\n") childLines := strings.Split(lines[a], "\n")
out += fmt.Sprintf(" %s%s\n", NodeT, childLines[0]) out += fmt.Sprintf(" %s%s%s", NodeT, childLines[0], CloseLine)
for b := 1; b < len(childLines); b++ { for b := 1; b < len(childLines); b++ {
out += fmt.Sprintf(" %s%s\n", NodeB, childLines[b]) out += fmt.Sprintf(" %s%s%s", NodeB, childLines[b], CloseLine)
} }
} }
childLines := strings.Split(lines[treeSize-1], "\n") childLines := strings.Split(lines[treeSize-1], "\n")
out += fmt.Sprintf(" %s%s\n", NodeL, childLines[0]) out += fmt.Sprintf(" %s%s%s", NodeL, childLines[0], CloseLine)
for a := 1; a < len(childLines); a++ { for a := 1; a < len(childLines); a++ {
out += fmt.Sprintf("%s%s\n", " ", childLines[a]) out += fmt.Sprintf("%s%s%s", " ", childLines[a], CloseLine)
} }
return out return out
} }
@ -201,6 +215,8 @@ type Intermediate struct {
tree []Node tree []Node
collect []<-chan int64 collect []<-chan int64
refer chan int64 refer chan int64
minLimit int64
maxLimit int64
level int level int
complete bool complete bool
} }
@ -215,7 +231,7 @@ func NewIntermediate(path string, parent Node) (*Intermediate, chan int64) {
} }
func (i *Intermediate) SetUnit(unit string) { func (i *Intermediate) SetUnit(unit string) {
i.unit = setUnit(unit) i.unit = parseUnit(unit)
} }
func (i *Intermediate) GetUnit() ByteSize { func (i *Intermediate) GetUnit() ByteSize {
@ -272,6 +288,11 @@ func (i *Intermediate) AddCollector(collect chan int64) {
i.collect = append(i.collect, collect) i.collect = append(i.collect, collect)
} }
func (i *Intermediate) SetLimits(min, max int64) {
i.minLimit = min
i.maxLimit = max
}
func (i *Intermediate) Level() int { func (i *Intermediate) Level() int {
return i.level return i.level
} }
@ -292,33 +313,38 @@ func (i *Intermediate) Complete() bool {
func (i *Intermediate) Depth() int { func (i *Intermediate) Depth() int {
var depth int var depth int
for _, child := range i.tree { for _, child := range i.tree {
if isIntoLimits(child.Size(), i.minLimit, i.maxLimit) {
depth += child.Depth() depth += child.Depth()
} }
}
return depth return depth
} }
func (i *Intermediate) String() string { func (i *Intermediate) String() string {
var lines []string var lines []string
out := fmt.Sprintf("(%s) %s\n", fmtSize(i.Size(), i.GetUnit()), i.Name()) out := fmt.Sprintf("(%s) %s\n", fmtSize(i.Size(), i.GetUnit()), i.Name())
treeSize := len(i.tree) treeSize := 0
for _, child := range i.tree { for _, child := range i.tree {
if isIntoLimits(child.Size(), i.minLimit, i.maxLimit) {
lines = append(lines, fmt.Sprintf("%s", child)) lines = append(lines, fmt.Sprintf("%s", child))
treeSize += 1
}
} }
for a := 0; a < treeSize-1; a++ { for a := 0; a < treeSize-1; a++ {
childLines := strings.Split(lines[a], "\n") childLines := strings.Split(lines[a], "\n")
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeT, childLines[0]) out += fmt.Sprintf("%s%s%s", NodePad, NodeT, childLines[0])
for b := 1; b < len(childLines); b++ { for b := 1; b < len(childLines); b++ {
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeB, childLines[b]) out += fmt.Sprintf("%s%s%s", NodePad, NodeB, childLines[b])
} }
} }
childLines := strings.Split(lines[treeSize-1], "\n") childLines := strings.Split(lines[treeSize-1], "\n")
lenChildLines := len(childLines) lenChildLines := len(childLines)
if lenChildLines > 1 { if lenChildLines > 1 {
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeL, childLines[0]) out += fmt.Sprintf("%s%s%s", NodePad, NodeL, childLines[0])
for a := 1; a < lenChildLines-2; a++ { for a := 1; a < lenChildLines-2; a++ {
out += fmt.Sprintf(" %s%s\n", NodePad, childLines[a]) out += fmt.Sprintf(" %s%s", NodePad, childLines[a])
} }
out += fmt.Sprintf(" %s%s", NodePad, childLines[lenChildLines-1]) out += fmt.Sprintf(" %s%s", NodePad, childLines[lenChildLines-1])
} else { } else {
@ -368,6 +394,8 @@ type Bottom struct {
walker Walker walker Walker
collect chan int64 collect chan int64
refer chan int64 refer chan int64
minLimit int64
maxLimit int64
level int level int
complete bool complete bool
} }
@ -381,7 +409,7 @@ func NewBottom(path string, parent Node) (*Bottom, chan int64) {
} }
func (b *Bottom) SetUnit(unit string) { func (b *Bottom) SetUnit(unit string) {
b.unit = setUnit(unit) b.unit = parseUnit(unit)
} }
func (b *Bottom) GetUnit() ByteSize { func (b *Bottom) GetUnit() ByteSize {
@ -414,6 +442,11 @@ func (b *Bottom) AddCollector(collect chan int64) {
b.collect = collect b.collect = collect
} }
func (b *Bottom) SetLimits(min, max int64) {
b.minLimit = min
b.maxLimit = max
}
func (b *Bottom) Level() int { func (b *Bottom) Level() int {
return b.level return b.level
} }
@ -455,7 +488,7 @@ func NewSingle(path string) *Single {
} }
func (s *Single) SetUnit(unit string) { func (s *Single) SetUnit(unit string) {
s.unit = setUnit(unit) s.unit = parseUnit(unit)
} }
func (s *Single) GetUnit() ByteSize { func (s *Single) GetUnit() ByteSize {
@ -485,6 +518,11 @@ func (s *Single) AddCollector(collect chan int64) {
s.collect = collect s.collect = collect
} }
func (s *Single) SetLimits(min, max int64) {
// just ignore limits
return
}
func (s *Single) Level() int { func (s *Single) Level() int {
return 0 return 0
} }

View file

@ -1,10 +1,17 @@
package tree package tree
import ( import (
"errors"
"fmt" "fmt"
"math"
"regexp"
"strconv"
"sync" "sync"
) )
var ErrUnparsableSize = errors.New("unparsable size")
var matchSize = regexp.MustCompile("^(\\d+(\\.\\d+)?)([KMGTP]?B?)$")
type ByteSize uint type ByteSize uint
const ( const (
@ -20,7 +27,7 @@ func isLastLevel(node Node, maxLevel int) bool {
return maxLevel-1 == node.Level() return maxLevel-1 == node.Level()
} }
func setUnit(unit string) ByteSize { func parseUnit(unit string) ByteSize {
switch unit { switch unit {
case "B": case "B":
return B return B
@ -40,6 +47,19 @@ func setUnit(unit string) ByteSize {
} }
} }
func parseUnitSafe(unit string) (ByteSize, error) {
var returnVal ByteSize
var err error
defer func() {
if r := recover(); r != nil {
returnVal = 0
err = ErrUnparsableSize
}
}()
returnVal = parseUnit(unit)
return returnVal, err
}
func fmtUnit(unit ByteSize) string { func fmtUnit(unit ByteSize) string {
switch unit { switch unit {
case B: case B:
@ -66,6 +86,36 @@ func fmtSize(size int64, unit ByteSize) string {
return fmt.Sprintf("%.2f %s", dimension, fmtUnit(unit)) return fmt.Sprintf("%.2f %s", dimension, fmtUnit(unit))
} }
func ParseSize(size string) (int64, error) {
result := matchSize.FindStringSubmatch(size)
sizeNum := result[1]
if sizeNum == "" {
return 0, ErrUnparsableSize
}
unit, err := parseUnitSafe(result[3])
if err != nil {
return 0, err
}
num, err := strconv.ParseFloat(sizeNum, 64)
if err != nil {
return 0, err
}
return int64(math.Floor(num * float64(unit))), nil
}
func isIntoLimits(size, min, max int64) bool {
if min <= size {
if max == 0 {
return true
}
if size <= max {
return true
}
}
return false
}
func merge(cs []<-chan int64) <-chan int64 { func merge(cs []<-chan int64) <-chan int64 {
var wg sync.WaitGroup var wg sync.WaitGroup
out := make(chan int64) out := make(chan int64)