 Thank you for all coming and braving the weather. The clock is ticking. I have a little clock here staring me down. So what I'd like to talk to you today is not how to make a command line tool. Like, we all know how to do that. Go does it very well. What I'd like to talk about is how to design, intentionally design a command line tool that is easy for people to use, and people actually be willing to maintain and contribute to so that it's not just your problem to maintain for the rest of your life or whatever company or project you happen to work on. So when I talk about a tool that people love, what does that mean? Because it's not just touchy-feely. I want to make a command line tool that's predictable. I want a tool that, if I learn one command, or I already know how to use another tool within that ecosystem, I can guess. I don't need to go to the help text or the documentation. And I can probably figure out what the other commands look like, and I'm able to just use muscle memory and feel like an amazing person and just use the rest of the commands all by myself. Another design goal here that we're going to cover is I'd like to make task-oriented commands. I'd like to understand what it is that person who is using the CLI is doing and make that task easier, not just an HTTP put, post, patch, whatever, but a series of steps that may involve ugly things. For example, making key pairs and posting it somewhere and then signing something and doing all sorts of random things that you may need to do and turning that into one simple command. Another design goal that we're going to cover today is how to make a command friendly both to an interactive session where you're slowly, painfully typing and pressing back space a lot, and then also when you're automating something in a script. And then another thing is one of the things that I do, just in Women a Go and Open Source in general, is I work on command line tools pretty often, and I am always onboarding new contributors. And the reason why is because new contributors are really attracted to command line tools. Instead of jumping into, say, the control loop, which touches like 16 different things, and it's really gnarly, and the tests require a lot of knowledge, usually if you jump into a command line tool, you need to know how to talk to an HTTP server, and you need to know Go. So it's pretty easy for someone to get into. So if you do things, I'm going to say right, but whatever, you do things good enough, it's a great place to get new contributors into your project and then bring them into that ugly control loop that you may have. So that's what we're trying to do here. So I did say I did a lot of command line tools. Real quick, I'm going to be mentioning them and mentioning the names. So I just want to go over real quick what they are, so you know what I'm talking about. I wrote the Docker version manager. It's called DVM. You may have heard of DEP. I'm sorry. That was me. Kubernetes service catalog. The CLI for that is SVcat, a brand new one that I'm working on. It's related to Kubernetes and Cloud Native. I don't know what Cloud Native is, but it has to do with Cloud Native application bundles. And it's called Porter. And I made that wonderful little emoji icon there, and I'm so proud of it. And like Apple pie-summy, because I don't hear a lot of these emojis, but whatever. OK, so this is what I'm going to go over as far as what it takes to make a CLI. We're going to talk about command design, what frameworks I'm going to tell you to use, how to structure your packages, dirty word dependency injection. I hate using something like that. I feel like I'm Java, but whatever. We'll get through it. And then how to write tests. So the very first thing I want to talk about here is how to design your commands. And the reason why it's first is because if you commit code and someone starts learning whatever you came up with first, they're going to be angry if you change it. And you're going to have to argue for why you need to do it differently. And you're probably not going to win. Inertia wins over all other arguments. So don't let that be one of the arguments kind of on your pro-con list of why you want to change things. So one of the very first things I look at is selecting a grammar. So I'm actually from the US, from an English school. So I don't know what grammar is, but I'm going to try. OK? So again, native English. So subject, verb, noun is usually what I'm used to when I see a command line tool written by someone who knows English. So it may be like kubectl get services or helm list, something like that. That's what we're talking about when we're saying grammar. What's nice about having a grammar and picking it up front is that when you read it to yourself, it sounds like a sentence. You're reducing that cognitive overload or just that burden of going, repeating it to yourself over and over and over again so that you can remember it from going to the documentation to typing in the command line. Anything you can do to help someone reduce how often they're copying and pasting and actually learning these commands and being able to come up with them themselves is someone who's going to use that tool more and maybe like it a little bit more and not curse you out. So that's what I'm getting at with grammar right here. Another piece about grammar, though, is that when you're selecting these nouns and verbs, especially, is you want to be consistent. So I'm going to pick a helm because we're going to helm team now. So one, that's really cool. I just started this week. That's exciting. Is that you want to be consistent. Helm is not consistent. It's helm install. What is the command to remove something? It's helm delete, not uninstall. That messes me up every single time, and I have it aliased. That is an example of being inconsistent with your grammar. And that is something that is going to cause people trouble every single time they use your command line tool. So you make a decision about how am I going to make. You make a decision about how you're going to make decisions. So you can argue with your coworkers in PRs a month from now when they want to add a new command. You're not figuring out here all the commands that we may have in the future, but you're trying to figure out how are we as a team going to make these decisions. So it's easier to just make them quickly and justify them and move on so that as a whole as we add new things, it doesn't start looking like beautiful Katamari balls. If you don't know what that is, it's a really great video game and you should play it. So the other thing you're going to want to look at is understanding the ecosystem that you live in. Very few of us get to write a really cool new tool and then not have to worry about what other tools people are running alongside it. So for example, SVcat, which is a service catalog tool, always gets used next to Qubectl for Kubernetes. And I don't really get to pick and choose how that works. They made decisions. It looks like Qubectl get services. And they made decisions about what verbs are going to use, what they're called, what flags they're going to use. They use dash dash output, not dash dash format. This is what a printed output looks like, et cetera. I can't change something just because I disagree. I may have aesthetic differences or very held opinion, like strongly held opinions. No one cares how I feel about it because if I change it for change's sake, I've made your lives harder. So SVcat looks very similar to Qubectl for anything that's related to it. So for example, SVcat lets you list brokers. No one cares how brokers are. It doesn't matter. But the command is SVcat get brokers and it's guessable. It's predictable. Now, I have a couple of commands that look nothing like Qubectl. I have SVcat sink. And there's no noun after that. That's because sink means something very specific in my domain of service catalog. It doesn't matter what it is. But because people who are in my world of SVcat kind of understand that domain, it makes sense to them. Now, I was able to make that decision. It's additive. But I don't get to go back and change things underneath. I need like shared understanding. Cool. Now you all hate service catalog, just like me. So we can move on. So the Docker version manager, DVM, is a version manager. There's 12 version managers out there. Maybe who knows? So we copied the Node version manager, NVM. And because it kind of did the same thing, we reused all the same commands and patterns and how it sourced variables, et cetera, for the most part. Because we wanted to drive home how you would use it and it would look exactly the same. So most people are going, wait, jerks. It doesn't work at all like Glide. Why'd you do that? It's because it doesn't work at all like Glide under the hood. We didn't want people to assume that and think it. So we went out of our way to make sure that if you use the command line tool, we didn't accidentally encourage these biases of going, oh, yeah, it should probably work like Glide. All the commands are the same. Nope. So we went out of our way to make sure that we didn't use the same verbs, and nouns, and flags, and things like that. So that's enough talking. Let's have cute emojis. Actually, that's not an emoji. I learned this is an emoticon. It's a very important distinction. They're really hard to type, though. They look cool. That's a shrug. But I always have to go to Google. So I want a tool that helps me make these. So I'm going to make a tool called a moat. And I'm going to have a verb, add. And the noun will be a emoticon. So I'm going to add one called gopher. And I'm pretending that's gopher. It's probably not. You can tell me later it's not. That's fine. So I'm going to be able to add a gopher. And I'm going to be able to remove one. I'm obviously not going to remove gopher. So I'm going to remove anxiety about my talk. OK. And then I want to be able to add a repository. So here's a new noun that I'm introducing. I'm reusing that verb. I'm adding a noun, so I'm being consistent. And I also like to have a list of all my repositories. So one of the other tenants that we talked about a little bit earlier, though, was being nice to scripts. Parsing this in bash sucks. So let's also add dash dash output JSON and let people be able to do this. This will just help as they try to automate my CLI. I'll probably have this on a bunch of my different commands, actually. So now I'm going to start doing something a little bit weird. And everyone's like, wait, Caroline, I want to tell you you're wrong. Just wait. You can tell me I'm wrong later. But I'm also going to have one called a moat list. And I'll list out all the different modicons I have. And I'm going to have one more. And this is the one I wanted the whole time, which was shrug, burp, hug, or whatever it is. It'll copy it to the clipboard so I don't have to know how to make these elaborate wonderful modicons. All right, now you get to help me. I broke with the grammar, the convention. You notice that for list and shrug, I dropped an noun. I dropped a noun and a verb. I just, what I do. And the reason why is I made a judgment call. And I said that it was more important to be true to my domain and the task that the user was trying to do, which is as quickly as possible, copy that darn a modicon to my clipboard and type something cool and then put it in Slack, which is really, that's all I would use this for. Then it is to follow some holy tenets of, I must use subject, noun, verb. What you could do is backfill and have those longer things. So for example, a moat list, a modicons. But the other thing to look at is look at the command that I'm using. It's a moat. The command almost kind of tells you, this is the domain I'm working with. Heck, I mean, you add a couple more characters. It is a modicon itself. A lot of people will get pretty quickly with that shorn in thing, what it is we're dealing with anyway. So again, it's something that you get to pick as a team what are the rules you want to break. So again, moving on though, one of the things you really want to do is you want to make tasks easier. All too often, I see people writing a CLI for the first time. And what they do is they wrap the HTTP commands calls that are there. And it basically looks like a CodeGen client. Don't just write CodeGen and then call it a day. Anyone can write CodeGen and go, we'll just do it for you. That's not helpful. We want to add that human layer to it and accomplish tasks for people because otherwise you're going to see 30 different artisanal forks that add in this stuff for you. And you've missed this opportunity to help everyone out. So let's just talk about frameworks real quick. There's a whole bunch of different ways to deal with command line tools. The most popular ones out there, though, are really easy to work with and are the most testable. I've worked with CodeGangster and a couple other ones, and they get the job done. Not saying they're bad, but some of these are way more testable. So I'll just thank Steve, and I recommend you use some of these. I'm not going to tell you how to use them because I've got nine minutes. So instead, I'm just going to call out what these frameworks are and what you would use each one for. So Cobra is the first one. And there's a whole bunch of code up there. The code that matters is that we're initializing a new Cobra command. And then in use, we're saying, what is the command that Cobra should be taking over and helping with? So in this case, the command that it's going to be defining is for Helm. And it's built out a bunch of different things. We define the help text. We're defining the description. So that's what short is. That's a short description. It gets printed out of your type Helm, and you give up, and you don't give it any other options. You can add in subcommands. So I'm defining Helm install and Helm list. Helm's huge. There's way more, but we just have a slide here. And then the other thing Cobra does is it basically takes over your main because it'll do things like print out the help text if people do things wrong, if people type in Helm, all the different commands, and then there's an error. It'll print it out. It'll handle printing out all the different output that came out of your command, any weird validation, et cetera. So this is what it looks like when you wire it up. Yeah, in Cobra, we trust. Cool. So Viper is the other one I really like. I like to go sting in the Viper. I don't know. It sounds cool. And Viper can do a whole bunch of different things, and you can totally use Viper in ways that make your life super hard. But what I always like to bust it out for is when people get in arguments about Toml versus JSON versus YAML versus HCL versus, I don't know, whatever, come out since then. And so what this lets you do and what this code snippet does is that I can have in a directory a configuration file for my app, and it can be a Toml file, JSON file, whatever, a huge list of whatever it wants to be. And I can give Viper just the first part of the file name. In this case, this is for duffel, which is another random CLI I work on. I'll just give it duffel. And it'll go through the directory and look for duffel.toml. It'll look for duffel.json, duffel.yaml, whatever. It'll go through all of them until it finds one. It knows how to parse. It'll load it up into memory. And then un-martial it onto a structure for me. Now, the next thing you may have noticed is that my structure looks a little funky. It doesn't say YAML or Toml or whatever. It doesn't have six different attributes on it. It just has one called map structure. And that's how it gets to handle all of them at once. So let's me write a tiny piece code and only decorate it in a generic way and then be able to handle anything. And then hopefully my team stops arguing about what type of code they want to use. I did say that you can totally shoot yourself on the foot with Viper, mostly from the standpoint of how you use it. So some people don't like the fact that you can read in your configuration environment from anywhere. You can do Viper.get and then read in random configuration options that came out of your config file from environment variable really deep within your code. And that means anyone who's using your code has to have Viper. You can get weird dependency things where anybody who uses your code needs Viper. So just be careful when you do that if you're a library, because people may come after you. They have me. Maybe let's just put that way. So it used to be that I just used Cobra. And then I was like, now I love Cobra and Viper. And now, as of three months ago, Affero, which I don't even think is a lizard or a snake. I'm not quite sure. Someone told me later why he picked that name. But what's cool about it is actually super subtle. It lets you stop using hard-coded IOU till and be able to swap out your dependency in the file system with something that you can mock out and hit something else entirely. So instead of doing always IOU till that read file or walking a directory tree or anything like that, I can swap it out with what you see as FS. It could be in memory database or in that database file system that I mocked out in one of my tests. It can actually hit more exciting things, like HTTP things, stuff like that. Doesn't matter. It's easy to swap out. And that's what I want to stress right now. When I look at tests, we'll see why this is fun. So all of this was setting up, how do we make this testable? We've made this nice for our users. How do we make this nice so that we're going to have to be the only person on the team who maintains this? I don't care what you call your packages. I'm saying make it least two. One, where all of your dependencies on Cobra and Viper and Affero live. And one, where all your logic lives. And it kind of maybe doesn't even look like a CLI. So wire in one place and write like it isn't even a CLI in the other. So under CMD, that's what your main is. Keep this as tiny as possible. Just a reminder, anything that's in main can't be exported, can't be reused. Usually it has funky, like anonymous functions, deeply connected to Cobra. And you have to know Cobra in order to test it. And it's not easy to test. No one can reuse it. And no one really wants to force everyone to take weird dependencies on CLI frameworks in order to use one cool utility function. So put all your cool stuff somewhere else. Dave Cheney would say not PKG. I don't care. Put it wherever. And what I suggest, either through structs or functions or whatever, do one-to-one mapping. If your CLI has a command called do the boogie woogie, I don't want to see a function called do the boogie woogie somewhere in package right here. And the reason why is because someone else who wants to reuse your command line tool and then do really cool things with it, extend it and bake it into their own, they shouldn't have to copy and paste your code and recreate it. They should be able to take one of your functions out of this package and be able to use it. Additionally, if I want to test things, I don't want to have to piece together what happened in that CMD package. I should just have one function inside of PKG with corresponding tests for it. It just makes it cleaner and easy to use. And just like Bob Ross says, happy little packages with happy little tests for everything. If you are doing code gen or you're doing band-aids and wrappers and hiding really how awful your API is designed, again, this is where it all goes. So yeah. So when I say we want to put wiring inside that CMD package, this is what it looks like. If you look at Porter, which is the latest command line tool that I wrote, everything looks like this. And it's very boring. There's very few tests because it's really hard to mess up this wiring. For the most part, they're just tests for validation. And it always calls out something else. So for the validation, you can see it's just calling out to the printer package. And for the implementation, again, it's calling out to the printer package. And then it's calling print mixins. So if I want to test something, all the tests are actually in those other packages. And all I need to do is make sure that in CMD, I wired it up. I'm going to go through really fast on this. But one of the things that's really important that I said earlier is that you want to be able to treat that second package that doesn't have all the wiring that has all just the logic for your CLI. You don't want to treat it like a CLI. And you want to just do the right test for it. Like you normally write tests for anything else. And one thing that really helps with that is to be able to inject things like the console. So you can capture the output and go walk out printed. Or be able to inject things like the file system. So you go, did I make things right? Did I put in a config file? Can I mock in standard in? Stuff like that. So I like to make something called contacts. And again, that's not the right name for it. I don't care. But that's what I call it. And I also like to do things that eventually turn out to making, I call it an SDK, whatever. It's essentially a structure that replicates my CLI. But doesn't have any of the CLI frameworks associated with it. So it's called Porter here. And it's got everything with it. And then you can just use it like your CLI, but 100% programmatically through code. And this is what it ends up looking like. And then you can write your tests. And your tests for your CLI end up only testing your validation, your output, and your formatting. I tried. Tried, everyone. Thank you all for your time. Appreciate it.