static int BundleCommand(IEnumerable <string> args) { var help = Ref.Create(false); var verbose = Ref.Create(false); var force = false; var bundlePath = (string)null; var options = new OptionSet(CreateStrictOptionSetArgumentParser()) { Options.Help(help), Options.Verbose(verbose), Options.Debug, { "f|force", "overwrite bundle if exists", _ => force = true }, { "o|out=", "write bundle at {PATH}", v => bundlePath = v }, }; var tail = options.Parse(args); var log = verbose ? Console.Error : null; if (log != null) { Trace.Listeners.Add(new TextWriterTraceListener(log)); } if (help) { Help(options); return(0); } var queryPath = tail.FirstOrNone() switch { (SomeT, var arg) => arg, _ => throw new Exception("Missing LINQPad query path argument") }; var query = LinqPadQuery.Load(Path.GetFullPath(queryPath)); if (query.ValidateSupported() is Exception e) { throw e; } var logbundlePath = bundlePath == null; bundlePath ??= Path.ChangeExtension(query.FilePath, ".zip"); if (!force && File.Exists(bundlePath)) { throw new Exception("Target bundle file already exists: " + bundlePath); } File.Delete(bundlePath); var tempZipFilePath = Path.GetRandomFileName(); using var _ = Defer(tempZipFilePath, File.Delete); using var zip = ZipFile.Open(tempZipFilePath, ZipArchiveMode.Create); var queryFileName = Path.GetFileName(query.FilePath); var epEntry = zip.CreateEntry(Path.GetFileName("lpless-bundle.ini")); epEntry.LastWriteTime = File.GetLastWriteTime(query.FilePath); using (var stream = epEntry.Open()) using (var writer = new StreamWriter(stream, Utf8.BomlessEncoding)) writer.WriteLine("entrypoint=" + queryFileName); var loads = new List <(string Name, string SourceFilePath)>(); var mainEntry = zip.CreateEntry(queryFileName); mainEntry.LastWriteTime = File.GetLastWriteTime(query.FilePath); using (var stream = mainEntry.Open()) using (var writer = new StreamWriter(stream, Utf8.BomlessEncoding)) { var eomLineNumber = LinqPad.GetEndOfMetaLineNumber(new FileInfo(queryPath)); foreach (var line in File.ReadLines(queryPath).Take(eomLineNumber)) { writer.WriteLine(line); } var loadNameSet = new HashSet <string>(StringComparer.OrdinalIgnoreCase) { mainEntry.Name }; foreach (var(line, load) in query.Code.Lines() .Index(1) .LeftJoin(query.Loads, e => e.Key, e => e.LineNumber, line => (line.Value, default), (line, load) => (line.Value, load))) { if (load != null) { var fileName = Path.GetFileName(load.Path); var originalName = Path.GetFileNameWithoutExtension(fileName); var extension = Path.GetExtension(fileName); for (var counter = 1; !loadNameSet.Add(fileName); counter++) { fileName = originalName + counter.ToString(CultureInfo.InvariantCulture) + extension; } loads.Add((fileName, load.Path)); writer.WriteLine($@"#load "".\{fileName}"" // {line}"); } else { writer.WriteLine(line); } } } if (loads.Count == 0) { Console.Error.WriteLine("Warning! No load directives found. Bundle is redundant."); } foreach (var(name, path) in loads) { log?.WriteLine("+ " + path); var entry = zip.CreateEntry(name); entry.LastWriteTime = File.GetLastWriteTime(path); using var output = entry.Open(); using var input = File.OpenRead(path); input.CopyTo(output); output.Flush(); } zip.Dispose(); File.Move(tempZipFilePath, bundlePath); if (logbundlePath) { Console.Error.WriteLine(bundlePath); } return(0); }
static LinqPadQuery Parse(string source, string path, bool parseLoads, bool resolveLoads) { var eomLineNumber = LinqPad.GetEndOfMetaLineNumber(source); return(new LinqPadQuery(path, source, eomLineNumber, parseLoads, resolveLoads)); }
static T Compile <T>(string queryFilePath, IPackageRepository repo, string packagesPath, IEnumerable <PackageReference> extraPackageReferences, IEnumerable <string> extraImports, FrameworkName targetFramework, bool verbose, IndentingLineWriter writer, Func <QueryLanguage, string, IEnumerable <string>, IEnumerable <Reference>, T> selector) { var eomLineNumber = LinqPad.GetEndOfMetaLineNumber(queryFilePath); var lines = File.ReadLines(queryFilePath); var xml = string.Join(Environment.NewLine, // ReSharper disable once PossibleMultipleEnumeration lines.Take(eomLineNumber)); var query = XElement.Parse(xml); if (verbose) { writer.Write(query); } QueryLanguage queryKind; if (!Enum.TryParse((string)query.Attribute("Kind"), true, out queryKind) || (queryKind != QueryLanguage.Statements && queryKind != QueryLanguage.Expression && queryKind != QueryLanguage.Program)) { throw new NotSupportedException("Only LINQPad " + "C# Statements and Expression queries are fully supported " + "and C# Program queries partially in this version."); } var nrs = from nrsq in new[] { from nr in query.Elements("NuGetReference") select new PackageReference((string)nr, SemanticVersion.ParseOptionalVersion((string)nr.Attribute("Version")), (bool?)nr.Attribute("Prerelease") ?? false), extraPackageReferences, } from nr in nrsq select new { nr.Id, nr.Version, nr.IsPrereleaseAllowed, Title = string.Join(" ", Seq.Return(nr.Id, nr.Version?.ToString(), nr.IsPrereleaseAllowed ? "(pre-release)" : null) .Filter()), }; nrs = nrs.ToArray(); if (verbose && nrs.Any()) { writer.WriteLine($"Packages referenced ({nrs.Count():N0}):"); writer.Indent().WriteLines(from nr in nrs select nr.Title); } writer.WriteLine($"Packages directory: {packagesPath}"); var pm = new PackageManager(repo, packagesPath); pm.PackageInstalling += (_, ea) => writer.WriteLine($"Installing {ea.Package}..."); pm.PackageInstalled += (_, ea) => writer.Indent().WriteLine($"Installed at {ea.InstallPath}"); writer.WriteLine($"Packages target: {targetFramework}"); var resolutionList = Enumerable.Repeat(new { Package = default(IPackage), AssemblyPath = default(string) }, 0) .ToList(); foreach (var nr in nrs) { var pkg = pm.LocalRepository.FindPackage(nr.Id, nr.Version, allowPrereleaseVersions: nr.IsPrereleaseAllowed, allowUnlisted: false); if (pkg == null) { pkg = repo.FindPackage(nr.Id, nr.Version, allowPrereleaseVersions: nr.IsPrereleaseAllowed, allowUnlisted: false); if (pkg == null) { throw new Exception("Package not found: " + nr.Title); } pm.InstallPackage(pkg.Id, pkg.Version); } writer.WriteLine("Resolving references..."); resolutionList.AddRange(GetReferencesTree(pm.LocalRepository, pkg, targetFramework, writer.Indent(), (r, p) => new { Package = p, AssemblyPath = r != null ? Path.Combine(pm.PathResolver.GetInstallPath(p), r.Path) : null })); } var packagesPathWithTrailer = packagesPath + Path.DirectorySeparatorChar; var resolution = resolutionList .GroupBy(r => r.Package) .Select(g => g.First()) .Select(r => new { r.Package, AssemblyPath = r.AssemblyPath != null ? MakeRelativePath(queryFilePath, packagesPathWithTrailer) + MakeRelativePath(packagesPathWithTrailer, r.AssemblyPath) : null, }) .Partition(r => r.AssemblyPath == null, (ok, nok) => new { ResolvedReferences = ok, ReferencelessPackages = from r in nok select r.Package.GetFullName(), }); resolution.ReferencelessPackages.StartIter(e => { writer.WriteLine($"Warning! Packages with no references for {targetFramework}:"); writer.Indent().WriteLines(e.ResumeFromCurrent()); }); var references = resolution.ResolvedReferences.ToArray(); references.Select(r => r.AssemblyPath).StartIter(e => { writer.WriteLine($"Resolved references ({references.Length:N0}):"); writer.Indent().WriteLines(e.ResumeFromCurrent()); }); return (selector( queryKind, // ReSharper disable once PossibleMultipleEnumeration string.Join(Environment.NewLine, lines.Skip(eomLineNumber)), LinqPad.DefaultNamespaces .Concat(from ns in query.Elements("Namespace") select(string) ns) .Concat(extraImports), LinqPad.DefaultReferences.Select(r => new Reference(r)) .Concat(from r in query.Elements("Reference") select(string) r into r select r.StartsWith(LinqPad.RuntimeDirToken, StringComparison.OrdinalIgnoreCase) ? r.Substring(LinqPad.RuntimeDirToken.Length) : r into r select new Reference(r)) .Concat(from r in references select new Reference(r.AssemblyPath, r.Package)))); }