Skip to content

Commit

Permalink
Merge pull request #1073 from zzdavid/master
Browse files Browse the repository at this point in the history
AssemblyInfoFile: add functions to read and update attributes
  • Loading branch information
forki committed Jan 14, 2016
2 parents 2e5a543 + 0822521 commit d4624e6
Show file tree
Hide file tree
Showing 2 changed files with 307 additions and 0 deletions.
112 changes: 112 additions & 0 deletions src/app/FakeLib/AssemblyInfoFile.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,39 @@
/// There is also a tutorial about the [AssemblyInfo tasks](../assemblyinfo.html) available.
module Fake.AssemblyInfoFile

open System
open System.IO
open System.Text.RegularExpressions

let internal assemblyVersionRegex = getRegEx @"([0-9]+.)+[0-9]+"

// matches [assembly: name(value)] and captures "name" and "value" as named captures. Variations for C#, F#, C++ and VB
let private regexAttrNameValueCs = @"^\s*\[\s*assembly:\s*(?<name>\w+?)\s*\((?<value>.*)\)\s*\]\s*$"
let private regexAttrNameValueFs = @"^\s*\[\<\s*assembly:\s*(?<name>\w+?)\s*\((?<value>.*)\)\s*\>\]\s*$"
let private regexAttrNameValueCpp = @"^\s*\[\s*assembly:\s*(?<name>\w+?)\s*\((?<value>.*)\)\s*\]\s*;\s*$"
let private regexAttrNameValueVb = @"^\s*\<\s*assembly:\s*(?<name>\w+?)\s*\((?<value>.*)\)\s*\>\s*$"

// matches [assembly: name(value)] but only captures the value. Variations for C#, F#, C++ and VB
let private regexAttrValueCs name =
@"(?<=^\s*\[\s*assembly:\s*" + name + @"(?:Attribute)?\s*\()" // look-behind for "[assembly: name[Attribute]("
+ @"(.*)" // value
+ @"(?=\)\s*\]\s*$)" // look-ahead for ")]"

let private regexAttrValueFs name =
@"(?<=^\s*\[\<\s*assembly:\s*" + name + @"(?:Attribute)?\s*\()" // look-behind for "[<assembly: name[Attribute]("
+ @"(.*)" // value
+ @"(?=\)\s*\>\]\s*$)" // look-ahead for ")>]"

let private regexAttrValueCpp name =
@"(?<=^\s*\[\s*assembly:\s*" + name + @"(?:Attribute)?\s*\()" // look-behind for "[assembly: name[Attribute]("
+ @"(.*)" // value
+ @"(?=\)\s*\]\s*;\s*$)" // look-ahead for ")];"

let private regexAttrValueVb name =
@"(?<=^\s*\<\s*assembly:\s*" + name + @"(?:Attribute)?\s*\()" // look-behind for "<assembly: name[Attribute]("
+ @"(.*)" // value
+ @"(?=\)\s*\>\s*$)" // look-ahead for ")>"

let private NormalizeVersion version =
let m = assemblyVersionRegex.Match(version)
if m.Captures.Count > 0 then m.Captures.[0].Value
Expand Down Expand Up @@ -231,3 +262,84 @@ let CreateVisualBasicAssemblyInfo outputFileName attributes =
/// Creates a C++/CLI AssemblyInfo file with the given attributes.
let CreateCppCliAssemblyInfo outputFileName attributes =
CreateCppCliAssemblyInfoWithConfig outputFileName attributes AssemblyInfoFileConfig.Default

let private removeAtEnd (textToRemove:string) (text:string) =
if text.EndsWith(textToRemove) then
text.Substring(0, text.Length - textToRemove.Length)
else
text

/// Read attributes from an AssemblyInfo file and return as a sequence of Attribute.
/// ## Parameters
/// - `assemblyInfoFile` - The file to read attributes from. Language C#, F#, VB or C++ is determined from the extension.
let GetAttributes assemblyInfoFile =
let text = File.ReadAllText assemblyInfoFile

let regex =
if assemblyInfoFile.ToLower().EndsWith(".cs") then regexAttrNameValueCs
elif assemblyInfoFile.ToLower().EndsWith(".fs") then regexAttrNameValueFs
elif assemblyInfoFile.ToLower().EndsWith(".vb") then regexAttrNameValueVb
elif assemblyInfoFile.ToLower().EndsWith(".cpp") then regexAttrNameValueCpp
else
failwithf "Assembly info file type not supported: %s" assemblyInfoFile

Regex.Matches(text, regex, RegexOptions.Multiline)
|> Seq.cast<Match>
|> Seq.map (fun m -> Attribute(m.Groups.["name"].Value |> removeAtEnd "Attribute",
m.Groups.["value"].Value,
""))

/// Read a single attribute from an AssemblyInfo file.
/// ## Parameters
/// - `attrName` - Name of the attribute without "Attribute" at the end.
/// - `assemblyInfoFile` - The file to read from. Language C#, F#, VB or C++ is determined from the extension.
let GetAttribute attrName assemblyInfoFile =
assemblyInfoFile |> GetAttributes |> Seq.tryFind (fun a -> a.Name = attrName)

/// Read the value of a single attribute from an AssemblyInfo file. Note that string values are returned with surrounding "".
/// ## Parameters
/// - `attrName` - Name of the attribute without "Attribute" at the end.
/// - `assemblyInfoFile` - The file to read from. Language C#, F#, VB or C++ is determined from the extension.
let GetAttributeValue attrName assemblyInfoFile =
match GetAttribute attrName assemblyInfoFile with
| Some attr -> Some attr.Value
| None -> None

let private updateAttr regexFactory text (attribute:Attribute) =
let regex = regexFactory attribute.Name

let m = Regex.Match(text, regex, RegexOptions.Multiline)

// Replace if found with different value
if m.Success && m.Value <> attribute.Value then
tracefn "Attribute '%s' updated: %s" attribute.Name attribute.Value
Regex.Replace(text, regex, attribute.Value, RegexOptions.Multiline)

// Do nothing if found with the same value
elif m.Success then
tracefn "Attribute '%s' is already correct: %s" attribute.Name attribute.Value
text

// Fail if not found
else
failwithf "Attribute '%s' not found" attribute.Name

/// Update a set of attributes in an AssemblyInfo file. Fails if any attribute is not found.
/// ## Parameters
/// - `assemblyInfoFile` - The file to update. Language C#, F#, VB or C++ is determined from the extension.
/// - `attributes` - The Attributes that should be updated matched on Name (Namespace is not used).
let UpdateAttributes assemblyInfoFile (attributes: seq<Attribute>) =
tracefn "Updating attributes in: %s" assemblyInfoFile

let regexFactory =
if assemblyInfoFile.ToLower().EndsWith(".cs") then regexAttrValueCs
elif assemblyInfoFile.ToLower().EndsWith(".fs") then regexAttrValueFs
elif assemblyInfoFile.ToLower().EndsWith(".vb") then regexAttrValueVb
elif assemblyInfoFile.ToLower().EndsWith(".cpp") then regexAttrValueCpp
else
failwithf "Assembly info file type not supported: %s" assemblyInfoFile

let text = File.ReadAllText assemblyInfoFile
let newText = attributes |> Seq.fold (updateAttr regexFactory) text

File.WriteAllText(assemblyInfoFile, newText)
195 changes: 195 additions & 0 deletions src/test/Test.FAKECore/AssemblyInfoSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,50 @@ public class when_using_fsharp_task_with_default_config
File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It update_attributes_should_update_attributes_in_fs_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".fs");
const string original = "namespace System\r\nopen System.Reflection\r\n\r\n" +
"[<assembly: AssemblyProduct(\"TestLib\")>]\r\n" +
"[<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>]\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLibNew"),
AssemblyInfoFile.Attribute.Version("2.0.0.0")
};
AssemblyInfoFile.UpdateAttributes(infoFile, attributes);

// Assert
const string expected = "namespace System\r\nopen System.Reflection\r\n\r\n" +
"[<assembly: AssemblyProduct(\"TestLibNew\")>]\r\n" +
"[<assembly: AssemblyVersionAttribute(\"2.0.0.0\")>]\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It get_attribute_should_read_attribute_from_fs_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".fs");
const string original = "namespace System\r\nopen System.Reflection\r\n\r\n" +
"[<assembly: AssemblyProduct(\"TestLib\")>]\r\n" +
"[<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>]\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var productAttr = AssemblyInfoFile.GetAttribute("AssemblyProduct", infoFile).Value;
var versionAttr = AssemblyInfoFile.GetAttribute("AssemblyVersion", infoFile).Value;

// Assert
productAttr.Value.ShouldEqual("\"TestLib\"");
versionAttr.Value.ShouldEqual("\"1.0.0.0\"");
};
}

public class when_using_fsharp_task_with_custom_config
Expand Down Expand Up @@ -65,6 +109,50 @@ public class when_using_csharp_task_with_default_config
File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It update_attributes_should_update_attributes_in_cs_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cs");
const string original = "// <auto-generated/>\r\nusing System.Reflection;\r\n\r\n" +
"[assembly: AssemblyProduct(\"TestLib\")]\r\n" +
"[assembly: AssemblyVersionAttribute(\"1.0.0.0\")]\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLibNew"),
AssemblyInfoFile.Attribute.Version("2.0.0.0")
};
AssemblyInfoFile.UpdateAttributes(infoFile, attributes);

// Assert
const string expected = "// <auto-generated/>\r\nusing System.Reflection;\r\n\r\n" +
"[assembly: AssemblyProduct(\"TestLibNew\")]\r\n" +
"[assembly: AssemblyVersionAttribute(\"2.0.0.0\")]\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It get_attribute_should_read_attribute_from_cs_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cs");
const string original = "// <auto-generated/>\r\nusing System.Reflection;\r\n\r\n" +
"[assembly: AssemblyProduct(\"TestLib\")]\r\n" +
"[assembly: AssemblyVersionAttribute(\"1.0.0.0\")]\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var productAttr = AssemblyInfoFile.GetAttribute("AssemblyProduct", infoFile).Value;
var versionAttr = AssemblyInfoFile.GetAttribute("AssemblyVersion", infoFile).Value;

// Assert
productAttr.Value.ShouldEqual("\"TestLib\"");
versionAttr.Value.ShouldEqual("\"1.0.0.0\"");
};
}


Expand All @@ -84,5 +172,112 @@ public class when_using_cppcli_task_with_default_config
File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It update_attributes_should_update_attributes_in_cpp_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cpp");
const string original = "// <auto-generated/>\r\nusing namespace System::Reflection;\r\n\r\n" +
"[assembly:AssemblyProduct(\"TestLib\")];\r\n" +
"[assembly:AssemblyVersionAttribute(\"1.0.0.0\")];\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLibNew"),
AssemblyInfoFile.Attribute.Version("2.0.0.0")
};
AssemblyInfoFile.UpdateAttributes(infoFile, attributes);

// Assert
const string expected = "// <auto-generated/>\r\nusing namespace System::Reflection;\r\n\r\n" +
"[assembly:AssemblyProduct(\"TestLibNew\")];\r\n" +
"[assembly:AssemblyVersionAttribute(\"2.0.0.0\")];\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It get_attribute_should_read_attribute_from_cpp_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cpp");
const string original = "// <auto-generated/>\r\nusing namespace System::Reflection;\r\n\r\n" +
"[assembly:AssemblyProduct(\"TestLib\")];\r\n" +
"[assembly:AssemblyVersionAttribute(\"1.0.0.0\")];\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var productAttr = AssemblyInfoFile.GetAttribute("AssemblyProduct", infoFile).Value;
var versionAttr = AssemblyInfoFile.GetAttribute("AssemblyVersion", infoFile).Value;

// Assert
productAttr.Value.ShouldEqual("\"TestLib\"");
versionAttr.Value.ShouldEqual("\"1.0.0.0\"");
};
}


public class when_using_vb_task_with_default_config
{
It should_emit_valid_syntax = () =>
{
string infoFile = Path.GetTempFileName();
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLib"),
AssemblyInfoFile.Attribute.Version("1.0.0.0")
};
AssemblyInfoFile.CreateVisualBasicAssemblyInfo(infoFile, attributes);
const string expected = "' <auto-generated/>\r\nImports System.Reflection\r\n\r\n<assembly: AssemblyProductAttribute(\"TestLib\")>\r\n<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>\r\nFriend NotInheritable Class AssemblyVersionInformation\r\n Friend Const Version As String = \"1.0.0.0\"\r\nEnd Class\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It update_attributes_should_update_attributes_in_vb_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vb");
const string original = "' <auto-generated/>\r\nImports System.Reflection\r\n\r\n" +
"<assembly: AssemblyProduct(\"TestLib\")>\r\n" +
"<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLibNew"),
AssemblyInfoFile.Attribute.Version("2.0.0.0")
};
AssemblyInfoFile.UpdateAttributes(infoFile, attributes);

// Assert
const string expected = "' <auto-generated/>\r\nImports System.Reflection\r\n\r\n" +
"<assembly: AssemblyProduct(\"TestLibNew\")>\r\n" +
"<assembly: AssemblyVersionAttribute(\"2.0.0.0\")>\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It get_attribute_should_read_attribute_from_vb_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vb");
const string original = "' <auto-generated/>\r\nImports System.Reflection\r\n\r\n" +
"<assembly: AssemblyProduct(\"TestLib\")>\r\n" +
"<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var productAttr = AssemblyInfoFile.GetAttribute("AssemblyProduct", infoFile).Value;
var versionAttr = AssemblyInfoFile.GetAttribute("AssemblyVersion", infoFile).Value;

// Assert
productAttr.Value.ShouldEqual("\"TestLib\"");
versionAttr.Value.ShouldEqual("\"1.0.0.0\"");
};
}
}

0 comments on commit d4624e6

Please sign in to comment.