Skip to content

Commit

Permalink
Merge pull request #125 from dotnet/client-globbing
Browse files Browse the repository at this point in the history
Add globbing support and concurrency.
  • Loading branch information
Oren Novotny authored Oct 7, 2019
2 parents 26e9251 + 3b25f69 commit d377388
Show file tree
Hide file tree
Showing 5 changed files with 441 additions and 52 deletions.
22 changes: 8 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ You'll need to create an `appsettings.json` similar to the following:
}
```

Then, somewhere in your build, you'll need to call the client tool. I use VSTS and call the following
Then, somewhere in your build, you'll need to call the client tool. I use Azure Pipelines and call the following
script to sign my files.


Expand All @@ -115,21 +115,13 @@ if([string]::IsNullOrEmpty($env:SignClientSecret)){
# Setup Variables we need to pass into the sign client tool
$appSettings = "$currentDirectory\appsettings.json"
$nupgks = ls $currentDirectory\..\*.nupkg | Select -ExpandProperty FullName
dotnet tool install --tool-path "$currentDirectory" SignClient
foreach ($nupkg in $nupgks){
Write-Host "Submitting $nupkg for signing"
& "$currentDirectory\SignClient" 'sign' -c $appSettings -i $nupkg -r $env:SignClientUser -s $env:SignClientSecret -n 'Zeroconf' -d 'Zeroconf' -u 'https://github.com/onovotny/zeroconf'
if ($LASTEXITCODE -ne 0) {
exit 1
}
Write-Host "Finished signing $nupkg"
& "$currentDirectory\SignClient" 'sign' -c $appSettings -b $Env:\ArtifactDirectory -i **/*.nupkg -r $env:SignClientUser -s $env:SignClientSecret -n 'Zeroconf' -d 'Zeroconf' -u 'https://github.com/onovotny/zeroconf'
if ($LASTEXITCODE -ne 0) {
exit 1
}
Write-Host "Sign-package complete"
```

The parameters to the signing client are as follows:
Expand All @@ -148,11 +140,12 @@ After signing contents of the archive, the archive itself is signed if supported
(currently `VSIX`).

```
usage: SignClient sign [-c <arg>] [-i <arg>] [-o <arg>]
[-f <arg>] [-s <arg>] [-n <arg>] [-d <arg>] [-u <arg>]
usage: SignClient sign [-c <arg>] [-i <arg>] [-b <arg>] [-o <arg>]
[-f <arg>] [-s <arg>] [-n <arg>] [-d <arg>] [-u <arg>] [-m <arg>]
-c, --config <arg> Path to config json file
-i, --input <arg> Path to input file
-b --baseDirectory <arg> Base directory for files to override the working directory
-o, --output <arg> Path to output file. May be same
as input to overwrite
-f, --filter <arg> Path to file containing paths of
Expand All @@ -161,6 +154,7 @@ usage: SignClient sign [-c <arg>] [-i <arg>] [-o <arg>]
-n, --name <arg> Name of project for tracking
-d, --description <arg> Description
-u, --descriptionUrl <arg> Description Url
-m, --maxConcurrency <arg> Maximum concurrency (default is 4)
```

## ClickOnce
Expand Down
306 changes: 306 additions & 0 deletions src/SignClient/Globber.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;

// "Borrowed" from https://github.com/Wyamio/Wyam/blob/c7a26da931477a48006f6ebc574c074458719774/src/core/Wyam.Core/IO/Globbing/Globber.cs
// Copyright (c) 2017 Dave Glick
namespace Wyam.Core.IO.Globbing
{
/// <summary>
/// Helper methods to work with globbing patterns.
/// </summary>
public static class Globber
{
static readonly Regex HasBraces = new Regex(@"\{.*\}");
static readonly Regex NumericSet = new Regex(@"^\{(-?[0-9]+)\.\.(-?[0-9]+)\}");

/// <summary>
/// Gets files from the specified directory using globbing patterns.
/// </summary>
/// <param name="directory">The directory to search.</param>
/// <param name="patterns">The globbing pattern(s) to use.</param>
/// <returns>Files that match the globbing pattern(s).</returns>
public static IEnumerable<FileInfo> GetFiles(DirectoryInfo directory, params string[] patterns) =>
GetFiles(directory, (IEnumerable<string>)patterns);

/// <summary>
/// Gets files from the specified directory using globbing patterns.
/// </summary>
/// <param name="directory">The directory to search.</param>
/// <param name="patterns">The globbing pattern(s) to use.</param>
/// <returns>Files that match the globbing pattern(s).</returns>
public static IEnumerable<FileInfo> GetFiles(DirectoryInfo directory, IEnumerable<string> patterns)
{
// Initially based on code from Reliak.FileSystemGlobbingExtensions (https://github.com/reliak/Reliak.FileSystemGlobbingExtensions)

var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);

// Expand braces
var expandedPatterns = patterns
.SelectMany(ExpandBraces)
.Select(f => f.Replace("\\{", "{").Replace("\\}", "}")); // Unescape braces

// Add the patterns, any that start with ! are exclusions
foreach (var expandedPattern in expandedPatterns)
{
var isExclude = expandedPattern[0] == '!';
var finalPattern = isExclude ? expandedPattern.Substring(1) : expandedPattern;
finalPattern = finalPattern
.Replace("\\!", "!") // Unescape negation
.Replace("\\", "/"); // Normalize slashes

// Add exclude or include pattern to matcher
if (isExclude)
{
matcher.AddExclude(finalPattern);
}
else
{
matcher.AddInclude(finalPattern);
}
}

DirectoryInfoBase directoryInfo = new DirectoryInfoWrapper(directory);
var result = matcher.Execute(directoryInfo);
return result.Files.Select(match => directoryInfo.GetFile(match.Path)).Select(fb => new FileInfo(fb.FullName));
}

/// <summary>Expands all brace ranges in a pattern, returning a sequence containing every possible combination.</summary>
/// <param name="pattern">The pattern to expand.</param>
/// <returns>The expanded globbing patterns.</returns>
public static IEnumerable<string> ExpandBraces(string pattern)
{
// Initially based on code from Minimatch (https://github.com/SLaks/Minimatch/blob/master/Minimatch/Minimatcher.cs)
// Brace expansion:
// a{b,c}d -> abd acd
// a{b,}c -> abc ac
// a{0..3}d -> a0d a1d a2d a3d
// a{b,c{d,e}f}g -> abg acdfg acefg
// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg
//
// Invalid sets are not expanded.
// a{2..}b -> a{2..}b
// a{b}c -> a{b}c

if (!HasBraces.IsMatch(pattern))
{
// shortcut. no need to expand.
return new[] { pattern };
}

var escaping = false;
int i;

// examples and comments refer to this crazy pattern:
// a{b,c{d,e},{f,g}h}x{y,z}
// expected:
// abxy
// abxz
// acdxy
// acdxz
// acexy
// acexz
// afhxy
// afhxz
// aghxy
// aghxz

// everything before the first \{ is just a prefix.
// So, we pluck that off, and work with the rest,
// and then prepend it to everything we find.
if (pattern[0] != '{')
{
string prefix = null;
for (i = 0; i < pattern.Length; i++)
{
var c = pattern[i];
if (c == '\\')
{
escaping = !escaping;
}
else if (c == '{' && !escaping)
{
prefix = pattern.Substring(0, i);
break;
}
else
{
escaping = false;
}
}

// actually no sets, all { were escaped.
if (prefix == null)
{
// no sets
return new[] { pattern };
}

return ExpandBraces(pattern.Substring(i)).Select(t =>
{
var neg = string.Empty;

// Check for negated subpattern
if (t.Length > 0 && t[0] == '!')
{
if (prefix[0] != '!')
{
// Only add a new negation if there isn't already one
neg = "!";
}
t = t.Substring(1);
}

// Remove duplicated path separators (can happen when there's an empty expansion like "baz/{foo,}/bar")
if (t.Length > 0 && t[0] == '/' && prefix[prefix.Length - 1] == '/')
{
t = t.Substring(1);
}

return neg + prefix + t;
});
}

// now we have something like:
// {b,c{d,e},{f,g}h}x{y,z}
// walk through the set, expanding each part, until
// the set ends. then, we'll expand the suffix.
// If the set only has a single member, then'll put the {} back

// first, handle numeric sets, since they're easier
var numset = NumericSet.Match(pattern);
if (numset.Success)
{
// console.error("numset", numset[1], numset[2])
var suf = ExpandBraces(pattern.Substring(numset.Length)).ToList();
int start = int.Parse(numset.Groups[1].Value),
end = int.Parse(numset.Groups[2].Value),
inc = start > end ? -1 : 1;
var retVal = new List<string>();
for (var w = start; w != (end + inc); w += inc)
{
// append all the suffixes
retVal.AddRange(suf.Select(t => w + t));
}
return retVal;
}

// ok, walk through the set
// We hope, somewhat optimistically, that there
// will be a } at the end.
// If the closing brace isn't found, then the pattern is
// interpreted as braceExpand("\\" + pattern) so that
// the leading \{ will be interpreted literally.
var depth = 1;
var set = new List<string>();
var member = string.Empty;
escaping = false;

for (i = 1 /* skip the \{ */; i < pattern.Length && depth > 0; i++)
{
var c = pattern[i];

if (escaping)
{
escaping = false;
member += "\\" + c;
}
else
{
switch (c)
{
case '\\':
escaping = true;
continue;

case '{':
depth++;
member += "{";
continue;

case '}':
depth--;

// if this closes the actual set, then we're done
if (depth == 0)
{
set.Add(member);
member = string.Empty;

// pluck off the close-brace
break;
}
else
{
member += c;
continue;
}

case ',':
if (depth == 1)
{
set.Add(member);
member = string.Empty;
}
else
{
member += c;
}
continue;

default:
member += c;
continue;
} // switch
} // else
} // for

// now we've either finished the set, and the suffix is
// pattern.substr(i), or we have *not* closed the set,
// and need to escape the leading brace
if (depth != 0)
{
// didn't close pattern
return ExpandBraces("\\" + pattern);
}

// ["b", "c{d,e}","{f,g}h"] -> ["b", "cd", "ce", "fh", "gh"]
var addBraces = set.Count == 1;

set = set.SelectMany(ExpandBraces).ToList();

if (addBraces)
{
set = set.Select(s => "{" + s + "}").ToList();
}

// now attach the suffixes.
// x{y,z} -> ["xy", "xz"]
// console.error("set", set)
// console.error("suffix", pattern.substr(i))
return ExpandBraces(pattern.Substring(i)).SelectMany(suf =>
{
var negated = false;
if (suf.Length > 0 && suf[0] == '!')
{
negated = true;
suf = suf.Substring(1);
}
return set.Select(s =>
{
var neg = string.Empty;
if (negated && (s.Length == 0 || s[0] != '!'))
{
// Only add a new negation if there isn't already one
neg = "!";
}
return neg + s + suf;
});
});
}
}
}
8 changes: 5 additions & 3 deletions src/SignClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,21 @@ public static int Main(string[] args)
cfg.Description = "Signs a file or set of files";
cfg.HelpOption("-? | -h | --help");
var configFile = cfg.Option("-c | --config", "Path to config json file", CommandOptionType.SingleValue);
var inputFile = cfg.Option("-i | --input", "Path to config input file", CommandOptionType.SingleValue);
var outputFile = cfg.Option("-o | --output", "Path to output file. May be same as input to overwrite", CommandOptionType.SingleValue);
var inputFile = cfg.Option("-i | --input", "Path to input file", CommandOptionType.SingleValue);
var baseDirectory = cfg.Option("-b | --baseDirectory", "Base directory for files to override the working directory", CommandOptionType.SingleValue);
var outputFile = cfg.Option("-o | --output", "Path to output. May be same as input to overwrite", CommandOptionType.SingleValue);
var fileList = cfg.Option("-f | --filelist", "Full path to file containing paths of files to sign within an archive", CommandOptionType.SingleValue);
var secret = cfg.Option("-s | --secret", "Client Secret", CommandOptionType.SingleValue);
var user = cfg.Option("-r | --user", "Username", CommandOptionType.SingleValue);
var name = cfg.Option("-n | --name", "Name of project for tracking", CommandOptionType.SingleValue);
var description = cfg.Option("-d | --description", "Description", CommandOptionType.SingleValue);
var descUrl = cfg.Option("-u | --descriptionUrl", "Description Url", CommandOptionType.SingleValue);
var maxConcurrency = cfg.Option("-m | --maxConcurrency", "Maximum concurrency (default is 4)", CommandOptionType.SingleValue);

cfg.OnExecute(() =>
{
var sign = new SignCommand(application);
return sign.SignAsync(configFile, inputFile, outputFile, fileList, secret, user, name, description, descUrl);
return sign.Sign(configFile, inputFile, baseDirectory, outputFile, fileList, secret, user, name, description, descUrl, maxConcurrency);
});
});

Expand Down
Loading

0 comments on commit d377388

Please sign in to comment.