Skip to content

Instantly share code, notes, and snippets.

@aspyct
Last active March 17, 2017 23:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aspyct/e21878cf5e40fe6589c7847f09d6700e to your computer and use it in GitHub Desktop.
Save aspyct/e21878cf5e40fe6589c7847f09d6700e to your computer and use it in GitHub Desktop.
Generate a .immutable.cs containing constructors and Derive() method for an immutable c# class.
#!/usr/bin/env python
# Usage: ./immutable.py path/to/State.cs
# Will generate/overwrite State.immutable.cs
import sys
import re
import os.path
def regex(pattern):
return re.compile(pattern, re.I | re.M)
using_pattern = regex(r'^using\s+([^;]+)\s*;\s*$')
class_pattern = regex(r'^\s+partial class\s+([^\s]+)\s*$')
namespace_pattern = regex(r'^namespace\s+([^\s]+)')
property_pattern = regex(r'^\s*public\s+readonly\s+(.+?)\s+([\w_]+)\s+=')
generated_warning = "// File generated by immutable.py"
input_path = os.path.realpath(sys.argv[1])
basename, ext = os.path.splitext(input_path)
output_path = "{}.immutable{}".format(basename, ext)
# Verify that we can overwrite this file
if os.path.isfile(output_path):
with open(output_path) as f:
first_line = f.readline().decode('utf-8-sig').strip()
if first_line != generated_warning:
# It's not our file, abort
print("This doesn't seem to be a generated file:")
print(output_path)
print("I won't overwrite it. Aborting...")
sys.exit(1)
with open(input_path) as cs:
source = cs.read().decode('utf-8-sig')
usings = using_pattern.findall(source)
namespace = namespace_pattern.findall(source)
cls = class_pattern.findall(source)
properties = property_pattern.findall(source)
assert len(namespace) == 1
assert len(cls) == 1
indent = " "
template_params = {
'namespace': namespace[0],
'class': cls[0],
'usings': "\n".join(
map(lambda x: "using %s;" % x, usings)
),
'ctor_params': (",\n" + indent * 3).join(
map(lambda x: " ".join(x), properties)
),
'ctor_statements': ("\n" + indent * 3).join(
map(lambda (_, p): "this.%s = %s;" % (p, p), properties)
),
'derive_params': (",\n" + indent * 3).join(
map(lambda x: " ".join(x) + " = null", properties)
),
'derive_statements': (",\n" + indent * 4).join(
map(lambda (_, p): "%s: %s ?? this.%s" % (p, p, p), properties)
),
'generated_warning': generated_warning
}
template = """{generated_warning}
// https://gist.github.com/aspyct/e21878cf5e40fe6589c7847f09d6700e
{usings}
namespace {namespace}
{{
partial class {class}
{{
public {class}() {{}}
private {class}(
{ctor_params}
)
{{
{ctor_statements}
}}
public {class} Derive(
{derive_params}
)
{{
return new {class}(
{derive_statements}
);
}}
}}
}}"""
with open(output_path, 'w') as outf:
generated = template.format(**template_params)
outf.write(generated)
using System;
using Monad;
namespace NightOut.Core.ViewModel.CheckIn.LocationSelector
{
[Serializable]
partial class State
{
public readonly Maybe<string> longitude = Nothing<string>.Default;
public readonly Maybe<string> latitude = Nothing<string>.Default;
}
}
// File generated by immutable.py
// https://gist.github.com/aspyct/e21878cf5e40fe6589c7847f09d6700e
using System;
using Monad;
namespace NightOut.Core.ViewModel.CheckIn.LocationSelector
{
partial class State
{
public State() {}
private State(
Maybe<string> longitude,
Maybe<string> latitude
)
{
this.longitude = longitude;
this.latitude = latitude;
}
public State Derive(
Maybe<string> longitude = null,
Maybe<string> latitude = null
)
{
return new State(
longitude: longitude ?? this.longitude,
latitude: latitude ?? this.latitude
);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment