13 ms·
Diving into Go by building a CLI application
- radicalriddler 6y agoI had a similar idea a couple of months ago. Make a CLI in golang that allows me to easily open all my favorite timewaster sites at the start of the work day. But it ended up in the "Started, but never looked at again" pile. Maybe I'll have another look at it on the weekend.
- eryb 6y agoSame thing with me, however I am forcing myself to complete pending projects in this Covid lockdown. Trust me it is always difficult to restart work on pending projects, once you give it two minutes you'll get glued to it. Just force yourself to get started.
- hybridbanner 6y agoOne method I've used which puts some additional pressure on me to work on side projects is to write them out in the open in a public GitHub repo. Even though there's probably nobody following your development, the idea that anyone could be following along can provide some motivation to keep going.
- moreaccountspls 6y agoIs your goal to learn go (or other language), or to accomplish a specific task (both valid and wonderful goals!)? The reason I ask because if it's the second, I've become dramatically more productive by really learning shell. Just as an example: comic_number=321; curl -L http://xkcd.com/"$comic_number"/info.0.json | jq '{title, number: .num, title, date: "\(.day)-\(.month)-\(.year)", description: .alt, image: .img}' That took maybe a minute for me to write, vs. like 20 or so minutes for Go. I personally can get stuck in a such a paralysis of doing things the "right" way in a "real" language. So, it's nice to be able to bang out a prototype super quickly and then iterate from there if I want. edit: getting them all, because who can resist some fun code golf: yes | awk 'BEGIN{count=1} {print count++;}' | head -n 2311 | xargs -I {} curl -sL http://xkcd.com/"{}"/info.0.json | jq '{title, number: .num, title, date: "\(.day)-\(.month)-\(.year)", description: .alt, image: .img}'
- toomuchtodo 6y agoIn case you don’t get around to writing that golang code. https://unix.stackexchange.com/questions/17659/opening-multiple-urls-from-a-text-file-as-different-tabs-in-firefox-chrome https://unix.stackexchange.com/questions/17659/opening-multi...
- dinkleberg 6y agoIf you're on mac, Alfred makes it super easy to create workflows like this. I've sped up the time to goofing off drastically !
- yjftsjthsd-h 6y agoBy all means write code if it's fun, but couldn't you just make a bookmark folder and right click > open all?
- verdverm 6y agoHave a look at https://github.com/hofstadter-io/hofmod-cli https://github.com/hofstadter-io/hofmod-cli It makes framing out advanced Golang CLIs a breeze *(creator)
- earthboundkid 6y agoSeems overcooked in some places and undercooked in others. I of course prefer my own CLI template but so it goes: https://github.com/carlmjohnson/go-cli https://github.com/carlmjohnson/go-cli
- eryb 6y agoWould you mind sharing some concrete feedback? I'll make improvements for next time.
- levi_n 6y agoWhile following along locally I got an unhelpful error the first time I ran the program. Ultimately the bug was that I was using a lowercase 'o' instead of a zero '0' in the url builder. The API returned a 404, but the code ignored that and tried to marshal the html error response to the struct and failed. For your guide, it would be helpful to include a status code check on the response and returning a helpful error message around that.
- eryb 6y agoUnderstood
- earthboundkid 6y agoSure, I’m on a real keyboard now. You don’t need separate client and models packages. You should have two packages: main (which has only one line) and everything else (call it package xkcd). Your main continues even after it finds an error in fetching the comic. This is because you can’t return err in main and you didn’t use panic/log.Fatal. You also don’t exit with a non-zero code on error. The solution to all of this is the one line main function. Main should look something like os.Exit(xkcd.Run(os.Args[1:])). I have a helper package at github.com/carlmjohnson/exitcode so you can return an error with an associated exit code, but you can also be simple and just return an int. Separate flag gathering/processing from execution. You’re not using global flags, which is a good step, but you should go further and make an appEnv struct that consolidates what you’re taking in from the flags. See here: https://play.golang.org/p/-gs5nqXBSuB https://play.golang.org/p/-gs5nqXBSuB Your client package basically doesn’t need to exist. What you want are some simple convenience wrappers around the http package and a URL builder for XKCD. Make something generic for HTTP and you can copypasta it into your future projects. Basically, it just needs to be httphelper.GetJSON(cl ∗http.Client, url string, data interface{}) error. Saving to disk is a separate idea that you’re conflating with downloading. Make something like jsonhelper.SaveToDisk(path string, data interface{}) error. The timeout stuff you’re doing is overblown. Either just use context.WithTimeout or put a ∗http.Client in your appEnv and have ParseArgs set the default timeout on that. The stuff with the base URL is unnecessary. Obviously you know the XKCD base URL is a constant and will never change. But don’t you need to set it in an XKCDClient struct for testing purposes? The answer is no. If you take in a ∗http.Client, that can set a different http.RoundTripper for testing purposes and the test RoundTripper can read from memory or do whatever you want. (You can see this principle at work in Google’s Go http libraries. Once you realize how powerful ∗http.Client is, it makes a lot of the hoops other libraries jump through see like a waste of time. It can do all your auth stuff, caching stuff, everything. It’s great.) The model package should just be a file in your xkcd package. I find the names Comic vs. ComicResponse confusing. Do you need Comic at all? Maybe just add some nice helper methods onto ComicResponse. Don’t do cr.FormattedDate() string. Do cr.Date() time.Time and let the output layers handler formatting, not the model layer. The c.JSON() string method doesn’t need to exist. With Go, you can run into this problem of trying to make things more convenient by adding helpers but you end up with methods that just run two commands and don’t actually make things more convenient on net. Is this really a model level concern or should it just be in the xkcd.Run() function? Anyway, not to be overly negative. For such a small app, none of this really matters. I’ve been making a lot of Go CLIs for a long time[1] and my experience is that the most important thing is to separate flag stuff from execution, and everything else is not a big deal to let evolve over time. The main challenge is avoiding create abstractions that don’t actually pay for themselves in setup time vs. time saved in extension. [1]: https://blog.carlmjohnson.net/post/2018/go-cli-tools/ https://blog.carlmjohnson.net/post/2018/go-cli-tools/
- dashwav 6y agoSince everyone else is throwing out recommendations I personally think https://github.com/spf13/cobra https://github.com/spf13/cobra is the best CLI templating system, especially because of how well it pairs with https://github.com/spf13/viper https://github.com/spf13/viper. Large projects like Hugo and Kubernetes have used Cobra to build their CLI tools, and it's fairly light as well even if you need simpler usage. We use it at my workplace simply for wrapping our microservices and the few commands (serve, migrate, etc)
- deleted 6y ago[deleted]
- Thorentis 6y agoGreat stuff! Does anything like Cobra exist for Java?
- bingo_cannon 6y agoAbsolutely! I've used picocli[1] and airline[2]. There is always the Apache Commons CLI if you feel like building it all yourself. 1: https://picocli.info/ https://picocli.info/ 2: https://github.com/airlift/airline https://github.com/airlift/airline Bonus: picocli lets you create native images using Graal, so you can really build native cli executable using Java.
- pansa2 6y agoSame question, but for C++? I’ve struggled to find a well-supported command-line argument parser that supports subcommands.
- brown9-2 6y agoargparse4j is really easy to use and feels similar to argparse in Python.
- sagichmal 6y agoCobra is actually probably the worst CLI package to choose, because it encourages/forces you into a globals-based architecture. https://pkg.go.dev/github.com/peterbourgon/ff/v3/ffcli https://pkg.go.dev/github.com/peterbourgon/ff/v3/ffcli is one that I've been using recently, which has been a lot nicer.
- christiansakai 6y agoWow talk about timing. I just created a simple Markdown viewer with a CSS switcher. Shameless promotion: https://github.com/christiansakai/md https://github.com/christiansakai/md
- fermienrico 6y agoIs it possible to display images in terminal? Terminal is simultaneously powerful and painful tool. I know a guy that refuses to use anything but CLI and suffers a lot. But most basic apps can be written it in like those BIOS menus from a 2005 dell computer.
- benatkin 6y agoSort of. Here's a library that does it. https://github.com/sindresorhus/terminal-image https://github.com/sindresorhus/terminal-image
- deleted 6y ago[deleted]
- nerdponx 6y agoIt depends on the console/terminal. iTerm has a native ability to do this, the built-in Linux console has some programs like FIM http://www.nongnu.org/fbi-improved/ http://www.nongnu.org/fbi-improved/.
- theshrike79 6y agoiTerm 2 supports imgcat, which shows images inline in the terminal: https://www.iterm2.com/documentation-images.html https://www.iterm2.com/documentation-images.html
- emptychombu 6y agoAre you talking about ncurses like interfaces? I have been using tview - https://github.com/rivo/tview/ https://github.com/rivo/tview/ (based on another library tcell - https://github.com/gdamore/tcell https://github.com/gdamore/tcell). Very useful.
- fermienrico 6y agoI am imagining like a gallery app such as Instagram purely in terminal lol never mind the consequences of the user base being primarily sys admins and software engineers. I could imagine browsing a rather weird Instagram clone.
- integrii 6y agoI wrote my own flags package called flaggy and think its the easiest to use and makes the most sense! Up to 600 stars om github now. https://github.com/integrii/flaggy https://github.com/integrii/flaggy
- zouhair 6y agoJust a note, xkcd has 2 image sizes. Small: https://imgs.xkcd.com/comics/confidence_interval.png https://imgs.xkcd.com/comics/confidence_interval.png Big: https://imgs.xkcd.com/comics/confidence_interval_2x.png https://imgs.xkcd.com/comics/confidence_interval_2x.png Though not for older ones.
- chewxy 6y agoEveryone's throwing out suggestions for CLI libraries, so let me plug my mate's: https://github.com/jpillora/opts https://github.com/jpillora/opts I definitely prefer it to cobra
- boyter 6y agoGo is a particularly good language for CLI's I have found. At least compared to Java/C#/Python. It's reasonably fast, compiles down to a simple to distribute binary, and the language is forgiving enough that you can do exploratory programming in it. Go-routines make it especially easy to deal with network calls in it as well. For anything that needs absolute performance though look elsewhere, but even then Go might be a good choice for prototyping. I actually started learning Go with CLI applications. I have found that https://github.com/spf13/cobra https://github.com/spf13/cobra tends to be one of the better CLI helpers you can get into but https://github.com/jpillora/opts https://github.com/jpillora/opts is one I have been meaning to try following a presentation I saw on it once.
- throwaway894345 6y agoNot sure why this is downvoted. Java, C#, and Python all tend to have slow CLIs. This is very much in-line with my experience. Even if the VMs do start up quickly, people tend to do a lot of expensive initialization, class loading, etc before the program starts. Go’s runtime is minimal by comparison and there is much less work done at initialization (by convention) than in Python, Java, etc.
- Groxx 6y agoCLIs are sorta my ideal use-case for Go. Goroutines are so error-prone to control since you don't have many options for abstraction, so it's relatively difficult to build long-running highly-stable programs... But CLIs don't usually need that. They can be ctrl-C'd if they go off the rails, and any dangling goroutines just die when the process dies. The simple distribution, fast startup, simple type system, and yolo-concurrency really pay off in pleasantly small and performant tools. And the stdlib is cross platform, quite capable, and very friendly to use. It's almost exactly what you want when you need to go beyond a tiny bash script, to something that might be a up to a couple thousand lines. I do wish the built-in flags lib wasn't so abhorrent though. Pulling in a replacement lib is step 1 for any CLI.
- boyter 6y agoInterestingly my current CLI project https://github.com/boyter/cs/ https://github.com/boyter/cs/ does need to be stable because I am putting a TUI mode into it... and yes dealing with the dangling goroutines in it for the TUI mode itself is especially painful. Generally if you just stick to fan-out-in processing though I find goroutines not too bad.
- mongol 6y agoI liked go-flags https://github.com/jessevdk/go-flags https://github.com/jessevdk/go-flags
- winrid 6y agoI personally love building CLI tools in Node, since I and my co-workers all have it installed. Nice synchronous STD lib for file manipulation and async/await makes for compact async code. If I worked in a Go shop I'd probably use Go, though.
- Cthulhu_ 6y agoNode is nice, but maybe a bit verbose and the CLI utilities are a bit low-level. The main downside to Node is that you need an external runtime to run your applications. But I guess the counter-downside to Go is that you need to compile different versions for different platforms; the binaries are not portable.
- pansa2 6y ago> Go [...] binaries are not portable. Maybe not, but Go has excellent support for cross-compilation. You can still support users on multiple platforms while only developing and building on one.
- eryb 6y agoBinaries are obviously not portable, you can't run ARM binary on x86 and vice versa without emulation. Unlike any other language Go lets you create binaries for almost all architectures and OS with a single command, you won't find this in any language.
- sagichmal 6y ago> But I guess the counter-downside to Go is that you need to compile different versions for different platforms; the binaries are not portable. Why is this a downside? Native binaries are the Platonic ideal for distributing applications.
- mpfundstein 6y agoi always find it 'perverse' to start up a whole async event loop for a tool that then transforms a csv or does some other single task :-) also portability quite sucks. if you use features that my node version doesn't support, i screwed. with go (or C, Rust...), I just compile and distribute the binaries. look for instance how easy it is to install nomad.
- ayoisaiah 6y agoI've been building CLIs with Go as part of a 52 projects in 52 weeks challenge[1] and I'm loving it so far. I released a batch renaming tool [2] just a few days ago. [1]: https://github.com/ayoisaiah/project52 https://github.com/ayoisaiah/project52 [2]: https://github.com/ayoisaiah/goname https://github.com/ayoisaiah/goname
- edem 6y agoWhy would I dive into Go in the first place?
- tmountain 6y agoHere are some arbitrary reasons that Go is worth a look: It's easy to learn, tractable, consistent, performant, and safe. It's great for writing services. The type system is predictable and catches a lot of problems ahead of time. It's opinionated about basic stuff which helps avoid clutter (no unused variables or imports allowed, etc). It fills a lot of the same niche as Python, Java, Ruby, etc, but with some distinct advantages when compared to each of them (and some drawbacks depending on your preferences). Binaries are standalone, easy to compile (cross compilation), and easy to distribute. It's fun (subjective, but I enjoy writing Go).
- pulkitsh1234 6y agoI wrote a Go CLI Boilerplate sometime back: https://github.com/pulkitsharma07/go-cli-boilerplate https://github.com/pulkitsharma07/go-cli-boilerplate, it addresses some common issue which I faced while developing a full-fledged CLI. Features (From Readme): * Unit and Integration test structure for the CLI * Opinionated directory structure for organizing code for commands. * Docker-based cross-platform build pipeline * Travis CI-based release workflow * Makefile for common tasks like generating documentation and building the binary.
- assafmo 6y agoBash Bonus #2: seq 1 10 | xargs -n 1 ./go-grab-xkcd -s -n
- henvic 6y agoI love using Go for CLIs. Previously, I developed CLIs with JavaScript (NodeGH, something with the same goal as GitHub's CLI), and "kind of" with PHP as well (for internal tasks on a server). Nothing compares with writing one with Go, though. I used to be the maintainer of a CLI for a PaaS until a year ago: https://asciinema.org/a/192043 https://asciinema.org/a/192043 https://github.com/henvic/wedeploycli https://github.com/henvic/wedeploycli
- maallooc 6y agoAs much as I like Go I don't think it's ideal for CLI apps. IMO Python is the best at creating simple to complex CLI apps due to it being interpreted and simple. It's an overcharged bash.
- pansa2 6y agoThe problem with Python is that it’s really hard to distribute a Python app to users. Nothing beats Go’s ability to compile into a self-contained binary. Python is also slow and has poor support for parallelism. Finally, “Python is simple” only really applies to its syntax. Overall, Go is a significantly simpler language than Python.
- throwaway894345 6y ago"Slow" isn't a typically big deal for CLI apps, but Python culture tends to do a lot of work on initialization, and I've consequently seen a lot of Python CLIs that take nearly 10 seconds to print --help. Because the way most apps and libraries are written, you end up loading and initializing every single library that any part of your CLI uses, even if the subcommand in question doesn't use those libraries (e.g., printing --help). This is solvable via lazy imports, but few go through the hassle of doing this, especially since it makes the code ugly and non-idiomatic.
- bloblaw 6y agoI do like Python, but I think Perl is actually designed (and actually is) a super-charged bash. Perl was created with the purpose of simplifying the combination of bash + awk + sed + grep, a goal it has achieved quite well. Python has better OO and has won more mindshare than Perl, but I don't think it was ever meant (or accomplishes the goal) of being an "overcharged" bash.
- sandreas 6y agoMy Opinion: The best cli lib I found: https://github.com/urfave/cli https://github.com/urfave/cli For deployment I recommend: https://github.com/goreleaser/goreleaser https://github.com/goreleaser/goreleaser During development I recommend: https://github.com/golangci/golangci-lint https://github.com/golangci/golangci-lint and https://github.com/stretchr/testify https://github.com/stretchr/testify
- aladine 6y agoAgreed! urfave/cli is very handy.
- csixty4 6y agoI only recently discovered goreleaser and I can't get over how simple it was to add to my CI setup. Distributing binaries was my goal for having a "real" open source project people could use. After a year of avoiding the issue, I discovered goreleaser and got it running in less than an hour.
- sandreas 6y agoI wrote a few small cli tools and the first 2 things i always add is urfave cli and goreleaser... even if you only deploy and use local, this is a great way to release getopt compilant multiple platform builds with or without subcommands.
- sascha_sl 6y agoWhatever you do, do not use the flag library in a package that might ever, EVER be imported. Google did this in the horribly written glog port to go which until recently was used everywhere in Kubernetes. The only way to determine the value of the "v" flag they define globally for your entire executable is to call the V(n) function with n incrementing until it returns false. pflag, which is used by cobra, is a much nicer library.
- guggle 6y agoI want something like Argh (https://github.com/neithere/argh/ https://github.com/neithere/argh/) but for Go... any hint ? There are a gazillion cli libs, it's hard to test all of them.
- FlashBlaze 6y agoIs Go a good option to create some background tasks. For eg. monitor which Spotify songs are playing and and store that in a json file, etc. Or is Python more suitable for this?
- eryb 6y agoPython has more libraries to write automation tasks than Go. It really depends on you situation, if I have a lot of time I'll do it in Go because everybody does it in Python :P
- FlashBlaze 6y agoThanks. If possible can you suggest some good libraries for background tasks in Python?
- maxioatic 6y agoIf anyone would like a book on this subject, I recommend Powerful Command-Line Applications in Go: https://pragprog.com/book/rggo/powerful-command-line-applications-in-go https://pragprog.com/book/rggo/powerful-command-line-applica... It's currently in Beta but the first 6 chapters are finished and available. As someone learning Go I found it a nice complement to reading The Go Programming Language.
- subsaharancoder 6y agoTook your code, added a random number generator and threw it into a Go HTTP server and deployed it as a GCP Cloud Function :-) https://us-central1-bookshelf-app-1103.cloudfunctions.net/RandomXkcd https://us-central1-bookshelf-app-1103.cloudfunctions.net/Ra... Voila!! Serverless Random XKCD..
- eryb 6y agoAmazing... :)