Polyglot Programming: TypeScript, Go, & Rust Projector CLI App in TypeScript
Transcript from the "Projector CLI App in TypeScript" Lesson
>> So let's keep on going. Let's do these three operations, we have print, add and remove. We're not gonna be doing the displaying and the saving in this case yet, we're just gonna kind of take care of each one of those operations cuz they all really build on each other.
[00:00:13] And each language will kind of do its own way, Go being the language in which you look upon stack overflow. And the answer is always, it's not hard to just write that yourself, that's Go's answer for everything. TypeScript I'm sure there's an NPM package we can import, we're not gonna do that and Rust, there'll be a macro that you can import, it will do it.
[00:00:31] That's pretty much how I've kind of figured out life at this point, we'll see what happens, all right? So let's jump back out, let's go back to TypeScript because TypeScript is our foundational language, it's the language I assume everyone's fairly familiar with. All right, so let's start by creating a file called projector, right?
[00:01:03] That's the thing that we use, which is definitely not changing from one energy form to another so what do we need to do with this projector? Well, we need to be able to take in a config and create a projector. The projector should be able to take its values and walk the tree, get a value, set a value at that location, or remove the value at that location.
[00:01:24] That sounds about like the right three set of operations and so I'm gonna kind of separate out two things, I kind of want a data object. So I'm gonna go like this, export type data with a capital D, I'm sure you can really call this projector data or whatever you wanna do.
[00:01:38] We'll just keep our type short while we're typing. And this is just gonna have a simple single key projector and that is going to be a key string, remember, cuz this right here is our present working directory, or it's our directory structure, right? Cuz we need to represent this underneath the hood and then this one will be a key string string, right?
[00:01:59] So as you can see, this is our key and our value, right? So that makes sense, right, because that way if we have multiple directories, they all are kind of stored on the same level, we can jump in. You're probably asking, why did I do this? Well, in the actual projector program that I built, I also have another top level item called links in which I can link all values from one directory into another directory.
[00:02:24] And so that was kind of why I kept these top level items cuz that way I can add an options, we could do other things we may want to expand upon later. But for the sake of this, we're just gonna keep it simple, keep it like that, always able to add more things, awesome.
[00:02:37] So there we go, I feel like that's a good type and the reason why it kind of kept it as its own type is that ultimately I just wanna be able to go JSON.stringify, right? And so long as I have that as its own thing off to the side, this should be really easy to do, or else I'm gonna have some other experience that I don't want to have, awesome.
[00:02:54] So now we're gonna do it, we're gonna create a class, why? Well, sometimes you gotta create a class, it just feels good to create a class. I said we're gonna do that, we're gonna do that now. Now, I know there's a lot of people out there in the TypeScript world that just threw up.
[00:03:08] It's okay, we're gonna get through this together, we're gonna new with the best of them, and yeah, I'll be fun. All right, so let's create a static method called static fromFile or from config. And this thing is gonna just take in a config, you can see these beautiful auto completes for me, right there.
[00:03:24] This seems like the most reasonable one yet. I'm just gonna take in a config and I'm gonna produce a projector. The reason why I'm doing this is that I don't wanna put a bunch of file operations, or things that could fail inside of the constructor. Instead I have this nice little method over here.
[00:03:39] So if I'm doing some testing, I could just potentially nude up if I really wanted to, right? I can kind of get around any of the problems, makes testing a little bit easier. So let's go like this. Apparently that's an unused declaration. Let's go over here, let's import in the config, awesome.
[00:03:54] Now we just need to return a projector. So how do we think about this operation? Effectively, we have to use the path for the config to go and find it if it exists on our operating system. If it doesn't exist, I should just return some sort of default set of data, right?
[00:04:12] But if it does exist, I have to JSON parse that. If it fails I should just return the default data or should I fail the program? I think to keep this thing running smooth at all times, I'll just simply return a default config, one that doesn't exist. We could throw an error, that's really up to how do you feel if somehow you have JSON?
[00:04:31] If you were hand editing, would you want all of your projector values just to disappear cuz I decided, you have a parsing error, therefore this is an invalid file, therefore you should get a new one, right? You could kind of debate how this will work. But I think the easiest form is just any error produces a default config, Or we read it from a file successfully.
[00:04:51] So let's do that, so the first thing I'm gonna do is I'm gonna go like this, constant default config. Create a nice little one that's very easy to do, projector and have that as an empty string, right? There you go, that is literally just the default config, right, the empty one.
[00:05:08] Awesome and so now what we should be able to do is just go through this thing one step at a time. The first thing is if the config which has the thing config, maybe we should rename that, right? Does this file exist? So I can use something called Fs.
[00:05:24] If you're familiar with Fs, has a bunch of file system operations, l they're gonna look very similar to Go or to Rust. And this one already has existsSync. Since we don't necessarily care about performance, I'm perfectly fine using sync operations, simplifies the code. We don't even have to have anything async, we don't have to have the wrapping function in the main file and just makes makes life a little bit easier.
[00:05:47] So remember, we're gonna do this whole as Fs thing, we could just fix our TS config, but we're gonna keep on going with it. There we go, so let's go existsSync, which by the way, I am 100% positive if I went exist, sync NPM, look at that, you can even install a package in case you really want it to.
[00:06:11] Look at that, even was downloaded almost 1.3 million times just this last week. Kind of funny that something that exists inside of node, yes, right, anyway. So now that we have this beautiful piece of code, we can say, hey, if this thing exists, we should just read this file, right?
[00:06:27] So I'm gonna do a little try catch, of course and we're gonna have a nice conf- let's see a data object right here. I'll just have like that for now, this thing will be a data object. I think you have to use the word undefined or else you can get in some weird black hole issues if you don't get things correctly done.
[00:06:45] So we'll have this nice data object. We're gonna just figure out which one it's supposed to be. I will now do an Fs read file sync, of course, pass in the config config. So we're gonna go like this, toString and then of course, I'm going to do JSON.parse, awesome and do data equals that, perfect.
[00:07:05] So long as this thing is excess, we have our data object. Else I'll do a data equals the default data. I don't know why I called it config, it actually should be called the default data, it's probably a little bit better, right? And we can just return data, awesome.
[00:07:21] So we've done it, we've successfully did the harder part, which is if the thing exists, let's read it out, let's make sure it's parsed, it's all fantastic. Else, we can just return a default data, right? Beautiful, I'm sure we can make this function a little bit cleaner, move some things up here and there and have ourselves a nice function but this is good enough for us.
[00:07:41] There we go, we have it but what am I doing? We don't wanna return this, sorry, mistake right here. We need to create a constructor now, right? Constructor, what should we take in for the constructor? Well, at the end of this operation, who should be responsible for saving the data object?
[00:07:59] I'm gonna make the projector the one that has the safe operation, that's just how we're gonna do it. You could do it any other way really, I can't really make a strong argument why it should be one way versus the other. The only thing that's nice about this is I can actually have the underlying data be private versus it being public and you have to grab it and stringify it yourself, right?
[00:08:18] Kinda hide some of the implementation details. So let's just have the config passed in here so we have both the present working directory and the path to the config. And let's also have the data which is gonna be the data object passed in, awesome. And then of course I'm not sure if this is good or bad TypeScript but I do like this.
[00:08:36] There we go, make those two private, we have them accessible, awesome. We're good to go, go down to this thing and let's just go new projector and do the config and the data. And of course this is default data, right? Perfect, so we now have our two projectors that can happen, correct?
[00:08:58] Awesome, jump up here, let's probably export default this so we have this as an available item. And now we're pretty much done with the creation of the projector. We could test this. Personally, I tend to avoid testing anything that touches kind of external items such as the file system, anything like that.
[00:09:18] I always find that there's a lot of pain and so often what I'll end up doing is finding a way to abstract this thing out so I can just provide like, hey, here's the string you're supposed to be. If it throws I expect this but we don't need to go to that for Bossier when it comes to testing this, fairly simple program.
[00:09:32] I think we're fine just kind of avoiding it. So we need to move on and if you remember, what are the three operations we need to do? Print, add, remove, awesome. So let's do all those three right now. Now remember, print does have two modes, right? It has print the key that you provided and print the entire directory.
[00:09:53] So in case you wanted to use JQL. If you've never used JQL, great little command line utility. You could cat out all of your values from projector, then pass it to JQL and see what values have available. Pluck it out, do whatever else you wanna do, fun times.
[00:10:07] All right, so let's do the four methods, right? So our first method of course is gonna be, we'll say get get value, all right. We'll have a get value, we'll have a set value, right? And we'll have a remove value. That seems like a pretty reasonable interface for all this, right?
[00:10:30] I just realized I made this in the Rust style so I should probably do all these nice things right here. It's really hard having to switch back and forth between pascal or camel case and snake case. All right, I'm willing to settle in the middle and just do screaming snake case, maybe everyone will be happy with that.
[00:10:50] All right, so what's our out here? I would say that our out should be the exact same thing as this right here, correct? That should be the out here or should it? No, I think that this should probably be our out, right? That makes more sense, we don't wanna include where each one of the values came from.
[00:11:07] We just simply want to include all the values you find. So we'll just use this one right here, awesome. Go right here, paste that in. And of course that should be a, what is that? I think that's it, right? Beautiful, all right, so we have the correct return type here.
[00:11:29] We'll do the exact same thing with get value. Get value really should be either a string or if the thing's not found, an undefined. Right, that seems about correct. And it should also take in a key, which should be a string. So we've kind of made a definitive thing here.
[00:11:44] We wanna separate out these two methods. And as you can probably see, pattern matching in Rust will make this very, very easy whereas with TypeScript, it's still a really easy operation, it's just an additional if statement. But nonetheless, you can't make the decisions based on types. You're making it based on runtime checking of these datas.
[00:12:05] Those are those small decisions that slowly add up over time that can make, I think, easy problems happen. All right, set value, exact same thing, we should have a key, we should have a value. And of course removing a key or removing a value, should be string, there we go.