I Want My … C# Macros

c-sharp-macros
OK Google: keep passwords out of git

Sometimes, you just have to show off.

You rarely hear an engineer say “Hold my beer”. But when you do, it’s usually in response to someone telling them what they want to do is impossible – or at least that it’s never been done before. Today was just such a day. Unfortunately I was home sick, yet still working because another quirk of the engineer is… that’s what we do when we’re bored. So since I (a) didn’t have a beer, and (b) didn’t have anyone tell me IRL (“in real life”) that it’s impossible – I decided to write about it.

So to you I say, “Hold my beer.”

Why? I need c# macros. Now, before you rant on about “code readability” and the evils of macros, let me tell you why. I’m publishing some open source c# code that uses OAuth2, and I don’t want to expose my API passwords to the world. Back when I was a unix hacker, I’d just throw the variables into my environment and let Make define them for the c pre-processor on the command line. But this is the day and age of “managed code” and fancy code editors, not vi, so my old big iron tricks aren’t quite so useful anymore. But I really don’t want to modify my code every time I commit, because the truth is I AM getting older, so one day I’ll surely forget and accidentally push those secrets right up to github. Here’s what the rest of the world does when they ask the same question:

OK Google: keep passwords out of git

  1. git – What is the best practice for dealing with passwords in github (http://stackoverflow.com/questions/2397822/what-is-the-best-practice-for-dealing-with-passwords-in-github): Make a settings.config.sample file, modify your .gitignore… Annoying! I despise duplicate code.
  2. John Resig – Keeping Passwords in Source Control (http://ejohn.org/blog/keeping-passwords-in-source-control/): A JSON config file, some OpenSSL scripts to encrypt/decrypt, are you kidding me? But hey, at least he gets to use Makefiles. Talk about reaching around your back to scratch your nose… Sorry John, it is in fact brilliant, but really? Environment variables, sed and awk are your friends!
  3. PSA: Don’t upload your important passwords to GitHub | Ars Technica (https://arstechnica.com/security/2013/01/psa-dont-upload-your-important-passwords-to-github/): Yeah, no kidding. Thanks for defining my problem quite nicely. But how to solve when you’re using Visual Studio C#?

I just want to define a macro to replace my dummy strings (placeholders) in code with my own private secrets just before compiling. A quick Google of the topic “macros in c#” told me it’s impossible. Here’s what some folks have to say about that topic:

OK Google: Macros in C sharp

  1. C# Macro definitions in Preprocessor – Stack Overflow (http://stackoverflow.com/questions/709463/c-sharp-macro-definitions-in-preprocessor) “No, C# does not support preprocessor macros like C.” Ugh!!!
  2. Why aren’t there macros in C# – Stack Overflow (http://stackoverflow.com/questions/1369725/why-arent-there-macros-in-c) The top rated answer: “I can answer in three words (plus a trademark): Macros Are Evil™ – Randolpho Sep 2 ’09 at 19:52”. No!!! No they’re not! You just have to use them right!
  3. Why doesn’t C# support #define macros? | C# Frequently Asked … (@ MSDN) (https://blogs.msdn.microsoft.com/csharpfaq/2004/03/09/why-doesnt-c-support-define-macros/) Microsoft says,” There are a few reasons why. The first is one of readability.” Oh Microsoft, we’re leading with the readability argument, huh?

Impossible? Hold my beer. I’m gonna show you how an old big-iron coder gets to work. Don’t tell me it can’t be done!

  1. First, make your actual settings file (which in c# is probably a public static class member variable), go ahead, put your macro placeholders in there, add it to the project, check it into source control and wait for it…
    class AppSettings { public static String myApiSecret = “_MYAPISECRET”; }
  2. Make your *private* settings file. Name this config.user, since .gitignore already by default has an entry for *.user, we don’t have to tell GIT to ignore this file. One less step. Make it a simple text file with your substitutions (or as we’d call it back in the day, your #defines)
    _MYAPISECRET=12#$%SEC*&R3T
  3. Add this super cool powershell Macro Pre-Processor to you project. Maybe I’ll actually alter this script one day to use the #define syntax just for fun.
    <# .SYNOPSIS
        This is a simple powershell script that performs forwards and backwards string replacements within a file ala macro preprocessing in C.
        Read this help via: powershell Get-Help .\CreateMacros.ps1 -full
    
    .DESCRIPTION
        Four parameters are required in the following order including:
         1) mapfile: a text file containing only name=value pairs delimited by a single equal sign (=) character, one name=value per line
         2) source: a file containing "macros" to be expanded (strings to be replaced by their values)
         3) destination: the file name to be written out 
         4) direction: either forward or reverse. Forward implies macro substitution with the variable values, reverse puts the macros back
    
         **Note: if mutiple variables map to the same value or the value appears in the text in other places, reverse processing will not result
         in reproducing the original file
    
         ***WARNING: Destination file will be overwritten WITHOUT WARNING. You've been warned!
    
    .EXAMPLE
        C:\PS> CreateMacros "mapfile.txt" "sourcefile.txt" "destination.txt" "forward"
    
    .NOTES
        Author: Jamie Anne Harrell
        Date:   February 21, 2017
        License: MIT / Open Source 
    
        Copyright (c) 2017 Emory Goizueta Business School
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
    
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE. 
    
    
    #>
    if ($args.Length -ne 4) {
        $ick = "usage: CreateMacros [mapfile]  [destination] [direction=forward|reverse]"
        $ick
        exit
    }
    
    $replacements = $args[0]
    $source = $args[1]
    $destination = $args[2]
    $direction = $args[3]
    
    if (-NOT (Test-Path $replacements)) {
        $ick = "Cannot load replacements file:" + $replacements
        $ick
        exit
    }
    
    if (-NOT (Test-Path $source)) {
        $ick = "Cannot load source file:" + $source
        $ick
        exit
    }
    
    if (($direction -ne 'forward') -And ($direction -ne 'reverse')) {
        $ick = "Direction must be either forward or reverse"
        $ick 
        exit
    }
    
    $lookupTable = @{}
    $in = ""
    $out = ""
    $reader = [System.IO.File]::OpenText($replacements)
    try {
        for() {
            $line = $reader.ReadLine()
            if ($line -eq $null) { break }
            $thevar = $line -split '='
            if($direction -eq 'forward') 
                {
                $lookupTable[$thevar[0]] = $thevar[1] 
                } 
                else
                {
                $lookupTable[$thevar[1]] = $thevar[0] 
                }
            }
        }
    finally {
        $reader.Close()
        }
    
    $reader = [System.IO.File]::OpenText($source)
    try {
        $count=0
        for() {
            $count += 1
            $line = $reader.ReadLine()
            if ($line -eq $null) { break }
            $in += $line
            $in += "`n"
            $lookupTable.GetEnumerator() | ForEach-Object {
                if ($line -cmatch $_.Key)
                {
                    $line = $line -creplace $_.Key, $_.Value
                }
            }
            if ($count -gt 1) { $out+="`r`n" }
            $out += $line
            #$out += "`r`n"
            }
        }
    finally {
        $reader.Close()
        }
    
    $out | Out-File $destination -NoNewline -Encoding ASCII
    
  4. Call that awesome little powershell script from your Fancy C# code-completing-do-it-for-me-I-don’t-need-to-know-what-a-pointer-is-cause-I-learned-java-as-my-first-language Visual Studio 2015 pre-build script settingvisual-studio-pre-build-events Pre-build event command line:
    powershell.exe "& \"$(ProjectDir)CreateMacros.ps1\"" \"$(ProjectDir)api.user\" \"$(ProjectDir)ApiSettings.cs\" \"$(ProjectDir)ApiSettings.cs\" forward

    Post-build event command line:

    powershell.exe "& \"$(ProjectDir)CreateMacros.ps1\"" \"$(ProjectDir)api.user\" \"$(ProjectDir)ApiSettings.cs\" \"$(ProjectDir)ApiSettings.cs\" reverse

Sometimes you just have to show off to prove, even if just to yourself,  that ya’ still got it.

[Drops mic. Grabs beer. Jamie Out.]

Oh… the truth is I took a salt bath then some CVS Maximum Strength Nighttime Cold & Flu after finishing this article instead of chugging a beer. It just seemed a touch more civilized.

### GIRLS WHO CODE ROCK ###

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s