Restructure solution layout by module
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		| @@ -0,0 +1,178 @@ | ||||
| using System.Text.Json; | ||||
|  | ||||
| namespace StellaOps.Scanner.Analyzers.Lang.Rust.Internal; | ||||
|  | ||||
| internal static class RustFingerprintScanner | ||||
| { | ||||
|     private static readonly EnumerationOptions Enumeration = new() | ||||
|     { | ||||
|         MatchCasing = MatchCasing.CaseSensitive, | ||||
|         IgnoreInaccessible = true, | ||||
|         RecurseSubdirectories = true, | ||||
|         AttributesToSkip = FileAttributes.Device | FileAttributes.ReparsePoint, | ||||
|     }; | ||||
|  | ||||
|     private static readonly string FingerprintSegment = $"{Path.DirectorySeparatorChar}.fingerprint{Path.DirectorySeparatorChar}"; | ||||
|  | ||||
|     public static IReadOnlyList<RustFingerprintRecord> Scan(string rootPath, CancellationToken cancellationToken) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(rootPath)) | ||||
|         { | ||||
|             throw new ArgumentException("Root path is required", nameof(rootPath)); | ||||
|         } | ||||
|  | ||||
|         var results = new List<RustFingerprintRecord>(); | ||||
|         foreach (var path in Directory.EnumerateFiles(rootPath, "*.json", Enumeration)) | ||||
|         { | ||||
|             cancellationToken.ThrowIfCancellationRequested(); | ||||
|  | ||||
|             if (!path.Contains(FingerprintSegment, StringComparison.Ordinal)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (TryParse(path, out var record)) | ||||
|             { | ||||
|                 results.Add(record); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return results; | ||||
|     } | ||||
|  | ||||
|     private static bool TryParse(string path, out RustFingerprintRecord record) | ||||
|     { | ||||
|         record = default!; | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); | ||||
|             using var document = JsonDocument.Parse(stream); | ||||
|             var root = document.RootElement; | ||||
|  | ||||
|             var pkgId = TryGetString(root, "pkgid") | ||||
|                 ?? TryGetString(root, "package_id") | ||||
|                 ?? TryGetString(root, "packageId"); | ||||
|  | ||||
|             var (name, version, source) = ParseIdentity(pkgId, path); | ||||
|             if (string.IsNullOrWhiteSpace(name)) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             var profile = TryGetString(root, "profile"); | ||||
|             var targetKind = TryGetKind(root); | ||||
|  | ||||
|             record = new RustFingerprintRecord( | ||||
|                 Name: name!, | ||||
|                 Version: version, | ||||
|                 Source: source, | ||||
|                 TargetKind: targetKind, | ||||
|                 Profile: profile, | ||||
|                 AbsolutePath: path); | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
|         catch (JsonException) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         catch (IOException) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         catch (UnauthorizedAccessException) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static (string? Name, string? Version, string? Source) ParseIdentity(string? pkgId, string filePath) | ||||
|     { | ||||
|         if (!string.IsNullOrWhiteSpace(pkgId)) | ||||
|         { | ||||
|             var span = pkgId.AsSpan().Trim(); | ||||
|             var firstSpace = span.IndexOf(' '); | ||||
|             if (firstSpace > 0 && firstSpace < span.Length - 1) | ||||
|             { | ||||
|                 var name = span[..firstSpace].ToString(); | ||||
|                 var remaining = span[(firstSpace + 1)..].Trim(); | ||||
|  | ||||
|                 var secondSpace = remaining.IndexOf(' '); | ||||
|                 if (secondSpace < 0) | ||||
|                 { | ||||
|                     return (name, remaining.ToString(), null); | ||||
|                 } | ||||
|  | ||||
|                 var version = remaining[..secondSpace].ToString(); | ||||
|                 var potentialSource = remaining[(secondSpace + 1)..].Trim(); | ||||
|  | ||||
|                 if (potentialSource.Length > 1 && potentialSource[0] == '(' && potentialSource[^1] == ')') | ||||
|                 { | ||||
|                     potentialSource = potentialSource[1..^1].Trim(); | ||||
|                 } | ||||
|  | ||||
|                 var source = potentialSource.Length == 0 ? null : potentialSource.ToString(); | ||||
|                 return (name, version, source); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var directory = Path.GetDirectoryName(filePath); | ||||
|         if (string.IsNullOrEmpty(directory)) | ||||
|         { | ||||
|             return (null, null, null); | ||||
|         } | ||||
|  | ||||
|         var crateDirectory = Path.GetFileName(directory); | ||||
|         if (string.IsNullOrWhiteSpace(crateDirectory)) | ||||
|         { | ||||
|             return (null, null, null); | ||||
|         } | ||||
|  | ||||
|         var dashIndex = crateDirectory.LastIndexOf('-'); | ||||
|         if (dashIndex <= 0) | ||||
|         { | ||||
|             return (crateDirectory, null, null); | ||||
|         } | ||||
|  | ||||
|         var maybeName = crateDirectory[..dashIndex]; | ||||
|         return (maybeName, null, null); | ||||
|     } | ||||
|  | ||||
|     private static string? TryGetKind(JsonElement root) | ||||
|     { | ||||
|         if (root.TryGetProperty("target_kind", out var array) && array.ValueKind == JsonValueKind.Array && array.GetArrayLength() > 0) | ||||
|         { | ||||
|             var first = array[0]; | ||||
|             if (first.ValueKind == JsonValueKind.String) | ||||
|             { | ||||
|                 return first.GetString(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (root.TryGetProperty("target", out var target) && target.ValueKind == JsonValueKind.String) | ||||
|         { | ||||
|             return target.GetString(); | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     private static string? TryGetString(JsonElement element, string propertyName) | ||||
|     { | ||||
|         if (element.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.String) | ||||
|         { | ||||
|             return value.GetString(); | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|  | ||||
| internal sealed record RustFingerprintRecord( | ||||
|     string Name, | ||||
|     string? Version, | ||||
|     string? Source, | ||||
|     string? TargetKind, | ||||
|     string? Profile, | ||||
|     string AbsolutePath); | ||||
		Reference in New Issue
	
	Block a user