So, my patch got rejected for a variety of good reasons. One is the standard problem of not having checked everything: I spent so much time making sure that the nant build worked I missed that I hadn’t included the relevant csproj files. Of course, I have any number of problems getting castle to build at the best of times, but I’ve started to regard this as a constant of the universe.
Another is the Castle coding standards. Now, trivial coding standards are important, by which I mean that whilst it doesn’t matter if tabs are two or four spaces, it definitely matters that the entire code base applies the same convention. Jeff wrote more eloquently than I can manage on the subject, so go read what he has to say rather than let me labour the point. What i will say is that I am heartily tired of all of the arguments I’ve witnessed (and to my shame, sometimes participated in) on the subject of code formatting. Castle uses a couple of conventions I don’t usually, mostly because it’s more convenient for me to use different ones, but these things are easily fixable with some hack coding. More seriously was the lack of any documentation other than the tests, so I went back and fixed that up.
Then there’s the question of the Common Service Locator code. I mean, it’s all very well that AutoGen passes Ayende’s tests, but the CSL is an MS-PL codebase, and Castle is Apache. It’s not clear that you can even include the assemblies. This is a conceptual minefield at the least. So, I’ve removed the CSL dependency and rewritten the tests. This may not be the end of the matter.
Anyway, here’s the code to hack source files into Castle compliance. Not elegant, but it gets the job done.
namespace CastleStandard {
using System;
using System.Collections.Generic;
using System.IO;
class Standardizer {
private string licenseHeader =
@"// Copyright 2004-2009 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the ""License"");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an ""AS IS"" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.";
private int tabSize = 4;
public string Standardize(string code) {
var result = InternalStandardize(code);
if (result != InternalStandardize(result)) {
throw new Exception("Standardization code was not idempotent.");
}
return result;
}
string InternalStandardize(string code) {
var reader = new StringReader(code);
var licenseReader = new StringReader(licenseHeader);
var usingStatements = new List<string>();
var writer = new StringWriter();
string codeLine = null;
bool isLicensePresent = true;
foreach (var licenseLine in Lines(licenseReader)) {
if (isLicensePresent) {
codeLine = reader.ReadLine();
}
writer.WriteLine(licenseLine);
isLicensePresent = codeLine == licenseLine;
}
// If the license wasn't present, we have a line we need to push back onto the reader
var lines = isLicensePresent
? Lines(reader)
: Prepend(codeLine, Lines(reader));
bool hasEncounteredNamespace = false;
foreach (var sourceLine in lines) {
if (usingStatements != null) {
usingStatements = ProcessUsingStatements(writer, usingStatements, ref hasEncounteredNamespace, sourceLine);
} else {
writer.WriteLine(Tabify(sourceLine));
}
}
if (usingStatements != null) {
throw new Exception("Using statements were never dealt with.");
}
return writer.ToString();
}
private List<string> ProcessUsingStatements(TextWriter writer, List<string> usingStatements, ref bool hasEncounteredNamespace, string sourceLine) {
if (!hasEncounteredNamespace && sourceLine.StartsWith("using")) {
usingStatements.Add(sourceLine);
} else if (sourceLine.StartsWith("namespace")) {
hasEncounteredNamespace = true;
if (!sourceLine.Contains("{")) {
writer.WriteLine(Tabify(sourceLine));
}
}
if (hasEncounteredNamespace && sourceLine.Contains("{")) {
writer.WriteLine(sourceLine);
if (usingStatements.Count > 0) {
foreach (var usingLine in usingStatements) {
writer.WriteLine("t" + usingLine);
}
writer.WriteLine();
}
usingStatements = null;
}
return usingStatements;
}
IEnumerable<T> Prepend<T>(T value, IEnumerable<T> list) {
yield return value;
foreach (var t in list) {
yield return t;
}
}
string Tabify(string line) {
int spaces = 0;
int chars = 0;
foreach (char c in line) {
if (c == 't') {
chars++;
spaces += tabSize;
} else if (c == ' ') {
chars++;
spaces++;
} else {
break;
}
}
return string.Concat(
new string('t', spaces / tabSize),
new string(' ', spaces % tabSize),
line.Substring(chars)
);
}
IEnumerable<string> Lines(TextReader reader) {
string line;
while (null != (line = reader.ReadLine())) {
yield return line;
}
}
}
}