├── LICENSE ├── README.md ├── cmd ├── bar3d │ ├── bar3d.go │ ├── data.d │ ├── f.pdf │ ├── go.mod │ └── go.sum ├── between │ ├── between.go │ └── go.mod ├── bezpoints │ ├── README.md │ ├── bezpoints.go │ ├── f.tex │ └── go.mod ├── c19chart │ ├── README.md │ ├── c19chart.go │ ├── f.deck-00001.png │ ├── go.mod │ └── go.sum ├── cc │ ├── README.md │ ├── cc.png │ ├── d1.png │ ├── d2.png │ ├── f.pdf │ ├── go.mod │ └── main.go ├── cl │ └── main.go ├── csv2poly │ ├── csv2poly │ ├── csv2poly.go │ └── go.mod ├── csvread │ ├── csvread.go │ └── go.mod ├── ctime │ ├── ctime.go │ └── go.mod ├── deckle │ ├── deckle.go │ ├── go.mod │ ├── go.sum │ └── test.sh ├── desordres │ ├── 01.png │ ├── 04-color.png │ ├── 05.png │ ├── 14-hot.png │ ├── README.md │ ├── ajs.pal │ ├── black-green-thin-14.png │ ├── blue-10.png │ ├── blue-thin-14.png │ ├── blue-thin.png │ ├── blue-tick.png │ ├── def.png │ ├── default.pal │ ├── desordres.go │ ├── f-00001.png │ ├── go.mod │ ├── gray.png │ ├── gray14.png │ ├── hot-14-20-60.png │ ├── mist-gb-00001.png │ ├── rainbow.png │ ├── rb-00001.png │ ├── readpalette.go │ └── red-14.png ├── dicechart │ ├── README.md │ ├── big.csv │ ├── d.csv │ ├── d.pdf │ ├── d.xml │ ├── data.csv │ ├── dicechart │ ├── dicechart.png │ ├── f │ ├── f.pdf │ ├── go.mod │ └── main.go ├── dict │ ├── dict.go │ ├── go.mod │ └── go.sum ├── distable │ ├── README.md │ ├── af-km.d │ ├── africa.pdf │ ├── data │ ├── distable.go │ ├── f.d │ ├── go.mod │ ├── go.sum │ ├── mkall │ ├── mkt │ ├── morris.d │ ├── morris.pdf │ ├── morris.png │ ├── original-chart.png │ ├── us-cities-km.d │ ├── us-cities-miles.d │ ├── us-km.pdf │ └── us-miles.pdf ├── dotspiral │ ├── README.md │ ├── dotspiral.png │ ├── go.mod │ └── main.go ├── dpi │ ├── dpi.go │ └── go.mod ├── fanchart │ ├── 1900-occupations.csv │ ├── README.md │ ├── binaries │ │ ├── fanchart-arm-mac │ │ ├── fanchart-intel-mac │ │ ├── fanchart-linux │ │ ├── fanchart-win32.exe │ │ └── fanchart-win64.exe │ ├── charts.png │ ├── f.pdf │ ├── go.mod │ ├── lr.pdf │ ├── lrchart.png │ ├── main.go │ ├── mkbinaries │ ├── mkdeck │ ├── occupations.csv │ ├── tb.pdf │ └── tbchart.png ├── feed │ ├── feed.go │ ├── go.mod │ └── go.sum ├── fox │ ├── 2-bit-demichrome-00001.png │ ├── 2-bit-grayscale-00001.png │ ├── README.md │ ├── ajs.pal │ ├── ajstarks-00001.png │ ├── arq4-00001.png │ ├── autumn-decay-00001.png │ ├── ayy4-00001.png │ ├── blk-aqu4-00001.png │ ├── blu-scribbles-00001.png │ ├── dark-mode-00001.png │ ├── default.pal │ ├── dense-00001.png │ ├── f-00001.png │ ├── fox.png │ ├── funk-it-up-00001.png │ ├── go.mod │ ├── hollow-00001.png │ ├── hot.png │ ├── ice-cream-gb-00001.png │ ├── kankei4-00001.png │ ├── kirokaze-gameboy-00001.png │ ├── links-awakening-sgb-00001.png │ ├── main.go │ ├── mist-gb-00001.png │ ├── moonlight-gb-00001.png │ ├── nintendo-gameboy-bgb-00001.png │ ├── nintendo-super-gameboy-00001.png │ ├── north-00001.png │ ├── nostalgia-00001.png │ ├── pen-n-paper-00001.png │ ├── pokemon-sgb-00001.png │ ├── polished-gold-00001.png │ ├── rb-00001.png │ ├── readpalette.go │ ├── red-brick-00001.png │ ├── rustic-gb-00001.png │ └── spacehaze-00001.png ├── fstat │ ├── fstat.go │ └── go.mod ├── gitdate │ ├── README.md │ ├── gitdate.go │ ├── gitdate.png │ └── go.mod ├── gurl │ ├── README.md │ ├── go.mod │ └── gurl.go ├── hsv2rgb │ └── hsv2rgb.go ├── imgcat │ ├── README.md │ ├── go.mod │ └── imgcat.go ├── imgps │ ├── go.mod │ ├── go.sum │ └── main.go ├── ims │ ├── go.mod │ └── ims.go ├── jsonfeed │ ├── go.mod │ └── jsonfeed.go ├── latlongdeck │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go ├── mapcoord │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go ├── mdtopdf │ ├── fpdf.png │ ├── go.mod │ ├── go.sum │ ├── hiking.png │ ├── main.go │ ├── test.md │ └── test.pdf ├── mfunc │ ├── go.mod │ └── mfunc.go ├── mkpoly │ ├── go.mod │ └── mkpoly.go ├── nythead │ ├── README.md │ ├── go.mod │ └── nythead.go ├── polar │ ├── go.mod │ └── polar.go ├── popio │ └── main.go ├── randgen │ ├── go.mod │ └── randgen.go ├── rmcsv │ ├── README.md │ ├── go.mod │ ├── main.go │ └── ss.png ├── roadmap │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── p2p.png │ ├── p2p.svg │ ├── p2p.xml │ └── roadmap.go ├── setdeckfont │ ├── go.mod │ ├── go.sum │ └── setdeckfont.go ├── slopechart │ ├── f.pdf │ ├── go.mod │ ├── go.sum │ ├── slope.d │ └── slopechart.go ├── spl │ ├── go.mod │ └── spl.go ├── svgcolor │ ├── go.mod │ └── svgcolor.go ├── swiss │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── p1.png │ ├── p2.png │ ├── p3.png │ └── swiss.go ├── thomas │ ├── go.mod │ └── thomas.go ├── utab │ ├── go.mod │ ├── go.sum │ └── utab.go ├── vmap │ ├── go.mod │ └── vmap.go └── ws │ ├── go.mod │ └── ws.go └── readpalette ├── go.mod └── readpalette.go /README.md: -------------------------------------------------------------------------------- 1 | # Useful commands 2 | 3 | * between - print the time (hours, minutes, seconds, milliseconds) between two dates 4 | * bezpoints - compute the points along a bezier curve 5 | * c19chart - covid-19 charts 6 | * cc - concentric circles 7 | * csvread - parse and display CSV files 8 | * csv2poly - makes x,y pairs in CSV files into polygons 9 | * ctime - show the execution time of a command 10 | * desordres - visuals inspired by Vera Moldar's Des Ordres 11 | * deckle - make a deckled edge using deck markup 12 | * dicechart - make Work-style dice charts 13 | * dict - lookup words via dictionary servers 14 | * distable - make a distance table 15 | * dpi - compute dots per inch and aspect ratio 16 | * dotspiral - make a dot spiral 17 | * fanchart - make Dubois-style fan charts 18 | * feed - read Friday Feed markup, generate to deck, html, RTF, plain text, JSON 19 | * fox - visuals in the form of "Fox I" by Anni Albers 20 | * fstat - show file status 21 | * gitdate - visualize git commits over time 22 | * gurl - get URL 23 | * hsv2rgb - convert hsv to rgb colors 24 | * imgcat - make a image catalog with deck markup 25 | * imgps - show an image's GPS coordinates 26 | * ims - show image size (width and height) 27 | * jsonfeed - parse and list JSON feeds 28 | * mdtopdf - markdown to PDF 29 | * mfunc - math functions 30 | * mkpoly - generate decksh polygons from x,y pairs 31 | * nythead - show New York Times headlines (API key required) 32 | * polar - return Cartesion coordinates from polar coordinate parameters 33 | * randgen - generate random numbers 34 | * rmcsv - convert roadmap CSV files to XML 35 | * roadmap - make planning roadmaps from an XML description 36 | * setdeckfont - make default path for deckfonts 37 | * slopechart - make slope charts 38 | * svgcolor - lookup SVG named colors 39 | * swiss - make a clock based on the iconic Swiss Railway clock 40 | * utab - make PDF unicode tables using TrueType fonts 41 | * vmap - map data ranges 42 | * ws - web server 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /cmd/bar3d/bar3d.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/ajstarks/deck/generate" 11 | ) 12 | 13 | // bar3d makes a 3D bar 14 | func bar3d(deck *generate.Deck, x, y, w, h float64, tcolor, lcolor string) { 15 | wh := w / 2 16 | th := w * 0.5 17 | th2 := th / 2 18 | yh := y + h 19 | topx := []float64{x, x - wh, x, x + wh} 20 | topy := []float64{yh - th, yh - th2, yh, yh - th2} 21 | leftx := []float64{x, x - wh, x - wh, x} 22 | liney := []float64{y, y + th2, yh - th2, yh - th} 23 | rightx := []float64{x, x + wh, x + wh, x} 24 | 25 | deck.Polygon(topx, topy, tcolor) 26 | deck.Polygon(leftx, liney, lcolor) 27 | deck.Polygon(rightx, liney, lcolor, 60) 28 | } 29 | 30 | //vmap maps one interval to another 31 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 32 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 33 | } 34 | 35 | // bardata reads data from the io.Reader, and plots bars 36 | func bardata(deck *generate.Deck, r io.Reader, left, bottom, top float64, tcolor, lcolor string) { 37 | x := left 38 | y := bottom 39 | width := 5.0 40 | scanner := bufio.NewScanner(r) 41 | for scanner.Scan() { 42 | fields := strings.Fields(scanner.Text()) 43 | if len(fields) != 2 { 44 | continue 45 | } 46 | value, err := strconv.ParseFloat(fields[1], 64) 47 | if err != nil { 48 | continue 49 | } 50 | yp := vmap(value, 0, 200, 0, top-bottom) 51 | bar3d(deck, x, y, width, yp, tcolor, lcolor) 52 | deck.TextMid(x, y-2, fields[0], "sans", 1.5, "") 53 | x += width 54 | } 55 | } 56 | 57 | func main() { 58 | deck := generate.NewSlides(os.Stdout, 0, 0) 59 | deck.StartDeck() 60 | deck.StartSlide("rgb(30,10,10)", "linen") 61 | bardata(deck, os.Stdin, 20, 10, 80, "maroon", "linen") 62 | deck.EndSlide() 63 | deck.EndDeck() 64 | } 65 | -------------------------------------------------------------------------------- /cmd/bar3d/data.d: -------------------------------------------------------------------------------- 1 | one 10 2 | two 20 3 | three 30 4 | four 40 5 | five 50 6 | six 60 7 | seven 70 8 | eight 80 9 | nine 90 10 | ten 100 -------------------------------------------------------------------------------- /cmd/bar3d/f.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/bar3d/f.pdf -------------------------------------------------------------------------------- /cmd/bar3d/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/bar3d 2 | 3 | go 1.16 4 | 5 | require github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 6 | -------------------------------------------------------------------------------- /cmd/bar3d/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= 2 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 3 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 h1:Z5Dn/2Ml6/8fP1PWxv4ijaK2PwHsX8A+gZiKJVP+XDs= 4 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 5 | -------------------------------------------------------------------------------- /cmd/between/between.go: -------------------------------------------------------------------------------- 1 | // between: compute the time (hours, minutes, or seconds) between two dates 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "os" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | const isofmt = "2006-01-02" 13 | var begintime, endtime, unit string 14 | flag.StringVar(&begintime, "begin", "", "begin time") 15 | flag.StringVar(&endtime, "end", "", "end time") 16 | flag.StringVar(&unit, "unit", "hour", "time unit (month, hour, minute, second, ms)") 17 | flag.Parse() 18 | 19 | if begintime == "" || endtime == "" { 20 | fmt.Fprintf(os.Stderr, "usage: between -begin YYYY-MM-DD -end YYYY-MM-DD -unit (hour, minute, or second\n") 21 | os.Exit(1) 22 | } 23 | 24 | t0, err := time.Parse(isofmt, begintime) 25 | if err != nil { 26 | fmt.Fprintf(os.Stderr, "%s is not a valid time\n", begintime) 27 | os.Exit(2) 28 | } 29 | t1, err := time.Parse(isofmt, endtime) 30 | if err != nil { 31 | fmt.Fprintf(os.Stderr, "%s is not a valid time\n", endtime) 32 | os.Exit(3) 33 | } 34 | var between float64 35 | switch unit { 36 | case "hr", "hour", "h": 37 | between = t1.Sub(t0).Hours() 38 | case "min", "minute", "m": 39 | between = t1.Sub(t0).Minutes() 40 | case "sec", "second", "s": 41 | between = t1.Sub(t0).Seconds() 42 | case "ms": 43 | between = float64(t1.Sub(t0).Milliseconds()) 44 | default: 45 | fmt.Fprintf(os.Stderr, "%s is not a valid time unit (use one of hr, min, sec, ms)\n", unit) 46 | os.Exit(4) 47 | } 48 | fmt.Printf("%s %s %.2f %s\n", begintime, endtime, between, unit) 49 | } 50 | -------------------------------------------------------------------------------- /cmd/between/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/between 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/bezpoints/README.md: -------------------------------------------------------------------------------- 1 | # bezpoints -- generate decksh markup for a filled quadratic bezier curve 2 | 3 | ``` 4 | sage of bezpoints: 5 | -color string 6 | color (default "gray") 7 | -cx float 8 | control x (default 50) 9 | -cy float 10 | control y (default 80) 11 | -ex float 12 | end x (default 80) 13 | -ey float 14 | end y (default 50) 15 | -n int 16 | number of points (default 100) 17 | -opacity float 18 | opacity (default 50) 19 | -sx float 20 | start x (default 20) 21 | -sy float 22 | start y (default 50) 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /cmd/bezpoints/bezpoints.go: -------------------------------------------------------------------------------- 1 | // bezpoints generated decksh code for a filled quadratic bezier 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | ) 8 | 9 | type point struct { 10 | x, y float64 11 | } 12 | 13 | // bezpoints computes the coordinates of a quadratic bezier curve 14 | // source: https://en.wikipedia.org/wiki/B%C3%A9zier_curve 15 | // for t between 0 and 1, where p0 is the start, p1 is conttol, p2 is end 16 | // p_x = (1-t)^2*p0_x + 2(1-t)*t*p1_x + t^2*p2_x 17 | // p_y = (1-t)^2*p0_y + 2(1-t)*t*p1_y + t^2*p2_y 18 | func bezpoints(start, end, control point, npoints int) []point { 19 | p := make([]point, npoints) 20 | step := 1.0 / float64(npoints) 21 | t := 0.0 22 | for i := 0; i < npoints; i++ { 23 | p[i].x = (1-t)*(1-t)*start.x + 2*(1-t)*t*control.x + t*t*end.x 24 | p[i].y = (1-t)*(1-t)*start.y + 2*(1-t)*t*control.y + t*t*end.y 25 | t += step 26 | } 27 | return p 28 | } 29 | 30 | // showcurve generates the decksh code for the computed coordinates 31 | func showcurve(style string, start, end, control point, n int, color string, opacity float64) { 32 | coordinates := bezpoints(start, end, control, n) 33 | lines := style == "l" 34 | if lines { 35 | fmt.Printf("polyline \"%.3f ", start.x) 36 | } else { 37 | fmt.Printf("polygon \"%.3f ", start.x) 38 | } 39 | for _, p := range coordinates { 40 | fmt.Printf("%.3f ", p.x) 41 | } 42 | fmt.Printf("%.3f\" \"%.3f ", end.x, start.y) 43 | for _, p := range coordinates { 44 | fmt.Printf("%.3f ", p.y) 45 | } 46 | if lines { 47 | fmt.Printf("%.3f\" %g \"%s\" %g\n", end.y, 0.2, color, opacity) 48 | } else { 49 | fmt.Printf("%.3f\" \"%s\" %g\n", end.y, color, opacity) 50 | } 51 | } 52 | 53 | func main() { 54 | var sx, sy, ex, ey, cx, cy, opacity float64 55 | var style, color string 56 | var npoints int 57 | 58 | flag.Float64Var(&sx, "sx", 20, "start x") 59 | flag.Float64Var(&sy, "sy", 50, "start y") 60 | flag.Float64Var(&cx, "cx", 50, "control x") 61 | flag.Float64Var(&cy, "cy", 80, "control y") 62 | flag.Float64Var(&ex, "ex", 80, "end x") 63 | flag.Float64Var(&ey, "ey", 50, "end y") 64 | flag.IntVar(&npoints, "n", 100, "number of points") 65 | flag.StringVar(&color, "color", "gray", "color") 66 | flag.Float64Var(&opacity, "opacity", 50, "opacity") 67 | flag.StringVar(&style, "style", "s", "object style") 68 | flag.Parse() 69 | 70 | start := point{sx, sy} 71 | end := point{ex, ey} 72 | control := point{cx, cy} 73 | showcurve(style, start, end, control, npoints, color, opacity) 74 | } 75 | -------------------------------------------------------------------------------- /cmd/bezpoints/f.tex: -------------------------------------------------------------------------------- 1 | for $t$ between 0 and 1, where $p0$ is the start point, $p1$ is control point, $p2$ is end point, and $c$ are the computed coordinates. 2 | $$ 3 | c_x = (1-t)^2 \times p0_x + 2(1-t) \times t \times p1_x + t^2 \times p2_x 4 | $$ 5 | 6 | $$ 7 | c_y = (1-t)^2 \times p0_y + 2(1-t) \times t \times p1_y + t^2 \times p2_y 8 | $$ 9 | \bye 10 | -------------------------------------------------------------------------------- /cmd/bezpoints/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/bezpoints 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /cmd/c19chart/README.md: -------------------------------------------------------------------------------- 1 | # c19chart make covid-19 charts 2 | 3 | ![chart](f.deck-00001.png) -------------------------------------------------------------------------------- /cmd/c19chart/f.deck-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/c19chart/f.deck-00001.png -------------------------------------------------------------------------------- /cmd/c19chart/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/c19chart 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/ajstarks/dchart2 v0.0.0-20200422132333-d422fc36b888 7 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 8 | ) 9 | -------------------------------------------------------------------------------- /cmd/c19chart/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/dchart2 v0.0.0-20200422132333-d422fc36b888 h1:MWGm3uzeqVvHjdyT15TrMYFpJXqet7swgDfJUlk8RHM= 2 | github.com/ajstarks/dchart2 v0.0.0-20200422132333-d422fc36b888/go.mod h1:VN7x3oiGjh6Xa7pbX1ptjlAQFqSYYyzHnjron9Qom7o= 3 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= 4 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 5 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 h1:Z5Dn/2Ml6/8fP1PWxv4ijaK2PwHsX8A+gZiKJVP+XDs= 6 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 7 | -------------------------------------------------------------------------------- /cmd/cc/README.md: -------------------------------------------------------------------------------- 1 | # cc -- concentric circles 2 | ![](d1.png) 3 | ![](d2.png) 4 | -------------------------------------------------------------------------------- /cmd/cc/cc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/cc/cc.png -------------------------------------------------------------------------------- /cmd/cc/d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/cc/d1.png -------------------------------------------------------------------------------- /cmd/cc/d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/cc/d2.png -------------------------------------------------------------------------------- /cmd/cc/f.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/cc/f.pdf -------------------------------------------------------------------------------- /cmd/cc/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/cc 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /cmd/cc/main.go: -------------------------------------------------------------------------------- 1 | // cc -- concentric circle designs 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math" 7 | ) 8 | 9 | // circle draws a circle 10 | func circle(x, y, size float64, color string) { 11 | fmt.Printf("circle %.2f %.2f %.2f %q\n", x, y, size, color) 12 | } 13 | 14 | // cpolar places a circle at a polar coordinate 15 | func cpolar(x, y, r, t, size float64, color string) { 16 | fmt.Printf("p=polar %.2f %.2f %.2f %.2f\ncircle p_x p_y %.2f %q\n", x, y, r, t, size, color) 17 | } 18 | 19 | // polar returns Cartiesian coordinates from polar 20 | func polar(x, y, r, deg float64) (float64, float64) { 21 | theta := deg * (math.Pi / 180) 22 | px := x + (r * math.Cos(theta)) 23 | py := y + (r * math.Sin(theta)) 24 | return px, py 25 | } 26 | 27 | // begindeck begins decksh markup 28 | func begindeck() { 29 | fmt.Println("deck\ncanvas 1000 1000") 30 | } 31 | 32 | // enddeck end a deck 33 | func enddeck() { 34 | fmt.Println("edeck") 35 | } 36 | 37 | // beginslide begins a slide 38 | func beginslide(color string) { 39 | fmt.Printf("slide \"%s\"\n", color) 40 | } 41 | 42 | // endslide ends a slide 43 | func endslide() { 44 | fmt.Println("eslide") 45 | } 46 | 47 | // planet makes circles around a point 48 | func planet(x, y, size, radius, a1, a2, steps float64, color string) { 49 | for t := a1; t < a2; t += steps { 50 | cpolar(x, y, radius, t, size, color) 51 | } 52 | } 53 | 54 | // solar makes circles around a central circle 55 | func solar(x, y, csize, radius, psize, steps float64, ccolor, pcolor string) { 56 | circle(x, y, csize, ccolor) 57 | planet(x, y, psize, radius, 0, 360, steps, pcolor) 58 | } 59 | 60 | // d1 maes two concentric rings 61 | func d1(step float64) { 62 | beginslide("black") 63 | circle(50, 50, step, "red") 64 | for t := 0.0; t <= 360; t += step { 65 | px, py := polar(50, 50, 25, t) 66 | solar(px, py, 5, 5, 1, step, "red", "orange") 67 | } 68 | for t := step / 2; t <= 360; t += step { 69 | px, py := polar(50, 50, 40, t) 70 | solar(px, py, 5, 5, 1, step, "red", "orange") 71 | } 72 | endslide() 73 | } 74 | 75 | // hsv specifies a hue value in the hsv color space 76 | func hsv(hue, sat, value int) string { 77 | return fmt.Sprintf("hsv(%d,%d,%d)", hue, sat, value) 78 | } 79 | 80 | // cchue makes a series of 7 concentric rings, varying bu nue 81 | func cchue(r, step float64, starthue int, bgcolor string) { 82 | beginslide(bgcolor) 83 | cstep := 1.0 84 | c := 1.0 85 | halfstep := step / 2 86 | csize := r * 1.5 87 | hue := starthue 88 | 89 | circle(50, 50, csize, hsv(hue, 100, 100)) 90 | planet(50, 50, c, r, 0, 360, step, hsv(hue, 100, 100)) 91 | r += 2 92 | c += cstep 93 | hue += 7 94 | planet(50, 50, c, r, halfstep, 360, step, hsv(hue, 100, 100)) 95 | r += 3 96 | c += cstep 97 | hue += 7 98 | planet(50, 50, c, r, 0, 360, step, hsv(hue, 100, 100)) 99 | r += 4 100 | c += cstep 101 | hue += 7 102 | planet(50, 50, c, r, halfstep, 360, step, hsv(hue, 100, 100)) 103 | r += 5 104 | c += cstep 105 | hue += 7 106 | planet(50, 50, c, r, 0, 360, step, hsv(hue, 100, 100)) 107 | r += 6 108 | c += cstep 109 | hue += 7 110 | planet(50, 50, c, r, halfstep, 360, step, hsv(hue, 100, 100)) 111 | r += 8 112 | hue += 7 113 | planet(50, 50, 7, r, 0, 360, step, hsv(hue, 100, 100)) 114 | 115 | for t := 0.0; t <= 360; t += step { 116 | px, py := polar(50, 50, r, t) 117 | planet(px, py, 1, 5, 0, 360, 30, hsv(starthue, 100, 100)) 118 | } 119 | endslide() 120 | } 121 | 122 | func main() { 123 | begindeck() 124 | cchue(10, 20, 0, "black") 125 | d1(30) 126 | enddeck() 127 | } 128 | -------------------------------------------------------------------------------- /cmd/cl/main.go: -------------------------------------------------------------------------------- 1 | // cl: show a file with numbered lines, showing the whole file, a range, or a single line. 2 | package main 3 | 4 | import ( 5 | "bufio" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "os" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | // linerange returns the begin and end using a "-" as delimiter 15 | func linerange(s string) (int, int) { 16 | b := 0 17 | e := 0 18 | var err error 19 | l := strings.Split(s, "-") 20 | switch len(l) { 21 | case 1: // a single number (for example: "10") 22 | b, err = strconv.Atoi(l[0]) 23 | if err != nil { 24 | return 0, 0 25 | } 26 | case 2: // two numbers (for example "10-20") 27 | b, err = strconv.Atoi(l[0]) 28 | if err != nil { 29 | return 0, 0 30 | } 31 | e, err = strconv.Atoi(l[1]) 32 | if err != nil { 33 | return b, 0 34 | } 35 | default: 36 | return 0, 0 37 | } 38 | return b, e 39 | } 40 | 41 | func countLines(r io.Reader, lr string) { 42 | begin, end := linerange(lr) 43 | scanner := bufio.NewScanner(r) 44 | for n := 1; scanner.Scan(); n++ { 45 | t := scanner.Text() 46 | 47 | // show all lines 48 | if begin == 0 && end == 0 { 49 | fmt.Printf("%d: %s\n", n, t) 50 | continue 51 | } 52 | // show a single specified line 53 | if n == begin && end == 0 { 54 | fmt.Printf("%d: %s\n", n, t) 55 | break 56 | } 57 | // show a range of lines 58 | if n >= begin && n <= end { 59 | fmt.Printf("%d: %s\n", n, t) 60 | } 61 | } 62 | } 63 | 64 | func main() { 65 | var linerange string 66 | flag.StringVar(&linerange, "n", "all", "line range (begin-end)") 67 | flag.Parse() 68 | countLines(os.Stdin, linerange) 69 | } 70 | -------------------------------------------------------------------------------- /cmd/csv2poly/csv2poly: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/csv2poly/csv2poly -------------------------------------------------------------------------------- /cmd/csv2poly/csv2poly.go: -------------------------------------------------------------------------------- 1 | // csv2poly - generate decksh polygons from x,y pairs 2 | package main 3 | 4 | import ( 5 | "bufio" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "math" 10 | "os" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | smallest = -math.MaxFloat64 17 | largest = math.MaxFloat64 18 | ) 19 | 20 | type params struct { 21 | left, right, bottom, top float64 22 | label, color string 23 | } 24 | 25 | func main() { 26 | var p params 27 | flag.Float64Var(&p.left, "left", 0, "left") 28 | flag.Float64Var(&p.right, "right", 100, "right") 29 | flag.Float64Var(&p.bottom, "bottom", 0, "bottom") 30 | flag.Float64Var(&p.top, "top", 100, "top") 31 | flag.StringVar(&p.color, "color", "gray", "color") 32 | flag.StringVar(&p.label, "label", "", "label") 33 | flag.Parse() 34 | 35 | for _, f := range flag.Args() { 36 | if err := process(p, f); err != nil { 37 | fmt.Fprintf(os.Stderr, "%v\n", err) 38 | continue 39 | } 40 | } 41 | } 42 | 43 | // readata reads x, y pairs, checking for errors 44 | func readata(r io.Reader) ([]float64, []float64, error) { 45 | var x, y []float64 46 | var xp, yp float64 47 | var err error 48 | scanner := bufio.NewScanner(r) 49 | for scanner.Scan() { 50 | fields := strings.Split(scanner.Text(), ",") 51 | if len(fields) != 2 { 52 | continue 53 | } 54 | if xp, err = strconv.ParseFloat(fields[0], 64); err != nil { 55 | continue 56 | } 57 | if yp, err = strconv.ParseFloat(fields[1], 64); err != nil { 58 | continue 59 | } 60 | x = append(x, xp) 61 | y = append(y, yp) 62 | } 63 | return x, y, scanner.Err() 64 | } 65 | 66 | // process data in the filename 67 | func process(p params, filename string) error { 68 | r, err := os.Open(filename) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | fmt.Println("#", p.left, p.right, p.bottom, p.top) 74 | 75 | x, y, err := readata(r) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | pminx := largest 81 | pmaxx := smallest 82 | fmt.Printf("polygon \"") 83 | for i := 0; i < len(x); i++ { 84 | px := x[i] 85 | if px > pmaxx { 86 | pmaxx = px 87 | } 88 | if px < pminx { 89 | pminx = px 90 | } 91 | fmt.Printf("%.3g ", px) 92 | } 93 | fmt.Printf("%.3g\"", x[0]) 94 | 95 | pminy := largest 96 | pmaxy := smallest 97 | fmt.Printf(" \"") 98 | for i := 0; i < len(y); i++ { 99 | py := y[i] 100 | if py > pmaxy { 101 | pmaxy = py 102 | } 103 | if py < pminy { 104 | pminy = py 105 | } 106 | fmt.Printf("%.3g ", py) 107 | } 108 | fmt.Printf("%.3g\" \"%s\"\n", y[0], p.color) 109 | if len(p.label) > 0 { 110 | fmt.Printf("ctext \"%s\" %g %g 1\n", p.label, pminx+((pmaxx-pminx)/2), pminy+((pmaxy-pminy)/2)) 111 | } 112 | return r.Close() 113 | } 114 | 115 | // vmap maps one range to another 116 | func vmap(value, low1, high1, low2, high2 float64) float64 { 117 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 118 | } 119 | -------------------------------------------------------------------------------- /cmd/csv2poly/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/csv2poly 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/csvread/csvread.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/csv" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strconv" 10 | ) 11 | 12 | // column returns a number corresponding to the letter, or just the number 13 | func column(s string) int { 14 | if len(s) == 0 { 15 | return 0 16 | } 17 | first := s[0] 18 | switch { 19 | case first >= 'a' && first <= 'z': 20 | return int(first - 'a') 21 | case first >= 'A' && first <= 'Z': 22 | return int(first - 'A') 23 | default: 24 | n, err := strconv.Atoi(s) 25 | if err != nil { 26 | return 0 27 | } 28 | return n 29 | } 30 | } 31 | 32 | // getf turns a string slice of numbers into a slice of integers 33 | func getf(s []string) []int { 34 | fn := []int{} 35 | for _, f := range s { 36 | fn = append(fn, column(f)) 37 | } 38 | return fn 39 | } 40 | 41 | // output displays fields of data 42 | func output(s []string, w *csv.Writer, plain bool) { 43 | if plain { 44 | nl := len(s) - 1 45 | for i := 0; i < nl; i++ { 46 | fmt.Printf("%s\t", s[i]) 47 | } 48 | fmt.Println(s[nl]) 49 | } else { 50 | w.Write(s) 51 | } 52 | } 53 | 54 | func main() { 55 | var plainout = flag.Bool("plain", true, "plain output") 56 | var headskip = flag.Bool("headskip", false, "skip the first record (header)") 57 | var varfields = flag.Bool("varfields", true, "variable fields") 58 | var err error 59 | var data []string 60 | flag.Parse() 61 | r := csv.NewReader(os.Stdin) 62 | if *varfields { 63 | r.FieldsPerRecord = -1 64 | } 65 | w := csv.NewWriter(os.Stdout) 66 | r.LazyQuotes = true 67 | fields := getf(flag.Args()) 68 | 69 | // loop over the input, making output 70 | for n := 0; ; n++ { 71 | data, err = r.Read() 72 | if err == io.EOF { 73 | break 74 | } 75 | if n == 0 && *headskip { 76 | continue 77 | } 78 | if err != nil { 79 | fmt.Fprintf(os.Stderr, "%v\n", err) 80 | continue 81 | } 82 | if len(fields) > 0 { // output selected fields 83 | selection := []string{} 84 | for _, n := range fields { 85 | if n >= 0 && n < len(data) { 86 | selection = append(selection, data[n]) 87 | } 88 | } 89 | output(selection, w, *plainout) 90 | 91 | } else { // or output all fields 92 | output(data, w, *plainout) 93 | } 94 | } 95 | w.Flush() 96 | } 97 | -------------------------------------------------------------------------------- /cmd/csvread/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/csvread 2 | 3 | go 1.21.5 4 | -------------------------------------------------------------------------------- /cmd/ctime/ctime.go: -------------------------------------------------------------------------------- 1 | /* 2 | ctime - time a command 3 | 4 | Run the specified command, show the execution time in seconds to standard output. 5 | Any output is ignored. 6 | 7 | The -t option shows the specified string before the time display, 8 | otherwise the first argument of the command is shown. 9 | 10 | $ ctime sleep 10 11 | sleep 10.00685 12 | 13 | $ ctime -t "Go to sleep" sleep 5 14 | Go to sleep 5.00442 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "os" 23 | "os/exec" 24 | "time" 25 | ) 26 | 27 | func work(tag string, s []string) { 28 | if len(s) < 1 { 29 | return 30 | } 31 | if tag == "" { 32 | tag = s[0] 33 | } 34 | b := time.Now() 35 | err := exec.Command(s[0], s[1:]...).Run() 36 | e := time.Now() 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "%v\n", err) 39 | return 40 | } 41 | fmt.Printf("%v\t%.5f\n", tag, e.Sub(b).Seconds()) 42 | } 43 | 44 | func main() { 45 | var tag = flag.String("t", "", "tag for the command") 46 | flag.Parse() 47 | work(*tag, flag.Args()) 48 | } 49 | -------------------------------------------------------------------------------- /cmd/ctime/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/ctime 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/deckle/deckle.go: -------------------------------------------------------------------------------- 1 | // deckle -- generate deck markup for deckled edges (lines and filled) 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "math/rand" 8 | "os" 9 | 10 | "github.com/ajstarks/deck/generate" 11 | ) 12 | 13 | func polygon(deck *generate.Deck, raw bool, x, y []float64, color string) { 14 | if raw { 15 | deck.Polygon(x, y, color) 16 | } else { 17 | fmt.Fprintf(os.Stdout, "polygon \"") 18 | for i := 0; i < len(x); i++ { 19 | fmt.Fprintf(os.Stdout, "%.4g ", x[i]) 20 | } 21 | fmt.Fprintf(os.Stdout, "\" \"") 22 | for i := 0; i < len(y); i++ { 23 | fmt.Fprintf(os.Stdout, "%.4g ", y[i]) 24 | } 25 | fmt.Fprintf(os.Stdout, "\" %q\n", color) 26 | } 27 | } 28 | 29 | func line(deck *generate.Deck, raw bool, x1, y1, x2, y2, linewidth float64, color string) { 30 | if raw { 31 | deck.Line(x1, y1, x2, y2, linewidth, color) 32 | } else { 33 | fmt.Fprintf(os.Stdout, "line %.4g %.4g %.4g %.4g %.4g %q\n", x1, y1, x2, y2, linewidth, color) 34 | } 35 | } 36 | 37 | // hfill makes a (width long) horizontal deckled edge starting at (x,y) 38 | func hfill(deck *generate.Deck, mtype bool, x, y, width, height float64, color string, n int) { 39 | xp := make([]float64, n) 40 | yp := make([]float64, n) 41 | 42 | // left point 43 | xp[0] = x 44 | yp[0] = y 45 | 46 | // right point 47 | xp[n-2] = x + width 48 | yp[n-2] = y 49 | 50 | // back to the left 51 | xp[n-1] = x 52 | yp[n-1] = y 53 | 54 | xincr := width / float64(n) 55 | for i := 1; i <= n-3; i++ { 56 | xp[i] = xp[i-1] + xincr 57 | yp[i] = y + rand.Float64()*height 58 | } 59 | polygon(deck, mtype, xp, yp, color) 60 | } 61 | 62 | // vfill makes a (height high) vertical deckled edge 63 | func vfill(deck *generate.Deck, mtype bool, x, y, width, height float64, color string, n int) { 64 | xp := make([]float64, n) 65 | yp := make([]float64, n) 66 | 67 | // bottom point 68 | xp[0] = x 69 | yp[0] = y 70 | 71 | // top point 72 | xp[n-2] = x 73 | yp[n-2] = y + height 74 | 75 | // back to the bottom 76 | xp[n-1] = x 77 | yp[n-1] = y 78 | 79 | yincr := height / float64(n) 80 | for i := 1; i <= n-3; i++ { 81 | yp[i] = yp[i-1] + yincr 82 | xp[i] = x + rand.Float64()*width 83 | } 84 | polygon(deck, mtype, xp, yp, color) 85 | } 86 | 87 | // hline makes a (width long) horizontal deckled edge 88 | func hline(deck *generate.Deck, mtype bool, x, y, width, height, linewidth float64, color string, n int) { 89 | xincr := width / float64(n) 90 | hi := xincr / 2 91 | y1 := y 92 | for x1 := x; x1 < x+width; x1 += xincr { 93 | y2 := y1 + rand.Float64()*height 94 | line(deck, mtype, x1, y1, x1+(hi), y2, linewidth, color) 95 | line(deck, mtype, x1+(hi), y2, x1+xincr, y1, linewidth, color) 96 | } 97 | } 98 | 99 | // vline makes a (height high) vertical deckled edge 100 | func vline(deck *generate.Deck, mtype bool, x, y, width, height, linewidth float64, color string, n int) { 101 | yincr := height / float64(n) 102 | hi := yincr / 2 103 | x1 := x 104 | for y1 := y; y1 < y+height; y1 += yincr { 105 | x2 := x1 + rand.Float64()*width 106 | line(deck, mtype, x1, y1, x2, y1+(hi), linewidth, color) 107 | line(deck, mtype, x2, y1+(hi), x1, y1+yincr, linewidth, color) 108 | } 109 | } 110 | 111 | func main() { 112 | deck := generate.NewSlides(os.Stdout, 0, 0) 113 | var ( 114 | x, y, width, height, linewidth float64 115 | n int 116 | color, dtype string 117 | mtype bool 118 | ) 119 | 120 | flag.Float64Var(&x, "x", 10, "x") 121 | flag.Float64Var(&y, "y", 50, "y") 122 | flag.Float64Var(&width, "w", 80, "width") 123 | flag.Float64Var(&height, "h", 3, "height") 124 | flag.Float64Var(&linewidth, "lw", 0.1, "line width") 125 | flag.BoolVar(&mtype, "raw", false, "type of markup - true for deck, false for decksh") 126 | flag.IntVar(&n, "n", 50, "number of bumps") 127 | flag.StringVar(&color, "color", "gray", "color") 128 | flag.StringVar(&dtype, "type", "lh", "fv: filled vertical, fh: filled horizontal, lv: line vertical, lh: line horizontal") 129 | flag.Parse() 130 | 131 | switch dtype { 132 | case "fv": 133 | vfill(deck, mtype, x, y, width, height, color, n) 134 | case "fh": 135 | hfill(deck, mtype, x, y, width, height, color, n) 136 | case "lv": 137 | vline(deck, mtype, x, y, width, height, linewidth, color, n) 138 | case "lh": 139 | hline(deck, mtype, x, y, width, height, linewidth, color, n) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /cmd/deckle/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/deckle 2 | 3 | go 1.16 4 | 5 | require github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 6 | -------------------------------------------------------------------------------- /cmd/deckle/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= 2 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 3 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 h1:Z5Dn/2Ml6/8fP1PWxv4ijaK2PwHsX8A+gZiKJVP+XDs= 4 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 5 | -------------------------------------------------------------------------------- /cmd/deckle/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ( 3 | printf "" 4 | deckle -raw=t -x 10 -y 10 -h -1.5 -w 50 -type lh -n 20 -color red 5 | deckle -raw=t -x 10 -y 10 -h 50 -w -1.5 -type lv -n 20 -color blue 6 | deckle -raw=t -x 10 -y 60 -h 1.5 -w 50 -type lh -n 20 -color red 7 | deckle -raw=t -x 60 -y 10 -h 50 -w 1.5 -type lv -n 20 -color blue 8 | printf "" 9 | printf "" 10 | deckle -raw=t -x 10 -y 10 -h -1.5 -w 50 -type fh -n 20 -color red 11 | deckle -raw=t -x 10 -y 10 -h 50 -w -1.5 -type fv -n 20 -color blue 12 | deckle -raw=t -x 10 -y 60 -h 1.5 -w 50 -type fh -n 20 -color red 13 | deckle -raw=t -x 60 -y 10 -h 50 -w 1.5 -type fv -n 20 -color blue 14 | printf "" 15 | printf "" 16 | ) | pdfdeck $* -stdout - > r.pdf 17 | 18 | ( 19 | printf "deck\n" 20 | printf "slide\n" 21 | deckle -raw=f -x 10 -y 10 -h -1.5 -w 50 -type lh -n 20 -color red 22 | deckle -raw=f -x 10 -y 10 -h 50 -w -1.5 -type lv -n 20 -color blue 23 | deckle -raw=f -x 10 -y 60 -h 1.5 -w 50 -type lh -n 20 -color red 24 | deckle -raw=f -x 60 -y 10 -h 50 -w 1.5 -type lv -n 20 -color blue 25 | printf "eslide\n" 26 | printf "slide\n" 27 | deckle -raw=f -x 10 -y 10 -h -1.5 -w 50 -type fh -n 20 -color red 28 | deckle -raw=f -x 10 -y 10 -h 50 -w -1.5 -type fv -n 20 -color blue 29 | deckle -raw=f -x 10 -y 60 -h 1.5 -w 50 -type fh -n 20 -color red 30 | deckle -raw=f -x 60 -y 10 -h 50 -w 1.5 -type fv -n 20 -color blue 31 | printf "eslide\n" 32 | printf "edeck\n" 33 | ) | decksh | pdfdeck $* -stdout - > d.pdf -------------------------------------------------------------------------------- /cmd/desordres/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/01.png -------------------------------------------------------------------------------- /cmd/desordres/04-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/04-color.png -------------------------------------------------------------------------------- /cmd/desordres/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/05.png -------------------------------------------------------------------------------- /cmd/desordres/14-hot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/14-hot.png -------------------------------------------------------------------------------- /cmd/desordres/README.md: -------------------------------------------------------------------------------- 1 | # desordres 2 | 3 | desordres make visuals in the style of "Des Ordres" by Vera Molnár, using deck markup. 4 | 5 | For example: make a 1000x1000 pdf using default parameters: 6 | 7 | ``` 8 | desordres | pdfdeck -stdout -pagesize 1000x1000 - > gray.pdf 9 | ``` 10 | 11 | 12 | Make a 1000x1000 PNG file with random colors HSV(20-60, 100, 100), 14 tiles/row: 13 | 14 | ``` 15 | desordres -tiles 14 -color '20:60' -bgcolor=black > f.xml 16 | pngdeck -pagesize 1000x1000 f.xml 17 | ``` 18 | 19 | 20 | Use all hues, make a PDF: 21 | 22 | ``` 23 | desordres -tiles 5 -color '0:360' -bgcolor black | pdfdeck -stdout -pagesize 1000x1000 - > rainbow.pdf 24 | ``` 25 | 26 | 27 | 28 | 29 | Use a built-in color palette: 30 | 31 | ``` 32 | desordres -tiles 14 -color mist-gb > mist-gb.xml 33 | pngdeck -pagesize 500x500 mist-gb.xml 34 | ``` 35 | 36 | 37 | 38 | Load a custom palette: 39 | ``` 40 | desordres -p ajs.pal -color rainbow > rb.xml 41 | pngdeck -pagesize 500x500 rb.xml 42 | ``` 43 | 44 | 45 | 46 | 47 | ## options 48 | ``` 49 | Option Default Description 50 | ..................................................... 51 | -tiles 10 number of tiles/row 52 | -maxlw 1 maximim line thickness 53 | -p "" palette file 54 | -bgcolor white background color 55 | -color gray color name, h1:h2, or palette: 56 | 57 | 2-bit-demichrome [#211e20 #555568 #a0a08b #e9efec] 58 | nintendo-super-gameboy [#331e50 #a63725 #d68e49 #f7e7c6] 59 | blu-scribbles [#051833 #0a4f66 #0f998e #12cc7f] 60 | 2-bit-grayscale [#000000 #676767 #b6b6b6 #ffffff] 61 | hollow [#0f0f1b #565a75 #c6b7be #fafbf6] 62 | ayy4 [#00303b #ff7777 #ffce96 #f1f2da] 63 | pokemon-sgb [#181010 #84739c #f7b58c #ffefff] 64 | rustic-gb [#2c2137 #764462 #edb4a1 #a96868] 65 | spacehaze [#f8e3c4 #cc3495 #6b1fb1 #0b0630] 66 | moonlight-gb [#0f052d #203671 #36868f #5fc75d] 67 | nintendo-gameboy-bgb [#081820 #346856 #88c070 #e0f8d0] 68 | pen-n-paper [#e4dbba #a4929a #4f3a54 #260d1c] 69 | blk-aqu4 [#002b59 #005f8c #00b9be #9ff4e5] 70 | nostalgia [#d0d058 #a0a840 #708028 #405010] 71 | links-awakening-sgb [#5a3921 #6b8c42 #7bc67b #ffffb5] 72 | arq4 [#ffffff #6772a9 #3a3277 #000000] 73 | kankei4 [#ffffff #f42e1f #2f256b #060608] 74 | dark-mode [#212121 #454545 #787878 #a8a5a5] 75 | kirokaze-gameboy [#332c50 #46878f #94e344 #e2f3e4] 76 | mist-gb [#2d1b00 #1e606e #5ab9a8 #c4f0c2] 77 | red-brick [#eff9d6 #ba5044 #7a1c4b #1b0326] 78 | ice-cream-gb [#7c3f58 #eb6b6f #f9a875 #fff6d3] 79 | ``` 80 | 81 | ## palette files 82 | 83 | ```desordres``` has a built-in palette which may be overridden by specifying a palette file. 84 | 85 | Custom palette files are of the form of name followed by a list of colors, one name per line. 86 | Colors may be named colors or rgb triples in hex form (#rrggbb). 87 | 88 | name color1 color2 ... colorn 89 | 90 | for example: 91 | ``` 92 | ajstarks #aa0000 #aaaaaa #000000 #ffffff 93 | autumn-decay #313638 #574729 #975330 #c57938 #ffad3b #ffe596 94 | rainbow #ff0000 #ffa500 #ffff00 #008000 #0000ff #4b0082 #ee82ee 95 | funk-it-up #e4ffff #e63410 #a23737 #ffec40 #81913b #26f675 #4c714e #40ebda #394e4e #0a0a0a 96 | polished-gold #000000 #361c1b #754232 #cd894a #e6b983 #fff8bc #ffffff #2d2433 #4f4254 #b092a7 97 | funk-it-up #e4ffff #e63410 #a23737 #ffec40 #81913b #26f675 #4c714e #40ebda #394e4e #0a0a0a 98 | grayscale #111111 #222222 #333333 #444444 #555555 #666666 #777777 #888888 #999999 #aaaaaa #bbbbbb #cccccc #dddddd #eeeeee 99 | ``` 100 | 101 | 102 | -------------------------------------------------------------------------------- /cmd/desordres/ajs.pal: -------------------------------------------------------------------------------- 1 | ajstarks #aa0000 #aaaaaa #000000 #ffffff 2 | autumn-decay #313638 #574729 #975330 #c57938 #ffad3b #ffe596 3 | rainbow #ff0000 #ffa500 #ffff00 #008000 #0000ff #4b0082 #ee82ee 4 | polished-gold #000000 #361c1b #754232 #cd894a #e6b983 #fff8bc #ffffff #2d2433 #4f4254 #b092a7 5 | funk-it-up #e4ffff #e63410 #a23737 #ffec40 #81913b #26f675 #4c714e #40ebda #394e4e #0a0a0a 6 | grayscale #111111 #222222 #333333 #444444 #555555 #666666 #777777 #888888 #999999 #aaaaaa #bbbbbb #cccccc #dddddd #eeeeee 7 | -------------------------------------------------------------------------------- /cmd/desordres/black-green-thin-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/black-green-thin-14.png -------------------------------------------------------------------------------- /cmd/desordres/blue-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/blue-10.png -------------------------------------------------------------------------------- /cmd/desordres/blue-thin-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/blue-thin-14.png -------------------------------------------------------------------------------- /cmd/desordres/blue-thin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/blue-thin.png -------------------------------------------------------------------------------- /cmd/desordres/blue-tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/blue-tick.png -------------------------------------------------------------------------------- /cmd/desordres/def.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/def.png -------------------------------------------------------------------------------- /cmd/desordres/default.pal: -------------------------------------------------------------------------------- 1 | kirokaze-gameboy #332c50 #46878f #94e344 #e2f3e4 2 | ice-cream-gb #7c3f58 #eb6b6f #f9a875 #fff6d3 3 | 2-bit-demichrome #211e20 #555568 #a0a08b #e9efec 4 | mist-gb #2d1b00 #1e606e #5ab9a8 #c4f0c2 5 | rustic-gb #2c2137 #764462 #edb4a1 #a96868 6 | 2-bit-grayscale #000000 #676767 #b6b6b6 #ffffff 7 | hollow #0f0f1b #565a75 #c6b7be #fafbf6 8 | ayy4 #00303b #ff7777 #ffce96 #f1f2da 9 | nintendo-gameboy-bgb #081820 #346856 #88c070 #e0f8d0 10 | red-brick #eff9d6 #ba5044 #7a1c4b #1b0326 11 | nostalgia #d0d058 #a0a840 #708028 #405010 12 | spacehaze #f8e3c4 #cc3495 #6b1fb1 #0b0630 13 | moonlight-gb #0f052d #203671 #36868f #5fc75d 14 | links-awakening-sgb #5a3921 #6b8c42 #7bc67b #ffffb5 15 | arq4 #ffffff #6772a9 #3a3277 #000000 16 | blk-aqu4 #002b59 #005f8c #00b9be #9ff4e5 17 | pokemon-sgb #181010 #84739c #f7b58c #ffefff 18 | nintendo-super-gameboy #331e50 #a63725 #d68e49 #f7e7c6 19 | blu-scribbles #051833 #0a4f66 #0f998e #12cc7f 20 | kankei4 #ffffff #f42e1f #2f256b #060608 21 | dark-mode #212121 #454545 #787878 #a8a5a5 22 | ajstarks #aa0000 #aaaaaa #000000 #ffffff 23 | pen-n-paper #e4dbba #a4929a #4f3a54 #260d1c 24 | autumn-decay #313638 #574729 #975330 #c57938 #ffad3b #ffe596 25 | polished-gold #000000 #361c1b #754232 #cd894a #e6b983 #fff8bc #ffffff #2d2433 #4f4254 #b092a7 26 | funk-it-up #e4ffff #e63410 #a23737 #ffec40 #81913b #26f675 #4c714e #40ebda #394e4e #0a0a0a -------------------------------------------------------------------------------- /cmd/desordres/desordres.go: -------------------------------------------------------------------------------- 1 | // desordres -- tile blocks of lines as in Vera Molnar's Des Ordres, using deck markup 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "math/rand" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | linefmt = "\n" 15 | squarefmt = "\n" 16 | ) 17 | 18 | // random returns a random number between a range 19 | func random(min, max float64) float64 { 20 | return vmap(rand.Float64(), 0, 1, min, max) 21 | } 22 | 23 | // vmap maps one interval to another 24 | func vmap(value, low1, high1, low2, high2 float64) float64 { 25 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 26 | } 27 | 28 | // csquare makes a square with lines, using a specified width and color 29 | // if a hue range is set, the color is randomly selected in that range, 30 | // if a palette is specified, use a random color from it, 31 | // otherwise, the named color is used. 32 | func csquare(x, y, size, maxlw, h1, h2 float64, color string) { 33 | 34 | if c, ok := palette[color]; ok { // use a palette 35 | color = c[rand.Intn(len(c))] 36 | } 37 | if h1 > -1 && h2 > -1 { // hue range set 38 | color = fmt.Sprintf("hsv(%v,100,100)", random(h1, h2)) 39 | } 40 | // define the corners 41 | hs := size / 2 42 | tlx, tly := x-hs, y+hs 43 | trx, try := x+hs, y+hs 44 | blx, bly := x-hs, y-hs 45 | brx, bry := x+hs, y-hs 46 | 47 | lw := random(0.1, maxlw) 48 | // make the boundaries 49 | hline(tlx, tly, size, lw, color) 50 | hline(blx, bly, size, lw, color) 51 | vline(blx, bly, size, lw, color) 52 | vline(brx, bry, size, lw, color) 53 | // make the corners 54 | square(tlx, tly, lw, color) 55 | square(blx, bly, lw, color) 56 | square(brx, bry, lw, color) 57 | square(trx, try, lw, color) 58 | } 59 | 60 | // square makes a square 61 | func square(x, y, size float64, color string) { 62 | fmt.Printf(squarefmt, x, y, size, color) 63 | } 64 | 65 | // hline makes a horizontal line 66 | func hline(x, y, size, lw float64, color string) { 67 | fmt.Printf(linefmt, x, y, x+size, y, lw, color) 68 | } 69 | 70 | // vline makes a vertical line 71 | func vline(x, y, size, lw float64, color string) { 72 | fmt.Printf(linefmt, x, y, x, y+size, lw, color) 73 | } 74 | 75 | // desordres makes a series of concentric squares 76 | func desordres(x, y, minsize, maxsize, maxlw, h1, h2 float64, color string) { 77 | step := random(1, 5) 78 | for v := minsize; v < maxsize; v += step { 79 | csquare(x, y, v, maxlw, h1, h2, color) 80 | } 81 | } 82 | 83 | // parseHues parses a color string: if the string is of the form "h1:h2", 84 | // where h1, and h2 are numbers between 0 and 360, they are a range of hues. 85 | // Otherwise, set to -1 for invalid entries (use named colors instead) 86 | func parseHues(color string) (float64, float64) { 87 | var h1, h2 float64 = -1.0, -1.0 88 | hb := strings.Split(color, ":") 89 | if len(hb) == 2 { 90 | var err error 91 | h1, err = strconv.ParseFloat(hb[0], 64) 92 | if err != nil { 93 | h1 = -1 94 | } 95 | h2, err = strconv.ParseFloat(hb[1], 64) 96 | if err != nil { 97 | h2 = -1 98 | } 99 | } 100 | return h1, h2 101 | } 102 | 103 | func usage() { 104 | fmt.Fprintln(os.Stderr) 105 | fmt.Fprintf(os.Stderr, "Option Default Description\n") 106 | fmt.Fprintf(os.Stderr, ".....................................................\n") 107 | fmt.Fprintf(os.Stderr, "-tiles 10 number of tiles/row\n") 108 | fmt.Fprintf(os.Stderr, "-maxlw 1 maximim line thickness\n") 109 | fmt.Fprintf(os.Stderr, "-bgcolor white background color\n") 110 | fmt.Fprintf(os.Stderr, "-p \"\" palette file\n") 111 | fmt.Fprintf(os.Stderr, "-color gray color name, h1:h2, or palette:\n\n") 112 | for p, k := range palette { 113 | fmt.Fprintf(os.Stderr, "%-20s\t%v\n", p, k) 114 | } 115 | os.Exit(1) 116 | } 117 | 118 | func userpalette(pfile string) { 119 | if len(pfile) > 0 { 120 | var err error 121 | palette, err = LoadPalette(pfile) 122 | if err != nil { 123 | fmt.Fprintf(os.Stderr, "%v\n", err) 124 | os.Exit(1) 125 | } 126 | } 127 | } 128 | 129 | // slide generation functions 130 | func beginDeck() { fmt.Println("") } 131 | func endDeck() { fmt.Println("") } 132 | func beginSlide(color string) { fmt.Printf("\n", color) } 133 | func endSlide() { fmt.Println("") } 134 | 135 | func main() { 136 | var tiles, maxlw float64 137 | var bgcolor, color, pfile string 138 | var showhelp bool 139 | 140 | flag.Float64Var(&tiles, "tiles", 10, "tiles/row") 141 | flag.Float64Var(&maxlw, "maxlw", 1, "maximum line thickness") 142 | flag.StringVar(&bgcolor, "bgcolor", "white", "background color") 143 | flag.StringVar(&color, "color", "gray", "pen color: (named color, hue range (h1:h2), or palette name") 144 | flag.StringVar(&pfile, "p", "", "palette file") 145 | flag.BoolVar(&showhelp, "help", false, "show usage") 146 | flag.Parse() 147 | h1, h2 := parseHues(color) // set hue range, or named color/palette 148 | userpalette(pfile) 149 | if showhelp { 150 | usage() 151 | } 152 | 153 | size := 100 / tiles // size of each tile 154 | top := 100 - (size / 2) // top of the beginning row 155 | left := 100 - top // left of the beginning row 156 | 157 | beginDeck() 158 | beginSlide(bgcolor) 159 | for y := top; y > 0; y -= size { 160 | for x := left; x < 100; x += size { 161 | desordres(x, y, 2, size, maxlw, h1, h2, color) 162 | } 163 | } 164 | endSlide() 165 | endDeck() 166 | } 167 | -------------------------------------------------------------------------------- /cmd/desordres/f-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/f-00001.png -------------------------------------------------------------------------------- /cmd/desordres/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/desordres 2 | 3 | go 1.21.5 4 | -------------------------------------------------------------------------------- /cmd/desordres/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/gray.png -------------------------------------------------------------------------------- /cmd/desordres/gray14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/gray14.png -------------------------------------------------------------------------------- /cmd/desordres/hot-14-20-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/hot-14-20-60.png -------------------------------------------------------------------------------- /cmd/desordres/mist-gb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/mist-gb-00001.png -------------------------------------------------------------------------------- /cmd/desordres/rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/rainbow.png -------------------------------------------------------------------------------- /cmd/desordres/rb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/rb-00001.png -------------------------------------------------------------------------------- /cmd/desordres/readpalette.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "image/color" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type spalette map[string][]string 14 | type rgbpalette map[string][]color.NRGBA 15 | 16 | var palette = spalette{ 17 | "kirokaze-gameboy": {"#332c50", "#46878f", "#94e344", "#e2f3e4"}, 18 | "ice-cream-gb": {"#7c3f58", "#eb6b6f", "#f9a875", "#fff6d3"}, 19 | "2-bit-demichrome": {"#211e20", "#555568", "#a0a08b", "#e9efec"}, 20 | "mist-gb": {"#2d1b00", "#1e606e", "#5ab9a8", "#c4f0c2"}, 21 | "rustic-gb": {"#2c2137", "#764462", "#edb4a1", "#a96868"}, 22 | "2-bit-grayscale": {"#000000", "#676767", "#b6b6b6", "#ffffff"}, 23 | "hollow": {"#0f0f1b", "#565a75", "#c6b7be", "#fafbf6"}, 24 | "ayy4": {"#00303b", "#ff7777", "#ffce96", "#f1f2da"}, 25 | "nintendo-gameboy-bgb": {"#081820", "#346856", "#88c070", "#e0f8d0"}, 26 | "red-brick": {"#eff9d6", "#ba5044", "#7a1c4b", "#1b0326"}, 27 | "nostalgia": {"#d0d058", "#a0a840", "#708028", "#405010"}, 28 | "spacehaze": {"#f8e3c4", "#cc3495", "#6b1fb1", "#0b0630"}, 29 | "moonlight-gb": {"#0f052d", "#203671", "#36868f", "#5fc75d"}, 30 | "links-awakening-sgb": {"#5a3921", "#6b8c42", "#7bc67b", "#ffffb5"}, 31 | "arq4": {"#ffffff", "#6772a9", "#3a3277", "#000000"}, 32 | "blk-aqu4": {"#002b59", "#005f8c", "#00b9be", "#9ff4e5"}, 33 | "pokemon-sgb": {"#181010", "#84739c", "#f7b58c", "#ffefff"}, 34 | "nintendo-super-gameboy": {"#331e50", "#a63725", "#d68e49", "#f7e7c6"}, 35 | "blu-scribbles": {"#051833", "#0a4f66", "#0f998e", "#12cc7f"}, 36 | "kankei4": {"#ffffff", "#f42e1f", "#2f256b", "#060608"}, 37 | "dark-mode": {"#212121", "#454545", "#787878", "#a8a5a5"}, 38 | "pen-n-paper": {"#e4dbba", "#a4929a", "#4f3a54", "#260d1c"}, 39 | } 40 | 41 | func rgb(x uint32) (uint8, uint8, uint8) { 42 | r := x & 0xff0000 >> 16 43 | g := x & 0x00ff00 >> 8 44 | b := x & 0x0000ff 45 | return uint8(r), uint8(g), uint8(b) 46 | } 47 | 48 | func ReadString(r io.Reader) (spalette, error) { 49 | scanner := bufio.NewScanner(r) 50 | p := make(spalette) 51 | for scanner.Scan() { 52 | args := strings.Fields(scanner.Text()) 53 | l := len(args) 54 | if l < 2 { 55 | continue 56 | } 57 | name := args[0] 58 | p[name] = args[1:] 59 | } 60 | return p, scanner.Err() 61 | } 62 | 63 | func ReadRGB(r io.Reader) (rgbpalette, error) { 64 | 65 | palette, err := ReadString(r) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | rp := make(rgbpalette) 71 | for name, value := range palette { 72 | colors := make([]color.NRGBA, len(value)) 73 | i := 0 74 | for _, c := range value { 75 | if len(c) != 7 { 76 | continue // must be #nnnnnn 77 | } 78 | x, err := strconv.ParseUint(c[1:], 16, 32) 79 | if err != nil { 80 | fmt.Fprintf(os.Stderr, "%v\n", err) 81 | continue 82 | } 83 | r, g, b := rgb(uint32(x)) 84 | colors[i] = color.NRGBA{R: r, G: g, B: b, A: 0xff} 85 | i++ 86 | } 87 | rp[name] = colors 88 | } 89 | return rp, nil 90 | } 91 | 92 | func LoadPalette(filename string) (spalette, error) { 93 | r, err := os.Open(filename) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return ReadString(r) 98 | } 99 | 100 | func LoadRGBPalette(filename string) (rgbpalette, error) { 101 | r, err := os.Open(filename) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return ReadRGB(r) 106 | } 107 | -------------------------------------------------------------------------------- /cmd/desordres/red-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/desordres/red-14.png -------------------------------------------------------------------------------- /cmd/dicechart/README.md: -------------------------------------------------------------------------------- 1 | # dicechart 2 | 3 | Make dicecharts in the style of [Monroe Work's "Negro Year Book"](https://nightingaledvs.com/monroe-nathan-work-education-in-the-negro-year-book/), using deck markup 4 | 5 | ![dicechart](dicechart.png) 6 | 7 | ## Usage 8 | 9 | ```dicechart [options] [file.csv]``` 10 | 11 | where file.csv contains "label",value pairs 12 | 13 | for example: 14 | 15 | ``` 16 | "MARYLAND",57 17 | "NORTH CAROLINA",50 18 | "GEORGIA",48 19 | "VIRGINIA",47 20 | "TEXAS",47 21 | "FLORIDA",43 22 | "ALABAMA",27 23 | "SOUTH CAROLINA",26 24 | "LOUISIANA",23 25 | ``` 26 | If no file is specified, input is from the standard input. Output is always to standard output. 27 | Typical usage is to use pdfdeck to render the chart in pdf: 28 | 29 | ``` 30 | $ dicechart data.csv > chart.xml 31 | $ pdfdeck chart.xml 32 | ``` 33 | 34 | or in a pipeline: 35 | 36 | ``` 37 | $ dicechart data.csv | pdfdeck -stdout - > chart.pdf 38 | ``` 39 | 40 | Command options are: 41 | ``` 42 | 43 | -color string 44 | dotcolor (default "black") 45 | -dotsize float 46 | dot size (default 1) 47 | -ds float 48 | dice spacing (default 5) 49 | -dw float 50 | dice width (default 1.5) 51 | -dx float 52 | data left position (default 35) 53 | -height float 54 | canvas height (default 612) 55 | -lx float 56 | label left position (default 10) 57 | -textsize float 58 | canvas width (default 2) 59 | -title string 60 | chart title 61 | -top float 62 | top of the chart (default 85) 63 | -unit 64 | dice unit (default 5) 65 | -valsize float 66 | canvas width (default 2) 67 | -vskip float 68 | vertical skip (default 7) 69 | -width float 70 | canvas width (default 792) 71 | ``` 72 | -------------------------------------------------------------------------------- /cmd/dicechart/big.csv: -------------------------------------------------------------------------------- 1 | "MARYLAND",570 2 | "NORTH CAROLINA",500 3 | "GEORGIA",480 4 | "VIRGINIA",470 5 | "TEXAS",470 6 | "FLORIDA",430 7 | "ALABAMA",270 8 | "SOUTH CAROLINA",260 9 | "LOUISIANA",230 -------------------------------------------------------------------------------- /cmd/dicechart/d.csv: -------------------------------------------------------------------------------- 1 | "MARYLAND",57 2 | "NORTH CAROLINA",50 3 | "GEORGIA" 4 | "VIRGINIA",47 5 | "TEXAS",47 6 | "FLORIDA",43 7 | "ALABAMA" 8 | "SOUTH CAROLINA",26 9 | "LOUISIANA",23 -------------------------------------------------------------------------------- /cmd/dicechart/d.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/dicechart/d.pdf -------------------------------------------------------------------------------- /cmd/dicechart/data.csv: -------------------------------------------------------------------------------- 1 | "MARYLAND",57 2 | "NORTH CAROLINA",50 3 | "GEORGIA",48 4 | "VIRGINIA",47 5 | "TEXAS",47 6 | "FLORIDA",43 7 | "ALABAMA",27 8 | "SOUTH CAROLINA",26 9 | "LOUISIANA",23 -------------------------------------------------------------------------------- /cmd/dicechart/dicechart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/dicechart/dicechart -------------------------------------------------------------------------------- /cmd/dicechart/dicechart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/dicechart/dicechart.png -------------------------------------------------------------------------------- /cmd/dicechart/f: -------------------------------------------------------------------------------- 1 | type config struct { 2 | cw float64 3 | ch float64 4 | top float64 5 | vskip float64 6 | textsize float64 7 | valuesize float64 8 | labelx float64 9 | datax float64 10 | dicewidth float64 11 | dicespacing float64 12 | dotsize float64 13 | dotcolor string 14 | } 15 | -------------------------------------------------------------------------------- /cmd/dicechart/f.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/dicechart/f.pdf -------------------------------------------------------------------------------- /cmd/dicechart/go.mod: -------------------------------------------------------------------------------- 1 | module dicechart 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /cmd/dict/dict.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "golang.org/x/net/dict" 9 | ) 10 | 11 | var ( 12 | db = flag.String("d", "wn", "Dictionary database") 13 | dserver = flag.String("s", "dict.org:2628", "Dictionary Server") 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | c, err := dict.Dial("tcp", *dserver) 19 | if err != nil { 20 | fmt.Fprintln(os.Stderr, err) 21 | os.Exit(1) 22 | } 23 | defer c.Close() 24 | 25 | // no args, list dictionaries, exit 26 | if len(flag.Args()) == 0 { 27 | dicts, err := c.Dicts() 28 | if err != nil { 29 | fmt.Fprintf(os.Stderr, "%v\n", err) 30 | return 31 | } 32 | for _, dl := range dicts { 33 | fmt.Println(dl.Name, dl.Desc) 34 | } 35 | return 36 | } 37 | 38 | // define each word specified on the command line 39 | for _, word := range flag.Args() { 40 | defs, err := c.Define(*db, word) 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "%s: %v\n", word, err) 43 | continue 44 | } 45 | for _, result := range defs { 46 | fmt.Println(string(result.Text)) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/dict/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/dict 2 | 3 | go 1.23.4 4 | 5 | require golang.org/x/net v0.40.0 6 | -------------------------------------------------------------------------------- /cmd/dict/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 2 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 3 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 4 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 5 | -------------------------------------------------------------------------------- /cmd/distable/README.md: -------------------------------------------------------------------------------- 1 | # distable: make a distance table 2 | 3 | ![distable](morris.png) 4 | 5 | Derived from the design seen in the atlas of Morris county, New Jersey from 1868, 6 | ```distable``` makes a distance table using deck markup. 7 | 8 | ## Data format 9 | 10 | distable takes as input a file with this format: 11 | 12 | Place1 13 | place2:distance 14 | place3:distance 15 | 16 | For example: 17 | ``` 18 | Beavertown 19 | Boonton 20 | Beavertown:5.30 21 | Budds Lake 22 | Beavertown:21.30 23 | Boonton:16.00 24 | Chatham 25 | Beavertown:12.20 26 | Boonton:10.40 27 | Budds Lake:18.70 28 | Chester 29 | Beavertown:21.40 30 | Boonton:16.30 31 | Budds Lake:5.40 32 | Chatham:15.10 33 | ``` 34 | 35 | ## Command options 36 | 37 | Read from named files or standard input. 38 | ``` 39 | 40 | distable [options] file... 41 | 42 | Options: 43 | -left float 44 | left margin (default 1) 45 | -size float 46 | text size (default 1.1) 47 | -dsize float 48 | distance text size (default 0.65*size) 49 | -subtitle string 50 | subtitle (default "distance in miles") 51 | -title string 52 | chart title (default "Distances") 53 | -top float 54 | top (default 90) 55 | 56 | ``` 57 | 58 | 59 | ## Running 60 | 61 | $ distable.go morris.d | pdfdeck -stdout - > morris.pdf 62 | 63 | -------------------------------------------------------------------------------- /cmd/distable/africa.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/distable/africa.pdf -------------------------------------------------------------------------------- /cmd/distable/distable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/ajstarks/deck/generate" 13 | ) 14 | 15 | type place struct { 16 | name string 17 | distance float64 18 | } 19 | 20 | type distanceTable struct { 21 | name string 22 | dist []place 23 | } 24 | 25 | func main() { 26 | var title, subtitle string 27 | var left, top, size, dsize float64 28 | flag.StringVar(&title, "title", "Distances", "chart title") 29 | flag.StringVar(&subtitle, "subtitle", "", "subtitle") 30 | flag.Float64Var(&left, "left", 1, "left margin") 31 | flag.Float64Var(&top, "top", 90, "top") 32 | flag.Float64Var(&size, "size", 1.1, "text size") 33 | flag.Float64Var(&dsize, "dsize", size*0.65, "distance text size") 34 | flag.Parse() 35 | files := flag.Args() 36 | deck := generate.NewSlides(os.Stdout, 0, 0) 37 | deck.StartDeck() 38 | if len(files) == 0 { 39 | makeslide(deck, "-", os.Stdout, title, subtitle, left, top, size, dsize) 40 | } else { 41 | for _, f := range files { 42 | makeslide(deck, f, os.Stdout, title, subtitle, left, top, size, dsize) 43 | } 44 | } 45 | deck.EndDeck() 46 | 47 | } 48 | 49 | // makeside makes the slide deck 50 | func makeslide(deck *generate.Deck, f string, w io.Writer, title, subtitle string, left, top, size, dsize float64) { 51 | var data []distanceTable 52 | var err error 53 | var r io.Reader 54 | if f == "-" { 55 | r = os.Stdin 56 | } else { 57 | r, err = os.Open(f) 58 | if err != nil { 59 | return 60 | } 61 | } 62 | data, err = readtable(r) 63 | if err != nil { 64 | return 65 | } 66 | deck.StartSlide() 67 | deck.Text(40, 89, title, "sans", 3.5, "") 68 | deck.TextBlock(40, 85, subtitle, "serif", 1.5, 50, "") 69 | distable(deck, data, left, top, size, dsize) 70 | deck.EndSlide() 71 | } 72 | 73 | // readtable reads in distance table data 74 | // name1 75 | // place1:distance 76 | // place2:distance 77 | // ... 78 | func readtable(r io.Reader) ([]distanceTable, error) { 79 | var table []distanceTable 80 | var t distanceTable 81 | var p place 82 | var places []place 83 | 84 | scanner := bufio.NewScanner(r) 85 | n := -1 86 | for scanner.Scan() { 87 | text := scanner.Text() 88 | // single name 89 | if !strings.Contains(text, "\t") { 90 | t.name = text 91 | table = append(table, t) 92 | places = make([]place, 0) 93 | n++ 94 | continue 95 | } 96 | // name:distance 97 | if strings.Contains(text, "\t") { 98 | i := strings.Index(text, ":") 99 | if i > 0 && len(text) > 3 { 100 | d, _ := strconv.ParseFloat(strings.TrimSpace(text[i+1:]), 64) 101 | p.name = text[1:i] 102 | p.distance = d 103 | places = append(places, p) 104 | table[n].dist = places 105 | } 106 | } 107 | } 108 | return table, scanner.Err() 109 | } 110 | 111 | // dumptable prints out the distance table to an io.Writer 112 | func dumptable(w io.Writer, table []distanceTable, factor float64) { 113 | for _, t := range table { 114 | fmt.Fprintf(w, "%s\n", t.name) 115 | for _, d := range t.dist { 116 | fmt.Fprintf(w, "\t%s:%.2f\n", d.name, d.distance*factor) 117 | } 118 | } 119 | } 120 | 121 | // distable makes a distance table using deck markup 122 | func distable(deck *generate.Deck, table []distanceTable, left, top, size, dsize float64) { 123 | distleft := left + (size * 10) 124 | vspacing := size * 2.4 125 | hspacing := size * 2.4 126 | x := distleft 127 | y := top 128 | bottom := (top - (float64(len(table)) * vspacing)) - size 129 | 130 | // vertical column headings 131 | for _, t := range table { 132 | deck.TextRotate(x, y-vspacing, t.name, "", "serif", 90, size, "") 133 | deck.Line(x-size-0.2, y-1, x-size-0.2, bottom, 0.05, "gray") 134 | x += hspacing 135 | y -= vspacing 136 | } 137 | // horizontal headings, data 138 | x = left 139 | y = top - vspacing 140 | for _, t := range table { 141 | // place names 142 | deck.Text(x, y, t.name, "serif", size, "") 143 | dx := distleft 144 | dy := y 145 | // distances for each place 146 | for _, d := range t.dist { 147 | td := strconv.FormatFloat(d.distance, 'f', 1, 64) 148 | deck.TextMid(dx, dy, td, "mono", dsize, "") 149 | dx += hspacing 150 | } 151 | deck.Line(distleft-size, y-1, dx+size+0.3, y-1, 0.05, "gray") 152 | y -= vspacing 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /cmd/distable/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/distable 2 | 3 | go 1.17 4 | 5 | require github.com/ajstarks/deck/generate v0.0.0-20220116200525-3f887d0c5850 6 | 7 | require github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 // indirect 8 | -------------------------------------------------------------------------------- /cmd/distable/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= 2 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 3 | github.com/ajstarks/deck/generate v0.0.0-20220116200525-3f887d0c5850 h1:3s9s3Z30llE9vs4EdcrP0trpA6tlHNmktm/K/JPw+5w= 4 | github.com/ajstarks/deck/generate v0.0.0-20220116200525-3f887d0c5850/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 5 | -------------------------------------------------------------------------------- /cmd/distable/mkall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mono="Charter-Italic" 3 | serif="Charter-Regular" 4 | sans="PublicSans-Regular" 5 | go build && 6 | ./distable -title 'Morris County, New Jersey' -subtitle 'showing the distance in miles and tenths on an airline from place to place' morris.d | 7 | pdfdeck -stdout -serif $serif -mono $mono -sans $sans - > morris.pdf 8 | 9 | ./distable -title 'Distances between African Cities' -dsize 0.6 -subtitle 'travel time between African cities, kilometers' af-km.d | 10 | pdfdeck -stdout -serif $serif -mono $mono -sans $sans - > africa.pdf 11 | 12 | ./distable -title 'Distances between US Cities' -dsize 0.6 -subtitle 'travel time between US cities, kilometers' us-cities-km.d | 13 | pdfdeck -stdout -serif $serif -mono $mono -sans $sans - > us-km.pdf 14 | 15 | ./distable -title 'Distances between US Cities' -dsize 0.6 -subtitle 'travel time between US cities, kilometers' us-cities-miles.d | 16 | pdfdeck -stdout -serif $serif -mono $mono -sans $sans - > us-miles.pdf 17 | 18 | -------------------------------------------------------------------------------- /cmd/distable/mkt: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | city[1]="Anchorage" 3 | city[2]="Atlanta" 4 | city[3]="Austin" 5 | city[4]="Baltimore" 6 | city[5]="Boston" 7 | city[6]="Chicago" 8 | city[7]="Dallas" 9 | city[8]="Denver" 10 | city[9]="Honolulu" 11 | city[10]="Houston" 12 | city[11]="Indianapolis" 13 | city[12]="Jacksonville" 14 | city[13]="Las Vegas" 15 | city[14]="Los Angeles" 16 | city[15]="Memphis" 17 | city[16]="Miami" 18 | city[17]="New Orleans" 19 | city[18]="New York" 20 | city[19]="Newark" 21 | city[20]="Oakland" 22 | city[21]="Philadelphia" 23 | city[22]="Phoenix" 24 | city[23]="Portland" 25 | city[24]="San Antonio" 26 | city[25]="San Diego" 27 | city[26]="San Francisco" 28 | city[27]="San Jose" 29 | city[28]="Seattle" 30 | city[29]="Tampa" 31 | city[30]="Tucson" 32 | city[31]="Washington DC" 33 | 34 | n=10 35 | } 36 | /^:/ { 37 | printf "\t%s%s\n", city[n++],$1 38 | } 39 | 40 | /^[A-Z]/ { 41 | print $0 42 | n=1 43 | } -------------------------------------------------------------------------------- /cmd/distable/morris.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/distable/morris.pdf -------------------------------------------------------------------------------- /cmd/distable/morris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/distable/morris.png -------------------------------------------------------------------------------- /cmd/distable/original-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/distable/original-chart.png -------------------------------------------------------------------------------- /cmd/distable/us-km.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/distable/us-km.pdf -------------------------------------------------------------------------------- /cmd/distable/us-miles.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/distable/us-miles.pdf -------------------------------------------------------------------------------- /cmd/dotspiral/README.md: -------------------------------------------------------------------------------- 1 | # dotspiral 2 | 3 | Generate decksh code for a spiral where the size of the points increases 4 | 5 | ![dotspiral](dotspiral.png) 6 | 7 | ``` 8 | ./dotspiral -end 1200 -bgcolor black -color white | decksh | pdfdeck -stdout -pagesize 500x500 - > f.pdf 9 | ``` 10 | 11 | ### usage 12 | 13 | ``` 14 | -bgcolor string 15 | background color (default "white") 16 | -color string 17 | dot color (default "red") 18 | -dincr float 19 | size increment (default 0.5) 20 | -end float 21 | end angle (default 360) 22 | -op float 23 | dot opacity (default 50) 24 | -r float 25 | radius (default 10) 26 | -rincr float 27 | radius increment (default 1) 28 | -size float 29 | dot size (default 0.5) 30 | -start float 31 | start angle (default 180) 32 | -tincr float 33 | angle increment (default 10) 34 | ``` 35 | -------------------------------------------------------------------------------- /cmd/dotspiral/dotspiral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/dotspiral/dotspiral.png -------------------------------------------------------------------------------- /cmd/dotspiral/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/dotspiral 2 | 3 | go 1.21.4 4 | -------------------------------------------------------------------------------- /cmd/dotspiral/main.go: -------------------------------------------------------------------------------- 1 | // dotspiral -- concentric circle designs 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | ) 8 | 9 | const ( 10 | deckfmt = "deck\ncanvas %d %d\n" 11 | circlefmt = "circle %.2f %.2f %.2f %q\n" 12 | polarfmt = "p=polar %.2f %.2f %.2f %.2f\ncircle p_x p_y %.2f %q %.2f\n" 13 | slidefmt = "slide \"%s\"\n" 14 | ) 15 | 16 | type config struct { 17 | start, end, r, rincr, dincr, tincr, dotsize, dotop float64 18 | dotcolor, bgcolor string 19 | } 20 | 21 | // circle draws a circle 22 | func circle(x, y, size float64, color string) { 23 | fmt.Printf(circlefmt, x, y, size, color) 24 | } 25 | 26 | // cpolar places a circle at a polar coordinate 27 | func cpolar(x, y, r, t, size float64, color string, op float64) { 28 | fmt.Printf(polarfmt, x, y, r, t, size, color, op) 29 | } 30 | 31 | // begindeck begins decksh markup 32 | func begindeck(w, h int) { 33 | fmt.Printf(deckfmt, w, h) 34 | } 35 | 36 | // enddeck end a deck 37 | func enddeck() { 38 | fmt.Println("edeck") 39 | } 40 | 41 | // beginslide begins a slide 42 | func beginslide(color string) { 43 | fmt.Printf(slidefmt, color) 44 | } 45 | 46 | // endslide ends a slide 47 | func endslide() { 48 | fmt.Println("eslide") 49 | } 50 | 51 | // dotspiral makes a dot spiral 52 | func dotspiral(cx, cy float64, c config) { 53 | r := c.r 54 | dotsize := c.dotsize 55 | beginslide(c.bgcolor) 56 | for t := c.start; t <= c.end; t += c.tincr { 57 | cpolar(cx, cy, r, t, dotsize, c.dotcolor, c.dotop) 58 | r += c.rincr 59 | dotsize += c.dincr 60 | } 61 | endslide() 62 | } 63 | 64 | // configure set command line options 65 | func configure() config { 66 | var c config 67 | flag.Float64Var(&c.start, "start", 180, "start angle") 68 | flag.Float64Var(&c.end, "end", 360, "end angle") 69 | flag.Float64Var(&c.r, "r", 10.0, "radius") 70 | flag.Float64Var(&c.rincr, "rincr", 1.0, "radius increment") 71 | flag.Float64Var(&c.tincr, "tincr", 10.0, "angle increment") 72 | flag.Float64Var(&c.dincr, "dincr", 0.5, "size increment") 73 | flag.Float64Var(&c.dotsize, "size", 0.5, "dot size") 74 | flag.Float64Var(&c.dotop, "op", 50, "dot opacity") 75 | flag.StringVar(&c.dotcolor, "color", "red", "dot color") 76 | flag.StringVar(&c.bgcolor, "bgcolor", "white", "background color") 77 | flag.Parse() 78 | return c 79 | 80 | } 81 | 82 | func main() { 83 | begindeck(500, 500) 84 | dotspiral(50, 50, configure()) 85 | enddeck() 86 | } 87 | -------------------------------------------------------------------------------- /cmd/dpi/dpi.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "os" 7 | "strconv" 8 | ) 9 | 10 | func dpi(w, h, d float64) float64 { 11 | return math.Sqrt((w*w)+(h*h)) / d 12 | } 13 | 14 | func main() { 15 | if len(os.Args) < 4 { 16 | println("Usage: dpi w h diag [device]") 17 | os.Exit(1) 18 | } 19 | w, _ := strconv.ParseFloat(os.Args[1], 64) 20 | h, _ := strconv.ParseFloat(os.Args[2], 64) 21 | d, _ := strconv.ParseFloat(os.Args[3], 64) 22 | if len(os.Args) == 5 { 23 | fmt.Printf("%s ", os.Args[4]) 24 | } 25 | if h > 0 && d > 0 { 26 | fmt.Printf("w=%.0f h=%0.f diag=%.2f aspect=%.2f dpi=%.2f\n", w, h, d, w/h, dpi(w, h, d)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd/dpi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/dpi 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/fanchart/1900-occupations.csv: -------------------------------------------------------------------------------- 1 | name,percent,color 2 | "OCCUPATIONS OF NEGROES AND WHITES IN GEORGIA",, 3 | "NEGROES",, 4 | "AGRICULTURE, FISHERIES\nAND MINING",62,crimson 5 | "DOMESTIC AND\nPERSONAL SERVICE",28,gold 6 | "MANUFACTURING AND\nINDUSTRIES",5,steelblue 7 | "TRADE AND\nTRANSPORTATION",4.5,tan 8 | "PROFESSIONS",0.5,"rgb(101,67,33)" 9 | WHITES,, 10 | "AGRICULTURE, FISHERIES\nAND MINING",64,crimson 11 | "DOMESTIC AND\nPERSONAL SERVICE",5.5,gold 12 | "MANUFACTURING AND\nMECHANICAL INDUSTRIES",13.5,steelblue 13 | "TRADE AND\nTRANSPORTATION",13,tan 14 | "PROFESSIONS",4,"rgb(101,67,33)" 15 | -------------------------------------------------------------------------------- /cmd/fanchart/README.md: -------------------------------------------------------------------------------- 1 | # fanchart -- make Du Bois style fan charts 2 | 3 | ![fancharts](charts.png) 4 | 5 | Usage: fanchart [options] file.. 6 | 7 | Options: 8 | 9 | -h float 10 | canvas height (default 612) 11 | -dir string 12 | orientation (tb=Top/Bottom, lr=Left/Right) (default "tb") 13 | -size float 14 | fan/wing size (default 30) 15 | -w float 16 | canvas width (default 792) 17 | 18 | Fanchart generates deck markup which can be rendered as PDF, SVG, or PNG 19 | 20 | 21 | Data is a CSV file with this structure: 22 | 23 | column headers 24 | title,footnotes 25 | top/left section name 26 | item,value,color 27 | ... 28 | bottom/right section name 29 | item,value,color 30 | 31 | for example: given data.csv 32 | 33 | name,percent,color 34 | "Occupations of African American and Whites (USA, 2019)","Source, US Bureau of Labor Statistics", 35 | "African American",, 36 | Management,31.8,crimson 37 | Service,23.8,gold 38 | Sales,22.4,steelblue 39 | Construction,5.7,tan 40 | Production,16.3,"rgb(101,67,33)" 41 | White,, 42 | Management,41.4,crimson 43 | Service,15.9,gold 44 | Sales,21.3,steelblue 45 | Construction,10.1,tan 46 | Production,11.3,"rgb(101,67,33)" 47 | 48 | ```fanchart data.csv | pdfdeck -stdout - > tbchart.pdf``` makes 49 | 50 | ![tbchart](tbchart.png) 51 | 52 | ```fanchart data.csv -dir=lr | pdfdeck -stdout - > lrchart.pdf``` makes 53 | 54 | ![lrchart](lrchart.png) 55 | 56 | 57 | -------------------------------------------------------------------------------- /cmd/fanchart/binaries/fanchart-arm-mac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/binaries/fanchart-arm-mac -------------------------------------------------------------------------------- /cmd/fanchart/binaries/fanchart-intel-mac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/binaries/fanchart-intel-mac -------------------------------------------------------------------------------- /cmd/fanchart/binaries/fanchart-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/binaries/fanchart-linux -------------------------------------------------------------------------------- /cmd/fanchart/binaries/fanchart-win32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/binaries/fanchart-win32.exe -------------------------------------------------------------------------------- /cmd/fanchart/binaries/fanchart-win64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/binaries/fanchart-win64.exe -------------------------------------------------------------------------------- /cmd/fanchart/charts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/charts.png -------------------------------------------------------------------------------- /cmd/fanchart/f.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/f.pdf -------------------------------------------------------------------------------- /cmd/fanchart/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/fanchart 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/fanchart/lr.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/lr.pdf -------------------------------------------------------------------------------- /cmd/fanchart/lrchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/lrchart.png -------------------------------------------------------------------------------- /cmd/fanchart/mkbinaries: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | GOOS=windows GOARCH=amd64 go build -o binaries/fanchart-win64.exe 3 | GOOS=windows GOARCH=386 go build -o binaries/fanchart-win32.exe 4 | GOOS=darwin GOARCH=amd64 go build -o binaries/fanchart-intel-mac 5 | GOOS=darwin GOARCH=arm64 go build -o binaries/fanchart-arm-mac 6 | GOOS=linux GOARCH=amd64 go build -o binaries/fanchart-linux 7 | 8 | -------------------------------------------------------------------------------- /cmd/fanchart/mkdeck: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | go run main.go -dir=lr *.csv | pdfdeck -stdout - > lr.pdf 3 | go run main.go -dir=tb *.csv | pdfdeck -stdout - > tb.pdf 4 | -------------------------------------------------------------------------------- /cmd/fanchart/occupations.csv: -------------------------------------------------------------------------------- 1 | name,percent,color 2 | "Occupations of African American and Whites (USA, 2019)","Source, US Bureau of Labor Statistics", 3 | "African American",, 4 | Management,31.8,crimson 5 | Service,23.8,gold 6 | Sales,22.4,steelblue 7 | Construction,5.7,tan 8 | Production,16.3,"rgb(101,67,33)" 9 | White,, 10 | Management,41.4,crimson 11 | Service,15.9,gold 12 | Sales,21.3,steelblue 13 | Construction,10.1,tan 14 | Production,11.3,"rgb(101,67,33)" 15 | -------------------------------------------------------------------------------- /cmd/fanchart/tb.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/tb.pdf -------------------------------------------------------------------------------- /cmd/fanchart/tbchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fanchart/tbchart.png -------------------------------------------------------------------------------- /cmd/feed/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/feed 2 | 3 | go 1.16 4 | 5 | require github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 6 | -------------------------------------------------------------------------------- /cmd/feed/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= 2 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 3 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 h1:Z5Dn/2Ml6/8fP1PWxv4ijaK2PwHsX8A+gZiKJVP+XDs= 4 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 5 | -------------------------------------------------------------------------------- /cmd/fox/2-bit-demichrome-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/2-bit-demichrome-00001.png -------------------------------------------------------------------------------- /cmd/fox/2-bit-grayscale-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/2-bit-grayscale-00001.png -------------------------------------------------------------------------------- /cmd/fox/README.md: -------------------------------------------------------------------------------- 1 | # fox -- inspired by "Fox I" by Anni Albers (using deck markup) 2 | 3 | ![hot](hot.png) 4 | 5 | ```fox -color 0:40 -shadow 60 -bgcolor black``` 6 | 7 | ![example](fox.png) 8 | 9 | ```fox -color ajstarks -w 20,80,2.5 -h 20,80,2.5 -shadow 30 -bgcolor linen``` 10 | 11 | ![ex1](dense-00001.png) 12 | 13 | ```fox -w 5,95,1.25 -h 5,95,1.25 -color funk-it-up -shadow 30 -bgcolor black``` 14 | 15 | ![ex2](rb-00001.png) 16 | 17 | ```fox -p ajs.pal -color rainbow``` 18 | 19 | ![ex3](north-00001.png) 20 | 21 | ```fox -p ajs.pal -color rainbow -d "n"``` 22 | 23 | 24 | ## options 25 | 26 | ``` 27 | Option Default Description 28 | .................................................................. 29 | -help false show usage 30 | -w 10,95,5 percent begin,end,step for the width 31 | -h 10,95,5 percent begin,end,step for the height 32 | -shadow 40 shadow opacity,xoffset,ysoffset 33 | -d "n s e w nw sw ne se" shape directions 34 | -xshift 0.5 shadow x shift 35 | -yshift -0.5 shadow y shift 36 | -bgcolo white background color 37 | -p "" palette file 38 | -color gray color name, hue range (h1:h2), or palette: 39 | 40 | Palette Name Colors 41 | .......................................................... 42 | kirokaze-gameboy [#332c50 #46878f #94e344 #e2f3e4] 43 | rustic-gb [#2c2137 #764462 #edb4a1 #a96868] 44 | nintendo-gameboy-bgb [#081820 #346856 #88c070 #e0f8d0] 45 | nostalgia [#d0d058 #a0a840 #708028 #405010] 46 | arq4 [#ffffff #6772a9 #3a3277 #000000] 47 | blk-aqu4 [#002b59 #005f8c #00b9be #9ff4e5] 48 | kankei4 [#ffffff #f42e1f #2f256b #060608] 49 | spacehaze [#f8e3c4 #cc3495 #6b1fb1 #0b0630] 50 | nintendo-super-gameboy [#331e50 #a63725 #d68e49 #f7e7c6] 51 | dark-mode [#212121 #454545 #787878 #a8a5a5] 52 | ice-cream-gb [#7c3f58 #eb6b6f #f9a875 #fff6d3] 53 | 2-bit-demichrome [#211e20 #555568 #a0a08b #e9efec] 54 | 2-bit-grayscale [#000000 #676767 #b6b6b6 #ffffff] 55 | hollow [#0f0f1b #565a75 #c6b7be #fafbf6] 56 | links-awakening-sgb [#5a3921 #6b8c42 #7bc67b #ffffb5] 57 | blu-scribbles [#051833 #0a4f66 #0f998e #12cc7f] 58 | mist-gb [#2d1b00 #1e606e #5ab9a8 #c4f0c2] 59 | ayy4 [#00303b #ff7777 #ffce96 #f1f2da] 60 | red-brick [#eff9d6 #ba5044 #7a1c4b #1b0326] 61 | moonlight-gb [#0f052d #203671 #36868f #5fc75d] 62 | pokemon-sgb [#181010 #84739c #f7b58c #ffefff] 63 | pen-n-paper [#e4dbba #a4929a #4f3a54 #260d1c] 64 | 65 | ``` 66 | ## palette files 67 | 68 | ```fox``` has a built-in palette which may be overridden by specifying a palette file. 69 | 70 | Custom palette files are of the form of name followed by a list of colors, one name per line. 71 | Colors may be named colors or rgb triples in hex form (#rrggbb). 72 | 73 | name color1 color2 ... colorn 74 | 75 | for example: 76 | ``` 77 | ajstarks #aa0000 #aaaaaa #000000 #ffffff 78 | autumn-decay #313638 #574729 #975330 #c57938 #ffad3b #ffe596 79 | rainbow #ff0000 #ffa500 #ffff00 #008000 #0000ff #4b0082 #ee82ee 80 | funk-it-up #e4ffff #e63410 #a23737 #ffec40 #81913b #26f675 #4c714e #40ebda #394e4e #0a0a0a 81 | polished-gold #000000 #361c1b #754232 #cd894a #e6b983 #fff8bc #ffffff #2d2433 #4f4254 #b092a7 82 | funk-it-up #e4ffff #e63410 #a23737 #ffec40 #81913b #26f675 #4c714e #40ebda #394e4e #0a0a0a 83 | grayscale #111111 #222222 #333333 #444444 #555555 #666666 #777777 #888888 #999999 #aaaaaa #bbbbbb #cccccc #dddddd #eeeeee 84 | ``` 85 | -------------------------------------------------------------------------------- /cmd/fox/ajs.pal: -------------------------------------------------------------------------------- 1 | ajstarks #aa0000 #aaaaaa #000000 #ffffff 2 | autumn-decay #313638 #574729 #975330 #c57938 #ffad3b #ffe596 3 | rainbow #ff0000 #ffa500 #ffff00 #008000 #0000ff #4b0082 #ee82ee 4 | polished-gold #000000 #361c1b #754232 #cd894a #e6b983 #fff8bc #ffffff #2d2433 #4f4254 #b092a7 5 | funk-it-up #e4ffff #e63410 #a23737 #ffec40 #81913b #26f675 #4c714e #40ebda #394e4e #0a0a0a 6 | grayscale #111111 #222222 #333333 #444444 #555555 #666666 #777777 #888888 #999999 #aaaaaa #bbbbbb #cccccc #dddddd #eeeeee 7 | -------------------------------------------------------------------------------- /cmd/fox/ajstarks-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/ajstarks-00001.png -------------------------------------------------------------------------------- /cmd/fox/arq4-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/arq4-00001.png -------------------------------------------------------------------------------- /cmd/fox/autumn-decay-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/autumn-decay-00001.png -------------------------------------------------------------------------------- /cmd/fox/ayy4-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/ayy4-00001.png -------------------------------------------------------------------------------- /cmd/fox/blk-aqu4-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/blk-aqu4-00001.png -------------------------------------------------------------------------------- /cmd/fox/blu-scribbles-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/blu-scribbles-00001.png -------------------------------------------------------------------------------- /cmd/fox/dark-mode-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/dark-mode-00001.png -------------------------------------------------------------------------------- /cmd/fox/default.pal: -------------------------------------------------------------------------------- 1 | kirokaze-gameboy #332c50 #46878f #94e344 #e2f3e4 2 | ice-cream-gb #7c3f58 #eb6b6f #f9a875 #fff6d3 3 | 2-bit-demichrome #211e20 #555568 #a0a08b #e9efec 4 | mist-gb #2d1b00 #1e606e #5ab9a8 #c4f0c2 5 | rustic-gb #2c2137 #764462 #edb4a1 #a96868 6 | 2-bit-grayscale #000000 #676767 #b6b6b6 #ffffff 7 | hollow #0f0f1b #565a75 #c6b7be #fafbf6 8 | ayy4 #00303b #ff7777 #ffce96 #f1f2da 9 | nintendo-gameboy-bgb #081820 #346856 #88c070 #e0f8d0 10 | red-brick #eff9d6 #ba5044 #7a1c4b #1b0326 11 | nostalgia #d0d058 #a0a840 #708028 #405010 12 | spacehaze #f8e3c4 #cc3495 #6b1fb1 #0b0630 13 | moonlight-gb #0f052d #203671 #36868f #5fc75d 14 | links-awakening-sgb #5a3921 #6b8c42 #7bc67b #ffffb5 15 | arq4 #ffffff #6772a9 #3a3277 #000000 16 | blk-aqu4 #002b59 #005f8c #00b9be #9ff4e5 17 | pokemon-sgb #181010 #84739c #f7b58c #ffefff 18 | nintendo-super-gameboy #331e50 #a63725 #d68e49 #f7e7c6 19 | blu-scribbles #051833 #0a4f66 #0f998e #12cc7f 20 | kankei4 #ffffff #f42e1f #2f256b #060608 21 | dark-mode #212121 #454545 #787878 #a8a5a5 22 | ajstarks #aa0000 #aaaaaa #000000 #ffffff 23 | pen-n-paper #e4dbba #a4929a #4f3a54 #260d1c 24 | autumn-decay #313638 #574729 #975330 #c57938 #ffad3b #ffe596 25 | polished-gold #000000 #361c1b #754232 #cd894a #e6b983 #fff8bc #ffffff #2d2433 #4f4254 #b092a7 26 | funk-it-up #e4ffff #e63410 #a23737 #ffec40 #81913b #26f675 #4c714e #40ebda #394e4e #0a0a0a -------------------------------------------------------------------------------- /cmd/fox/dense-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/dense-00001.png -------------------------------------------------------------------------------- /cmd/fox/f-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/f-00001.png -------------------------------------------------------------------------------- /cmd/fox/fox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/fox.png -------------------------------------------------------------------------------- /cmd/fox/funk-it-up-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/funk-it-up-00001.png -------------------------------------------------------------------------------- /cmd/fox/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/fox 2 | 3 | go 1.21.6 4 | -------------------------------------------------------------------------------- /cmd/fox/hollow-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/hollow-00001.png -------------------------------------------------------------------------------- /cmd/fox/hot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/hot.png -------------------------------------------------------------------------------- /cmd/fox/ice-cream-gb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/ice-cream-gb-00001.png -------------------------------------------------------------------------------- /cmd/fox/kankei4-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/kankei4-00001.png -------------------------------------------------------------------------------- /cmd/fox/kirokaze-gameboy-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/kirokaze-gameboy-00001.png -------------------------------------------------------------------------------- /cmd/fox/links-awakening-sgb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/links-awakening-sgb-00001.png -------------------------------------------------------------------------------- /cmd/fox/mist-gb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/mist-gb-00001.png -------------------------------------------------------------------------------- /cmd/fox/moonlight-gb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/moonlight-gb-00001.png -------------------------------------------------------------------------------- /cmd/fox/nintendo-gameboy-bgb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/nintendo-gameboy-bgb-00001.png -------------------------------------------------------------------------------- /cmd/fox/nintendo-super-gameboy-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/nintendo-super-gameboy-00001.png -------------------------------------------------------------------------------- /cmd/fox/north-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/north-00001.png -------------------------------------------------------------------------------- /cmd/fox/nostalgia-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/nostalgia-00001.png -------------------------------------------------------------------------------- /cmd/fox/pen-n-paper-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/pen-n-paper-00001.png -------------------------------------------------------------------------------- /cmd/fox/pokemon-sgb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/pokemon-sgb-00001.png -------------------------------------------------------------------------------- /cmd/fox/polished-gold-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/polished-gold-00001.png -------------------------------------------------------------------------------- /cmd/fox/rb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/rb-00001.png -------------------------------------------------------------------------------- /cmd/fox/readpalette.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "image/color" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type spalette map[string][]string 14 | type rgbpalette map[string][]color.NRGBA 15 | 16 | var palette = spalette{ 17 | "kirokaze-gameboy": {"#332c50", "#46878f", "#94e344", "#e2f3e4"}, 18 | "ice-cream-gb": {"#7c3f58", "#eb6b6f", "#f9a875", "#fff6d3"}, 19 | "2-bit-demichrome": {"#211e20", "#555568", "#a0a08b", "#e9efec"}, 20 | "mist-gb": {"#2d1b00", "#1e606e", "#5ab9a8", "#c4f0c2"}, 21 | "rustic-gb": {"#2c2137", "#764462", "#edb4a1", "#a96868"}, 22 | "2-bit-grayscale": {"#000000", "#676767", "#b6b6b6", "#ffffff"}, 23 | "hollow": {"#0f0f1b", "#565a75", "#c6b7be", "#fafbf6"}, 24 | "ayy4": {"#00303b", "#ff7777", "#ffce96", "#f1f2da"}, 25 | "nintendo-gameboy-bgb": {"#081820", "#346856", "#88c070", "#e0f8d0"}, 26 | "red-brick": {"#eff9d6", "#ba5044", "#7a1c4b", "#1b0326"}, 27 | "nostalgia": {"#d0d058", "#a0a840", "#708028", "#405010"}, 28 | "spacehaze": {"#f8e3c4", "#cc3495", "#6b1fb1", "#0b0630"}, 29 | "moonlight-gb": {"#0f052d", "#203671", "#36868f", "#5fc75d"}, 30 | "links-awakening-sgb": {"#5a3921", "#6b8c42", "#7bc67b", "#ffffb5"}, 31 | "arq4": {"#ffffff", "#6772a9", "#3a3277", "#000000"}, 32 | "blk-aqu4": {"#002b59", "#005f8c", "#00b9be", "#9ff4e5"}, 33 | "pokemon-sgb": {"#181010", "#84739c", "#f7b58c", "#ffefff"}, 34 | "nintendo-super-gameboy": {"#331e50", "#a63725", "#d68e49", "#f7e7c6"}, 35 | "blu-scribbles": {"#051833", "#0a4f66", "#0f998e", "#12cc7f"}, 36 | "kankei4": {"#ffffff", "#f42e1f", "#2f256b", "#060608"}, 37 | "dark-mode": {"#212121", "#454545", "#787878", "#a8a5a5"}, 38 | "pen-n-paper": {"#e4dbba", "#a4929a", "#4f3a54", "#260d1c"}, 39 | } 40 | 41 | func rgb(x uint32) (uint8, uint8, uint8) { 42 | r := x & 0xff0000 >> 16 43 | g := x & 0x00ff00 >> 8 44 | b := x & 0x0000ff 45 | return uint8(r), uint8(g), uint8(b) 46 | } 47 | 48 | func ReadString(r io.Reader) (spalette, error) { 49 | scanner := bufio.NewScanner(r) 50 | p := make(spalette) 51 | for scanner.Scan() { 52 | args := strings.Fields(scanner.Text()) 53 | l := len(args) 54 | if l < 2 { 55 | continue 56 | } 57 | name := args[0] 58 | p[name] = args[1:] 59 | } 60 | return p, scanner.Err() 61 | } 62 | 63 | func ReadRGB(r io.Reader) (rgbpalette, error) { 64 | 65 | palette, err := ReadString(r) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | rp := make(rgbpalette) 71 | for name, value := range palette { 72 | colors := make([]color.NRGBA, len(value)) 73 | i := 0 74 | for _, c := range value { 75 | if len(c) != 7 { 76 | continue // must be #nnnnnn 77 | } 78 | x, err := strconv.ParseUint(c[1:], 16, 32) 79 | if err != nil { 80 | fmt.Fprintf(os.Stderr, "%v\n", err) 81 | continue 82 | } 83 | r, g, b := rgb(uint32(x)) 84 | colors[i] = color.NRGBA{R: r, G: g, B: b, A: 0xff} 85 | i++ 86 | } 87 | rp[name] = colors 88 | } 89 | return rp, nil 90 | } 91 | 92 | func LoadPalette(filename string) (spalette, error) { 93 | r, err := os.Open(filename) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return ReadString(r) 98 | } 99 | 100 | func LoadRGBPalette(filename string) (rgbpalette, error) { 101 | r, err := os.Open(filename) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return ReadRGB(r) 106 | } 107 | -------------------------------------------------------------------------------- /cmd/fox/red-brick-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/red-brick-00001.png -------------------------------------------------------------------------------- /cmd/fox/rustic-gb-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/rustic-gb-00001.png -------------------------------------------------------------------------------- /cmd/fox/spacehaze-00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/fox/spacehaze-00001.png -------------------------------------------------------------------------------- /cmd/fstat/fstat.go: -------------------------------------------------------------------------------- 1 | // fstat -- file status 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "io/fs" 8 | "os" 9 | "sort" 10 | ) 11 | 12 | const ( 13 | dirfmt = "%-15s %20s %15d\t%s\n" 14 | timefmt = "2006-01-02 15:04:05" 15 | ) 16 | 17 | // flags for sorting and printing 18 | type dirflags struct { 19 | na bool // name ascending 20 | sa bool // size ascending 21 | nd bool // name descending 22 | sd bool // size descending 23 | older bool // date oldest first 24 | newer bool // date newest first 25 | showdot bool // show dotfiles 26 | } 27 | 28 | // status gets file status 29 | func status(filename string) (os.FileInfo, bool, error) { 30 | f, err := os.Stat(filename) 31 | if err != nil { 32 | return nil, false, err 33 | } 34 | return f, f.IsDir(), err 35 | } 36 | 37 | // nameSortAsc sorts directory entries by name (ascending) 38 | func nameSortAsc(files []fs.DirEntry) { 39 | sort.Slice(files, func(i, j int) bool { 40 | return files[i].Name() < files[j].Name() 41 | }) 42 | } 43 | 44 | // nameSortDec sorts directory entries by name (ascending) 45 | func nameSortDec(files []fs.DirEntry) { 46 | sort.Slice(files, func(i, j int) bool { 47 | return files[i].Name() > files[j].Name() 48 | }) 49 | } 50 | 51 | // sizeSortAsc sort directory entries by file sizes (ascendiing) 52 | func sizeSortAsc(files []fs.DirEntry) { 53 | sort.Slice(files, func(i, j int) bool { 54 | fi, erri := files[i].Info() 55 | fj, errj := files[j].Info() 56 | if erri != nil || errj != nil { 57 | return false 58 | } 59 | ni := fi.Size() 60 | nj := fj.Size() 61 | return ni < nj 62 | }) 63 | } 64 | 65 | // sizeSortDec sort directory entries by file sizes (ascendiing) 66 | func sizeSortDec(files []fs.DirEntry) { 67 | sort.Slice(files, func(i, j int) bool { 68 | fi, erri := files[i].Info() 69 | fj, errj := files[j].Info() 70 | if erri != nil || errj != nil { 71 | return false 72 | } 73 | ni := fi.Size() 74 | nj := fj.Size() 75 | return ni > nj 76 | }) 77 | } 78 | 79 | // timeSortOlder sorts directory entries by time older to newer 80 | func timeSortOlder(files []fs.DirEntry) { 81 | sort.Slice(files, func(i, j int) bool { 82 | fi, erri := files[i].Info() 83 | fj, errj := files[j].Info() 84 | if erri != nil || errj != nil { 85 | return false 86 | } 87 | ti := fi.ModTime() 88 | tj := fj.ModTime() 89 | return ti.Before(tj) 90 | }) 91 | } 92 | 93 | // timeSortNewer sorts directory entries by time newer to older 94 | func timeSortNewer(files []fs.DirEntry) { 95 | sort.Slice(files, func(i, j int) bool { 96 | fi, erri := files[i].Info() 97 | fj, errj := files[j].Info() 98 | if erri != nil || errj != nil { 99 | return false 100 | } 101 | ti := fi.ModTime() 102 | tj := fj.ModTime() 103 | return ti.After(tj) 104 | }) 105 | } 106 | 107 | // dirstat shows directory infomation 108 | func dirstat(dirname string, sf dirflags) { 109 | f, err := os.Open(dirname) 110 | if err != nil { 111 | fmt.Fprintf(os.Stderr, "%v\n", err) 112 | return 113 | } 114 | di, err := f.ReadDir(0) 115 | if err != nil { 116 | fmt.Fprintf(os.Stderr, "%v\n", err) 117 | return 118 | } 119 | // set the sort option 120 | if sf.na { 121 | nameSortAsc(di) 122 | } 123 | if sf.nd { 124 | nameSortDec(di) 125 | } 126 | if sf.sa { 127 | sizeSortAsc(di) 128 | } 129 | if sf.sd { 130 | sizeSortDec(di) 131 | } 132 | if sf.older { 133 | timeSortOlder(di) 134 | } 135 | if sf.newer { 136 | timeSortNewer(di) 137 | } 138 | 139 | // print the entries 140 | for _, d := range di { 141 | fi, err := d.Info() 142 | if err != nil { 143 | fmt.Fprintf(os.Stderr, "%v\n", err) 144 | continue 145 | } 146 | if !sf.showdot && fi.Name()[0] == '.' { 147 | continue 148 | } 149 | printstat(fi) 150 | } 151 | } 152 | 153 | // printstat shows file status 154 | func printstat(f os.FileInfo) { 155 | fmt.Printf(dirfmt, f.Mode(), f.ModTime().Format(timefmt), f.Size(), f.Name()) 156 | } 157 | 158 | func main() { 159 | var df dirflags 160 | flag.BoolVar(&df.showdot, "a", false, "show dot files") 161 | flag.BoolVar(&df.na, "na", true, "sort by name ascending") 162 | flag.BoolVar(&df.nd, "nd", false, "sort by name descending") 163 | flag.BoolVar(&df.sa, "sa", false, "sort by size ascending") 164 | flag.BoolVar(&df.sd, "sd", false, "sort by size descending") 165 | flag.BoolVar(&df.older, "old", false, "sort by age oldest first") 166 | flag.BoolVar(&df.newer, "new", false, "sort by age newest first") 167 | flag.Parse() 168 | args := flag.Args() 169 | 170 | // if other options are set turn off the default 171 | if df.nd || df.sa || df.sd || df.older || df.newer { 172 | df.na = false 173 | } 174 | // if the default is set turn off other options 175 | if df.na { 176 | df.nd, df.sa, df.sd, df.older, df.newer = false, false, false, false, false 177 | } 178 | // make size and time sorting option are exclusive; either ascending OR descending 179 | if df.sa && df.sd { 180 | fmt.Fprintln(os.Stderr, "pick one: size ascending or descending") 181 | os.Exit(1) 182 | } 183 | if df.older && df.newer { 184 | fmt.Fprintln(os.Stderr, "pick one: old or new") 185 | os.Exit(1) 186 | } 187 | // if no args, show the current directory 188 | if len(args) == 0 { 189 | dirstat(".", df) 190 | return 191 | } 192 | // for every argument, print directory or file info 193 | for i, filename := range args { 194 | s, isdir, err := status(filename) 195 | if err != nil { 196 | fmt.Fprintf(os.Stderr, "%v\n", err) 197 | continue 198 | } 199 | if isdir { 200 | if i > 0 { 201 | fmt.Printf("%s:\n", filename) 202 | } 203 | dirstat(filename, df) 204 | } else { 205 | printstat(s) 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /cmd/fstat/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/fstat 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /cmd/gitdate/README.md: -------------------------------------------------------------------------------- 1 | # Visualize git history 2 | ![gidate](gitdate.png) 3 | 4 | Reads git log output, formatted in ISO date format and shows the git history between two points in time. 5 | 6 | 7 | $ git log --date=iso | awk '/^Date:/ {print $2,$3,$4}' | gitdate -color red -begin 2018-03-01T00:00:00+00:00 | decksh | pdfdeck -stdout - > git.pdf 8 | 9 | ``` 10 | Usage of gitdate: 11 | -begin string 12 | begin time (default "1970-01-01T00:00:00+00:00") 13 | -color string 14 | color (default "black") 15 | -end string 16 | end time (default "2021-12-29T15:48:36-05:00") 17 | -fulldeck 18 | full deck markup (default true) 19 | -left float 20 | left (default 10) 21 | -opacity float 22 | opacity (default 20) 23 | -r float 24 | radius (default 2) 25 | -right float 26 | right (default 90) 27 | -title string 28 | title (default "commit history") 29 | -y float 30 | y point (default 50) 31 | ``` -------------------------------------------------------------------------------- /cmd/gitdate/gitdate.go: -------------------------------------------------------------------------------- 1 | // gitdate: visualize git commit history 2 | // git log --date iso | awk '/^Date:/ {print $2, $3, $4}' | gitdate ... | decksh | ... 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "os" 11 | "time" 12 | ) 13 | 14 | const ( 15 | gitime = "2006-01-02 15:04:05 -0700" 16 | isotime = "2006-01-02T15:04:05-07:00" 17 | ) 18 | 19 | type config struct { 20 | title, btime, etime, color string 21 | left, right, radius, ypoint, opacity float64 22 | fulldeck bool 23 | } 24 | 25 | // seconds returns the number of seconds of the specified time 26 | // since the Unix epoch (Jan 1, 1970, 00:00:00 UTC) 27 | func seconds(s string) int64 { 28 | t, err := time.Parse(gitime, s) 29 | if err != nil { 30 | return -1 31 | } 32 | return t.Unix() 33 | } 34 | 35 | //vmap maps one interval to another 36 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 37 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 38 | } 39 | 40 | // process reads a series of line containing timestamps 41 | // in the ("Mon Jan 2 15:04:05 2006 -0700") format 42 | // and maps each time time to a linear scale. 43 | func process(w io.Writer, r io.Reader, c config) error { 44 | b, err := time.Parse(isotime, c.btime) 45 | if err != nil { 46 | return err 47 | } 48 | e, err := time.Parse(isotime, c.etime) 49 | if err != nil { 50 | return err 51 | } 52 | beg := b.Unix() 53 | end := e.Unix() 54 | 55 | labely := c.ypoint + 5 56 | if c.fulldeck { 57 | fmt.Fprintln(w, "deck\nslide") 58 | } 59 | fmt.Fprintf(w, "ctext %q %v %v %v\n", c.btime, c.left, labely, 1) 60 | fmt.Fprintf(w, "ctext %q %v %v %v\n", c.etime, c.right, labely, 1) 61 | fmt.Fprintf(w, "ctext %q %v %v 2\n", c.title, c.left+((c.right-c.left)/2), labely) 62 | fmt.Fprintf(w, "vline %v %v %v 0.1\n", c.left, c.ypoint, 4) 63 | fmt.Fprintf(w, "vline %v %v %v 0.1\n", c.right, c.ypoint, 4) 64 | scanner := bufio.NewScanner(r) 65 | for scanner.Scan() { 66 | t := scanner.Text() 67 | s := seconds(t) 68 | x := vmap(float64(s), float64(beg), float64(end), c.left, c.right) 69 | fmt.Fprintf(w, "circle %v %v %v %q %v\n", x, c.ypoint, c.radius, c.color, c.opacity) 70 | } 71 | if c.fulldeck { 72 | fmt.Fprintln(w, "eslide\nedeck") 73 | } 74 | return scanner.Err() 75 | } 76 | 77 | func main() { 78 | title := flag.String("title", "commit history", "title") 79 | btime := flag.String("begin", "1970-01-01T00:00:00+00:00", "begin time") 80 | etime := flag.String("end", time.Now().Format(isotime), "end time") 81 | ypoint := flag.Float64("y", 50, "y point") 82 | radius := flag.Float64("r", 2, "radius") 83 | color := flag.String("color", "black", "color") 84 | left := flag.Float64("left", 10, "left") 85 | right := flag.Float64("right", 90, "right") 86 | opacity := flag.Float64("opacity", 20, "opacity") 87 | fulldeck := flag.Bool("fulldeck", true, "full deck markup") 88 | flag.Parse() 89 | 90 | c := config{ 91 | title: *title, 92 | btime: *btime, 93 | etime: *etime, 94 | ypoint: *ypoint, 95 | radius: *radius, 96 | color: *color, 97 | opacity: *opacity, 98 | left: *left, 99 | right: *right, 100 | fulldeck: *fulldeck, 101 | } 102 | 103 | err := process(os.Stdout, os.Stdin, c) 104 | if err != nil { 105 | fmt.Fprintf(os.Stderr, "%v\n", err) 106 | os.Exit(1) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /cmd/gitdate/gitdate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/gitdate/gitdate.png -------------------------------------------------------------------------------- /cmd/gitdate/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/gitdate 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /cmd/gurl/README.md: -------------------------------------------------------------------------------- 1 | # gurl -- get url (with timeout) 2 | 3 | ``` 4 | gurl [-timeout n] url... 5 | ``` 6 | 7 | gurl retrieves the specified URLs and prints the response on the standard output. 8 | The retrieval will timeout using the specified interval (seconds). 9 | The default timeout is 30 seconds. -------------------------------------------------------------------------------- /cmd/gurl/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/gurl 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /cmd/gurl/gurl.go: -------------------------------------------------------------------------------- 1 | // gurl - get url 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "time" 11 | ) 12 | 13 | // netread derefernces a URL, returning the Reader, with an error 14 | func netread(url string, timeout int) (io.ReadCloser, error) { 15 | client := &http.Client{Timeout: time.Duration(timeout) * time.Second} 16 | resp, err := client.Get(url) 17 | if err != nil { 18 | return nil, err 19 | } 20 | if resp.StatusCode != http.StatusOK { 21 | return nil, fmt.Errorf("unable to get network data for %s (%s)", url, resp.Status) 22 | } 23 | return resp.Body, nil 24 | } 25 | 26 | func main() { 27 | timeout := flag.Int("timeout", 30, "time out (sec)") 28 | flag.Parse() 29 | for _, url := range flag.Args() { 30 | r, err := netread(url, *timeout) 31 | if err != nil { 32 | fmt.Fprintf(os.Stderr, "%v\n", err) 33 | continue 34 | } 35 | io.Copy(os.Stdout, r) 36 | r.Close() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cmd/hsv2rgb/hsv2rgb.go: -------------------------------------------------------------------------------- 1 | // hsv2rgb -- convert hsv to rgb colors 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | func main() { 13 | var hue, saturation, value float64 14 | var name string 15 | flag.Float64Var(&hue, "h", 360, "hue") 16 | flag.Float64Var(&saturation, "s", 50, "saturation") 17 | flag.Float64Var(&value, "v", 50, "value") 18 | flag.StringVar(&name, "n", "", "named color like 'hsv(0,20,50)'") 19 | flag.Parse() 20 | 21 | var r, g, b int 22 | if len(name) > 0 { 23 | v := colorNumbers(name) 24 | if len(v) == 3 { 25 | hue, _ = strconv.ParseFloat(v[0], 64) 26 | saturation, _ = strconv.ParseFloat(v[1], 64) 27 | value, _ = strconv.ParseFloat(v[2], 64) 28 | r, g, b = hsv2rgb(hue, saturation, value) 29 | } 30 | } else { 31 | r, g, b = hsv2rgb(hue, saturation, value) 32 | } 33 | fmt.Printf("hsv(%g, %g, %g) => rgb(%d, %d, %d)\n", hue, saturation, value, r, g, b) 34 | } 35 | 36 | // colorNumbers returns a list of numbers from a comma separated list, 37 | // in the form of xxx(n1, n2, n3), after removing tabs and spaces. 38 | func colorNumbers(s string) []string { 39 | return strings.Split(strings.NewReplacer(" ", "", "\t", "").Replace(s[4:len(s)-1]), ",") 40 | } 41 | 42 | // hsv2rgb converts hsv(h (0-360), s (0-100), v (0-100)) to rgb 43 | // reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB 44 | func hsv2rgb(h, s, v float64) (int, int, int) { 45 | s /= 100 46 | v /= 100 47 | if s > 1 || v > 1 { 48 | return 0, 0, 0 49 | } 50 | h = math.Mod(h, 360) 51 | c := v * s 52 | section := h / 60 53 | x := c * (1 - math.Abs(math.Mod(section, 2)-1)) 54 | 55 | var r, g, b float64 56 | switch { 57 | case section >= 0 && section <= 1: 58 | r = c 59 | g = x 60 | b = 0 61 | case section > 1 && section <= 2: 62 | r = x 63 | g = c 64 | b = 0 65 | case section > 2 && section <= 3: 66 | r = 0 67 | g = c 68 | b = x 69 | case section > 3 && section <= 4: 70 | r = 0 71 | g = x 72 | b = c 73 | case section > 4 && section <= 5: 74 | r = x 75 | g = 0 76 | b = c 77 | case section > 5 && section <= 6: 78 | r = c 79 | g = 0 80 | b = x 81 | default: 82 | return 0, 0, 0 83 | } 84 | m := v - c 85 | r += m 86 | g += m 87 | b += m 88 | return int(r * 255), int(g * 255), int(b * 255) 89 | } 90 | -------------------------------------------------------------------------------- /cmd/imgcat/README.md: -------------------------------------------------------------------------------- 1 | # imgcat -- make a multipage image catalog using deck markup 2 | 3 | imgcat generates deck markup for an image catalog, given a list of supported images (PNG, JPEG, GIF). The generated markup is usually fed to pdfdeck for rendering. 4 | 5 | ## usage 6 | 7 | ``` 8 | Usage of imgcat: 9 | -a int 10 | all n 11 | -bg string 12 | background color (default "white") 13 | -bottom float 14 | bottom margin (default 5) 15 | -h int 16 | canvas height (default 720) 17 | -l int 18 | landscape n 19 | -left float 20 | left margin (default 5) 21 | -p int 22 | portrait n 23 | -right float 24 | right margin (default 5) 25 | -showname 26 | show name 27 | -top float 28 | top margin (default 5) 29 | -w int 30 | canvas width (default 1280) 31 | ``` 32 | 33 | ## examples 34 | 35 | make a 3up (3 image/page) catalog for both portrait and landscape images 36 | ``` 37 | $ imgcat -a 3 *.jpg | pdfdeck -stdout - > 3up-catalog.pdf 38 | ``` 39 | 40 | make a 2 image/page catalog for portrait images 41 | 42 | ``` 43 | $ imgcat -p 2 *.jpg | pdfdeck -stdout - > 2-up.pdf 44 | ``` 45 | 46 | make a 3 up catalog of landscape images; include the filenames 47 | 48 | ``` 49 | $ imgcat -showname -l 3 *.png | pdfdeck -stdout - > catalog.pdf 50 | ``` -------------------------------------------------------------------------------- /cmd/imgcat/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/imgcat 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /cmd/imgcat/imgcat.go: -------------------------------------------------------------------------------- 1 | // imgcat: make a multipage image catalog, using deck markup 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "image" 8 | _ "image/gif" 9 | _ "image/jpeg" 10 | _ "image/png" 11 | 12 | "os" 13 | ) 14 | 15 | type Picture struct { 16 | x, y float64 17 | width, height int 18 | name string 19 | orientation string 20 | } 21 | 22 | type Pictures []Picture 23 | 24 | type Canvas struct { 25 | width, height int 26 | left, right, top, bottom float64 27 | bgcolor string 28 | showname bool 29 | } 30 | 31 | func truncstring(s string, n int) string { 32 | l := len(s) 33 | if n >= l || n < 5 { 34 | return s 35 | } 36 | return s[0:n] + "..." + s[l-5:] 37 | } 38 | 39 | func marginw(c Canvas, p Picture) (int, int) { 40 | aspect := float64(p.height) / float64(p.width) 41 | pw := float64(c.width) * (100.0 - (c.left + c.right)) / 100.0 42 | if int(pw) > p.width { 43 | return p.width, p.height 44 | } 45 | return int(pw), int(aspect * pw) 46 | } 47 | 48 | func marginh(c Canvas, p Picture) (int, int) { 49 | aspect := float64(p.height) / float64(p.width) 50 | ph := float64(c.height) * (100.0 - (c.top + c.bottom)) / 100.0 51 | if int(ph) > p.height { 52 | return p.width, p.height 53 | } 54 | return int(ph / aspect), int(ph) 55 | } 56 | 57 | func layout(c Canvas, p []Picture) { 58 | switch len(p) { 59 | case 1: 60 | p[0].x, p[0].y = 50, 50 61 | placepics(c, p[0:1], 90) 62 | case 2: 63 | p[0].x, p[1].x = 25, 75 64 | p[0].y, p[1].y = 50, 50 65 | placepics(c, p[0:2], 45) 66 | case 3: 67 | p[0].x, p[1].x, p[2].x = 17, 50, 83 68 | p[0].y, p[1].y, p[2].y = 50, 50, 50 69 | placepics(c, p[0:3], 30) 70 | } 71 | } 72 | 73 | func piclist(filelist []string) []Picture { 74 | var pic Picture 75 | p := []Picture{} 76 | for _, imagefile := range filelist { 77 | imf, err := os.Open(imagefile) 78 | if err != nil { 79 | fmt.Fprintln(os.Stderr, err) 80 | continue 81 | } 82 | im, _, err := image.DecodeConfig(imf) 83 | if err != nil { 84 | imf.Close() 85 | continue 86 | } 87 | pic.width = im.Width 88 | pic.height = im.Height 89 | pic.name = imagefile 90 | p = append(p, pic) 91 | imf.Close() 92 | } 93 | return p 94 | } 95 | 96 | func placepics(c Canvas, pics []Picture, targetpct float64) { 97 | fmt.Printf("\n", c.bgcolor) 98 | for _, p := range pics { 99 | fmt.Printf("\n", p.x, p.y, int(targetpct), p.name) 100 | if c.showname { 101 | fmt.Printf("%s\n", p.x, 5.0, 1.2, truncstring(p.name, 25)) 102 | } 103 | } 104 | fmt.Println("") 105 | } 106 | 107 | func ll(c Canvas, pics []Picture, n int) { 108 | lands := []Picture{} 109 | e := []Picture{} 110 | 111 | nl := 0 112 | for _, p := range pics { 113 | if p.width > p.height { 114 | nl++ 115 | lands = append(lands, p) 116 | if nl%n == 0 { 117 | layout(c, lands) 118 | lands = e 119 | } 120 | } 121 | } 122 | } 123 | 124 | func lp(c Canvas, pics []Picture, n int) { 125 | ports := []Picture{} 126 | e := []Picture{} 127 | 128 | np := 0 129 | for _, p := range pics { 130 | if p.width < p.height { 131 | np++ 132 | ports = append(ports, p) 133 | if np%n == 0 { 134 | layout(c, ports) 135 | ports = e 136 | } 137 | } 138 | } 139 | } 140 | 141 | func single(c Canvas, pics []Picture) { 142 | for i := 0; i < len(pics); i++ { 143 | if pics[i].width >= pics[i].height { 144 | layout(c, pics[i:i+1]) 145 | } else { 146 | layout(c, pics[i:i+1]) 147 | } 148 | } 149 | } 150 | 151 | func msingle(c Canvas, pics []Picture) { 152 | 153 | var pw, ph int 154 | for _, p := range pics { 155 | p.x, p.y = 50, 50 156 | if p.width > p.height { 157 | pw, ph = marginw(c, p) 158 | } else { 159 | pw, ph = marginh(c, p) 160 | } 161 | fmt.Printf("\n", c.bgcolor) 162 | fmt.Printf("\n", p.x, p.y, pw, ph, p.name) 163 | if c.showname { 164 | fmt.Printf("%s\n", p.name) 165 | } 166 | fmt.Printf("\n") 167 | } 168 | } 169 | 170 | func main() { 171 | cw := flag.Int("w", 1280, "canvas width") 172 | ch := flag.Int("h", 720, "canvas height") 173 | tm := flag.Float64("top", 5, "top margin") 174 | bm := flag.Float64("bottom", 5, "bottom margin") 175 | lm := flag.Float64("left", 5, "left margin") 176 | rm := flag.Float64("right", 5, "right margin") 177 | port := flag.Int("p", 0, "portrait n") 178 | land := flag.Int("l", 0, "landscape n") 179 | all := flag.Int("a", 0, "all n") 180 | showname := flag.Bool("showname", false, "show name") 181 | bgcolor := flag.String("bg", "white", "background color") 182 | flag.Parse() 183 | 184 | pics := piclist(flag.Args()) 185 | c := Canvas{width: *cw, height: *ch, left: *lm, right: *rm, top: *tm, bottom: *bm, bgcolor: *bgcolor, showname: *showname} 186 | fmt.Println("") 187 | switch { 188 | case *port > 0: 189 | lp(c, pics, *port) 190 | case *land > 0: 191 | ll(c, pics, *land) 192 | case *all > 0: 193 | ll(c, pics, *all) 194 | lp(c, pics, *all) 195 | default: 196 | msingle(c, pics) 197 | } 198 | fmt.Println("") 199 | } 200 | -------------------------------------------------------------------------------- /cmd/imgps/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/imgps 2 | 3 | go 1.21.5 4 | 5 | require github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd 6 | -------------------------------------------------------------------------------- /cmd/imgps/go.sum: -------------------------------------------------------------------------------- 1 | github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= 2 | github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= 3 | -------------------------------------------------------------------------------- /cmd/imgps/main.go: -------------------------------------------------------------------------------- 1 | // imgps -- show GPS coordinates contained in EXIF data 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | "github.com/rwcarlsen/goexif/exif" 10 | ) 11 | 12 | // for every file on the command line, report GPS coordinates 13 | func main() { 14 | for _, f := range os.Args[1:] { 15 | err := process(f) 16 | if err != nil { 17 | fmt.Fprintf(os.Stderr, "%s: %v\n", f, err) 18 | continue 19 | } 20 | } 21 | } 22 | 23 | // process retrieves GPS coordinates from a file 24 | func process(filename string) error { 25 | f, err := os.Open(filename) 26 | if err != nil { 27 | return err 28 | } 29 | defer f.Close() 30 | x, err := exif.Decode(f) 31 | if err == io.EOF { 32 | return fmt.Errorf("no exif data") 33 | } 34 | if err != nil { 35 | return err 36 | } 37 | lat, long, err := x.LatLong() 38 | if err != nil { 39 | return err 40 | } 41 | fmt.Printf("%s %.8f %.8f\n", filename, lat, long) 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /cmd/ims/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/ims 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/ims/ims.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | _ "image/gif" 7 | _ "image/jpeg" 8 | _ "image/png" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | for _, fname := range os.Args[1:] { 14 | f, ferr := os.Open(fname) 15 | if ferr != nil { 16 | fmt.Fprintf(os.Stderr, "%v\n", ferr) 17 | continue 18 | } 19 | im, _, err := image.DecodeConfig(f) 20 | if err != nil { 21 | fmt.Fprintf(os.Stderr, "%s: %v\n", fname, err) 22 | continue 23 | } 24 | fmt.Printf("%s %d %d\n", fname, im.Width, im.Height) 25 | f.Close() 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd/jsonfeed/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/jsonfeed 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/jsonfeed/jsonfeed.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | ) 10 | 11 | type JSONFeed struct { 12 | Version string `json:"version"` 13 | Title string `json:"title"` 14 | HomePageURL string `json:"home_page_url"` 15 | FeedURL string `json:"feed_url"` 16 | Description string `json:"description"` 17 | UserComment string `json:"user_comment"` 18 | NextURL string `json:"next_url"` 19 | Icon string `json:"icon"` 20 | Favicon string `json:"favicon"` 21 | Author author `json:"author"` 22 | Items []item `json:"items"` 23 | Expired bool `json:"expired"` 24 | Hubs []hub `json:"hubs"` 25 | } 26 | 27 | type author struct { 28 | Name string `json:"name"` 29 | URL string `json:"url"` 30 | Avatar string `json:"avatar"` 31 | } 32 | 33 | type hub struct { 34 | Type string `json:"type"` 35 | URL string `json:"url"` 36 | } 37 | 38 | type item struct { 39 | Id string `json:"id"` 40 | ContentText string `json:"content_text"` 41 | ContentHTML string `json:"content_html"` 42 | URL string `json:"url"` 43 | ExternalURL string `json:"external_url"` 44 | Title string `json:"title"` 45 | Summary string `json:"summary"` 46 | Image string `json:"image"` 47 | BannerImage string `json:"banner_image"` 48 | DatePublished string `json:"date_published"` 49 | DateModified string `json:"date_modified"` 50 | Author author `json:"author"` 51 | Tags []string `json:"tags"` 52 | Attachments []attachment `json:"attachments"` 53 | } 54 | 55 | type attachment struct { 56 | URL string `json:"url"` 57 | MIMEType string `json:"mime_type"` 58 | Title string `json:"title"` 59 | ByteSize int64 `json:"size_in_bytes"` 60 | Duration int64 `json:"duration_in_seconds"` 61 | } 62 | 63 | func ParseFeed(r io.Reader) (JSONFeed, error) { 64 | var feed JSONFeed 65 | err := json.NewDecoder(r).Decode(&feed) 66 | return feed, err 67 | } 68 | 69 | func Titles(w io.Writer, feed JSONFeed) { 70 | for _, items := range feed.Items { 71 | fmt.Fprintf(w, "Title: %s\n", items.Title) 72 | } 73 | } 74 | 75 | func Content(w io.Writer, feed JSONFeed) { 76 | for _, items := range feed.Items { 77 | if len(items.ContentText) > 0 { 78 | fmt.Fprintf(w, "Content: %s\n", items.ContentText) 79 | } 80 | if len(items.ContentHTML) > 0 { 81 | fmt.Fprintf(w, "HTML: %s\n", items.ContentHTML) 82 | } 83 | } 84 | } 85 | 86 | func Top(w io.Writer, feed JSONFeed) { 87 | fmt.Fprintln(w, "Top Level") 88 | fmt.Fprintf(w, "\tVersion: %s\n", feed.Version) 89 | fmt.Fprintf(w, "\tTitle: %s\n", feed.Title) 90 | fmt.Fprintf(w, "\tFeed URL: %s\n", feed.FeedURL) 91 | fmt.Fprintf(w, "\tDescription:%s\n", feed.Description) 92 | fmt.Fprintf(w, "\tComment: %s\n", feed.UserComment) 93 | fmt.Fprintf(w, "\tNext URL: %s\n", feed.NextURL) 94 | fmt.Fprintf(w, "\tIcon: %s\n", feed.Icon) 95 | fmt.Fprintf(w, "\tFavicon: %s\n", feed.Favicon) 96 | fmt.Fprintf(w, "\tExpired: %v\n", feed.Expired) 97 | fmt.Fprintf(w, "\tAuthor: %s\n", feed.Author.Name) 98 | fmt.Fprintf(w, "\tAuthor URL: %s\n", feed.Author.URL) 99 | fmt.Fprintf(w, "\tAvatar: %s\n", feed.Author.Avatar) 100 | } 101 | 102 | func Items(w io.Writer, feed JSONFeed) { 103 | for i, items := range feed.Items { 104 | fmt.Fprintf(w, "\nItem [%d]\n", i+1) 105 | fmt.Fprintf(w, "\tID: %s\n", items.Id) 106 | fmt.Fprintf(w, "\tURL: %s\n", items.URL) 107 | fmt.Fprintf(w, "\tExternal URL: %s\n", items.ExternalURL) 108 | fmt.Fprintf(w, "\tTitle: %s\n", items.Title) 109 | fmt.Fprintf(w, "\tContent: %s\n", items.ContentText) 110 | fmt.Fprintf(w, "\tHTML: %s\n", items.ContentHTML) 111 | fmt.Fprintf(w, "\tSummary: %s\n", items.Summary) 112 | fmt.Fprintf(w, "\tImage: %s\n", items.Image) 113 | fmt.Fprintf(w, "\tBanner Image: %s\n", items.BannerImage) 114 | fmt.Fprintf(w, "\tPublished on: %s\n", items.DatePublished) 115 | fmt.Fprintf(w, "\tModified on: %s\n", items.DateModified) 116 | fmt.Fprintf(w, "\tAuthor: %s\n", items.Author.Name) 117 | fmt.Fprintf(w, "\tAuthor URL: %s\n", items.Author.URL) 118 | fmt.Fprintf(w, "\tAvatar: %s\n", items.Author.Avatar) 119 | fmt.Fprintf(w, "\tTags:") 120 | for _, t := range items.Tags { 121 | fmt.Fprintf(w, " `%s`", t) 122 | } 123 | fmt.Fprintln(w) 124 | fmt.Fprintln(w, "\tAttachments:") 125 | for _, att := range items.Attachments { 126 | fmt.Fprintf(w, "\t\tTitle: %s\n", att.Title) 127 | fmt.Fprintf(w, "\t\tURL: %s\n", att.URL) 128 | fmt.Fprintf(w, "\t\tMIME Type: %s\n", att.MIMEType) 129 | if att.ByteSize > 0 { 130 | fmt.Fprintf(w, "\t\tSize: %d bytes\n", att.ByteSize) 131 | } 132 | if att.Duration > 0 { 133 | fmt.Fprintf(w, "\t\tDuration: %d seconds\n", att.Duration) 134 | } 135 | } 136 | } 137 | } 138 | 139 | func DumpFeed(w io.Writer, feed JSONFeed) { 140 | Top(w, feed) 141 | Items(w, feed) 142 | } 143 | 144 | func main() { 145 | var showtitle = flag.Bool("title", false, "show titles") 146 | var showcontent = flag.Bool("content", false, "show content") 147 | var showtop = flag.Bool("top", false, "show top level") 148 | var showall = flag.Bool("all", false, "show all attributes") 149 | var showitem = flag.Bool("item", false, "show items") 150 | flag.Parse() 151 | f, err := ParseFeed(os.Stdin) 152 | if err != nil { 153 | fmt.Fprintf(os.Stderr, "%v\n", err) 154 | return 155 | } 156 | if *showall { 157 | DumpFeed(os.Stdout, f) 158 | } 159 | if *showtop { 160 | Top(os.Stdout, f) 161 | } 162 | if *showtitle { 163 | Titles(os.Stdout, f) 164 | } 165 | if *showcontent { 166 | Content(os.Stdout, f) 167 | } 168 | if *showitem { 169 | Items(os.Stdout, f) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /cmd/latlongdeck/README.md: -------------------------------------------------------------------------------- 1 | # latlongdeck -- convert lat/long pairs to deck/decksh markup 2 | 3 | mapcoord reads space separated lat/long pairs from stdin or specified files, and emits deck/decksh markup denoting the path to stdout. 4 | Typically other programs will generate the input, for example ```fitscsvcoord``` reads CSV files with FITS data: 5 | 6 | ``` 7 | $ fitscsvcoord path.csv | latlongdeck [options] > path.dsh 8 | ``` 9 | 10 | ## Options 11 | 12 | ``` 13 | $ latlomgdeck --help 14 | Usage of mapcoord: 15 | -bbox string 16 | bounding box color ("" no box) 17 | -bgcolor string 18 | background color 19 | -color string 20 | line color (default "black") 21 | -fulldeck 22 | make a full deck (default true) 23 | -info 24 | only report bounding box, and center 25 | -latmax float 26 | latitude x maxmum (default 90) 27 | -latmin float 28 | latitude x minimum (default -90) 29 | -linewidth float 30 | line width (default 0.1) 31 | -longmax float 32 | longitude y maximum (default 180) 33 | -longmin float 34 | longitude y minimum (default -180) 35 | -shape string 36 | polygon, polyline (default "polyline") 37 | -style string 38 | deck, decksh, plain (default "deck") 39 | -xmax float 40 | canvas x maxmum (default 95) 41 | -xmin float 42 | canvas x minimum (default 5) 43 | -ymax float 44 | canvas y maximum (default 95) 45 | -ymin float 46 | canvas y minimum (default 5) 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /cmd/latlongdeck/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/latlongdeck 2 | 3 | go 1.21.5 4 | 5 | require github.com/ajstarks/kml v0.0.0-20231216032752-dd72e94de437 6 | -------------------------------------------------------------------------------- /cmd/latlongdeck/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/kml v0.0.0-20231216032752-dd72e94de437 h1:U2Wj3bR9XXLgS6rOkD5TOEmQTsUjulc57RDrv6CVwNw= 2 | github.com/ajstarks/kml v0.0.0-20231216032752-dd72e94de437/go.mod h1:cxil5A25+/6kc8Y3uk573iVqB7x0KTcF+G3EclM6U4g= 3 | -------------------------------------------------------------------------------- /cmd/latlongdeck/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/ajstarks/kml" 13 | ) 14 | 15 | // vmap maps one interval to another 16 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 17 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 18 | } 19 | 20 | // readData reads lat/long pairs (separated by white space) from a file, mapping to deck coordinates 21 | func readData(r io.Reader, g kml.Geometry) ([]float64, []float64, error) { 22 | x := []float64{} 23 | y := []float64{} 24 | s := bufio.NewScanner(r) 25 | for s.Scan() { 26 | t := s.Text() 27 | f := strings.Fields(t) 28 | if len(f) != 2 { 29 | continue 30 | } 31 | xp, err := strconv.ParseFloat(f[1], 64) // latitude 32 | if err != nil { 33 | continue 34 | } 35 | yp, err := strconv.ParseFloat(f[0], 64) // longitude 36 | if err != nil { 37 | continue 38 | } 39 | x = append(x, vmap(xp, g.Longmin, g.Longmax, g.Xmin, g.Xmax)) 40 | y = append(y, vmap(yp, g.Latmin, g.Latmax, g.Ymin, g.Ymax)) 41 | } 42 | return x, y, s.Err() 43 | } 44 | 45 | // readStats reads lat/long pairs, report on the computed bounding box and center 46 | func readStats(r io.Reader) { 47 | maxxval := -100000000.0 48 | minxval := 100000000.0 49 | maxyval := -100000000.0 50 | minyval := 100000000.0 51 | 52 | s := bufio.NewScanner(r) 53 | for s.Scan() { 54 | t := s.Text() 55 | f := strings.Fields(t) 56 | if len(f) != 2 { 57 | continue 58 | } 59 | xp, err := strconv.ParseFloat(f[1], 64) // latitude 60 | if err != nil { 61 | continue 62 | } 63 | yp, err := strconv.ParseFloat(f[0], 64) // longitude 64 | if err != nil { 65 | continue 66 | } 67 | if xp > maxxval { 68 | maxxval = xp 69 | } 70 | if xp < minxval { 71 | minxval = xp 72 | } 73 | 74 | if yp > maxyval { 75 | maxyval = yp 76 | } 77 | if yp < minyval { 78 | minyval = yp 79 | } 80 | } 81 | centerLong := minxval + (maxxval-minxval)/2 82 | centerLat := minyval + (maxyval-minyval)/2 83 | fmt.Fprintf(os.Stdout, "center=%v,%v -longmin=%v -longmax=%v -latmin=%v -latmax=%v\n", centerLat, centerLong, minxval, maxxval, minyval, maxyval) 84 | } 85 | 86 | // process processing input and options, making markup 87 | func process(filename string, info bool, shape, style, color, bbox string, linewidth float64, mapgeo kml.Geometry) { 88 | 89 | // read from stdin by default, open a file if specified 90 | r := os.Stdin 91 | if len(filename) > 0 { 92 | var rerr error 93 | r, rerr = os.Open(filename) 94 | if rerr != nil { 95 | fmt.Fprintf(os.Stderr, "%v\n", rerr) 96 | return 97 | } 98 | } 99 | 100 | // just show info, then return, if specified 101 | if info { 102 | readStats(r) 103 | return 104 | } 105 | 106 | // read coordinates 107 | x, y, err := readData(r, mapgeo) 108 | if err != nil { 109 | fmt.Fprintf(os.Stderr, "%v\n", err) 110 | return 111 | } 112 | // make a bounding box, if specified 113 | if len(bbox) > 0 { 114 | kml.BoundingBox(mapgeo, bbox, style) 115 | } 116 | // make the drawing 117 | kml.Deckshape(shape, style, x, y, linewidth, color, mapgeo) 118 | r.Close() 119 | } 120 | 121 | func main() { 122 | var mapgeo kml.Geometry 123 | var fulldeck, info bool 124 | var linewidth float64 125 | var color, bbox, shape, bgcolor, style string 126 | 127 | // options 128 | flag.Float64Var(&mapgeo.Xmin, "xmin", 5, "canvas x minimum") 129 | flag.Float64Var(&mapgeo.Xmax, "xmax", 95, "canvas x maxmum") 130 | flag.Float64Var(&mapgeo.Ymin, "ymin", 5, "canvas y minimum") 131 | flag.Float64Var(&mapgeo.Ymax, "ymax", 95, "canvas y maximum") 132 | flag.Float64Var(&mapgeo.Latmin, "latmin", -90, "latitude x minimum") 133 | flag.Float64Var(&mapgeo.Latmax, "latmax", 90, "latitude x maxmum") 134 | flag.Float64Var(&mapgeo.Longmin, "longmin", -180, "longitude y minimum") 135 | flag.Float64Var(&mapgeo.Longmax, "longmax", 180, "longitude y maximum") 136 | flag.Float64Var(&linewidth, "linewidth", 0.1, "line width") 137 | flag.StringVar(&color, "color", "black", "line color") 138 | flag.StringVar(&bbox, "bbox", "", "bounding box color (\"\" no box)") 139 | flag.StringVar(&shape, "shape", "polyline", "polygon, polyline") 140 | flag.StringVar(&style, "style", "deck", "deck, decksh, plain") 141 | flag.StringVar(&bgcolor, "bgcolor", "", "background color") 142 | flag.BoolVar(&fulldeck, "fulldeck", true, "make a full deck") 143 | flag.BoolVar(&info, "info", false, "only report bounding box, and center") 144 | flag.Parse() 145 | 146 | // don't do any generation if info only 147 | if info { 148 | fulldeck = false 149 | } 150 | // add deck/slide markup, if specified 151 | if fulldeck { 152 | kml.Deckshbegin(bgcolor) 153 | } 154 | // for every file (or stdin if no files are specified), make markup 155 | if len(flag.Args()) == 0 { 156 | process("", info, shape, style, color, bbox, linewidth, mapgeo) 157 | } else { 158 | for _, filename := range flag.Args() { 159 | process(filename, info, shape, style, color, bbox, linewidth, mapgeo) 160 | } 161 | } 162 | // end the deck, if specified 163 | if fulldeck { 164 | kml.Deckshend() 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /cmd/mapcoord/README.md: -------------------------------------------------------------------------------- 1 | # mapcoord -- convert lat/long pairs to deck/decksh markup 2 | 3 | mapcoord reads space separated lat/long pairs from stdin or specified files, and emits deck/decksh markup denoting the path to stdout. 4 | Typically other programs will generate the input, for example ```fitscsvcoord``` reads CSV files with FITS data: 5 | 6 | ``` 7 | $ fitscsvcoord path.csv | mapcoord [options] > path.dsh 8 | ``` 9 | 10 | ## Options 11 | 12 | ``` 13 | $ mapcoord --help 14 | Usage of mapcoord: 15 | -bbox string 16 | bounding box color ("" no box) 17 | -bgcolor string 18 | background color 19 | -color string 20 | line color (default "black") 21 | -fulldeck 22 | make a full deck (default true) 23 | -info 24 | only report bounding box, and center 25 | -latmax float 26 | latitude x maxmum (default 90) 27 | -latmin float 28 | latitude x minimum (default -90) 29 | -linewidth float 30 | line width (default 0.1) 31 | -longmax float 32 | longitude y maximum (default 180) 33 | -longmin float 34 | longitude y minimum (default -180) 35 | -shape string 36 | polygon, polyline (default "polyline") 37 | -style string 38 | deck, decksh, plain (default "deck") 39 | -xmax float 40 | canvas x maxmum (default 95) 41 | -xmin float 42 | canvas x minimum (default 5) 43 | -ymax float 44 | canvas y maximum (default 95) 45 | -ymin float 46 | canvas y minimum (default 5) 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /cmd/mapcoord/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/mapcoord 2 | 3 | go 1.21.5 4 | 5 | require github.com/ajstarks/kml v0.0.0-20231216032752-dd72e94de437 6 | -------------------------------------------------------------------------------- /cmd/mapcoord/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/kml v0.0.0-20231216032752-dd72e94de437 h1:U2Wj3bR9XXLgS6rOkD5TOEmQTsUjulc57RDrv6CVwNw= 2 | github.com/ajstarks/kml v0.0.0-20231216032752-dd72e94de437/go.mod h1:cxil5A25+/6kc8Y3uk573iVqB7x0KTcF+G3EclM6U4g= 3 | -------------------------------------------------------------------------------- /cmd/mapcoord/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/ajstarks/kml" 13 | ) 14 | 15 | // vmap maps one interval to another 16 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 17 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 18 | } 19 | 20 | // readData reads lat/long pairs (separated by white space) from a file, mapping to deck coordinates 21 | func readData(r io.Reader, g kml.Geometry) ([]float64, []float64, error) { 22 | x := []float64{} 23 | y := []float64{} 24 | s := bufio.NewScanner(r) 25 | for s.Scan() { 26 | t := s.Text() 27 | f := strings.Fields(t) 28 | if len(f) != 2 { 29 | continue 30 | } 31 | xp, err := strconv.ParseFloat(f[1], 64) // latitude 32 | if err != nil { 33 | continue 34 | } 35 | yp, err := strconv.ParseFloat(f[0], 64) // longitude 36 | if err != nil { 37 | continue 38 | } 39 | x = append(x, vmap(xp, g.Longmin, g.Longmax, g.Xmin, g.Xmax)) 40 | y = append(y, vmap(yp, g.Latmin, g.Latmax, g.Ymin, g.Ymax)) 41 | } 42 | return x, y, s.Err() 43 | } 44 | 45 | // readStats reads lat/long pairs, report on the computed bounding box and center 46 | func readStats(r io.Reader) { 47 | maxxval := -100000000.0 48 | minxval := 100000000.0 49 | maxyval := -100000000.0 50 | minyval := 100000000.0 51 | 52 | s := bufio.NewScanner(r) 53 | for s.Scan() { 54 | t := s.Text() 55 | f := strings.Fields(t) 56 | if len(f) != 2 { 57 | continue 58 | } 59 | xp, err := strconv.ParseFloat(f[1], 64) // latitude 60 | if err != nil { 61 | continue 62 | } 63 | yp, err := strconv.ParseFloat(f[0], 64) // longitude 64 | if err != nil { 65 | continue 66 | } 67 | if xp > maxxval { 68 | maxxval = xp 69 | } 70 | if xp < minxval { 71 | minxval = xp 72 | } 73 | 74 | if yp > maxyval { 75 | maxyval = yp 76 | } 77 | if yp < minyval { 78 | minyval = yp 79 | } 80 | } 81 | centerLong := minxval + (maxxval-minxval)/2 82 | centerLat := minyval + (maxyval-minyval)/2 83 | fmt.Fprintf(os.Stdout, "center=%v,%v -longmin=%v -longmax=%v -latmin=%v -latmax=%v\n", centerLat, centerLong, minxval, maxxval, minyval, maxyval) 84 | } 85 | 86 | // process processing input and options, making markup 87 | func process(filename string, info bool, shape, style, color, bbox string, linewidth float64, mapgeo kml.Geometry) { 88 | 89 | // read from stdin by default, open a file if specified 90 | r := os.Stdin 91 | if len(filename) > 0 { 92 | var rerr error 93 | r, rerr = os.Open(filename) 94 | if rerr != nil { 95 | fmt.Fprintf(os.Stderr, "%v\n", rerr) 96 | return 97 | } 98 | } 99 | 100 | // just show info, then return, if specified 101 | if info { 102 | readStats(r) 103 | return 104 | } 105 | 106 | // read coordinates 107 | x, y, err := readData(r, mapgeo) 108 | if err != nil { 109 | fmt.Fprintf(os.Stderr, "%v\n", err) 110 | return 111 | } 112 | // make a bounding box, if specified 113 | if len(bbox) > 0 { 114 | kml.BoundingBox(mapgeo, bbox, style) 115 | } 116 | // make the drawing 117 | kml.Deckshape(shape, style, x, y, linewidth, color, mapgeo) 118 | r.Close() 119 | } 120 | 121 | func main() { 122 | var mapgeo kml.Geometry 123 | var fulldeck, info bool 124 | var linewidth float64 125 | var color, bbox, shape, bgcolor, style string 126 | 127 | // options 128 | flag.Float64Var(&mapgeo.Xmin, "xmin", 5, "canvas x minimum") 129 | flag.Float64Var(&mapgeo.Xmax, "xmax", 95, "canvas x maxmum") 130 | flag.Float64Var(&mapgeo.Ymin, "ymin", 5, "canvas y minimum") 131 | flag.Float64Var(&mapgeo.Ymax, "ymax", 95, "canvas y maximum") 132 | flag.Float64Var(&mapgeo.Latmin, "latmin", -90, "latitude x minimum") 133 | flag.Float64Var(&mapgeo.Latmax, "latmax", 90, "latitude x maxmum") 134 | flag.Float64Var(&mapgeo.Longmin, "longmin", -180, "longitude y minimum") 135 | flag.Float64Var(&mapgeo.Longmax, "longmax", 180, "longitude y maximum") 136 | flag.Float64Var(&linewidth, "linewidth", 0.1, "line width") 137 | flag.StringVar(&color, "color", "black", "line color") 138 | flag.StringVar(&bbox, "bbox", "", "bounding box color (\"\" no box)") 139 | flag.StringVar(&shape, "shape", "polyline", "polygon, polyline") 140 | flag.StringVar(&style, "style", "deck", "deck, decksh, plain") 141 | flag.StringVar(&bgcolor, "bgcolor", "", "background color") 142 | flag.BoolVar(&fulldeck, "fulldeck", true, "make a full deck") 143 | flag.BoolVar(&info, "info", false, "only report bounding box, and center") 144 | flag.Parse() 145 | 146 | // don't do any generation if info only 147 | if info { 148 | fulldeck = false 149 | } 150 | // add deck/slide markup, if specified 151 | if fulldeck { 152 | kml.Deckshbegin(bgcolor) 153 | } 154 | // for every file (or stdin if no files are specified), make markup 155 | if len(flag.Args()) == 0 { 156 | process("", info, shape, style, color, bbox, linewidth, mapgeo) 157 | } else { 158 | for _, filename := range flag.Args() { 159 | process(filename, info, shape, style, color, bbox, linewidth, mapgeo) 160 | } 161 | } 162 | // end the deck, if specified 163 | if fulldeck { 164 | kml.Deckshend() 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /cmd/mdtopdf/fpdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/mdtopdf/fpdf.png -------------------------------------------------------------------------------- /cmd/mdtopdf/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/mdtopdf 2 | 3 | go 1.20 4 | 5 | require github.com/mandolyte/mdtopdf v1.3.2 6 | 7 | require ( 8 | github.com/jung-kurt/gofpdf v1.16.2 // indirect 9 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /cmd/mdtopdf/go.sum: -------------------------------------------------------------------------------- 1 | github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 4 | github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc= 5 | github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= 6 | github.com/mandolyte/mdtopdf v1.3.2 h1:255HiBnsxXdFPPrh9cUbV8VxHsphGqPKRq8i7qAr8/s= 7 | github.com/mandolyte/mdtopdf v1.3.2/go.mod h1:c28Ldk+tVc/y7QQcEcILStS/OFlerdXGGdBUzJQBgEo= 8 | github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 9 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 12 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 13 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= 14 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 15 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 16 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 17 | -------------------------------------------------------------------------------- /cmd/mdtopdf/hiking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/mdtopdf/hiking.png -------------------------------------------------------------------------------- /cmd/mdtopdf/main.go: -------------------------------------------------------------------------------- 1 | // mdtopdf -- convert markdown to PDF 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "github.com/mandolyte/mdtopdf" 11 | ) 12 | 13 | func die(err error) { 14 | fmt.Fprintf(os.Stderr, "%v\n", err) 15 | os.Exit(1) 16 | } 17 | 18 | func main() { 19 | var input, output string 20 | flag.StringVar(&input, "i", "", "input markdown file (default is standard input)") 21 | flag.StringVar(&output, "o", "", "output PDF file (required)") 22 | flag.Parse() 23 | 24 | if output == "" { 25 | flag.PrintDefaults() 26 | os.Exit(1) 27 | } 28 | var content []byte 29 | var err error 30 | 31 | if input == "" { 32 | content, err = io.ReadAll(os.Stdin) 33 | if err != nil { 34 | die(err) 35 | } 36 | } else { 37 | content, err = os.ReadFile(input) 38 | if err != nil { 39 | die(err) 40 | } 41 | } 42 | pf := mdtopdf.NewPdfRenderer("", "", output, "") 43 | err = pf.Process(content) 44 | if err != nil { 45 | die(err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cmd/mdtopdf/test.md: -------------------------------------------------------------------------------- 1 | # Heading One 2 | This is a paragraph with some *emphasized text* - OK? 3 | 4 | Here is an unordered list: 5 | 6 | - this is an unordered list 7 | - subitem 1.a 8 | - subitem 1.b 9 | - this is second item in unordered list 10 | - subitem 2.a 11 | - subitem 2.a.i 12 | - subitem 2.a.ii 13 | - subitem 2.b 14 | 15 | Here is an ordered/numbered list: 16 | 17 | 1. This is a numbered list 18 | 1. one 19 | 1. two 20 | 1. This is second item in numbered list 21 | 1. one 22 | 1. two 23 | 1. A 24 | 1. B 25 | 1. x 26 | 1. y 27 | 1. z 28 | 1. last numbered item 29 | 30 | ## Heading Two 31 | This is a paragraph with some **emphasized text** - OK? 32 | 33 | ### Heading Three 34 | This is a paragraph with some ___emphasized text___ - OK? 35 | 36 | #### Heading Four 37 | *A wee bit of text* 38 | 39 | #### Heading Five 40 | Just some normal text... nothing fancy 41 | 42 | This is a simple table: 43 | 44 | | Header | Another header | 45 | |---------|----------------| 46 | | field 1 | something | 47 | | field 2 | something else | 48 | | field 3 | something | 49 | | field 4 | something else | 50 | | field 5 | something | 51 | | field 6 | something else | 52 | 53 | ###### Heading Six 54 | This is really deeply nested. 55 | 56 | Here is some code: 57 | ``` 58 | 10 REM Old huh? 59 | 20 REM Yup, way old! 60 | ``` 61 | The file name is `oldcode.bas`. 62 | 63 | These three lines should 64 | only be one paragaph, but with BF retaining 65 | the newlines, the newlines must be replaced with spaces. 66 | 67 | The below is a blockquote with inner codeblocks. 68 | 69 | > Example: 70 | > 71 | > sub status { 72 | > print "working"; 73 | > } 74 | > 75 | > Or: 76 | > 77 | > sub status { 78 | > return "working"; 79 | > } 80 | 81 | Here is the gofpdf logo (inline): ![from https://github.com/jung-kurt/gofpdf/tree/master/image](fpdf.png) 82 | 83 | Here is a Gopher: 84 | 85 | ![from https://github.com/egonelbre/gophers](hiking.png "Optional title") 86 | 87 | *The Go gopher was designed by Renee French. The Gopher character design is licensed under the Creative Commons 3.0 Attributions license. Read http://blog.golang.org/gopher for more details.* 88 | 89 | 90 | __This is the last line of the document.__ 91 | -------------------------------------------------------------------------------- /cmd/mdtopdf/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/mdtopdf/test.pdf -------------------------------------------------------------------------------- /cmd/mfunc/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/mfunc 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /cmd/mfunc/mfunc.go: -------------------------------------------------------------------------------- 1 | // mfunc: math functions 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "math" 8 | ) 9 | 10 | type tfunc struct { 11 | label string 12 | function func(x float64) float64 13 | } 14 | 15 | func main() { 16 | funcname := flag.String("f", "sine", "function name") 17 | min := flag.Float64("min", 0.0, "minimum") 18 | max := flag.Float64("max", math.Pi*2, "maximum") 19 | incr := flag.Float64("incr", 0.1, "increment") 20 | xfmt := flag.String("xfmt", "%.2f", "x format") 21 | yfmt := flag.String("yfmt", "%.4f", "y format") 22 | flag.Parse() 23 | var f tfunc 24 | switch *funcname { 25 | case "sine", "sin": 26 | f = tfunc{"y=sin(x)", math.Sin} 27 | case "cosine", "cos": 28 | f = tfunc{"y=cos(x)", math.Cos} 29 | case "sqrt": 30 | f = tfunc{"y=sqrt(x)", math.Sqrt} 31 | case "log": 32 | f = tfunc{"y=log(x)", math.Log} 33 | case "log10": 34 | f = tfunc{"y=log10(x)", math.Log10} 35 | case "log2": 36 | f = tfunc{"y=log2(x)", math.Log2} 37 | case "tan": 38 | f = tfunc{"y=tan(x)", math.Tan} 39 | case "exp": 40 | f = tfunc{"y=exp(x)", math.Tan} 41 | case "sincos": 42 | f = tfunc{"y=sin(x) * cos(x)", 43 | func(x float64) float64 { return math.Sin(x) * math.Cos(x) }} 44 | default: 45 | f = tfunc{"y=1", func(float64) float64 { return 1 }} 46 | } 47 | fmt.Printf("# %s\n", f.label) 48 | format := *xfmt + "\t" + *yfmt + "\n" 49 | for x := *min; x <= *max; x += *incr { 50 | fmt.Printf(format, x, f.function(x)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cmd/mkpoly/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/mkpoly 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/mkpoly/mkpoly.go: -------------------------------------------------------------------------------- 1 | // mkpoly - generate decksh polygons from x,y pairs 2 | package main 3 | 4 | import ( 5 | "bufio" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "math" 10 | "os" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | smallest = -math.MaxFloat64 17 | largest = math.MaxFloat64 18 | ) 19 | 20 | type params struct { 21 | left, right, bottom, top, minx, maxx, miny, maxy float64 22 | label, color string 23 | } 24 | 25 | type pfunc func(p params, s string) 26 | 27 | func main() { 28 | var p params 29 | var outstyle string 30 | 31 | flag.Float64Var(&p.left, "left", 10, "left") 32 | flag.Float64Var(&p.right, "right", 90, "right") 33 | flag.Float64Var(&p.bottom, "bottom", 10, "bottom") 34 | flag.Float64Var(&p.top, "top", 90, "top") 35 | flag.Float64Var(&p.minx, "minx", smallest, "top") 36 | flag.Float64Var(&p.maxx, "maxx", largest, "minx") 37 | flag.Float64Var(&p.miny, "miny", smallest, "maxx") 38 | flag.Float64Var(&p.maxy, "maxy", largest, "miny") 39 | flag.StringVar(&p.color, "color", "gray", "color") 40 | flag.StringVar(&p.label, "label", "", "label") 41 | flag.StringVar(&outstyle, "style", "deck", "output style (deck or decksh") 42 | flag.Parse() 43 | 44 | process := deck 45 | if outstyle == "decksh" { 46 | process = decksh 47 | } 48 | 49 | for _, f := range flag.Args() { 50 | if err := process(p, f); err != nil { 51 | fmt.Fprintf(os.Stderr, "%v\n", err) 52 | continue 53 | } 54 | } 55 | } 56 | 57 | // readata reads x, y pairs, checking for errors 58 | func readata(r io.Reader) ([]float64, []float64, error) { 59 | var x, y []float64 60 | var xp, yp float64 61 | var err error 62 | scanner := bufio.NewScanner(r) 63 | for scanner.Scan() { 64 | fields := strings.Split(scanner.Text(), ",") 65 | if len(fields) != 2 { 66 | continue 67 | } 68 | if xp, err = strconv.ParseFloat(fields[0], 64); err != nil { 69 | continue 70 | } 71 | if yp, err = strconv.ParseFloat(fields[1], 64); err != nil { 72 | continue 73 | } 74 | x = append(x, xp) 75 | y = append(y, yp) 76 | } 77 | return x, y, scanner.Err() 78 | } 79 | 80 | func deck(p params, filename string) error { 81 | r, err := os.Open(filename) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | fmt.Println("") 87 | 88 | x, y, err := readata(r) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | pminx := largest 94 | pmaxx := smallest 95 | fmt.Printf(" pmaxx { 99 | pmaxx = px 100 | } 101 | if px < pminx { 102 | pminx = px 103 | } 104 | fmt.Printf("%.3g ", px) 105 | } 106 | fmt.Printf("%.3g\"", vmap(x[0], p.minx, p.maxx, p.left, p.right)) 107 | 108 | pminy := largest 109 | pmaxy := smallest 110 | fmt.Printf(" yc=\"") 111 | for i := 0; i < len(y); i++ { 112 | py := vmap(y[i], p.miny, p.maxy, p.bottom, p.top) 113 | if py > pmaxy { 114 | pmaxy = py 115 | } 116 | if py < pminy { 117 | pminy = py 118 | } 119 | fmt.Printf("%.3g ", py) 120 | } 121 | fmt.Printf("%.3g\" color=\"%s\"/>\n", vmap(y[0], p.miny, p.maxy, p.bottom, p.top), p.color) 122 | if len(p.label) > 0 { 123 | fmt.Printf("%s/>\n", pminx+((pmaxx-pminx)/2), pminy+((pmaxy-pminy)/2), p.label) 124 | } 125 | return r.Close() 126 | } 127 | 128 | // process data in the filename 129 | func decksh(p params, filename string) error { 130 | r, err := os.Open(filename) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | fmt.Println("#", p.minx, p.maxx, p.miny, p.maxy, p.left, p.right, p.bottom, p.top) 136 | 137 | x, y, err := readata(r) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | pminx := largest 143 | pmaxx := smallest 144 | fmt.Printf("polygon \"") 145 | for i := 0; i < len(x); i++ { 146 | px := vmap(x[i], p.minx, p.maxx, p.left, p.right) 147 | if px > pmaxx { 148 | pmaxx = px 149 | } 150 | if px < pminx { 151 | pminx = px 152 | } 153 | fmt.Printf("%.3g ", px) 154 | } 155 | fmt.Printf("%.3g\"", vmap(x[0], p.minx, p.maxx, p.left, p.right)) 156 | 157 | pminy := largest 158 | pmaxy := smallest 159 | fmt.Printf(" \"") 160 | for i := 0; i < len(y); i++ { 161 | py := vmap(y[i], p.miny, p.maxy, p.bottom, p.top) 162 | if py > pmaxy { 163 | pmaxy = py 164 | } 165 | if py < pminy { 166 | pminy = py 167 | } 168 | fmt.Printf("%.3g ", py) 169 | } 170 | fmt.Printf("%.3g\" \"%s\"\n", vmap(y[0], p.miny, p.maxy, p.bottom, p.top), p.color) 171 | if len(p.label) > 0 { 172 | fmt.Printf("ctext \"%s\" %g %g 1\n", p.label, pminx+((pmaxx-pminx)/2), pminy+((pmaxy-pminy)/2)) 173 | } 174 | return r.Close() 175 | } 176 | 177 | // vmap maps one range to another 178 | func vmap(value, low1, high1, low2, high2 float64) float64 { 179 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 180 | } 181 | -------------------------------------------------------------------------------- /cmd/nythead/README.md: -------------------------------------------------------------------------------- 1 | # nythead - print headlines from the New York Times 2 | 3 | ## usage 4 | 5 | ``` 6 | usage of nythead: 7 | -s string 8 | headline type (arts, health, sports, science, technology, u.s., world) (default "u.s.") 9 | ``` 10 | 11 | ## examples 12 | 13 | ``` 14 | $ nythead 15 | Key Takeaways From Trump’s Tax Returns 16 | Trump Tax Returns Released by House Democrats 17 | Reported Sexual Abuse at California Prep School Won’t Be Prosecuted 18 | Biden Signs Government Funding Bill, Preventing Shutdown 19 | Southwest Is California’s ‘Unofficial Airline.’ The Meltdown Has Residents Anxious. 20 | ``` 21 | 22 | ``` 23 | $ nythead -s arts 24 | Giancarlo Esposito Plays Other People So He Can Know Himself 25 | These Young Musicians Made an Album. Now It’s Nominated for a Grammy. 26 | The Complex History Behind a Vienna Philharmonic Tradition 27 | 5 Things to Do This Weekend 28 | Ian Tyson, Revered Canadian Folk Singer, Dies at 89 29 | ``` 30 | 31 | This program looks for the NYT API key in the environment variable ```NYTAPIKEY```, and fails if is not set. -------------------------------------------------------------------------------- /cmd/nythead/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/nythead 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /cmd/nythead/nythead.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "time" 11 | ) 12 | 13 | // API Info 14 | const ( 15 | NYTAPIkey = "NYTAPIKEY" // obtained from the environment 16 | NYTfmt = "http://api.nytimes.com/svc/news/v3/content/all/%s/.json?api-key=%s&limit=5" 17 | ) 18 | 19 | // NYTHeadlines is the headline info from the New York Times 20 | type NYTHeadlines struct { 21 | Status string `json:"status"` 22 | Copyright string `json:"copyright"` 23 | NumResults int `json:"num_results"` 24 | Results []result `json:"results"` 25 | } 26 | 27 | type result struct { 28 | Section string `json:"section"` 29 | Subsection string `json:"subsection"` 30 | Title string `json:"title"` 31 | Abstract string `json:"abstract"` 32 | Thumbnail string `json:"thumbnail_standard"` 33 | } 34 | 35 | func main() { 36 | var section = flag.String("s", "u.s.", "headline type (arts, health, sports, science, technology, u.s., world)") 37 | flag.Parse() 38 | nytheadlines(*section) 39 | } 40 | 41 | // apikey returns the API key from the environment, or the empty string if not found. 42 | func apikey(s string) string { 43 | key, ok := os.LookupEnv(s) 44 | if !ok { 45 | return "" 46 | } 47 | return key 48 | } 49 | 50 | // netread derefernces a URL, returning the Reader, with an error 51 | func netread(url string) (io.ReadCloser, error) { 52 | client := &http.Client{Timeout: 30 * time.Second} 53 | resp, err := client.Get(url) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if resp.StatusCode != http.StatusOK { 58 | return nil, fmt.Errorf("unable to get network data for %s (%s)", url, resp.Status) 59 | } 60 | return resp.Body, nil 61 | } 62 | 63 | // nytheadlines retrieves data from the New York Times API, decodes and displays it. 64 | func nytheadlines(section string) { 65 | key := apikey(NYTAPIkey) 66 | if len(key) == 0 { 67 | fmt.Fprintln(os.Stderr, "invalid API key") 68 | return 69 | } 70 | r, err := netread(fmt.Sprintf(NYTfmt, section, key)) 71 | if err != nil { 72 | fmt.Fprintf(os.Stderr, "headline read error: %v\n", err) 73 | return 74 | } 75 | defer r.Close() 76 | var data NYTHeadlines 77 | err = json.NewDecoder(r).Decode(&data) 78 | if err != nil { 79 | fmt.Fprintf(os.Stderr, "decode: %v\n", err) 80 | return 81 | } 82 | for i := 0; i < len(data.Results); i++ { 83 | fmt.Printf("%v\n", data.Results[i].Title) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /cmd/polar/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/polar 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/polar/polar.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math" 7 | ) 8 | 9 | func main() { 10 | var x, y, r, theta float64 11 | flag.Float64Var(&x, "x", 50, "x coordinate") 12 | flag.Float64Var(&y, "y", 50, "y coordinate") 13 | flag.Float64Var(&r, "r", 10, "radius") 14 | flag.Float64Var(&theta, "t", 90, "angle (degrees)") 15 | flag.Parse() 16 | 17 | rad := theta * (math.Pi / 180) 18 | fmt.Printf("x=%g y=%g r=%g t=%g -> %g %g\n", x, y, r, theta, x+(r*math.Cos(rad)), y+(r*math.Sin(rad))) 19 | } 20 | -------------------------------------------------------------------------------- /cmd/popio/main.go: -------------------------------------------------------------------------------- 1 | // popio: import and export images for popi 2 | package main 3 | 4 | import ( 5 | "bufio" 6 | "flag" 7 | "fmt" 8 | "image" 9 | "image/color" 10 | _ "image/jpeg" 11 | "image/png" 12 | "io" 13 | "os" 14 | ) 15 | 16 | // popin: image to raw 17 | func popin(w io.Writer, r io.Reader) (int, error) { 18 | img, _, err := image.Decode(r) 19 | if err != nil { 20 | return 0, err 21 | } 22 | // get image dimensions 23 | bounds := img.Bounds() 24 | width := bounds.Max.X - bounds.Min.X 25 | height := bounds.Max.Y - bounds.Min.Y 26 | // convert image pixels to grayscale 27 | data := make([]byte, width*height) 28 | i := 0 29 | for y := 0; y < height; y++ { 30 | for x := 0; x < width; x++ { 31 | r, g, b, _ := img.At(x, y).RGBA() 32 | data[i] = uint8((19595*r + 38470*g + 7471*b + 1<<15) >> 24) 33 | i++ 34 | } 35 | } 36 | return bufio.NewWriter(w).Write(data) 37 | } 38 | 39 | // popout: raw to PNG 40 | func popout(w io.Writer, r io.Reader, width, height int) error { 41 | data, err := io.ReadAll(r) 42 | if err != nil { 43 | return err 44 | } 45 | // convert raw data to grayscale pixels 46 | img := image.NewGray(image.Rect(0, 0, width, height)) 47 | i := 0 48 | for y := 0; y < height; y++ { 49 | for x := 0; x < width; x++ { 50 | img.Set(x, y, color.Gray{data[i]}) 51 | i++ 52 | } 53 | } 54 | // write the png 55 | if err := png.Encode(w, img); err != nil { 56 | return err 57 | } 58 | return nil 59 | } 60 | 61 | func main() { 62 | var read, write bool 63 | var width, height int 64 | flag.BoolVar(&read, "import", false, "image to raw popi grayscale") 65 | flag.BoolVar(&write, "export", false, "popi raw grayscale to PNG") 66 | flag.IntVar(&width, "width", 512, "image width") 67 | flag.IntVar(&height, "height", 512, "image height") 68 | flag.Parse() 69 | 70 | if read && write { 71 | fmt.Fprintln(os.Stderr, "pick one: -import or -export") 72 | os.Exit(3) 73 | } 74 | 75 | if read { 76 | n, err := popin(os.Stdout, os.Stdin) 77 | if err != nil { 78 | fmt.Fprintf(os.Stderr, "%v (%d bytes written)\n", err, n) 79 | os.Exit(1) 80 | } 81 | os.Exit(0) 82 | } 83 | if write { 84 | err := popout(os.Stdout, os.Stdin, width, height) 85 | if err != nil { 86 | fmt.Fprintf(os.Stderr, "%v\n", err) 87 | os.Exit(2) 88 | } 89 | os.Exit(0) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /cmd/randgen/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/randgen 2 | 3 | go 1.22.2 4 | -------------------------------------------------------------------------------- /cmd/randgen/randgen.go: -------------------------------------------------------------------------------- 1 | // randgen makes random numbers 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "math/rand/v2" 8 | ) 9 | 10 | func vmap(v, l1, h1, l2, h2 float64) float64 { 11 | return l2 + (h2-l2)*(v-l1)/(h1-l1) 12 | } 13 | 14 | func main() { 15 | nrand := flag.Int("n", 100, "number of items") 16 | min := flag.Float64("min", 0, "minimum value") 17 | max := flag.Float64("max", 1e6, "minimum value") 18 | ndec := flag.Int("dec", 3, "number of decimals") 19 | xint := flag.Float64("xint", 0, "x value interval") 20 | flag.Parse() 21 | xval := 0.0 22 | f := fmt.Sprintf("%%.%df", *ndec) 23 | for i := 0; i < *nrand; i++ { 24 | if *xint > 0 { 25 | fmt.Printf(f+"\t", xval) 26 | xval += *xint 27 | } 28 | fmt.Printf(f+"\n", vmap(rand.Float64(), 0, 1, *min, *max)) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /cmd/rmcsv/README.md: -------------------------------------------------------------------------------- 1 | # rmcsv -- convert roadmap csv files to XML 2 | 3 | Produces XML from CSV files named on the command line or standard input. 4 | The convered XML goes to standard output. 5 | 6 | ``` 7 | rmcsv [options] file.csv... > file.xml 8 | ``` 9 | 10 | The CSV files have five fields: Category,begin,duration,id and connection. 11 | 12 | The input: 13 | ``` 14 | "Category/Item",Begin,Duration,Id,Connection 15 | ,,,, 16 | "Facilities",,,, 17 | "Phase 1",2010/3,2,, 18 | "Phase 2",2010/6,2,, 19 | "Patch",2010/9,1,, 20 | ,,,, 21 | "Network",,,, 22 | "Install",2010/2,6,, 23 | "Decomission",2010/9,2,, 24 | ,,,, 25 | "Servers",,,, 26 | "Install",2010/5,3,, 27 | "Decomission",2010/9,2,,gl 28 | ,,,, 29 | "Applications",,,, 30 | "Develop",2010/1,8,, 31 | "Test",2010/5,4,, 32 | "Data Migration",2010/9,1,dm,gl 33 | ,,,, 34 | "Support",,,, 35 | "Transition",2010/1,10,,gl 36 | ,,,, 37 | "EVENTS",,,, 38 | "Freeze",2010/6,3,, 39 | "Go Live",2010/12,1,gl, 40 | 41 | 42 | ``` 43 | ![ss](ss.png) 44 | 45 | Produces: 46 | 47 | ``` 48 | 49 | 50 | 2022 51 | 52 | 53 | Jan 54 | Feb 55 | Mar 56 | Apr 57 | May 58 | Jun 59 | Jul 60 | Aug 61 | Sep 62 | Oct 63 | Nov 64 | Dec 65 | 66 | 67 | Phase 1 68 | Phase 2 69 | Patch 70 | 71 | 72 | Install 73 | Decomission 74 | 75 | 76 | Install 77 | Decomission 78 | 79 | 80 | Develop 81 | Test 82 | Data Migration 83 | 84 | 85 | Transition 86 | 87 | 88 | Freeze 89 | Go Live 90 | 91 | 92 | ``` 93 | 94 | 95 | 96 | # options 97 | ``` 98 | Usage of rmcsv: 99 | -begin int 100 | begin year (default 2022) 101 | -cp int 102 | category percent (default 12) 103 | -datehead 104 | include date header (default true) 105 | -end int 106 | end year (default 2023) 107 | -font string 108 | roadmap font (default "Calibri,sans-serif") 109 | -itemh int 110 | itemheight (default 30) 111 | -scale int 112 | scale (default 12) 113 | -shape string 114 | item shape (default "r") 115 | -title string 116 | Roadmap Title (default "Title") 117 | -vspace int 118 | vspace (default 35) 119 | ``` 120 | -------------------------------------------------------------------------------- /cmd/rmcsv/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/rmcsv 2 | 3 | go 1.21.0 4 | -------------------------------------------------------------------------------- /cmd/rmcsv/main.go: -------------------------------------------------------------------------------- 1 | // rmcsv -- convert csv roadmap files to xml 2 | package main 3 | 4 | import ( 5 | "encoding/csv" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | rmfmt = "\n" 15 | endrmfmt = "\t\n\n" 16 | yearcatfmt = "\t\n" 17 | yearitemfmt = "\t\t%d\n" 18 | endcat = "\t\n" 19 | monthcatfmt = "\t\n" 20 | monthitemfmt = "\t\t%s\n" 21 | catfmt = "\t\n" 22 | itemfmt1 = "\t\t%s\n" 23 | itemfmt2 = "\t\t%s\n" 24 | itemfmt3 = "\t\t%s\n" 25 | ) 26 | 27 | type rmconfig struct { 28 | title, shape, font string 29 | begin, end, catpercent, scale, vspace, itemheight int 30 | dh bool 31 | } 32 | 33 | // main: process roadmap files on the command line, 34 | // use stdin if no files specified. 35 | func main() { 36 | var config rmconfig 37 | flag.StringVar(&config.title, "title", "Title", "Roadmap Title") 38 | flag.StringVar(&config.shape, "shape", "r", "item shape") 39 | flag.StringVar(&config.font, "font", "Calibri,sans-serif", "roadmap font") 40 | flag.IntVar(&config.begin, "begin", 2022, "begin year") 41 | flag.IntVar(&config.end, "end", 2023, "end year") 42 | flag.IntVar(&config.catpercent, "cp", 12, "category percent") 43 | flag.IntVar(&config.scale, "scale", 12, "scale") 44 | flag.IntVar(&config.vspace, "vspace", 35, "vspace") 45 | flag.IntVar(&config.itemheight, "itemh", 30, "itemheight") 46 | flag.BoolVar(&config.dh, "datehead", true, "include date header") 47 | 48 | flag.Parse() 49 | files := flag.Args() 50 | nf := len(files) 51 | if nf == 0 { 52 | process("", config, os.Stdout) 53 | return 54 | } 55 | for i := 0; i < nf; i++ { 56 | process(files[i], config, os.Stdout) 57 | } 58 | } 59 | 60 | // process reads and processes a roadmap XML file 61 | func process(location string, config rmconfig, w io.Writer) { 62 | var r *os.File 63 | var err error 64 | r = os.Stdin 65 | if len(location) > 0 { 66 | r, err = os.Open(location) 67 | if err != nil { 68 | fmt.Fprintf(os.Stderr, "%v\n", err) 69 | return 70 | } 71 | } 72 | csvtoxml(w, r, config) 73 | r.Close() 74 | } 75 | 76 | // csvtoxml reads the roadmap CSV, converting to XML 77 | func csvtoxml(w io.Writer, r io.Reader, config rmconfig) { 78 | // roadmap root element 79 | fmt.Fprintf(w, rmfmt, xmlesc(config.title), xmlesc(config.font), xmlesc(config.shape), 80 | config.begin, config.end, config.catpercent, config.scale, config.itemheight, config.vspace) 81 | 82 | // read categories and items from csv, write XML 83 | input := csv.NewReader(r) 84 | nr := 0 85 | nc := 0 86 | if config.dh { 87 | dateheader(w, config.begin, config.end) 88 | } 89 | for { 90 | fields, csverr := input.Read() 91 | if csverr == io.EOF { 92 | break 93 | } 94 | if csverr != nil { 95 | fmt.Fprintf(os.Stderr, "%v %v\n", csverr, fields) 96 | continue 97 | } 98 | nr++ 99 | // skip header 100 | if nr == 1 { 101 | continue 102 | } 103 | // skip invalid fields 104 | if len(fields) < 5 { 105 | continue 106 | } 107 | if len(fields[0]) == 0 { 108 | continue 109 | } 110 | // process categories 111 | if len(fields[0]) > 0 && len(fields[1]) == 0 && len(fields[2]) == 0 { 112 | nc++ 113 | if nc > 1 { 114 | fmt.Fprint(w, endcat) 115 | } 116 | fmt.Fprintf(w, catfmt, xmlesc(fields[0]), config.itemheight, config.vspace) 117 | continue 118 | } 119 | // process items 120 | switch { 121 | case len(fields[3]) > 0 && len(fields[4]) > 0: 122 | fmt.Fprintf(w, itemfmt1, xmlesc(fields[3]), xmlesc(fields[1]), xmlesc(fields[2]), xmlesc(fields[4]), xmlesc(fields[0])) 123 | case len(fields[3]) > 0 && len(fields[4]) == 0: 124 | fmt.Fprintf(w, itemfmt2, xmlesc(fields[3]), xmlesc(fields[1]), xmlesc(fields[2]), xmlesc(fields[0])) 125 | default: 126 | fmt.Fprintf(w, itemfmt3, xmlesc(fields[1]), xmlesc(fields[2]), xmlesc(fields[0])) 127 | } 128 | } 129 | // end the XML 130 | fmt.Fprint(w, endrmfmt) 131 | } 132 | 133 | // xmlmap defines the XML substitutions 134 | var xmlmap = strings.NewReplacer( 135 | "&", "&", 136 | "<", "<", 137 | ">", ">") 138 | 139 | // xmlesc escapes XML 140 | func xmlesc(s string) string { 141 | return xmlmap.Replace(s) 142 | } 143 | 144 | // dateheader makes a date header 145 | func dateheader(w io.Writer, begin, end int) { 146 | // years 147 | fmt.Fprint(w, yearcatfmt) 148 | for y := begin; y < end; y++ { 149 | fmt.Fprintf(w, yearitemfmt, y, y) 150 | } 151 | fmt.Fprint(w, endcat) 152 | 153 | // months 154 | fmt.Fprint(w, monthcatfmt) 155 | for y := begin; y < end; y++ { 156 | for i, m := range []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} { 157 | fmt.Fprintf(w, monthitemfmt, y, i+1, m) 158 | } 159 | } 160 | fmt.Fprint(w, endcat) 161 | } 162 | -------------------------------------------------------------------------------- /cmd/rmcsv/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/rmcsv/ss.png -------------------------------------------------------------------------------- /cmd/roadmap/README.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | roadmap reads a XML description and outputs an SVG rendition of a roadmap. 4 | 5 | The XML description specifies a roadmap that contain categories, which in turn contain an arbitratry number of items. 6 | Items may contains dependencies to other items. 7 | 8 | ``` 9 | 10 | 11 | Item 1 12 | Item 2 13 | 14 | 15 | 16 | Item 3 17 | 18 | 19 | ``` 20 | 21 | ## Roadmap attributes 22 | * title -- title of the roadmap 23 | * begin -- begining year of the roadmap 24 | * end -- ending year of the roadmap (for example to specfiy a span of 2023-2025, begin="2023", end="2026") 25 | * scale -- the number of items per period, for example 12 for 12 months of the year 26 | * catpercent -- the canvas percentage from the left where catagory labels are placed 27 | * vspace -- the default amount of vertical space between items within a category 28 | * itemheight -- the default height of an item 29 | * fontname -- font used for the text 30 | * shape -- default shape of an item: 'r': rectangle (default), 'rr': rounded rectangle, 'c': circle, 'e': ellipse, 'a': arrow, 'l': line 31 | 32 | ## Category attributes 33 | * name -- the name of the category 34 | * color -- the category color 35 | * vspace -- vertical space between items 36 | * itemheight -- the height of items 37 | * shape -- shape of items in this category 38 | * bline -- if "on" draw a boundary line on top of the category 39 | * catdesc -- category description 40 | 41 | ## Item attributes 42 | * id -- item unique id used to connect items 43 | * begin -- beginning of the item in them ```year/number``` for example 2023/01 for January 2023 44 | * duration -- duration of the item in scale units, for example if the scale is set to 12, duration of 6 is six months 45 | * color -- item color, overriding the category color 46 | * shape -- item shape, overriding the category shape 47 | * align -- text alignment of the item (middle (default), end, start) 48 | * vspace -- vertical space between items 49 | * bline -- if set to "on" draw a boundary line 50 | * dep -- dependencies between items 51 | * desc -- item description 52 | 53 | ## Dependency attributes 54 | * dest -- the item id to point to 55 | * desc -- dependency description 56 | 57 | For example, given: 58 | ``` 59 | 61 | 62 | Jan 63 | Feb 64 | Mar 65 | Apr 66 | May 67 | Jun 68 | Jul 69 | Aug 70 | Sep 71 | Oct 72 | Nov 73 | Dec 74 | 75 | 76 | Phase 1 77 | Phase 2 78 | Patch 79 | 80 | 81 | Install 82 | Decomission 83 | 84 | 85 | Install 86 | Decomission 87 | 88 | 89 | Develop 90 | Test 91 | Data Migration 92 | 93 | 94 | Migration planning 95 | 96 | 97 | Transition 98 | 99 | 100 | Freeze 101 | Go Live 102 | 103 | 104 | 105 | ``` 106 | 107 | where the above file is ```p2p.xml```, the command ```roadmap p2p.xml > p2p.svg``` Produces: 108 | 109 | ![plan2plan](p2p.png) 110 | 111 | 112 | ## Command line options 113 | ``` 114 | Usage of roadmap: 115 | -align string 116 | label alignment (default "end") 117 | -b bold categories 118 | -bb 119 | bottom border 120 | -bg string 121 | background color (default "white") 122 | -cb 123 | category border 124 | -cc string 125 | connection color (default "red") 126 | -cfs float 127 | category font size (px) (default 14) 128 | -csv string 129 | write CSV to specified file 130 | -curves string 131 | curve line connections (default "0,0") 132 | -dc string 133 | description color (default "red") 134 | -de 135 | description at the end of the item (default true) 136 | -h float 137 | height (default 768) 138 | -ifs float 139 | item fontsize (px) (default 12) 140 | -lb 141 | left border (default true) 142 | -margin float 143 | margin (default 10) 144 | -rb 145 | right border 146 | -tb 147 | top border 148 | -tfs float 149 | title font size (px) (default 24) 150 | -w float 151 | width (default 1024) 152 | -wrap float 153 | text wrap (default 20) 154 | ``` 155 | -------------------------------------------------------------------------------- /cmd/roadmap/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/roadmap 2 | 3 | go 1.21.0 4 | 5 | require github.com/ajstarks/gensvg v0.0.0-20210923152200-4042c242e95e 6 | -------------------------------------------------------------------------------- /cmd/roadmap/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/gensvg v0.0.0-20210923152200-4042c242e95e h1:450GYH87+/+YEC6/RRshDbWgm3uzl5U8SOdOoDl+gaw= 2 | github.com/ajstarks/gensvg v0.0.0-20210923152200-4042c242e95e/go.mod h1:PtU6ofchbU0VxGlg+klpebsi0DY7V9GrxtYeMJkWutM= 3 | -------------------------------------------------------------------------------- /cmd/roadmap/p2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/roadmap/p2p.png -------------------------------------------------------------------------------- /cmd/roadmap/p2p.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | Jan 5 | Feb 6 | Mar 7 | Apr 8 | May 9 | Jun 10 | Jul 11 | Aug 12 | Sep 13 | Oct 14 | Nov 15 | Dec 16 | 17 | 18 | Phase 1 19 | Phase 2 20 | Patch 21 | 22 | 23 | Install 24 | Decomission 25 | 26 | 27 | Install 28 | Decomission 29 | 30 | 31 | Develop 32 | Test 33 | Data Migration 34 | 35 | 36 | Migration planning 37 | 38 | 39 | Transition 40 | 41 | 42 | Freeze 43 | Go Live 44 | 45 | 46 | -------------------------------------------------------------------------------- /cmd/setdeckfont/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/setdeckfont 2 | 3 | go 1.18 4 | 5 | require github.com/flopp/go-findfont v0.1.0 6 | -------------------------------------------------------------------------------- /cmd/setdeckfont/go.sum: -------------------------------------------------------------------------------- 1 | github.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU= 2 | github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw= 3 | -------------------------------------------------------------------------------- /cmd/setdeckfont/setdeckfont.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/flopp/go-findfont" 9 | ) 10 | 11 | func main() { 12 | sample := "" 13 | if len(os.Args) > 1 { 14 | sample = os.Args[1] 15 | } 16 | fontPath, err := findfont.Find(sample) 17 | if err != nil { 18 | fmt.Fprintf(os.Stderr, "%v\n", err) 19 | os.Exit(1) 20 | } 21 | fmt.Printf("DECKFONTS=%s\n", filepath.Dir(fontPath)) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /cmd/slopechart/f.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/slopechart/f.pdf -------------------------------------------------------------------------------- /cmd/slopechart/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/slopechart 2 | 3 | go 1.16 4 | 5 | require github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 6 | -------------------------------------------------------------------------------- /cmd/slopechart/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= 2 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 3 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 h1:Z5Dn/2Ml6/8fP1PWxv4ijaK2PwHsX8A+gZiKJVP+XDs= 4 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 5 | -------------------------------------------------------------------------------- /cmd/slopechart/slope.d: -------------------------------------------------------------------------------- 1 | # Test Slope Graphs 2 | one 20 First 3 | two 100 4 | three 40 Second 5 | four 50 6 | five 100 Third 7 | six 50 8 | seven 50 Fourth 9 | eight 50 10 | nine 100 Fifth 11 | ten 40 12 | eleven 20 Sixth 13 | twelve 5 14 | -------------------------------------------------------------------------------- /cmd/slopechart/slopechart.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "math" 9 | "os" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/ajstarks/deck/generate" 14 | ) 15 | 16 | type nameval struct { 17 | name string 18 | value float64 19 | label string 20 | } 21 | 22 | type options struct { 23 | min, max, left, right, bottom, top, textsize, linewidth float64 24 | color, vcolor string 25 | } 26 | 27 | var xmlmap = strings.NewReplacer( 28 | "&", "&", 29 | "<", "<", 30 | ">", ">") 31 | 32 | const ( 33 | largest = math.MaxFloat64 34 | smallest = -math.MaxFloat64 35 | ) 36 | 37 | // xmlesc escapes XML 38 | func xmlesc(s string) string { 39 | return xmlmap.Replace(s) 40 | } 41 | 42 | func readData(r io.ReadCloser) ([]nameval, string, float64, float64, error) { 43 | var d nameval 44 | var data []nameval 45 | var err error 46 | maxval := smallest 47 | minval := largest 48 | title := "" 49 | scanner := bufio.NewScanner(r) 50 | // read a line, parse into name, value pairs 51 | // compute min and max values 52 | for scanner.Scan() { 53 | t := scanner.Text() 54 | if len(t) == 0 { // skip blank lines 55 | continue 56 | } 57 | if len(t) > 2 && t[0] == '#' { 58 | title = strings.TrimSpace(t[1:]) 59 | } 60 | fields := strings.Split(t, "\t") 61 | if len(fields) < 2 { 62 | continue 63 | } 64 | if len(fields) > 2 { 65 | d.label = fields[2] 66 | } 67 | d.name = fields[0] 68 | d.value, err = strconv.ParseFloat(fields[1], 64) 69 | if err != nil { 70 | d.value = 0 71 | } 72 | if d.value > maxval { 73 | maxval = d.value 74 | } 75 | if d.value < minval { 76 | minval = d.value 77 | } 78 | data = append(data, d) 79 | } 80 | r.Close() 81 | return data, title, minval, maxval, err 82 | 83 | } 84 | 85 | // vmap maps one range into another 86 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 87 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 88 | } 89 | 90 | func slopechart(deck *generate.Deck, opts options, r io.ReadCloser) error { 91 | data, title, _, _, err := readData(r) 92 | if err != nil { 93 | return err 94 | } 95 | if len(data) < 2 { 96 | return fmt.Errorf("need at least two data points") 97 | } 98 | min := opts.min 99 | max := opts.max 100 | left := opts.left 101 | right := opts.right 102 | top := opts.top 103 | bottom := opts.bottom 104 | color := opts.color 105 | vcolor := opts.vcolor 106 | textsize := opts.textsize 107 | linewidth := opts.linewidth 108 | lw := linewidth / 2 109 | 110 | lsize := textsize * 0.75 111 | tsize := textsize * 1.5 112 | w := right - left 113 | h := top - bottom 114 | if len(title) > 0 { 115 | hsize := textsize * 2 116 | deck.Text(left, top+10, title, "sans", hsize, "") 117 | } 118 | 119 | hskip := w * .60 120 | vskip := h * 1.4 121 | x1 := left 122 | x2 := right 123 | for i := 0; i < len(data)-1; i += 2 { 124 | if len(data[i].label) > 0 { 125 | deck.TextMid(x1+(w/2), top+3, data[i].label, "sans", tsize, "") 126 | } 127 | v1 := data[i].value 128 | v2 := data[i+1].value 129 | v1y := vmap(v1, min, max, bottom, top) 130 | v2y := vmap(v2, min, max, bottom, top) 131 | deck.Line(x1, bottom, x1, top, lw, "black") 132 | deck.Line(x2, bottom, x2, top, lw, "black") 133 | deck.Circle(x1, v1y, textsize, color) 134 | deck.Circle(x2, v2y, textsize, color) 135 | deck.Line(x1, v1y, x2, v2y, linewidth, color) 136 | deck.TextMid(x1, bottom-2, data[i].name, "sans", textsize, "") 137 | deck.TextMid(x2, bottom-2, data[i+1].name, "sans", textsize, "") 138 | deck.TextEnd(x1-1, top, fmt.Sprintf("%g", max), "sans", lsize, "") 139 | deck.TextEnd(x1-1, v1y, fmt.Sprintf("%g", v1), "sans", lsize, vcolor) 140 | deck.Text(x2+1, v2y, fmt.Sprintf("%g", v2), "sans", lsize, vcolor) 141 | x1 += w + hskip 142 | x2 += w + hskip 143 | if x2 > 100 { 144 | x1 = left 145 | x2 = right 146 | top -= vskip 147 | bottom -= vskip 148 | } 149 | } 150 | return err 151 | } 152 | 153 | func main() { 154 | 155 | left := flag.Float64("left", 20, "left") 156 | right := flag.Float64("right", 40, "right") 157 | bottom := flag.Float64("bottom", 20, "bottom") 158 | top := flag.Float64("top", 60, "top") 159 | color := flag.String("color", "steelblue", "color") 160 | vcolor := flag.String("vcolor", "maroon", "value color") 161 | max := flag.Float64("max", 100, "max value") 162 | textsize := flag.Float64("textsize", 1.5, "text size") 163 | linewidth := flag.Float64("linewidth", 0.2, "line width") 164 | flag.Parse() 165 | 166 | opts := options{ 167 | min: 0, 168 | max: *max, 169 | left: *left, 170 | right: *right, 171 | top: *top, 172 | bottom: *bottom, 173 | linewidth: *linewidth, 174 | textsize: *textsize, 175 | color: *color, 176 | vcolor: *vcolor, 177 | } 178 | 179 | deck := generate.NewSlides(os.Stdout, 0, 0) 180 | deck.StartDeck() 181 | deck.StartSlide() 182 | if err := slopechart(deck, opts, os.Stdin); err != nil { 183 | fmt.Fprintf(os.Stderr, "%v\n", err) 184 | os.Exit(1) 185 | } 186 | deck.EndSlide() 187 | deck.EndDeck() 188 | os.Exit(0) 189 | } 190 | -------------------------------------------------------------------------------- /cmd/spl/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/spl 2 | 3 | go 1.22.0 4 | -------------------------------------------------------------------------------- /cmd/spl/spl.go: -------------------------------------------------------------------------------- 1 | // spl -- image catalogs 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "image" 8 | _ "image/gif" 9 | _ "image/jpeg" 10 | _ "image/png" 11 | "os" 12 | ) 13 | 14 | const ( 15 | bslide = "\n" 16 | eslide = "" 17 | imfmt = "\n" 18 | namefmt = "%s\n" 19 | snamefmt = "%s\n" 20 | simgfmt = "\n" 21 | sdeck = "" 22 | edeck = "" 23 | ) 24 | 25 | type Picture struct { 26 | x, y float64 27 | width, height int 28 | name string 29 | orientation string 30 | } 31 | 32 | type Pictures []Picture 33 | 34 | type Canvas struct { 35 | width, height int 36 | left, right, top, bottom float64 37 | bgcolor string 38 | showname bool 39 | } 40 | 41 | func marginw(c Canvas, p Picture) (int, int) { 42 | aspect := float64(p.height) / float64(p.width) 43 | pw := float64(c.width) * (100.0 - (c.left + c.right)) / 100.0 44 | if int(pw) > p.width { 45 | return p.width, p.height 46 | } 47 | return int(pw), int(aspect * pw) 48 | } 49 | 50 | func marginh(c Canvas, p Picture) (int, int) { 51 | aspect := float64(p.height) / float64(p.width) 52 | ph := float64(c.height) * (100.0 - (c.top + c.bottom)) / 100.0 53 | if int(ph) > p.height { 54 | return p.width, p.height 55 | } 56 | return int(ph / aspect), int(ph) 57 | } 58 | 59 | func landlayout(c Canvas, p []Picture) { 60 | switch len(p) { 61 | case 1: 62 | p[0].x, p[0].y = 50, 50 63 | placepicsh(c, p[0:1], 90) 64 | case 2: 65 | p[0].x, p[1].x = 25, 75 66 | p[0].y, p[1].y = 50, 50 67 | placepicsh(c, p[0:2], 45) 68 | case 3: 69 | p[0].x, p[1].x, p[2].x = 17, 50, 83 70 | p[0].y, p[1].y, p[2].y = 50, 50, 50 71 | placepicsh(c, p[0:3], 30) 72 | } 73 | } 74 | 75 | func portlayout(c Canvas, p []Picture) { 76 | switch len(p) { 77 | case 1: 78 | p[0].x, p[0].y = 50, 50 79 | placepicsw(c, p[0:1], 90) 80 | case 2: 81 | p[0].x, p[1].x = 25, 75 82 | p[0].y, p[1].y = 50, 50 83 | placepicsw(c, p[0:2], 45) 84 | case 3: 85 | p[0].x, p[1].x, p[2].x = 17, 50, 83 86 | p[0].y, p[1].y, p[2].y = 50, 50, 50 87 | placepicsw(c, p[0:3], 30) 88 | } 89 | } 90 | 91 | func placepicsh(c Canvas, pics []Picture, targetpct float64) { 92 | fmt.Printf(bslide, c.bgcolor) 93 | tp := (targetpct / 100) * float64(c.height) 94 | for _, p := range pics { 95 | scalepct := (tp / float64(p.height)) * 100 96 | fmt.Printf(imfmt, p.x, p.y, p.width, p.height, scalepct, p.name) 97 | if c.showname { 98 | fmt.Printf(namefmt, p.x, 5.0, p.name) 99 | } 100 | } 101 | fmt.Println(eslide) 102 | } 103 | 104 | func placepicsw(c Canvas, pics []Picture, targetpct float64) { 105 | fmt.Printf(bslide, c.bgcolor) 106 | tp := (targetpct / 100) * float64(c.width) 107 | for _, p := range pics { 108 | scalepct := (tp / float64(p.width)) * 100 109 | fmt.Printf(imfmt, p.x, p.y, p.width, p.height, scalepct, p.name) 110 | if c.showname { 111 | fmt.Printf(namefmt, p.x, 5.0, p.name) 112 | } 113 | } 114 | fmt.Println(eslide) 115 | } 116 | 117 | func piclist(filelist []string) []Picture { 118 | var pic Picture 119 | p := []Picture{} 120 | for _, imagefile := range filelist { 121 | imf, err := os.Open(imagefile) 122 | if err != nil { 123 | fmt.Fprintln(os.Stderr, err) 124 | continue 125 | } 126 | im, _, err := image.DecodeConfig(imf) 127 | if err != nil { 128 | imf.Close() 129 | continue 130 | } 131 | pic.width = im.Width 132 | pic.height = im.Height 133 | pic.name = imagefile 134 | p = append(p, pic) 135 | imf.Close() 136 | } 137 | return p 138 | } 139 | 140 | func ll(c Canvas, pics []Picture, n int) { 141 | lands := []Picture{} 142 | e := []Picture{} 143 | 144 | nl := 0 145 | for _, p := range pics { 146 | if p.width > p.height { 147 | nl++ 148 | lands = append(lands, p) 149 | if nl%n == 0 { 150 | landlayout(c, lands) 151 | lands = e 152 | } 153 | } 154 | } 155 | } 156 | 157 | func lp(c Canvas, pics []Picture, n int) { 158 | ports := []Picture{} 159 | e := []Picture{} 160 | 161 | np := 0 162 | for _, p := range pics { 163 | if p.width < p.height { 164 | np++ 165 | ports = append(ports, p) 166 | if np%n == 0 { 167 | portlayout(c, ports) 168 | ports = e 169 | } 170 | } 171 | } 172 | } 173 | 174 | func single(c Canvas, pics []Picture) { 175 | for i := 0; i < len(pics); i++ { 176 | if pics[i].width >= pics[i].height { 177 | landlayout(c, pics[i:i+1]) 178 | } else { 179 | portlayout(c, pics[i:i+1]) 180 | } 181 | } 182 | } 183 | 184 | func msingle(c Canvas, pics []Picture) { 185 | 186 | var pw, ph int 187 | for _, p := range pics { 188 | p.x, p.y = 50, 50 189 | if p.width > p.height { 190 | pw, ph = marginw(c, p) 191 | } else { 192 | pw, ph = marginh(c, p) 193 | } 194 | fmt.Printf(bslide, c.bgcolor) 195 | if c.showname { 196 | fmt.Printf(snamefmt, p.name) 197 | } 198 | fmt.Printf(simgfmt, p.x, p.y, pw, ph, p.name) 199 | fmt.Printf(eslide) 200 | } 201 | } 202 | 203 | func main() { 204 | cw := flag.Int("w", 1280, "canvas width") 205 | ch := flag.Int("h", 720, "canvas height") 206 | tm := flag.Float64("top", 5, "top margin") 207 | bm := flag.Float64("bottom", 5, "bottom margin") 208 | lm := flag.Float64("left", 5, "left margin") 209 | rm := flag.Float64("right", 5, "right margin") 210 | port := flag.Int("p", 0, "portrait n") 211 | land := flag.Int("l", 0, "landscape n") 212 | all := flag.Int("a", 0, "all n") 213 | showname := flag.Bool("showname", false, "show name") 214 | bgcolor := flag.String("bg", "white", "background color") 215 | flag.Parse() 216 | 217 | pics := piclist(flag.Args()) 218 | c := Canvas{width: *cw, height: *ch, left: *lm, right: *rm, top: *tm, bottom: *bm, bgcolor: *bgcolor, showname: *showname} 219 | fmt.Println(sdeck) 220 | // fmt.Printf("\n", c.width, c.height) 221 | switch { 222 | case *port > 0: 223 | lp(c, pics, *port) 224 | case *land > 0: 225 | ll(c, pics, *land) 226 | case *all > 0: 227 | ll(c, pics, *all) 228 | lp(c, pics, *all) 229 | default: 230 | msingle(c, pics) 231 | } 232 | fmt.Println(edeck) 233 | } 234 | -------------------------------------------------------------------------------- /cmd/svgcolor/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/svgcolor 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/swiss/README.md: -------------------------------------------------------------------------------- 1 | # Swiss Clock 2 | 3 | ## Description 4 | 5 | creates a Swiss Railway style clock using deck markup. 6 | 7 | ## running 8 | 9 | $ swiss | pdfdeck -stdout - > time.pdf 10 | 11 | ![p1](p1.png) 12 | ![p2](p2.png) 13 | ![p3](p3.png) 14 | -------------------------------------------------------------------------------- /cmd/swiss/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/swiss 2 | 3 | go 1.16 4 | 5 | require github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 6 | -------------------------------------------------------------------------------- /cmd/swiss/go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= 2 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 3 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494 h1:Z5Dn/2Ml6/8fP1PWxv4ijaK2PwHsX8A+gZiKJVP+XDs= 4 | github.com/ajstarks/deck/generate v0.0.0-20210223212949-8bd01c798494/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 5 | -------------------------------------------------------------------------------- /cmd/swiss/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/swiss/p1.png -------------------------------------------------------------------------------- /cmd/swiss/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/swiss/p2.png -------------------------------------------------------------------------------- /cmd/swiss/p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/utils/9db796a2d889eb0a2a123287cf805713234ed1e1/cmd/swiss/p3.png -------------------------------------------------------------------------------- /cmd/swiss/swiss.go: -------------------------------------------------------------------------------- 1 | // swiss: make a Swiss Railway clock 2 | // $ swiss | pdf -pagesize 500,500 3 | package main 4 | 5 | import ( 6 | "math" 7 | "os" 8 | "time" 9 | 10 | "github.com/ajstarks/deck/generate" 11 | ) 12 | 13 | var hrangles = [12]float64{ 14 | 90, 60, 30, // 12, 1, 2 15 | 0, 330, 300, // 3, 4, 5 16 | 270, 240, 210, // 6, 7, 8 17 | 180, 150, 120, // 9, 10, 11 18 | } 19 | 20 | var minangles = [60]float64{ 21 | 90, 84, 78, 72, 66, // 0 - 4 min 22 | 60, 54, 48, 42, 36, // 5 - 9 min 23 | 30, 24, 18, 12, 6, // 10 - 14 min 24 | 0, 354, 348, 342, 336, // 15 - 19 min 25 | 330, 324, 318, 312, 306, // 20 - 24 26 | 300, 294, 288, 282, 276, // 25 - 29 27 | 270, 264, 258, 252, 246, // 30 - 34 28 | 240, 234, 228, 222, 216, // 35 - 39 29 | 210, 204, 198, 192, 186, // 40 - 44 30 | 180, 174, 168, 162, 156, // 45 - 49 31 | 150, 144, 138, 132, 126, // 50 - 54 32 | 120, 114, 108, 102, 96, // 55 - 59 33 | } 34 | 35 | // polar converts from polar to cartesian coordinates 36 | func polar(cx, cy, r, degrees float64) (float64, float64) { 37 | a := degrees * (math.Pi / 180) 38 | x := r * math.Cos(a) 39 | y := r * math.Sin(a) 40 | return x + cx, y + cy 41 | } 42 | 43 | // clock draws a clock face with hour and minute markers 44 | func clock(deck *generate.Deck, x, y, r float64) { 45 | n := 0 46 | var r2, s2 float64 47 | 48 | // scale the dimensions of the hour and minute ticks 49 | // by the size of the clock face 50 | hsize := r / 5 51 | msize := hsize / 3 52 | 53 | hlen := r * 0.05 54 | mlen := hlen / 3 55 | 56 | deck.Circle(x, y, (r*2)+msize, "silver") 57 | deck.Circle(x, y, r*2, "white") 58 | 59 | // Around the circle, make hour and second ticks 60 | // for every 5, mark the hour, else mark the minute 61 | for t := 0.0; t < 360; t += 6 { 62 | if n%5 == 0 { // hours 63 | r2 = r - hsize 64 | s2 = hlen 65 | } else { // seconds 66 | r2 = r - msize 67 | s2 = mlen 68 | } 69 | n++ 70 | px1, py1 := polar(x, y, r, t) 71 | px2, py2 := polar(x, y, r2, t) 72 | deck.Line(px1, py1, px2, py2, s2, "black") 73 | } 74 | } 75 | 76 | // oppangle computes the opposite angle 77 | func oppangle(a float64) float64 { 78 | if a >= 0 && a <= 180 { 79 | return a + 180 80 | } 81 | return a - 180 82 | } 83 | 84 | // drawtime draws a clock face with hour, minute and second hands 85 | func drawtime(deck *generate.Deck, x, y, r float64, h, m, s int) { 86 | if (m > 59 || m < 0) || (s > 59 || s < 0) { 87 | return 88 | } 89 | clock(deck, x, y, r) 90 | linesize := r * 0.09 91 | extrar := r / 4 // length of the line past the centerline 92 | 93 | // get angles for hour, minute, second 94 | ha := hrangles[h%12] 95 | ma := minangles[m] 96 | sa := minangles[s] 97 | 98 | if m > 30 { // if the minute is > 30, adjust the hour angle proportionally 99 | ha = ha - (30.0 * float64(m) / 60.0) 100 | } 101 | 102 | // the hour, minute, and second hands are drawn in two parts. 103 | // part 1 is the line between the center point (x,y) and radius 104 | // part 2 is the extra line past the center whose angle is opposite the part 1 line. 105 | 106 | // hour line 107 | hx, hy := polar(x, y, r*0.7, ha) 108 | hx2, hy2 := polar(x, y, extrar, oppangle(ha)) 109 | deck.Line(x, y, hx, hy, linesize, "gray") 110 | deck.Line(x, y, hx2, hy2, linesize, "gray") 111 | 112 | // minute line 113 | mx, my := polar(x, y, r*0.95, ma) 114 | mx2, my2 := polar(x, y, extrar, oppangle(ma)) 115 | deck.Line(x, y, mx, my, linesize, "black") 116 | deck.Line(x, y, mx2, my2, linesize, "black") 117 | 118 | // second line -- includes dot at the end 119 | dotsize := r * 0.2 120 | slinesize := linesize * 0.275 121 | sx, sy := polar(x, y, (r*0.7)-dotsize/2, sa) 122 | sx2, sy2 := polar(x, y, extrar, oppangle(sa)) 123 | cx, cy := polar(x, y, r*0.7, sa) 124 | deck.Line(x, y, sx, sy, slinesize, "red") 125 | deck.Line(x, y, sx2, sy2, slinesize, "red") 126 | deck.Circle(cx, cy, dotsize, "red") 127 | } 128 | 129 | func main() { 130 | now := time.Now() 131 | deck := generate.NewSlides(os.Stdout, 0, 0) 132 | deck.StartDeck() 133 | deck.StartSlide() 134 | deck.TextMid(50, 2, now.Format(time.Kitchen), "sans", 4, "") 135 | drawtime(deck, 50, 50, 40, now.Hour(), now.Minute(), now.Second()) 136 | deck.EndSlide() 137 | 138 | deck.StartSlide() 139 | deck.TextMid(20, 30, "Now-3H", "sans", 2, "") 140 | deck.TextMid(50, 30, "Now", "sans", 2, "") 141 | deck.TextMid(80, 30, "Now+5H", "sans", 2, "") 142 | 143 | drawtime(deck, 20, 50, 10, now.Hour()-3, now.Minute(), now.Second()) 144 | drawtime(deck, 50, 50, 10, now.Hour(), now.Minute(), now.Second()) 145 | drawtime(deck, 80, 50, 10, now.Hour()+5, now.Minute(), now.Second()) 146 | deck.EndSlide() 147 | 148 | deck.StartSlide("black", "white") 149 | m := 0 150 | s := 5 151 | for y := 80.0; y >= 20; y -= 20 { 152 | for x := 20.0; x <= 80; x += 30 { 153 | drawtime(deck, x, y, 8, 12, m, s%60) 154 | m += 5 155 | s += 5 156 | } 157 | } 158 | deck.EndSlide() 159 | deck.EndDeck() 160 | } 161 | -------------------------------------------------------------------------------- /cmd/thomas/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/thomas 2 | 3 | go 1.22.0 4 | -------------------------------------------------------------------------------- /cmd/utab/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utab 2 | 3 | go 1.16 4 | 5 | require github.com/go-pdf/fpdf v0.9.0 6 | -------------------------------------------------------------------------------- /cmd/utab/go.sum: -------------------------------------------------------------------------------- 1 | github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 2 | github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= 5 | github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y= 6 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 7 | github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= 8 | github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 9 | github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 10 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 11 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= 14 | github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= 15 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 16 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 17 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 18 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 19 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 20 | golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= 21 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 22 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 24 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 25 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 26 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 27 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 28 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 29 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 30 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 31 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 37 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 38 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 39 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 40 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 41 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 42 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 43 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 44 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 45 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 46 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 47 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 48 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 49 | -------------------------------------------------------------------------------- /cmd/utab/utab.go: -------------------------------------------------------------------------------- 1 | // utab -- print a unicode font glyph table 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | 10 | "github.com/go-pdf/fpdf" 11 | ) 12 | 13 | func main() { 14 | la := len(os.Args) 15 | outfile := "utab.pdf" 16 | var fontname string 17 | var begin, end int64 18 | begin, end = 0, 255 19 | // fill in parameters from the command line 20 | if la > 1 { // utab file 21 | fontname = os.Args[1] 22 | } 23 | if la > 2 { // utab file begin 24 | begin, _ = strconv.ParseInt(os.Args[2], 0, 32) 25 | } 26 | if la > 3 { // utab file begin end 27 | end, _ = strconv.ParseInt(os.Args[3], 0, 32) 28 | } 29 | if la > 4 { // utab file begin end output 30 | outfile = os.Args[4] 31 | } 32 | // check for usage errors 33 | if begin >= end || len(fontname) == 0 || la <= 1 { 34 | fmt.Fprintf(os.Stderr, "Usage: utab fontname [begin (default=%d)] [end (default=%d)] [output default=%q]\n", begin, end, outfile) 35 | os.Exit(1) 36 | } 37 | // begin the document 38 | pdf := fpdf.New("P", "mm", "Letter", "") 39 | pdf.SetFontLocation(filepath.Dir(fontname)) 40 | pdf.AddUTF8Font("font", "", filepath.Base(fontname)) 41 | 42 | // set page parameters 43 | fontSize := 24.0 44 | left := 20.0 45 | top := 20.0 46 | right := 200.0 47 | bottom := 250.0 48 | footer := bottom + 20.0 49 | colsize := 18.0 50 | 51 | pdf.AddPage() 52 | x, y := left, top 53 | 54 | // for the specified range, make a font table, making new pages as needed. 55 | for i := begin; i <= end; i++ { 56 | pdf.SetTextColor(0, 0, 0) 57 | pdf.SetFont("font", "", fontSize) 58 | pdf.Text(x, y, string(i)) 59 | pdf.SetTextColor(127, 0, 0) 60 | pdf.SetFont("courier", "", 10) 61 | pdf.Text(x, y+5, fmt.Sprintf("%05x", i)) 62 | x += fontSize * 1.2 63 | if x > right { 64 | x = left 65 | y += colsize 66 | } 67 | if y > bottom { 68 | pdf.Text(left, footer, fontname) 69 | pdf.AddPage() 70 | x, y = left, top 71 | } 72 | } 73 | // write the PDF 74 | pdf.Text(left, footer, fontname) 75 | if err := pdf.OutputFileAndClose(outfile); err != nil { 76 | fmt.Fprintf(os.Stderr, "%v\n", err) 77 | os.Exit(2) 78 | } 79 | os.Exit(0) 80 | } 81 | -------------------------------------------------------------------------------- /cmd/vmap/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/vmap 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/vmap/vmap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | var value, low1, high1, low2, high2 float64 10 | var format string 11 | 12 | flag.Float64Var(&value, "value", 1, "value") 13 | flag.Float64Var(&low1, "low1", 0, "low1") 14 | flag.Float64Var(&high1, "high1", 10, "high1") 15 | flag.Float64Var(&low2, "low2", 0, "low12") 16 | flag.Float64Var(&high2, "high2", 100, "high2") 17 | flag.StringVar(&format, "fmt", "%v", "format") 18 | flag.Parse() 19 | 20 | fmt.Printf(fmt.Sprintf("%s (%s, %s) (%s, %s) = %s\n", format, format, format, format, format, format), value, low1, high1, low2, high2, vmap(value, low1, high1, low2, high2)) 21 | 22 | } 23 | 24 | // vmap maps one interval to another 25 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 26 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/ws/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/cmd/ws 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /cmd/ws/ws.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | var ( 10 | port = flag.String("port", ":8080", "http service address") 11 | logging = flag.Bool("log", false, "logging") 12 | cert = flag.String("cert", "", "cert path") 13 | key = flag.String("key", "", "key path") 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | http.Handle("/", http.HandlerFunc(sf)) 19 | log.Printf("ws: listen on %s", *port) 20 | 21 | var err error 22 | if *cert != "" && *key != "" { 23 | err = http.ListenAndServeTLS(*port, *cert, *key, nil) 24 | } else { 25 | err = http.ListenAndServe(*port, nil) 26 | } 27 | if err != nil { 28 | log.Fatal("ListenAndServe:", err) 29 | } 30 | } 31 | 32 | func sf(c http.ResponseWriter, req *http.Request) { 33 | if *logging { 34 | log.Printf("%v %v %v\n", req.RemoteAddr, req.URL.Path, req.UserAgent()) 35 | } 36 | if len(req.URL.Path) > 2 { 37 | http.ServeFile(c, req, req.URL.Path[1:]) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /readpalette/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/utils/readpalette 2 | 3 | go 1.21.6 4 | -------------------------------------------------------------------------------- /readpalette/readpalette.go: -------------------------------------------------------------------------------- 1 | package readpalette 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "image/color" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type spalette map[string][]string 14 | type rgbpalette map[string][]color.NRGBA 15 | 16 | func rgb(x uint32) (uint8, uint8, uint8) { 17 | r := x & 0xff0000 >> 16 18 | g := x & 0x00ff00 >> 8 19 | b := x & 0x0000ff 20 | return uint8(r), uint8(g), uint8(b) 21 | } 22 | 23 | func ReadString(r io.Reader) (spalette, error) { 24 | scanner := bufio.NewScanner(r) 25 | p := make(spalette) 26 | for scanner.Scan() { 27 | args := strings.Fields(scanner.Text()) 28 | l := len(args) 29 | if l < 2 { 30 | continue 31 | } 32 | name := args[0] 33 | p[name] = args[1:] 34 | } 35 | return p, scanner.Err() 36 | } 37 | 38 | func ReadRGB(r io.Reader) (rgbpalette, error) { 39 | 40 | palette, err := ReadString(r) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | rp := make(rgbpalette) 46 | for name, value := range palette { 47 | colors := make([]color.NRGBA, len(value)) 48 | i := 0 49 | for _, c := range value { 50 | if len(c) != 7 { 51 | continue // must be #nnnnnn 52 | } 53 | x, err := strconv.ParseUint(c[1:], 16, 32) 54 | if err != nil { 55 | fmt.Fprintf(os.Stderr, "%v\n", err) 56 | continue 57 | } 58 | r, g, b := rgb(uint32(x)) 59 | colors[i] = color.NRGBA{R: r, G: g, B: b, A: 0xff} 60 | i++ 61 | } 62 | rp[name] = colors 63 | } 64 | return rp, nil 65 | } 66 | --------------------------------------------------------------------------------