Recently I was working on some C# and Java code. And along the way, I used Python and Vim to (re)write my code. A small Python script and a 6-keystroke Vim macro did it faster and better than a human would.
Every programmer should learn a good scripting language and use a programmable editor like Vim. Why? Here are two examples, after the break.
Episode I: INotifyPropertyChanged, or Python writing C#
I was building a private C# weekend project (that turned into a weeklong project) — and by the way, WPF and C# are quite pleasant (Windows Forms is a trainwreck, though). One of the things I used in that project was a DataGrid bound to a list of custom objects (a DataGrid is a table, basically). And in order to use it, you need to use the INotifyPropertyChanged interface (MSDN). It involves doing something like this:
privatestringname_{get;set;};// can also be a field[JsonProperty]publicstringname{get{returnname_;}set{if(value!=name_){name_=value;NotifyPropertyChanged("name");}}}
That’s 12 lines of code (excluding [JsonProperty] which comes from the Json.NET library) for that pattern. Oh: and I need to do that for every field/property of my class, because otherwise any changes to them would not be reflected in the tables (and maybe one or two fields were not in the table).
Doing that by hand is really not feasible: you need to copy-paste this large block 14 times and take care of 5 instances of the name (3 with underscores, 2 without), 2 instances of the type, and the [JsonProperty] attribute (which does not appear on all properties).
So, I used one of those intelligent computer things to do it for me. I wrote a really simple Python script and ran it. And I ended up with all 14 fields built for me.
code-writing-code/write_properties.py
#!/usr/bin/env python3TEMPLATE="""\%spublic %s%s { get { return %s_; } set { if (value != %s_) {%s_ = value; NotifyPropertyChanged("%s"); } } }"""JSONPROPERTY_TEMPLATE='[JsonProperty]\n 'defwrite(has_jsonproperty,vtype,name):ifhas_jsonproperty:jsonproperty=JSONPROPERTY_TEMPLATEelse:jsonproperty=''returnTEMPLATE%(jsonproperty,vtype,name,name,name,name,name)properties=['1 string name','0 int another',# 12 fields omitted for brevity]properties_split=[p.split()forpinproperties]# Private definitions (internal)forhas_jsonproperty,vtype,nameinproperties_split:print(" private %s%s_ { get; set; }"%(vtype,name))print()# Public definitions (with notifications)forhas_jsonproperty,vtype,nameinproperties_split:print(write(has_jsonproperty=='1',vtype,name))
That script takes a list of properties and spits out a block of code, ready to be pasted into the code. Visual Studio has a nice Insert File as Text feature, so redirecting the output to a file and using that option is enough.
Episode II: Fixing argument order, or Vim (re)writing Java
Another project, Number Namer, written in Java, and it does what it says on the tin: takes a number and writes it out as words, while being multilingual and extensible. I used Eclipse for this project, because it looks good, is really helpful with its code linting, and does not run slowly on my aging system (I’m looking at you, IntelliJ IDEA aka PyCharm aka Android Studio…)
And so, I was building a test suite, using JUnit. It’s pretty straightforward, and I remember the syntax from Python’s unittest (even though I write tests with pytest nowadays). Or so I thought.
// (incorrect)assertEquals("Basic integers (7) failed",namer.name(7L),"seven");// (fixed) ^ cursorassertEquals("Basic integers (7) failed","seven",namer.name(7L));
You see, the typical Python spelling is self.assertEquals(actual, expected). Java adds a String message parameter and it also swaps actual and expected. Which I didn’t notice at first, and I wrote my assertions incorrectly. While it doesn’t really matter (it will still work), the output looked a bit weird.
And I noticed only when I finished writing my tests (and I had a typo in my expected output). I wanted to fix them all — not manually, of course. So, I closed this file, brought up Vim, searched for the motion I need (it’s t{char}— see :help t). And I ended up with this (cursor placed on the comma after the first argument):
What does this do, you may ask? It’s actually pretty self-explanatory:
delete till comma, (go) till closing parenthesis, paste.
This fixes one line. Automatically. Make it a macro (wrap in qq… q, use with @q) and now you can run it on all lines, either by moving manually or by searching for , and pressing n@q until you run out of lines.
Epilogue
Some of you might say “but VS/Eclipse/IDEA has an option for that somewhere” or “[expensive tool] can do that” — and a Google search shows that there is an Eclipse plugin to swap arguments and that I could also write a regex to solve my second issue. Nevertheless, Python is a great tool in a programmer’s toolbox — especially the interactive interpreter. And Vim is an awesome editor that can accomplish magic in a few keystrokes — and there are many more things you can do with it.
Also: don’t even bother with VsVim or IdeaVim or any other Vim emulation plugins, they work in unusual ways and often don’t give you everything — for example, VsVim has a Vim visual mode (v key) and Visual Studio selection mode (mouse), and only one allows Vim keystrokes (the other will replace selected text).