Skip to content

Instantly share code, notes, and snippets.

@Cheesebaron
Forked from praeclarum/CodeShareReport.cs
Last active August 29, 2015 14:11
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 Cheesebaron/a8faad78ec055eecfcbe to your computer and use it in GitHub Desktop.
Save Cheesebaron/a8faad78ec055eecfcbe to your computer and use it in GitHub Desktop.
LinqPad script counting lines. Yay \o/
// based on https://gist.github.com/praeclarum/1608597
void Main()
{
var projects = new List<Solution> {
new Solution {
Name = "Windows 8",
ProjectFiles = new List<string> {
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp.Store\\NoiseSentinelApp.Store.Windows\\NoiseSentinelApp.Store.Windows.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp.Store\\NoiseSentinelApp.Store.Shared\\NoiseSentinelApp.Store.Shared.projitems",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp.Models\\NoiseSentinelApp.Models.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp\\NoiseSentinelApp.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\Custom Views\\MiniChart.WindowsCommon\\MiniChart.WindowsCommon.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\Custom Views\\MiniChart.Core\\MiniChart.Core.csproj",
},
},
new Solution {
Name = "Android",
ProjectFiles = new List<string> {
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp.Droid\\NoiseSentinelApp.Droid.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp.Models\\NoiseSentinelApp.Models.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp\\NoiseSentinelApp.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\Custom Views\\MiniChart.Droid\\MiniChart.Droid.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\Custom Views\\MiniChart.Core\\MiniChart.Core.csproj"
},
},
new Solution {
Name = "Core",
ProjectFiles = new List<string> {
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp.Models\\NoiseSentinelApp.Models.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\NoiseSentinelApp\\NoiseSentinelApp.csproj",
"C:\\vcs\\git\\MastersThesis\\NoiseSentinelApp\\Custom Views\\MiniChart.Core\\MiniChart.Core.csproj"
},
},
};
var excludedFiles = new[] { "App.xaml", "NoiseSentinelTheme.xaml", "OxyplotStyles.xaml", "Resource.Designer.cs",
"AssemblyInfo.cs", };
new CodeShareReport().Run(projects, excludedFiles);
}
class CodeShareReport
{
Dictionary<string, FileInfo> _files = new Dictionary<string, FileInfo>();
void AddCodeRef (string path, Solution sln)
{
if (_files.ContainsKey(path))
{
_files[path].Solutions.Add(sln);
sln.CodeFiles.Add(_files[path]);
}
else
{
var info = new FileInfo { Path = path, };
info.Solutions.Add(sln);
_files[path] = info;
sln.CodeFiles.Add(info);
}
}
void AddViewRef (string path, Solution sln)
{
if (_files.ContainsKey(path))
{
_files[path].Solutions.Add(sln);
sln.ViewFiles.Add(_files[path]);
}
else
{
var info = new FileInfo { Path = path };
info.Solutions.Add(sln);
_files[path] = info;
sln.ViewFiles.Add(info);
}
}
const string SharedProjectPrefix = "$(MSBuildThisFileDirectory)";
static string[] AndroidViewFileEndings = new[] { ".axml", ".xml" };
static string[] ViewCodeBehindFileEndings = new [] { "Fragment.cs", ".xaml.cs", "View.cs" };
public void Run (List<Solution> solutions, IEnumerable<string> excludedFiles)
{
//
// Find all the files
//
foreach (var sln in solutions)
{
foreach (var projectFile in sln.ProjectFiles)
{
var dir = Path.GetDirectoryName(projectFile);
var projectName = Path.GetFileNameWithoutExtension(projectFile);
var doc = XDocument.Load(projectFile);
var q = GetPaths(doc, "Compile", excludedFiles: excludedFiles);
foreach (var inc in q)
{
var inc1 = inc;
if (inc.StartsWith(SharedProjectPrefix)) {
inc1 = inc.Remove(0, SharedProjectPrefix.Length);
}
var path = Path.GetFullPath(Path.Combine(dir, inc1));
if (ViewCodeBehindFileEndings.Any(x => path.ToLowerInvariant().Contains(x.ToLowerInvariant())))
AddViewRef(path, sln);
else
AddCodeRef(path, sln);
}
// xaml (very naive implementation)
q = GetPaths(doc, "Page", excludedFiles: excludedFiles);
foreach(var inc in q)
{
var inc1 = inc;
if (inc.StartsWith(SharedProjectPrefix)) {
inc1 = inc.Remove(0, SharedProjectPrefix.Length);
}
var path = Path.GetFullPath(Path.Combine(dir, inc1));
AddViewRef(path, sln);
}
//axml
q = GetPaths(doc, "AndroidResource",
allowedFileEndings: AndroidViewFileEndings, excludedFiles: excludedFiles);
foreach(var inc in q)
{
var path = Path.GetFullPath(Path.Combine(dir, inc));
AddViewRef(path, sln);
}
}
}
//
// Get the lines of code
//
foreach (var f in _files.Values)
{
try
{
f.LinesOfCode = File.ReadAllLines(f.Path).Length;
}
catch (Exception) { }
}
//
// Output
//
var table = new ConsoleTable("Sln", "Total [l]", "Unique [l]", "Shared [l]", "View [l]", "Unique [%]", "Shared [%]", "View [%]");
foreach (var sln in solutions)
{
table.AddRow(
sln.Name,
sln.TotalLinesOfCode,
sln.UniqueLinesOfCode,
sln.SharedLinesOfCode,
sln.ViewLinesOfCode,
string.Format("{0:p}", sln.UniqueLinesOfCode / (double)sln.TotalLinesOfCode),
string.Format("{0:p}", sln.SharedLinesOfCode / (double)sln.TotalLinesOfCode),
string.Format("{0:p}", sln.ViewLinesOfCode / (double)sln.TotalLinesOfCode));
}
Console.WriteLine(table.ToString());
Console.WriteLine("\tLegend:\r\n\t\tl = lines");
}
private IEnumerable<string> GetPaths(XDocument doc, string elementName, string attribute = "Include", IEnumerable<string> allowedFileEndings = null,
IEnumerable<string> excludedFiles = null)
{
if (allowedFileEndings == null)
allowedFileEndings = new[] {".cs", ".xaml", ".xaml.cs", ".axml", ".xml"};
if (excludedFiles == null)
excludedFiles = new[] { "App.xaml", "Resource.Designer.cs" };
var items = from x in doc.Descendants()
let e = x as XElement
where e != null
where e.Name.LocalName == elementName
where e.Attributes().Any(a => a.Name.LocalName == attribute)
where allowedFileEndings.Any(a => e.Attribute(attribute).Value.ToLowerInvariant().Contains(a.ToLowerInvariant()))
where !excludedFiles.Any(a => e.Attribute(attribute).Value.ToLowerInvariant().Contains(a.ToLowerInvariant()))
select e.Attribute(attribute).Value;
return items;
}
}
class FileInfo
{
public string Path = "";
public HashSet<Solution> Solutions = new HashSet<Solution>();
public int LinesOfCode = 0;
public override string ToString ()
{
return Path;
}
}
class Solution
{
public string Name = "";
public List<string> ProjectFiles = new List<string>();
public List<FileInfo> CodeFiles = new List<FileInfo>();
public List<FileInfo> ViewFiles = new List<FileInfo>();
public override string ToString ()
{
return Name;
}
public int UniqueLinesOfCode
{
get
{
return (from f in CodeFiles
where f.Solutions.Count == 1
select f.LinesOfCode).Sum();
}
}
public int SharedLinesOfCode
{
get
{
return (from f in CodeFiles
where f.Solutions.Count > 1
select f.LinesOfCode).Sum();
}
}
public int LinesOfCode
{
get
{
return (from f in CodeFiles
select f.LinesOfCode).Sum();
}
}
public int ViewLinesOfCode
{
get
{
return (from f in ViewFiles
select f.LinesOfCode).Sum();
}
}
public int TotalLinesOfCode
{
get { return LinesOfCode + ViewLinesOfCode; }
}
}
#region ConsoleTable
// https://github.com/khalidabuhakmeh/ConsoleTables/blob/master/ConsoleTables.Core/ConsoleTable.cs
public class ConsoleTable
{
public IList<string> Columns { get; protected set; }
public IList<object[]> Rows { get; protected set; }
public ConsoleTable(params string[] columns)
{
Columns = new List<string>(columns);
Rows = new List<object[]>();
}
public ConsoleTable AddColumn(string[] names)
{
foreach (var name in names)
Columns.Add(name);
return this;
}
public ConsoleTable AddRow(params object[] values)
{
if (values == null)
throw new ArgumentNullException("values");
if (!Columns.Any())
throw new Exception("Please set the columns first");
if (Columns.Count != values.Length)
throw new Exception(string.Format("The number columns in the row ({0}) does not match the values ({1}",
Columns.Count, values.Length));
Rows.Add(values);
return this;
}
public static ConsoleTable From<T>(IEnumerable<T> values)
{
var table = new ConsoleTable();
var columns = typeof(T).GetProperties().Select(x => x.Name).ToArray();
table.AddColumn(columns);
foreach (var propertyValues in values.Select(value => columns.Select(column => typeof(T).GetProperty(column).GetValue(value, null))))
table.AddRow(propertyValues.ToArray());
return table;
}
public override string ToString()
{
var builder = new StringBuilder();
// find the longest column by searching each row
var columnLengths = Columns
.Select((t, i) => Rows.Select(x => x[i])
.Union(Columns)
.Where(x => x != null)
.Select(x => x.ToString().Length).Max())
.ToList();
// create the string format with padding
var format = Enumerable.Range(0, Columns.Count)
.Select(i => " | {" + i + ", -" + columnLengths[i] + " }")
.Aggregate((s, a) => s + a) + " |";
var longestLine = 0;
var results = new List<string>();
// find the longest formatted line
foreach (var result in Rows.Select(row => string.Format(format, row)))
{
longestLine = Math.Max(longestLine, result.Length);
results.Add(result);
}
// create the divider
var line = " " + string.Join("", Enumerable.Repeat("-", longestLine - 1)) + " ";
builder.AppendLine(line);
builder.AppendLine(string.Format(format, Columns.ToArray()));
foreach (var row in results)
{
builder.AppendLine(line);
builder.AppendLine(row);
}
builder.AppendLine(line);
builder.AppendLine("");
return builder.ToString();
}
public void Write() { Console.WriteLine(ToString()); }
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment