Skip to content

Instantly share code, notes, and snippets.

@TheBuzzSaw
Last active January 8, 2023 23:51
Show Gist options
  • Save TheBuzzSaw/251a9002ae9a838272c60e2a5f28238f to your computer and use it in GitHub Desktop.
Save TheBuzzSaw/251a9002ae9a838272c60e2a5f28238f to your computer and use it in GitHub Desktop.
File scope namespace formatter
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FileScopeNamespacer;
class Program
{
static async Task<int> Main(string[] args)
{
try
{
var stopwatch = Stopwatch.StartNew();
using var cancellationTokenSource = new CancellationTokenSource();
foreach (var arg in args)
{
if (File.Exists(arg))
await ConvertFileAsync(arg, cancellationTokenSource.Token);
else if (Directory.Exists(arg))
await ConvertProjectFoldersAsync(arg, cancellationTokenSource.Token);
else
Console.WriteLine("Cannot locate path: " + arg);
}
Console.WriteLine("Finished in " + stopwatch.Elapsed);
return 0;
}
catch (Exception ex)
{
Console.ResetColor();
Console.WriteLine();
Console.WriteLine(new string('-', 60));
Console.WriteLine(ex);
Console.WriteLine();
return 1;
}
}
static async Task ConvertProjectFoldersAsync(string path, CancellationToken cancellationToken)
{
if (Directory.EnumerateFiles(path, "*.csproj").Any())
{
await ConvertFolderAsync(path, cancellationToken);
}
else
{
foreach (var folder in Directory.EnumerateDirectories(path))
{
var folderName = Path.GetFileName(path);
if (!folderName.StartsWith('.'))
await ConvertProjectFoldersAsync(folder, cancellationToken);
}
}
}
static async Task ConvertFolderAsync(string path, CancellationToken cancellationToken)
{
foreach (var file in Directory.EnumerateFiles(path, "*.cs"))
await ConvertFileAsync(file, cancellationToken);
foreach (var folder in Directory.EnumerateDirectories(path))
{
var folderName = Path.GetFileName(folder);
if (!folderName.StartsWith('.') && folderName != "bin" && folderName != "obj")
await ConvertFolderAsync(folder, cancellationToken);
}
}
static async Task ConvertFileAsync(string path, CancellationToken cancellationToken)
{
var fullPath = Path.GetFullPath(path);
Console.WriteLine(fullPath);
var originalText = await File.ReadAllTextAsync(path, cancellationToken);
var convertedText = ConvertSource(originalText);
if (convertedText is not null)
await File.WriteAllTextAsync(path, convertedText, cancellationToken);
}
static bool IsLower(int c) => 'a' <= c && c <= 'z';
static bool IsUpper(int c) => 'A' <= c && c <= 'Z';
static bool IsDigit(int c) => '0' <= c && c <= '9';
static bool IsAlphanumeric(int c) => IsLower(c) || IsUpper(c) || IsDigit(c);
static bool IsNamespaceSafe(int c) => IsAlphanumeric(c) || c == '.';
static bool IsWhitespace(int c) => c == ' ' || c == '\t';
static bool IsLineEnding(int c) => c == '\n' || c == '\r';
static bool IsEmptiness(int c) => IsWhitespace(c) || IsLineEnding(c);
static int AdvanceWhile<T>(ReadOnlySpan<T> span, int startIndex, Predicate<T> predicate)
{
var result = startIndex;
while (result < span.Length && predicate.Invoke(span[result]))
++result;
return result;
}
static int AdvanceWhile(string? s, int startIndex, Predicate<char> predicate)
=> AdvanceWhile<char>(s, startIndex, predicate);
static bool Check<T>(ReadOnlySpan<T> span, int index, T value)
{
return 0 <= index &&
index < span.Length &&
EqualityComparer<T>.Default.Equals(span[index], value);
}
static bool Check(string? s, int index, char value)
=> Check<char>(s, index, value);
static string? ConvertSource(string source)
{
const string Namespace = "namespace";
var indexOfNamespace = source.IndexOf(Namespace);
if (indexOfNamespace == -1)
return null;
var endOfNamespace = indexOfNamespace + Namespace.Length;
endOfNamespace = AdvanceWhile(source, endOfNamespace, c => IsEmptiness(c));
endOfNamespace = AdvanceWhile(source, endOfNamespace, c => IsNamespaceSafe(c));
var afterNamespace = AdvanceWhile(source, endOfNamespace, c => IsEmptiness(c));
if (!Check(source, afterNamespace, '{'))
return null;
var closingBrace = source.LastIndexOf('}');
if (closingBrace < afterNamespace)
return null;
var startIndex = afterNamespace + 1;
var builder = new StringBuilder(source.Length)
.Append(source.AsSpan(0, endOfNamespace))
.AppendLine(";")
.Append(source.AsSpan(startIndex, closingBrace - startIndex))
.Replace("\n ", "\n");
return builder.ToString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment