diff --git a/.gitignore b/.gitignore index 0dc4ba7a..8d248eae 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /projects /archive /release +/applibs /env.cmd /*.zip diff --git a/BenchManager/BenchCLI/ArgumentValidation.cs b/BenchManager/BenchCLI/ArgumentValidation.cs new file mode 100644 index 00000000..19b3c473 --- /dev/null +++ b/BenchManager/BenchCLI/ArgumentValidation.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Mastersign.Bench.Cli +{ + static class ArgumentValidation + { + public static bool ContainsOneOfChars(string v, char[] chars) + { + foreach (var c in chars) + { + if (v.Contains(new string(new[] { c }))) return true; + } + return false; + } + + public static bool IsValidPath(string v) + { + return !ContainsOneOfChars(v, Path.GetInvalidPathChars()); + } + + public static bool IsIdString(string v) + { + return !string.IsNullOrEmpty(v) && !v.Contains(" "); + } + } +} diff --git a/BenchManager/BenchCLI/BenchCLI.csproj b/BenchManager/BenchCLI/BenchCLI.csproj new file mode 100644 index 00000000..ad80307c --- /dev/null +++ b/BenchManager/BenchCLI/BenchCLI.csproj @@ -0,0 +1,138 @@ + + + + + + Debug + AnyCPU + {64E94A41-026F-473C-BC48-70F8D5EB977A} + Exe + Properties + Mastersign.Bench.Cli + bench + v2.0 + 512 + true + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + logo.ico + + + + ..\packages\Mastersign.Sequence.1.1.0\lib\net20\Mastersign.Sequence.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3ff60d62-d733-40e8-b759-848fae5fea93} + BenchLib + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BenchManager/BenchCLI/CliTools/ArgumentCompletionConsoleDialog.cs b/BenchManager/BenchCLI/CliTools/ArgumentCompletionConsoleDialog.cs new file mode 100644 index 00000000..9c53bdcb --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/ArgumentCompletionConsoleDialog.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.Docs; + +namespace Mastersign.CliTools +{ + public class ArgumentCompletionConsoleDialog : ConsoleDialog + { + private const char HELP_MNEMONIC = '?'; + private const char FLAG_OR_OPTION_MNEMONIC = '-'; + + private readonly ArgumentParser parser; + + public ArgumentCompletionConsoleDialog(ArgumentParser parser) + { + this.parser = parser; + } + + public ArgumentParsingResult ShowFor(ArgumentParsingResult prelimResult) + { + if (prelimResult.Type != ArgumentParsingResultType.MissingArgument && + prelimResult.Type != ArgumentParsingResultType.NoCommand) + { + throw new InvalidOperationException("The arguments are already satisfying."); + } + + var flags = prelimResult.Flags; + var optionValues = prelimResult.OptionValues; + var positionalValues = prelimResult.PositionalValues; + + var positionalArgs = parser.GetPositionals(); + var missingPositionalArgs = new List(); + foreach (var pArg in positionalArgs) + { + if (prelimResult.GetPositionalValue(pArg.Name) == null) + { + missingPositionalArgs.Add(pArg); + } + } + var hasMissingPositionalArguments = missingPositionalArgs.Count > 0; + + var selectedCommand = prelimResult.Command; + var isCommandMissing = parser.GetCommands().Length > 0 && selectedCommand == null; + + if (isCommandMissing) + { + CommandMenuResult result; + do + { + result = ShowCommandMenu(ref selectedCommand); + switch (result) + { + case CommandMenuResult.Escape: + return null; + case CommandMenuResult.Help: + return prelimResult.DeriveHelp(); + case CommandMenuResult.Command: + if (selectedCommand != null) + { + Console.Write("Selected Command: "); + Console.ForegroundColor = ConsoleColor.White; + Console.Write(selectedCommand); + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine(); + } + break; + case CommandMenuResult.FlagOrOption: + Argument arg; + var result2 = ShowFlagAndOptionMenu(flags, optionValues, out arg, MenuFollowUp.Return); + switch (result2) + { + case FlagAndOptionMenuResult.Exit: + break; + case FlagAndOptionMenuResult.Help: + return prelimResult.DeriveHelp(); + case FlagAndOptionMenuResult.FlagOrOption: + PrintFlagOrOptionCompletion(flags, optionValues, arg); + break; + default: + throw new NotSupportedException(); + } + break; + default: + throw new NotSupportedException(); + } + } while (result == CommandMenuResult.FlagOrOption); + } + else if (hasMissingPositionalArguments && + (parser.GetFlags().Length > 0 || parser.GetOptions().Length > 0)) + { + Argument arg; + FlagAndOptionMenuResult result; + do + { + result = ShowFlagAndOptionMenu(flags, optionValues, out arg, MenuFollowUp.Proceed); + switch (result) + { + case FlagAndOptionMenuResult.Exit: + break; + case FlagAndOptionMenuResult.Help: + return prelimResult.DeriveHelp(); + case FlagAndOptionMenuResult.FlagOrOption: + PrintFlagOrOptionCompletion(flags, optionValues, arg); + break; + default: + throw new NotSupportedException(); + } + } while (result == FlagAndOptionMenuResult.FlagOrOption); + } + + if (hasMissingPositionalArguments) + { + foreach (var arg in missingPositionalArgs) + { + positionalValues[arg.Name] = ReadArgumentValue(arg.Name, + arg.ValuePredicate, arg.Description, arg.PossibleValueInfo); + } + } + + return prelimResult.DeriveInteractivelyCompleted(selectedCommand); + } + + private void PrintFlagOrOptionCompletion(IDictionary flags, IDictionary optionValues, Argument arg) + { + if (arg is FlagArgument) + { + Write("Flag {0}: ", arg.Name); + Console.ForegroundColor = ConsoleColor.White; + WriteLine(flags.ContainsKey(arg.Name) && flags[arg.Name] ? "active" : "inactive"); + Console.ForegroundColor = ConsoleColor.Gray; + } + else if (arg is OptionArgument) + { + Write("Option {0}: ", arg.Name); + Console.ForegroundColor = ConsoleColor.White; + WriteLine(optionValues[arg.Name]); + Console.ForegroundColor = ConsoleColor.Gray; + } + } + + private enum CommandMenuResult + { + Escape, + Help, + Command, + FlagOrOption + } + + private CommandMenuResult ShowCommandMenu(ref string selectedCommand) + { + var commandArgs = parser.GetCommands(); + SortArgumentsByMnemonic(commandArgs); + var result = CommandMenuResult.Command; + Open(); + WriteLine(); + WriteLine("Choose one of the following commands:"); + WriteLine(); + var keyChars = new List(); + var quitMnemonic = ArgumentParser.MenuQuitMnemonic; + keyChars.Add(ESC); + keyChars.Add(quitMnemonic); + keyChars.Add(HELP_MNEMONIC); + WriteMenuItem(HELP_MNEMONIC, "Display the help."); + if (parser.GetFlags().Length > 0 || parser.GetOptions().Length > 0) + { + keyChars.Add(FLAG_OR_OPTION_MNEMONIC); + WriteMenuItem(FLAG_OR_OPTION_MNEMONIC, "Specify a flag or an option value."); + } + foreach (var arg in commandArgs) + { + WriteMenuItem(arg.Mnemonic, arg.Description.ToString()); + keyChars.Add(arg.Mnemonic); + } + WriteLine(); + Write("Press a character key to choose a menu item or ESC/" + quitMnemonic + " to quit. "); + var selectedMnemonic = ReadExpectedChar(keyChars); + Close(); + if (selectedMnemonic == ESC || selectedMnemonic == quitMnemonic) + { + result = CommandMenuResult.Escape; + } + else if (selectedMnemonic == HELP_MNEMONIC) + { + result = CommandMenuResult.Help; + } + else if (selectedMnemonic == FLAG_OR_OPTION_MNEMONIC) + { + result = CommandMenuResult.FlagOrOption; + } + else + { + foreach (var cmd in commandArgs) + { + if (cmd.Mnemonic == selectedMnemonic) + { + selectedCommand = cmd.Name; + break; + } + } + } + return result; + } + + private enum FlagAndOptionMenuResult + { + Exit, + Help, + FlagOrOption + } + + private enum MenuFollowUp + { + Return, + Proceed + } + + private FlagAndOptionMenuResult ShowFlagAndOptionMenu( + IDictionary flags, IDictionary optionValues, + out Argument selectedArgument, MenuFollowUp followUp) + { + var flagArgs = parser.GetFlags(); + SortArgumentsByMnemonic(flagArgs); + var optionArgs = parser.GetOptions(); + SortArgumentsByMnemonic(optionArgs); + var hasFlags = flagArgs.Length > 0; + var hasOptions = optionArgs.Length > 0; + var result = FlagAndOptionMenuResult.FlagOrOption; + Open(); + WriteLine(); + if (hasFlags && hasOptions) + WriteLine("Choose one of the following flags or options:"); + else if (hasFlags) + WriteLine("Choose one of the following flags:"); + else if (hasOptions) + WriteLine("Choose one of the following options:"); + WriteLine(); + var keyChars = new List(); + var quitMnemonic = ArgumentParser.MenuQuitMnemonic; + if (followUp == MenuFollowUp.Return) + { + keyChars.Add(ESC); + keyChars.Add(quitMnemonic); + } + else + keyChars.Add(ENTER); + keyChars.Add(HELP_MNEMONIC); + if (hasFlags) + { + WriteLine("Flags"); + foreach (var arg in flagArgs) + { + WriteMenuItem(arg.Mnemonic, arg.Description.ToString()); + keyChars.Add(arg.Mnemonic); + } + } + if (hasOptions) + { + WriteLine("Options"); + foreach (var arg in optionArgs) + { + WriteMenuItem(arg.Mnemonic, arg.Description.ToString()); + keyChars.Add(arg.Mnemonic); + } + } + WriteLine(); + if (followUp == MenuFollowUp.Return) + Write("Press a character key to choose a menu item or ESC to go back. "); + else + Write("Press a character key to choose a menu item or ENTER to proceed. "); + var selectedMnemonic = ReadExpectedChar(keyChars); + Close(); + selectedArgument = null; + if (selectedMnemonic == ESC || selectedMnemonic == quitMnemonic || selectedMnemonic == ENTER) + { + result = FlagAndOptionMenuResult.Exit; + } + else if (selectedMnemonic == HELP_MNEMONIC) + { + result = FlagAndOptionMenuResult.Help; + } + else + { + foreach (var arg in flagArgs) + { + if (arg.Mnemonic == selectedMnemonic) + { + selectedArgument = arg; + if (flags.ContainsKey(arg.Name) && flags[arg.Name]) + flags.Remove(arg.Name); + else + flags[arg.Name] = true; + return result; + } + } + foreach (var arg in optionArgs) + { + if (arg.Mnemonic == selectedMnemonic) + { + selectedArgument = arg; + optionValues[arg.Name] = ReadArgumentValue(arg.Name, + arg.ValuePredicate, arg.Description, arg.PossibleValueInfo); + return result; + } + } + } + return result; + } + + private string ReadArgumentValue(string name, ArgumentValuePredicate predicate, + Document description, Document possibleValueInfo) + { + string result = null; + var valid = true; + do + { + Open(); + WriteLine(); + WriteLine("Enter value for {0}", name); + WriteLine(); + WriteLine("Description: " + description.ToString()); + if (!valid) + { + Console.ForegroundColor = ConsoleColor.Yellow; + WriteLine("The given value for '{0}' is invalid, try again.", name); + WriteLine("Expected: " + possibleValueInfo.ToString()); + Console.ForegroundColor = ConsoleColor.Gray; + } + else + { + WriteLine("Expected: " + possibleValueInfo.ToString()); + } + WriteLine(); + Write("Value for {0}: ", name); + result = ReadLine(); + Close(); + if (predicate != null) + { + valid = predicate(result); + } + } while (!valid); + return result; + } + + private static void SortArgumentsByMnemonic(T[] args) + where T : NamedArgument + { + Array.Sort(args, (c1, c2) => c1.Mnemonic.CompareTo(c2.Mnemonic)); + } + + private void WriteMenuItem(char mnemonic, string description) + { + Console.ForegroundColor = ConsoleColor.White; + Write(" {0} ", mnemonic); + Console.ForegroundColor = ConsoleColor.Gray; + WriteLine(description); + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/ArgumentParser.cs b/BenchManager/BenchCLI/CliTools/ArgumentParser.cs new file mode 100644 index 00000000..986f180a --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/ArgumentParser.cs @@ -0,0 +1,628 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.Docs; + +namespace Mastersign.CliTools +{ + public class ArgumentParser + { + public ArgumentParserType ParserType { get; set; } + + public static string[] HelpIndicators = new[] { "/?", "-?", "-h", "--help" }; + + public static string MainHelpIndicator = "-?"; + + public static char MenuQuitMnemonic = 'q'; + + private readonly Dictionary arguments = new Dictionary(); + + public string Name { get; private set; } + + public Document Description { get; private set; } + + public ArgumentParser(string name, IEnumerable arguments) + { + Name = name; + ParserType = ArgumentParserType.CaseSensitive; + Description = new Document(); + foreach (var arg in arguments) + { + RegisterArgument(arg); + } + } + + public ArgumentParser(string name, params Argument[] arguments) + : this(name, (IEnumerable)arguments) + { + } + + public void RegisterArgument(Argument arg) + { + switch (arg.Type) + { + case ArgumentType.Flag: + if (!(arg is FlagArgument)) + throw new ArgumentException("Expected type " + nameof(FlagArgument) + "."); + if (MnemonicExists((NamedArgument)arg)) + throw new ArgumentException("The arguments mnemonic is already in use."); + break; + case ArgumentType.Option: + if (!(arg is OptionArgument)) + throw new ArgumentException("Expected type " + nameof(OptionArgument) + "."); + if (MnemonicExists((NamedArgument)arg)) + throw new ArgumentException("The arguments mnemonic is already in use."); + break; + case ArgumentType.Command: + if (!(arg is CommandArgument)) + throw new ArgumentException("Expected type " + nameof(CommandArgument) + "."); + if (MnemonicExists((NamedArgument)arg)) + throw new ArgumentException("The arguments mnemonic is already in use."); + break; + case ArgumentType.Positional: + if (!(arg is PositionalArgument)) + throw new ArgumentException("Expected type " + nameof(PositionalArgument) + "."); + break; + default: + throw new ArgumentException("Argument type not supported."); + } + arguments.Add(arg.Name, arg); + } + + public void RegisterArguments(params Argument[] arguments) + { + foreach (var arg in arguments) + { + RegisterArgument(arg); + } + } + + private T[] FilterArguments(ArgumentType type) where T : Argument + { + var res = new List(); + foreach (var a in arguments.Values) + { + if (a.Type == type) + { + res.Add((T)a); + } + } + res.Sort((a, b) => a.Name.CompareTo(b.Name)); + return res.ToArray(); + } + + private bool MnemonicExists(NamedArgument arg) + { + var m = arg.Mnemonic; + if (arg is CommandArgument) + { + foreach (var cmdArg in GetCommands()) + if (cmdArg.Mnemonic == m) return true; + return false; + } + if (arg is FlagArgument || arg is OptionArgument) + { + foreach (var flagArg in GetFlags()) + if (flagArg.Mnemonic == m) return true; + foreach (var optArg in GetOptions()) + if (optArg.Mnemonic == m) return true; + return false; + } + return false; + } + + public FlagArgument[] GetFlags() + => FilterArguments(ArgumentType.Flag); + + public OptionArgument[] GetOptions() + => FilterArguments(ArgumentType.Option); + + public PositionalArgument[] GetPositionals() + { + var list = new List(FilterArguments(ArgumentType.Positional)); + list.Sort((a1, a2) => a1.OrderIndex.CompareTo(a2.OrderIndex)); + return list.ToArray(); + } + + public CommandArgument[] GetCommands() + => FilterArguments(ArgumentType.Command); + + private bool IsHelpIndicator(string v) + { + foreach (var i in HelpIndicators) + { + if (i.Equals(v, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + return false; + } + + private string[] MissingPositinalArguments(int foundArgumentsCount) + { + var positionalArgs = GetPositionals(); + var missingArgs = Math.Max(0, positionalArgs.Length - foundArgumentsCount); + var list = new string[missingArgs]; + for (int i = 0; i < missingArgs; i++) + { + list[i] = positionalArgs[foundArgumentsCount + i].Name; + } + return list; + } + + public ArgumentParsingResult Parse(string[] args) + { + var index = new ArgumentIndex( + ParserType == ArgumentParserType.CaseSensitive, + arguments.Values); + IDictionary flagValues = new Dictionary(); + IDictionary optionValues = new Dictionary(); + IDictionary positionalValues = new Dictionary(); + string command = null; + var position = 0; + var help = false; + string invalid = null; + + while (position < args.Length && command == null) + { + var arg = args[position]; + if (IsHelpIndicator(arg)) + { + help = true; + position++; + continue; + } + var a = index.LookUp(args[position], positionalValues.Count); + if (a == null) + { + if (GetCommands().Length > 0) + { + invalid = args[position]; + } + break; + } + switch (a.Type) + { + case ArgumentType.Flag: + flagValues[a.Name] = true; + break; + case ArgumentType.Option: + position++; + if (args.Length <= position) + { + invalid = args[position - 1] + " ???"; + break; + } + var opt = (OptionArgument)a; + if (opt.ValuePredicate != null && !opt.ValuePredicate(args[position])) + { + invalid = args[position - 1] + " " + args[position]; + break; + } + optionValues[a.Name] = args[position]; + break; + case ArgumentType.Positional: + var pArg = (PositionalArgument)a; + if (pArg.ValuePredicate != null && !pArg.ValuePredicate(args[position])) + { + invalid = pArg.Name + ": " + args[position]; + break; + } + positionalValues[a.Name] = args[position]; + break; + case ArgumentType.Command: + command = a.Name; + break; + default: + throw new NotSupportedException(); + } + if (invalid != null) break; + position++; + } + if (help) + { + return new ArgumentParsingResult(this, ArgumentParsingResultType.Help, + null, null, null, optionValues, flagValues, positionalValues); + } + if (invalid != null) + { + return new ArgumentParsingResult(this, ArgumentParsingResultType.InvalidArgument, + null, invalid, null, optionValues, flagValues, positionalValues); + } + var missingPositionalArguments = MissingPositinalArguments(positionalValues.Count); + if (missingPositionalArguments.Length > 0) + { + return new ArgumentParsingResult(this, ArgumentParsingResultType.MissingArgument, + null, string.Join(", ", missingPositionalArguments), + null, optionValues, flagValues, positionalValues); + } + var rest = new string[args.Length - position]; + Array.Copy(args, position, rest, 0, rest.Length); + if (command != null) + { + return new ArgumentParsingResult(this, ArgumentParsingResultType.Command, + command, null, rest, optionValues, flagValues, positionalValues); + } + return new ArgumentParsingResult(this, ArgumentParsingResultType.NoCommand, + null, null, rest, optionValues, flagValues, positionalValues); + } + } + + internal class ArgumentIndex + { + private readonly Dictionary flags = new Dictionary(); + private readonly Dictionary flagMnemonics = new Dictionary(); + private readonly Dictionary options = new Dictionary(); + private readonly Dictionary optionMnemonics = new Dictionary(); + private readonly Dictionary commands = new Dictionary(); + private readonly Dictionary commandMnemonics = new Dictionary(); + private readonly List positionals = new List(); + + private readonly bool CaseSensitive; + + public ArgumentIndex(bool caseSensitive, IEnumerable args) + { + CaseSensitive = caseSensitive; + foreach (var arg in args) + { + AddArgument(arg); + } + } + + private string PrepareArgument(string arg) + { + if (arg == null) throw new ArgumentNullException(); + return CaseSensitive ? arg : arg.ToLowerInvariant(); + } + + private char PrepareArgument(char arg) + { + return CaseSensitive ? arg : char.ToLowerInvariant(arg); + } + + private void AddArgument(Argument arg) + { + var namedArg = arg as NamedArgument; + switch (arg.Type) + { + case ArgumentType.Flag: + flags[PrepareArgument(arg.Name)] = arg; + foreach (var alias in namedArg.Aliases) + { + flags[PrepareArgument(alias)] = arg; + } + flagMnemonics[PrepareArgument(namedArg.Mnemonic)] = arg; + break; + case ArgumentType.Option: + options[PrepareArgument(arg.Name)] = arg; + foreach (var alias in namedArg.Aliases) + { + options[PrepareArgument(alias)] = arg; + } + optionMnemonics[PrepareArgument(namedArg.Mnemonic)] = arg; + break; + case ArgumentType.Command: + commands[PrepareArgument(arg.Name)] = arg; + foreach (var alias in namedArg.Aliases) + { + commands[PrepareArgument(alias)] = arg; + } + commandMnemonics[PrepareArgument(namedArg.Mnemonic)] = arg; + break; + case ArgumentType.Positional: + positionals.Add(arg); + break; + default: + throw new NotSupportedException(); + } + positionals.Sort((a1, a2) => + ((PositionalArgument)a1).OrderIndex.CompareTo(((PositionalArgument)a2).OrderIndex)); + } + + public Argument LookUp(string v, int consumedPositionals) + { + Argument a; + v = PrepareArgument(v); + if (v.StartsWith("--")) + { + var name = v.Substring(2); + if (flags.TryGetValue(name, out a)) return a; + if (options.TryGetValue(name, out a)) return a; + return null; + } + if (v.Length == 2 && v.StartsWith("-")) + { + var mnemonic = v[1]; + if (flagMnemonics.TryGetValue(mnemonic, out a)) return a; + if (optionMnemonics.TryGetValue(mnemonic, out a)) return a; + return null; + } + if (commands.TryGetValue(v, out a)) return a; + if (v.Length == 1 && commandMnemonics.TryGetValue(v[0], out a)) return a; + + if (consumedPositionals < positionals.Count) return positionals[consumedPositionals]; + + return null; + } + } + + public enum ArgumentParserType + { + CaseSensitive, + CaseInsensitive + } + + public enum ArgumentType + { + Flag, + Option, + Positional, + Command + } + + public abstract class Argument + { + public ArgumentType Type { get; private set; } + + public string Name { get; private set; } + + + public Document Description { get; private set; } + + protected Argument(ArgumentType type, string name) + { + Type = type; + Name = name; + Description = new Document(); + } + } + + public abstract class NamedArgument : Argument + { + public char Mnemonic { get; private set; } + + public string[] Aliases { get; private set; } + + protected NamedArgument(ArgumentType type, string name, char mnemonic, + params string[] aliases) + : base(type, name) + { + if (mnemonic == ArgumentParser.MenuQuitMnemonic) + { + throw new ArgumentException("The mnemonic equals the menu quit mnemonic."); + } + Mnemonic = mnemonic; + Aliases = aliases; + } + } + + public class FlagArgument : NamedArgument + { + public FlagArgument(string name, char mnemonic, + params string[] aliases) + : base(ArgumentType.Flag, name, mnemonic, aliases) + { + } + } + + public delegate bool ArgumentValuePredicate(string value); + + public class OptionArgument : NamedArgument + { + public Document PossibleValueInfo { get; private set; } + + public Document DefaultValueInfo { get; private set; } + + public ArgumentValuePredicate ValuePredicate { get; private set; } + + public OptionArgument(string name, char mnemonic, + ArgumentValuePredicate valuePredicate, + params string[] aliases) + : base(ArgumentType.Option, name, mnemonic, aliases) + { + PossibleValueInfo = new Document(); + DefaultValueInfo = new Document(); + ValuePredicate = valuePredicate; + } + } + + public class EnumOptionArgument : OptionArgument + { + private static bool IsEnumMember(string v) + { + try + { + Enum.Parse(typeof(T), v, true); + return true; + } + catch (ArgumentException) + { + return false; + } + } + + public T DefaultValue { get; private set; } + + public EnumOptionArgument(string name, char mnemonic, + T defaultValue, params string[] aliases) + : base(name, mnemonic, IsEnumMember) + { + DefaultValue = defaultValue; + var enumNames = Enum.GetNames(typeof(T)); + for (int i = 0; i < enumNames.Length; i++) + { + if (i > 0) PossibleValueInfo.Text(" | "); + PossibleValueInfo.Keyword(enumNames[i]); + } + DefaultValueInfo.Keyword(defaultValue.ToString()); + } + } + + public class PositionalArgument : Argument + { + public Document PossibleValueInfo { get; private set; } + + public int OrderIndex { get; private set; } + + public ArgumentValuePredicate ValuePredicate { get; private set; } + + public PositionalArgument(string name, + ArgumentValuePredicate valuePredicate, int position) + : base(ArgumentType.Positional, name) + { + PossibleValueInfo = new Document(); + OrderIndex = position; + ValuePredicate = valuePredicate; + } + } + + public class CommandArgument : NamedArgument + { + public Document SyntaxInfo { get; private set; } + + public CommandArgument(string name, char mnemonic, + params string[] aliases) + : base(ArgumentType.Command, name, mnemonic, aliases) + { + SyntaxInfo = new Document(); + } + } + + public class ArgumentParsingResult + { + public ArgumentParser Parser { get; private set; } + + public ArgumentParsingResultType Type { get; private set; } + + public string Command { get; private set; } + + public string ErrorMessage { get; private set; } + + public string[] Rest { get; private set; } + + private readonly IDictionary options; + + public IDictionary OptionValues => options; + + private readonly IDictionary flags; + + public IDictionary Flags => flags; + + private readonly IDictionary positionals; + + public IDictionary PositionalValues => positionals; + + public bool IsCompletedInteractively { get; private set; } + + public ArgumentParsingResult(ArgumentParser parser, + ArgumentParsingResultType type, + string command, string errorMessage, string[] rest, + IDictionary options, + IDictionary flags, + IDictionary positionals) + { + Parser = parser; + Type = type; + Command = command; + ErrorMessage = errorMessage; + Rest = rest; + this.options = options ?? new Dictionary(); + this.flags = flags ?? new Dictionary(); + this.positionals = positionals ?? new Dictionary(); + } + + public ArgumentParsingResult DeriveHelp() + { + return new ArgumentParsingResult(Parser, ArgumentParsingResultType.Help, + Command, null, Rest, options, flags, positionals); + } + + public ArgumentParsingResult DeriveInteractivelyCompleted( + string command) + { + return new ArgumentParsingResult(Parser, + command != null + ? ArgumentParsingResultType.Command + : ArgumentParsingResultType.NoCommand, + command, null, Rest, options, flags, positionals) + { IsCompletedInteractively = true }; + } + + public string GetOptionValue(string name, string def = null) + { + string res; + return options.TryGetValue(name, out res) ? res : def; + } + + public bool GetFlag(string name) + { + return flags.ContainsKey(name); + } + + public string GetPositionalValue(string name, string def = null) + { + string res; + return positionals.TryGetValue(name, out res) ? res : def; + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendLine("Result Type: " + Type); + switch (Type) + { + case ArgumentParsingResultType.InvalidArgument: + sb.AppendLine("Invalid Argument: " + ErrorMessage); + break; + case ArgumentParsingResultType.MissingArgument: + sb.AppendLine("Missing Argument(s): " + ErrorMessage); + break; + case ArgumentParsingResultType.Command: + case ArgumentParsingResultType.NoCommand: + if (flags.Count > 0) + { + var flagNames = new List(flags.Keys); + sb.AppendLine("Flags: " + string.Join(", ", flagNames.ToArray())); + } + if (options.Count > 0) + { + sb.AppendLine("Options:"); + var optionNames = new List(options.Keys); + optionNames.Sort(); + foreach (var n in optionNames) + { + sb.AppendLine(" * " + n + " = " + options[n]); + } + } + if (positionals.Count > 0) + { + sb.AppendLine("Positionals:"); + var positionalNames = new List(positionals.Keys); + positionalNames.Sort(); + foreach (var n in positionalNames) + { + sb.AppendLine(" * " + n + " = " + options[n]); + } + } + if (Rest != null && Rest.Length > 0) + { + sb.AppendLine("Rest: " + string.Join(" ", Rest)); + } + break; + default: + break; + } + return sb.ToString(); + } + } + + public enum ArgumentParsingResultType + { + InvalidArgument, + MissingArgument, + Help, + Command, + NoCommand + } +} diff --git a/BenchManager/BenchCLI/CliTools/CommandBase.cs b/BenchManager/BenchCLI/CliTools/CommandBase.cs new file mode 100644 index 00000000..5527ef4e --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/CommandBase.cs @@ -0,0 +1,450 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.Docs; + +namespace Mastersign.CliTools +{ + public abstract class CommandBase + { + private ArgumentParser argParser; + + public ArgumentParser ArgumentParser + { + get + { + if (argParser == null) + { + argParser = new ArgumentParser(Name); + InitializeArgumentParser(argParser); + } + return argParser; + } + } + + protected abstract void InitializeArgumentParser(ArgumentParser parser); + + protected ArgumentParsingResult Arguments { get; set; } + + public abstract string Name { get; } + + private CommandBase parent; + + public CommandBase Parent { get; protected set; } + + public IDictionary SubCommands { get; private set; } + + protected CommandBase() + { + SubCommands = new Dictionary(); + } + + protected void RegisterSubCommand(CommandBase subCommand) + { + SubCommands[subCommand.Name] = subCommand; + subCommand.Parent = this; + } + + #region Bubbeling Properties + + private string toolName; + + public string ToolName + { + get { return Parent != null ? Parent.ToolName : toolName; } + protected set { toolName = value; } + } + + private string toolVersion; + + public string ToolVersion + { + get { return Parent != null ? Parent.ToolVersion : toolVersion; } + protected set { toolVersion = value; } + } + + private Document toolDescription; + + public Document ToolDescription + { + get + { + if (Parent != null) return Parent.ToolDescription; + if (toolDescription == null) toolDescription = new Document(); + return toolDescription; + } + } + + private bool verbose; + + public bool Verbose + { + get { return Parent != null ? Parent.Verbose : verbose; } + protected set { verbose = value; } + } + + private bool noAssurance; + + public bool NoAssurance + { + get { return Parent != null ? Parent.NoAssurance : noAssurance; } + protected set { noAssurance = value; } + } + + private DocumentOutputFormat helpFormat; + + public DocumentOutputFormat HelpFormat + { + get { return Parent != null ? Parent.HelpFormat : helpFormat; } + set { helpFormat = value; } + } + + #endregion + + #region Console Output + + protected void WriteLine(string message) + => Console.WriteLine(message); + + protected void WriteLine(string format, params object[] args) + => Console.WriteLine(format, args); + + protected void WriteError(string message) + { + var colorBackup = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("[ERROR] (cli) " + message); + Console.ForegroundColor = colorBackup; + } + + protected void WriteError(string format, params object[] args) + => WriteError(string.Format(format, args)); + + protected void WriteInfo(string message) + { + if (!Verbose) return; + var colorBackup = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("[INFO] (cli) " + message); + Console.ForegroundColor = colorBackup; + } + + protected void WriteInfo(string format, params object[] args) + => WriteInfo(string.Format(format, args)); + + protected void WriteDetail(string message) + { + if (!Verbose) return; + var colorBackup = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine("[VERBOSE] (cli) " + message); + Console.ForegroundColor = colorBackup; + } + + protected void WriteDetail(string format, params object[] args) + => WriteDetail(string.Format(format, args)); + + private void Backspace(int l) + { + Console.Write("".PadRight(l, (char)0x08)); + } + + protected virtual bool AskForAssurance(string question) + { + if (NoAssurance) return true; + + var colorBackup = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + var extent = "(y/N)"; + Console.Write(question + " " + extent); + bool? result = null; + while (result == null) + { + var key = Console.ReadKey(true); + if (key.Key == ConsoleKey.Enter) + result = false; + else if (key.Key == ConsoleKey.N) + result = false; + else if (key.Key == ConsoleKey.Y) + result = true; + } + Backspace(extent.Length); + Console.Write("<- "); + if (result.Value) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("Yes"); + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Write("No"); + } + Console.ForegroundColor = colorBackup; + Console.WriteLine(); + return result.Value; + } + + #endregion + + #region Help + + public CommandBase[] CommandChain(bool withRoot = true) + { + var cmds = new List(); + var cmd = this; + while (cmd != null && (withRoot || cmd.Parent != null)) + { + cmds.Add(cmd); + cmd = cmd.Parent; + } + cmds.Reverse(); + return cmds.ToArray(); + } + + private string[] CommandChainNames(bool withRoot = true) + { + var names = new List(); + foreach (var cmd in CommandChain(withRoot)) + { + names.Add(cmd.Name); + } + return names.ToArray(); + } + + public string CommandChain(string separator, bool withRoot = true) + => string.Join(separator, CommandChainNames(withRoot)); + + protected CommandBase[] CommandHierarchyDepthSearch() + { + var result = new List(); + CommandHierarchyDepthSearch(result); + return result.ToArray(); + } + + private void CommandHierarchyDepthSearch(ICollection coll) + { + coll.Add(this); + if (SubCommands.Count > 0) + { + var children = new List(SubCommands.Values); + children.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + foreach (var item in children) + { + item.CommandHierarchyDepthSearch(coll); + } + } + } + + public CommandBase RootCommand + { + get + { + var cmd = this; + while (cmd.Parent != null) cmd = cmd.Parent; + return cmd; + } + } + + protected virtual void PrintHelpHint() + { + WriteLine("Use '{0} {1}' to display the help.", + CommandChain(" "), + ArgumentParser.MainHelpIndicator); + } + + protected virtual void PrintInvalidArgumentWarning(string arg) + { + WriteError("Invalid Argument: " + arg); + PrintHelpHint(); + } + + protected virtual void PrintMissingArgumentWarning(string arg) + { + WriteError("Missing Argument(s): " + arg); + PrintHelpHint(); + } + + protected virtual void PrintHelp() + { + using (var w = DocumentWriterFactory.Create(HelpFormat)) + { + PrintHelp(w); + } + } + + protected virtual void PrintHelp(DocumentWriter w) + { + w.Begin(BlockType.Document); + w.Title("{0} v{1}", ToolName, ToolVersion); + PrintCommandHelp(w, withHelpSection: true, withCommandLinks: false); + w.End(BlockType.Document); + } + + public void PrintFullHelp(DocumentWriter w, + bool withTitle = true, bool withVersion = true, bool withIndex = true) + { + w.Begin(BlockType.Document); + if (withTitle) w.Title(ToolName); + if (withVersion) w.Paragraph("Version: {0}", ToolVersion); + w.Append(ToolDescription); + + var commands = CommandHierarchyDepthSearch(); + if (withIndex) + { + w.Headline2("index", "Commands"); + w.Begin(BlockType.List); + foreach (var cmd in commands) + { + w.Begin(BlockType.ListItem) + .Begin(BlockType.Link) + .LinkTarget("#" + HelpFormatter.CommandAnchor(cmd)) + .Begin(BlockType.LinkContent) + .Append(HelpFormatter.SlimCommandChain, cmd) + .End(BlockType.LinkContent) + .End(BlockType.Link) + .End(BlockType.ListItem); + } + w.End(BlockType.List); + } + foreach (var cmd in commands) + { + w.Headline1(HelpFormatter.CommandAnchor(cmd), cmd.CommandChain(" ", true)); + cmd.PrintCommandHelp(w, withHelpSection: cmd == this, withCommandLinks: true); + } + + w.End(BlockType.Document); + } + + private void PrintCommandHelp(DocumentWriter w, + bool withHelpSection = true, bool withCommandLinks = false) + { + if (Parent != null) + { + w.Begin(BlockType.Paragraph); + w.Text("Command: "); + var chain = CommandChainNames(); + for (int i = 0; i < chain.Length; i++) + { + if (i > 0) w.Text(" "); + w.Keyword(chain[i]); + } + w.End(BlockType.Paragraph); + } + HelpFormatter.WriteHelp(w, this, withHelpSection, withCommandLinks); + } + + private ArgumentParsingResult CompleteInteractively(ArgumentParsingResult arguments) + { + var dialog = new ArgumentCompletionConsoleDialog(ArgumentParser); + return dialog.ShowFor(Arguments); + } + + #endregion + + #region Execution + + public virtual bool Process(string[] args) + { + WriteDetail("Arguments: {0}", string.Join(" ", args)); + return Process(ArgumentParser.Parse(args)); + } + + protected bool Process(ArgumentParsingResult arguments) + { + Arguments = arguments; + if (Arguments.Type == ArgumentParsingResultType.Help || + Arguments.Type == ArgumentParsingResultType.NoCommand || + Arguments.Type == ArgumentParsingResultType.Command) + { + try + { + if (!ValidateArguments()) + return false; + } + catch (Exception e) + { + WriteError(e.Message); + return false; + } + } + if (Arguments.Type == ArgumentParsingResultType.Help) + { + PrintHelp(); + return true; + } + if (Arguments.Type == ArgumentParsingResultType.InvalidArgument) + { + PrintInvalidArgumentWarning(Arguments.ErrorMessage); + return false; + } + if (Arguments.Type == ArgumentParsingResultType.MissingArgument) + { + if (Arguments.IsCompletedInteractively) + { + PrintMissingArgumentWarning(arguments.ErrorMessage); + return false; + } + var arguments2 = CompleteInteractively(Arguments); + if (arguments2 == null) + { + WriteDetail("Canceled by user."); + return false; + } + return Process(arguments2); + } + if (Arguments.Type == ArgumentParsingResultType.NoCommand) + { + return ExecuteCommand(Arguments.Rest); + } + if (Arguments.Type == ArgumentParsingResultType.Command) + { + WriteDetail("Command: {0}", Arguments.Command); + return ExecuteSubCommand(Arguments.Command, Arguments.Rest); + } + + WriteError("Argument parsing result not supported."); + return false; + } + + protected virtual bool ValidateArguments() + { + return true; + } + + protected virtual bool ExecuteSubCommand(string command, string[] args) + { + CommandBase cmd; + if (SubCommands.TryGetValue(command, out cmd)) + return cmd.Process(args); + else + return ExecuteUnknownSubCommand(command, args); + } + + protected virtual bool ExecuteUnknownSubCommand(string command, string[] args) + { + WriteError("The sub-command '{0}' is not implemented.", command); + PrintHelpHint(); + return false; + } + + protected virtual bool ExecuteCommand(string[] args) + { + if (Arguments.IsCompletedInteractively) + { + WriteError("This command has no meaning on its own. Try specifying a sub-command."); + PrintHelpHint(); + return false; + } + var arguments2 = CompleteInteractively(Arguments); + if (arguments2 == null) + { + WriteDetail("Canceled by user."); + return false; + } + return Process(arguments2); + } + + #endregion + } +} diff --git a/BenchManager/BenchCLI/CliTools/ConsoleDialog.cs b/BenchManager/BenchCLI/CliTools/ConsoleDialog.cs new file mode 100644 index 00000000..fa12ec7b --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/ConsoleDialog.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + public class ConsoleDialog : ConsoleOperation + { + protected const char ESC = (char)27; + protected const char ENTER = (char)13; + + private int lines; + + protected void Write(string text) + { + Console.Write(text); + } + + protected void WriteLine(string text = null) + { + Console.WriteLine(text ?? string.Empty); + lines++; + } + + protected string ReadLine() + { + lines++; + return Console.ReadLine(); + } + + protected void Write(string format, params object[] args) + => Write(string.Format(format, args)); + + protected void WriteLine(string format, params object[] args) + => WriteLine(string.Format(format, args)); + + protected void ClearLine(int row) + { + Console.SetCursorPosition(0, row); + Console.Write(new string(' ', Console.BufferWidth)); + } + + public void Open() + { + lines = 0; + } + + public void Close() + { + var bottom = Console.CursorTop; + var top = Math.Max(0, bottom - lines); + Console.SetCursorPosition(0, top); + BackupState(); + for (int r = top; r <= bottom; r++) ClearLine(r); + RestoreState(); + } + + protected char ReadExpectedChar(IList expected) + { + char k; + do + { + k = Console.ReadKey(true).KeyChar; + } while (!expected.Contains(k)); + return k; + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/ConsoleMapWriter.cs b/BenchManager/BenchCLI/CliTools/ConsoleMapWriter.cs new file mode 100644 index 00000000..7afff284 --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/ConsoleMapWriter.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + public class ConsoleMapWriter : IMapWriter + { + public void Dispose() + { + } + + public void Write(string key, object value) + { + if (value == null) WriteNull(key); + else if (value is bool) WriteValue(key, (bool)value); + else if (value is string) WriteValue(key, (string)value); + else if (value is string[]) WriteValue(key, (string[])value); + else if (value is IDictionary) WriteValue(key, (IDictionary)value); + else WriteUnknown(key); + } + + private string EscapeString(string value) + { + return value != null + ? "\"" + value.Replace(@"\", @"\\").Replace("\"", "\\\"") + "\"" + : null; + } + + public void WriteValue(string key, IDictionary value) + { + var pairs = new List(); + foreach (var kvp in (Dictionary)value) + { + pairs.Add(string.Format("{0}: {1}", + EscapeString(kvp.Key), EscapeString(kvp.Value))); + } + Console.WriteLine(key + " = {" + string.Join(", ", pairs.ToArray()) + "}"); + } + + public void WriteValue(string key, string[] value) + { + var items = new List(); + foreach (var item in value) + { + items.Add(EscapeString(item)); + } + Console.WriteLine("{0} = [{1}]", key, string.Join(", ", items.ToArray())); + } + + public void WriteValue(string key, string value) + { + Console.WriteLine("{0} = {1}", key, EscapeString(value.ToString())); + } + + public void WriteValue(string key, bool value) + { + Console.WriteLine("{0} = {1}", key, value ? "True" : "False"); + } + + public void WriteNull(string key) + { + Console.WriteLine("{0} = Null", key); + } + + public void WriteUnknown(string key) + { + Console.WriteLine("{0} = Unsupported Data Type", key); + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/ConsoleOperation.cs b/BenchManager/BenchCLI/CliTools/ConsoleOperation.cs new file mode 100644 index 00000000..05200963 --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/ConsoleOperation.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + public class ConsoleOperation + { + public static ConsoleColor DefaultForegroundColor = Console.ForegroundColor; + public static ConsoleColor DefaultBackgroundColor = Console.BackgroundColor; + + private int backupRow; + private int backupColumn; + private ConsoleColor backupForegroundColor; + private ConsoleColor backupBackgroundColor; + + public int StoredCursorTop => backupRow; + public int StoredCursorLeft => backupColumn; + + protected void BackupState() + { + backupRow = Console.CursorTop; + backupColumn = Console.CursorLeft; + backupForegroundColor = Console.ForegroundColor; + backupBackgroundColor = Console.BackgroundColor; + Console.CursorVisible = false; + } + + protected void RestoreState() + { + Console.ForegroundColor = backupForegroundColor; + Console.BackgroundColor = backupBackgroundColor; + Console.SetCursorPosition(backupColumn, backupRow); + Console.CursorVisible = true; + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/ConsoleTableWriter.cs b/BenchManager/BenchCLI/CliTools/ConsoleTableWriter.cs new file mode 100644 index 00000000..2f90235e --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/ConsoleTableWriter.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + class ConsoleTableWriter : ITableWriter + { + private string[] columns; + private List rows; + private bool isDisposed; + + public void Initialize(params string[] columns) + { + this.columns = columns; + this.rows = new List(); + } + + public void Write(params object[] values) + { + if (isDisposed) throw new ObjectDisposedException(nameof(ConsoleTableWriter)); + if (columns == null) throw new InvalidOperationException(); + if (values.Length != columns.Length) throw new ArgumentException("Incorrect number of values."); + var row = new List(); + for (int i = 0; i < values.Length; i++) + { + row.Add(Format(values[i])); + } + rows.Add(row.ToArray()); + } + + private string Format(object value) + { + if (value == null) return string.Empty; + if (value is bool) return ((bool)value) ? "TRUE" : "FALSE"; + if (value is string) return (string)value; + return "UNSUPPORTED TYPE"; + } + + private void WriteTable() + { + var c = columns.Length; + var lengths = new int[c]; + for (int i = 0; i < c; i++) lengths[i] = columns[i].Length; + foreach (var row in rows) + { + for (int i = 0; i < c; i++) lengths[i] = Math.Max(lengths[i], row[i].Length); + } + for (int i = 0; i < c; i++) + { + if (i > 0) Write(" | "); + Write(columns[i].PadRight(lengths[i])); + } + NewLine(); + for (int i = 0; i < c; i++) + { + if (i > 0) Write("-|-"); + Write(new string('-', lengths[i])); + } + NewLine(); + foreach (var row in rows) + { + for (int i = 0; i < c; i++) + { + if (i > 0) Write(" | "); + Write(row[i].PadRight(lengths[i])); + } + NewLine(); + } + } + + private int lineLength = 0; + private void Write(string value) + { + var w = Console.WindowWidth; + if (lineLength >= w) return; + if (lineLength + value.Length < w) + { + Console.Write(value); + lineLength += value.Length; + } + else + { + Console.Write(value.Substring(0, w - lineLength)); + lineLength = w; + } + } + + private void NewLine() + { + Console.WriteLine(); + lineLength = 0; + } + + public void Dispose() + { + if (isDisposed) return; + WriteTable(); + isDisposed = true; + columns = null; + rows = null; + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/DataOutputFormat.cs b/BenchManager/BenchCLI/CliTools/DataOutputFormat.cs new file mode 100644 index 00000000..6175cd6c --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/DataOutputFormat.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + public enum DataOutputFormat + { + Plain, + Markdown, + // Json, + // Xml, + } +} diff --git a/BenchManager/BenchCLI/CliTools/HelpFormatter.cs b/BenchManager/BenchCLI/CliTools/HelpFormatter.cs new file mode 100644 index 00000000..0b44f6da --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/HelpFormatter.cs @@ -0,0 +1,366 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.Docs; + +namespace Mastersign.CliTools +{ + public static class HelpFormatter + { + private static void FormatFlag(DocumentWriter w, NamedArgument a) + { + w.Keyword("--" + a.Name); + foreach (var alias in a.Aliases) + { + w.Text(" | "); + w.Keyword("--" + alias); + } + w.Text(" | "); + w.Keyword("-" + a.Mnemonic); + } + + private static void FormatOption(DocumentWriter w, OptionArgument a) + { + FormatFlag(w, a); + w.Text(" "); + w.Variable("value"); + } + + private static void FormatPositional(DocumentWriter w, PositionalArgument a) + { + w.Variable(a.Name); + } + + private static void FormatCommand(DocumentWriter w, CommandArgument a) + { + w.Keyword(a.Name); + foreach (var alias in a.Aliases) + { + w.Text(", "); + w.Keyword(alias); + } + w.Text(", "); + w.Keyword(new string(a.Mnemonic, 1)); + } + + private static bool HasFlags(ArgumentParser p) + { + return p.GetFlags().Length > 0; + } + + private static bool HasOptions(ArgumentParser p) + { + return p.GetOptions().Length > 0; + } + + private static bool HasCommands(ArgumentParser p) + { + return p.GetCommands().Length > 0; + } + + private static bool HasPositionals(ArgumentParser p) + { + return p.GetPositionals().Length > 0; + } + + public static void FlagsAndOptionsGeneric(DocumentWriter w, ArgumentParser p) + { + var hasFlags = HasFlags(p); + var hasOptions = HasOptions(p); + if (hasFlags && hasOptions) + { + w.Text(" (").Variable("flag").Text(" | ") + .Variable("option").Text(")*"); + } + else + { + if (hasFlags) + { + w.Text(" ").Variable("flag").Text("*"); + } + if (hasOptions) + { + w.Text(" ").Variable("option").Text("*"); + } + } + foreach (var a in p.GetPositionals()) + { + w.Text(" ").Append(FormatPositional, a); + } + } + + public static void FullCommandChain(DocumentWriter w, CommandBase cmd) + { + var cmdChain = cmd.CommandChain(); + for (int i = 0; i < cmdChain.Length; i++) + { + if (i > 0) w.Text(" "); + var c = cmdChain[i]; + w.Keyword(c.Name).Append(FlagsAndOptionsGeneric, c.ArgumentParser); + } + } + + public static void CommandSyntax(DocumentWriter w, CommandBase cmd) + { + var cmdChain = cmd.CommandChain(); + for (int i = 0; i < cmdChain.Length; i++) + { + if (i > 0) w.Text(" "); + var c = cmdChain[i]; + w.Keyword(c.Name); + } + if (cmdChain.Length > 0) + { + var p = cmdChain[cmdChain.Length - 1].ArgumentParser; + w.Append(FlagsAndOptionsGeneric, p); + } + if (cmd.ArgumentParser.GetCommands().Length > 0) + { + w.Text(" ").Variable("sub-command"); + } + } + + public static void SlimCommandChain(DocumentWriter w, CommandBase cmd) + { + var cmdChain = cmd.CommandChain(); + for (int i = 0; i < cmdChain.Length; i++) + { + if (i > 0) w.Text(" "); + var c = cmdChain[i]; + w.Keyword(c.Name); + } + } + + public static string CommandAnchor(CommandBase cmd) + { + return "cmd_" + cmd.CommandChain("-"); + } + + public static string CommandAnchor(CommandBase cmd, string subCmd) + { + return CommandAnchor(cmd) + "-" + subCmd; + } + + private static Document allHelpIndicators; + private static Document AllHelpIndicators + { + get + { + if (allHelpIndicators == null) + { + var d = new Document(); + var helpIndicators = ArgumentParser.HelpIndicators; + for (int i = 0; i < helpIndicators.Length; i++) + { + if (i > 0) d.Text(", "); + d.Keyword(helpIndicators[i]); + } + allHelpIndicators = d; + } + return allHelpIndicators; + } + } + + public static void WriteHelp(DocumentWriter w, CommandBase cmd, + bool withHelpSection = true, bool withCommandLinks = false) + { + var parser = cmd.ArgumentParser; + w.Append(parser.Description); + WriteUsage(w, cmd, withHelp: !withHelpSection); + if (withHelpSection) WriteHelpUsage(w, cmd); + WriteFlags(w, cmd); + WriteOptions(w, cmd); + WritePositionals(w, cmd); + WriteCommands(w, cmd, withCommandLinks); + } + + private static void WriteUsage(DocumentWriter w, CommandBase cmd, + bool withHelp = false) + { + w.Headline2(CommandAnchor(cmd) + "_usage", "Usage"); + + w.Begin(BlockType.List); + if (withHelp) + { + w.Begin(BlockType.ListItem) + .Append(SlimCommandChain, cmd).Text(" ") + .Keyword(ArgumentParser.MainHelpIndicator); + w.End(BlockType.ListItem); + } + w.ListItem(FullCommandChain, cmd); + if (HasCommands(cmd.ArgumentParser)) + { + w.Begin(BlockType.ListItem) + .Append(FullCommandChain, cmd) + .Text(" ").Variable("command").Text(" ..."); + w.End(BlockType.ListItem); + } + w.End(BlockType.List); + } + + private static void WriteHelpUsage(DocumentWriter w, CommandBase cmd) + { + w.Headline2(CommandAnchor(cmd) + "_help", "Help"); + w.Begin(BlockType.Paragraph) + .Text("Showing the help can be triggered by one of the following flags: ") + .Append(AllHelpIndicators) + .Text("."); + w.End(BlockType.Paragraph); + w.Begin(BlockType.List); + w.Begin(BlockType.ListItem) + .Append(SlimCommandChain, cmd).Text(" ") + .Keyword(ArgumentParser.MainHelpIndicator); + w.End(BlockType.ListItem); + if (HasCommands(cmd.ArgumentParser)) + { + w.Begin(BlockType.ListItem) + .Append(SlimCommandChain, cmd) + .Text(" ").Variable("command").Text(" ") + .Keyword(ArgumentParser.MainHelpIndicator); + w.End(BlockType.ListItem); + } + w.End(BlockType.List); + } + + private static void WriteFlags(DocumentWriter w, CommandBase cmd) + { + var flags = cmd.ArgumentParser.GetFlags(); + if (flags.Length > 0) + { + w.Headline2(CommandAnchor(cmd) + "_flags", "Flags"); + w.Begin(BlockType.DefinitionList); + foreach (var flag in flags) + { + w.Begin(BlockType.Definition); + w.DefinitionTopic(FormatFlag, flag); + w.DefinitionContent(flag.Description); + w.End(BlockType.Definition); + } + w.End(BlockType.DefinitionList); + } + } + + private static void WriteOptions(DocumentWriter w, CommandBase cmd) + { + var options = cmd.ArgumentParser.GetOptions(); + if (options.Length > 0) + { + w.Headline2(CommandAnchor(cmd) + "_options", "Options"); + w.Begin(BlockType.DefinitionList); + foreach (var option in options) + { + var hasDefinitions = option.PossibleValueInfo != null || option.DefaultValueInfo != null; + w.Begin(BlockType.Definition); + w.DefinitionTopic(FormatOption, option); + w.Begin(BlockType.DefinitionContent); + if (hasDefinitions) + { + if (!option.Description.IsEmpty) + { + w.Paragraph(option.Description); + } + w.Begin(BlockType.PropertyList); + if (!option.PossibleValueInfo.IsEmpty) + { + w.Property("Expected", option.PossibleValueInfo); + } + if (!option.DefaultValueInfo.IsEmpty) + { + w.Property("Default", option.DefaultValueInfo); + } + w.End(BlockType.PropertyList); + } + else if (!option.Description.IsEmpty) + { + w.Append(option.Description); + } + w.End(BlockType.DefinitionContent); + w.End(BlockType.Definition); + } + w.End(BlockType.DefinitionList); + } + } + + private static void WritePositionals(DocumentWriter w, CommandBase cmd) + { + var positionals = cmd.ArgumentParser.GetPositionals(); + if (positionals.Length > 0) + { + w.Headline2(CommandAnchor(cmd) + "_positionals", "Positional Arguments"); + w.Begin(BlockType.DefinitionList); + foreach (var pArg in positionals) + { + var hasDefinitions = pArg.PossibleValueInfo != null; + w.Begin(BlockType.Definition); + w.Begin(BlockType.DefinitionTopic) + .Text(pArg.OrderIndex.ToString().PadLeft(2) + ". ") + .Text(pArg.Name) + .End(BlockType.DefinitionTopic); + w.Begin(BlockType.DefinitionContent); + if (hasDefinitions) + { + if (!pArg.Description.IsEmpty) + { + w.Paragraph(pArg.Description); + } + w.Begin(BlockType.PropertyList); + if (!pArg.PossibleValueInfo.IsEmpty) + { + w.Property("Expected", pArg.PossibleValueInfo); + } + w.End(BlockType.PropertyList); + } + else if (!pArg.Description.IsEmpty) + { + w.Append(pArg.Description); + } + w.End(BlockType.DefinitionContent); + w.End(BlockType.Definition); + } + w.End(BlockType.DefinitionList); + } + } + + private static void WriteCommands(DocumentWriter w, CommandBase cmd, bool withLinks = false) + { + var commands = cmd.ArgumentParser.GetCommands(); + if (commands.Length > 0) + { + w.Headline2(CommandAnchor(cmd) + "_commands", "Commands"); + w.Begin(BlockType.DefinitionList); + foreach (var cmdArg in commands) + { + w.Begin(BlockType.Definition); + w.Begin(BlockType.DefinitionTopic); + if (withLinks) + { + w.Begin(BlockType.Link); + w.LinkTarget("#" + CommandAnchor(cmd, cmdArg.Name)); + w.Begin(BlockType.LinkContent); + } + w.Append(FormatCommand, cmdArg); + if (withLinks) + { + w.End(BlockType.LinkContent); + w.End(BlockType.Link); + } + w.End(BlockType.DefinitionTopic); + w.Begin(BlockType.DefinitionContent); + if (!cmdArg.Description.IsEmpty) + { + w.Paragraph(cmdArg.Description); + } + if (!cmdArg.SyntaxInfo.IsEmpty) + { + w.Begin(BlockType.PropertyList); + w.Property("Syntax", cmdArg.SyntaxInfo); + w.End(BlockType.PropertyList); + } + w.End(BlockType.DefinitionContent); + w.End(BlockType.Definition); + } + w.End(BlockType.DefinitionList); + } + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/IMapWriter.cs b/BenchManager/BenchCLI/CliTools/IMapWriter.cs new file mode 100644 index 00000000..9bf62652 --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/IMapWriter.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + public interface IMapWriter : IDisposable + { + void Write(string key, object value); + } +} diff --git a/BenchManager/BenchCLI/CliTools/ITableWriter.cs b/BenchManager/BenchCLI/CliTools/ITableWriter.cs new file mode 100644 index 00000000..80e06967 --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/ITableWriter.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + public interface ITableWriter : IDisposable + { + void Initialize(params string[] columns); + + void Write(params object[] values); + } +} diff --git a/BenchManager/BenchCLI/CliTools/MapWriterFactory.cs b/BenchManager/BenchCLI/CliTools/MapWriterFactory.cs new file mode 100644 index 00000000..a8560cf8 --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/MapWriterFactory.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + public static class MapWriterFactory + { + public static IMapWriter Create(DataOutputFormat format) + { + switch (format) + { + case DataOutputFormat.Plain: + return new ConsoleMapWriter(); + case DataOutputFormat.Markdown: + return new MarkdownMapWriter(Console.OpenStandardOutput()); + //case OutputFormat.JSON: + // return new JsonPropertyWriter(Console.OpenStandardOutput()); + //case OutputFormat.XML: + // return new XmlPropertyWriter(Console.OpenStandardOutput()); + default: + throw new NotSupportedException(); + } + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/MarkdownMapWriter.cs b/BenchManager/BenchCLI/CliTools/MarkdownMapWriter.cs new file mode 100644 index 00000000..9022fe35 --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/MarkdownMapWriter.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Mastersign.CliTools +{ + public class MarkdownMapWriter : IMapWriter + { + private TextWriter writer; + + public MarkdownMapWriter(Stream stream) + { + writer = new StreamWriter(stream, new UTF8Encoding(false)); + } + + public void Dispose() + { + if (writer != null) + { + writer.Flush(); + writer.Dispose(); + } + writer = null; + } + + public void Write(string key, object value) + { + if (value == null) WriteNull(key); + else if (value is bool) WriteValue(key, (bool)value); + else if (value is string) WriteValue(key, (string)value); + else if (value is string[]) WriteValue(key, (string[])value); + else if (value is IDictionary) WriteValue(key, (IDictionary)value); + else WriteUnknown(key); + } + + + private string EscapeValue(string value) + { + if (value == null) + return ""; + if (Uri.IsWellFormedUriString(value, UriKind.Absolute)) + return "<" + value + ">"; + return "`" + value + "`"; + } + + private string EscapeValue(bool value) + { + return "`" + (value ? "true" : "false") + "`"; + } + + private void WriteValue(string key, IDictionary value) + { + writer.WriteLine("* `{0}`:", key); + foreach (var kvp in value) + { + writer.WriteLine(" + `{0}`: {1}", kvp.Key, EscapeValue(kvp.Value)); + } + } + + private void WriteValue(string key, string[] value) + { + var sum = 0; + var list = new List(value); + for (int i = 0; i < list.Count; i++) + { + sum += list[i].Length + 3; + list[i] = "`" + list[i] + "`"; + } + if (sum <= 100) + { + writer.WriteLine("* `{0}`: {1}", key, string.Join(", ", list.ToArray())); + } + else + { + writer.WriteLine("* `{0}`:", key); + foreach (var item in value) + { + writer.WriteLine(" + {0}", EscapeValue(item)); + } + } + } + + private void WriteValue(string key, string value) + { + writer.WriteLine("* `{0}`: {1}", key, EscapeValue(value)); + } + + private void WriteValue(string key, bool value) + { + writer.WriteLine("* `{0}`: {1}", key, EscapeValue(value)); + } + + private void WriteNull(string key) + { + writer.WriteLine("* `{0}`:", key); + } + + private void WriteUnknown(string key) + { + writer.WriteLine("* `{0}`: _Unsupported Data Type_", key); + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/MarkdownTableWriter.cs b/BenchManager/BenchCLI/CliTools/MarkdownTableWriter.cs new file mode 100644 index 00000000..a9579729 --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/MarkdownTableWriter.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Mastersign.CliTools +{ + class MarkdownTableWriter : ITableWriter + { + private string[] columns; + private List rows; + + private TextWriter writer; + + public MarkdownTableWriter(TextWriter writer) + { + if (writer == null) throw new ArgumentNullException(); + this.writer = writer; + } + + public MarkdownTableWriter(Stream target) + : this(new StreamWriter(target, new UTF8Encoding(false))) + { + } + + public void Initialize(params string[] columns) + { + this.columns = columns; + this.rows = new List(); + } + + public void Write(params object[] values) + { + if (writer == null) throw new ObjectDisposedException(nameof(ConsoleTableWriter)); + if (columns == null) throw new InvalidOperationException(); + if (values.Length != columns.Length) throw new ArgumentException("Incorrect number of values."); + var row = new List(); + for (int i = 0; i < values.Length; i++) + { + row.Add(Format(values[i])); + } + rows.Add(row.ToArray()); + } + + private string Format(object value) + { + if (value == null) return string.Empty; + if (value is bool) return ((bool)value) ? "`true`" : "`false`"; + if (value is string) return (string)value; + return "_UNSUPPORTED TYPE_"; + } + + private void WriteTable() + { + var c = columns.Length; + var lengths = new int[c]; + for (int i = 0; i < c; i++) lengths[i] = columns[i].Length; + foreach (var row in rows) + { + for (int i = 0; i < c; i++) lengths[i] = Math.Max(lengths[i], row[i].Length); + } + Write("| "); + for (int i = 0; i < c; i++) + { + if (i > 0) Write(" | "); + Write(columns[i].PadRight(lengths[i])); + } + Write(" |"); + NewLine(); + Write("|:"); + for (int i = 0; i < c; i++) + { + if (i > 0) Write("-|:"); + Write(new string('-', lengths[i])); + } + Write("-|"); + NewLine(); + foreach (var row in rows) + { + Write("| "); + for (int i = 0; i < c; i++) + { + if (i > 0) Write(" | "); + Write(row[i].PadRight(lengths[i])); + } + Write(" |"); + NewLine(); + } + } + + private void Write(string value) + { + writer.Write(value); + } + + private void NewLine() + { + writer.WriteLine(); + } + + public void Dispose() + { + if (writer == null) return; + WriteTable(); + writer.Dispose(); + writer = null; + } + } +} diff --git a/BenchManager/BenchCLI/CliTools/TableWriterFactory.cs b/BenchManager/BenchCLI/CliTools/TableWriterFactory.cs new file mode 100644 index 00000000..09c59092 --- /dev/null +++ b/BenchManager/BenchCLI/CliTools/TableWriterFactory.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.CliTools +{ + public static class TableWriterFactory + { + public static ITableWriter Create(DataOutputFormat format) + { + switch (format) + { + case DataOutputFormat.Plain: + return new ConsoleTableWriter(); + case DataOutputFormat.Markdown: + return new MarkdownTableWriter(Console.OpenStandardOutput()); + //case OutputFormat.JSON: + // return new JsonPropertyWriter(Console.OpenStandardOutput()); + //case OutputFormat.XML: + // return new XmlPropertyWriter(Console.OpenStandardOutput()); + default: + throw new NotSupportedException(); + } + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppActivateCommand.cs b/BenchManager/BenchCLI/Commands/AppActivateCommand.cs new file mode 100644 index 00000000..ec2d73fd --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppActivateCommand.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppActivateCommand : BenchCommand + { + private const string POSITIONAL_APP_ID = "App ID"; + + public override string Name => "activate"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command marks an app as activated.") + .End(BlockType.Paragraph) + .Begin(BlockType.Paragraph) + .Text("To actually install the app, you have to run the ").Keyword("setup").Text(" command.") + .End(BlockType.Paragraph) + .Begin(BlockType.Paragraph) + .Text("If the app is currently active as a dependency, it is marked as activated anyways.").LineBreak() + .Text("If the app is required by Bench, it is not marked as activated.").LineBreak() + .Text("If the app is marked as deactivated, this mark is removed.") + .End(BlockType.Paragraph); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to activate."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + positionalAppId); + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + var cfg = LoadConfiguration(); + + if (!cfg.ContainsGroup(appId)) + { + WriteError("The app '{0}' was not found.", appId); + return false; + } + + var activationFile = cfg.GetStringValue(PropertyKeys.AppActivationFile); + if (!File.Exists(activationFile)) + { + WriteError("The activation file for apps was not found."); + WriteLine(" " + activationFile); + return false; + } + WriteDetail("Found activation file: " + activationFile); + var deactivationFile = cfg.GetStringValue(PropertyKeys.AppDeactivationFile); + if (!File.Exists(deactivationFile)) + { + WriteError("The deactivation file for apps was not found."); + WriteLine(" " + deactivationFile); + return false; + } + WriteDetail("Found deactivation file: " + deactivationFile); + var activationList = new ActivationFile(activationFile); + var deactivationList = new ActivationFile(deactivationFile); + + var app = cfg.Apps[appId]; + WriteDetail("App ID: " + appId); + if (app.IsDeactivated) + { + WriteDetail("Removing the app from the deactivation file."); + deactivationList.SignOut(appId); + } + if (app.IsRequired) + { + WriteDetail("The app is required and does not need to be activated."); + return true; + } + if (app.IsActivated) + { + WriteDetail("The app is already activated."); + return true; + } + WriteDetail("Adding the app to the activation file."); + activationList.SignIn(appId); + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppCommand.cs b/BenchManager/BenchCLI/Commands/AppCommand.cs new file mode 100644 index 00000000..26f5aa8c --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppCommand.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppCommand : BenchCommand + { + private readonly BenchCommand appInfoCommand = new AppInfoCommand(); + private readonly BenchCommand appPropertyCommand = new AppPropertyCommand(); + private readonly BenchCommand appListPropertiesCommand = new AppListPropertiesCommand(); + private readonly BenchCommand appActivateCommand = new AppActivateCommand(); + private readonly BenchCommand appDeactivateCommand = new AppDeactivateCommand(); + private readonly BenchCommand appDownloadCommand = new AppDownloadCommand(); + private readonly BenchCommand appInstallCommand = new AppInstallCommand(); + private readonly BenchCommand appReinstallCommand = new AppReinstallCommand(); + private readonly BenchCommand appUpgradeCommand = new AppUpgradeCommand(); + private readonly BenchCommand appUninstallCommand = new AppUninstallCommand(); + private readonly BenchCommand appExecuteCommand = new AppExecuteCommand(); + + public override string Name => "app"; + + public AppCommand() + { + RegisterSubCommand(appInfoCommand); + RegisterSubCommand(appPropertyCommand); + RegisterSubCommand(appListPropertiesCommand); + RegisterSubCommand(appActivateCommand); + RegisterSubCommand(appDeactivateCommand); + RegisterSubCommand(appDownloadCommand); + RegisterSubCommand(appInstallCommand); + RegisterSubCommand(appReinstallCommand); + RegisterSubCommand(appUpgradeCommand); + RegisterSubCommand(appUninstallCommand); + RegisterSubCommand(appExecuteCommand); + } + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(Docs.BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command allows interacting with Bench apps.") + .End(Docs.BlockType.Paragraph) + .Paragraph("Use the sub-commands to select the kind of interaction."); + + var commandProperty = new CommandArgument(appPropertyCommand.Name, 'p', "prop"); + commandProperty.Description + .Text("Reads an app property value."); + commandProperty.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appPropertyCommand); + + var commandInfo = new CommandArgument(appInfoCommand.Name, 'i'); + commandInfo.Description + .Text("Shows a detailed, human readable info of an app."); + commandInfo.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appInfoCommand); + + var commandListProperties = new CommandArgument(appListPropertiesCommand.Name, 'l', "list"); + commandListProperties.Description + .Text("Lists the properties of an app."); + commandListProperties.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appListPropertiesCommand); + + var commandActivate = new CommandArgument(appActivateCommand.Name, 'a', "enable"); + commandActivate.Description + .Text("Activates an app."); + commandActivate.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appActivateCommand); + + var commandDeactivate = new CommandArgument(appDeactivateCommand.Name, 'd', "disable"); + commandDeactivate.Description + .Text("Deactivates an app."); + commandDeactivate.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appDeactivateCommand); + + var commandDownload = new CommandArgument(appDownloadCommand.Name, 'c', "cache"); + commandDownload.Description + .Text("Downloads an apps resource."); + commandDownload.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appDownloadCommand); + + var commandInstall = new CommandArgument(appInstallCommand.Name, 's', "setup"); + commandInstall.Description + .Text("Installs an app, regardless of its activation state."); + commandInstall.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appInstallCommand); + + var commandReinstall = new CommandArgument(appReinstallCommand.Name, 'r'); + commandReinstall.Description + .Text("Reinstalls an app."); + commandReinstall.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appReinstallCommand); + + var commandUpgrade = new CommandArgument(appUpgradeCommand.Name, 'u'); + commandUpgrade.Description + .Text("Upgrades an app."); + commandUpgrade.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appUpgradeCommand); + + var commandUninstall = new CommandArgument(appUninstallCommand.Name, 'x', "remove"); + commandUninstall.Description + .Text("Uninstalls an app, regardless of its activation state."); + commandUninstall.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appUninstallCommand); + + var commandExecute = new CommandArgument(appExecuteCommand.Name, 'e', "exec", "launch", "run"); + commandExecute.Description + .Text("Starts an apps main executable."); + commandExecute.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appExecuteCommand); + + parser.RegisterArguments( + commandProperty, + commandInfo, + commandListProperties, + commandActivate, + commandDeactivate, + commandDownload, + commandInstall, + commandReinstall, + commandUpgrade, + commandUninstall, + commandExecute); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppDeactivateCommand.cs b/BenchManager/BenchCLI/Commands/AppDeactivateCommand.cs new file mode 100644 index 00000000..1d02b1b4 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppDeactivateCommand.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppDeactivateCommand : BenchCommand + { + private const string POSITIONAL_APP_ID = "App ID"; + + public override string Name => "deactivate"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command removes an app from the activation list or marks it as deactivated.") + .End(BlockType.Paragraph) + .Begin(BlockType.Paragraph) + .Text("To actually uninstall the app, you have to run the ").Keyword("setup").Text(" command.") + .End(BlockType.Paragraph) + .Begin(BlockType.Paragraph) + .Text("If the app is currently on the activation list, it is removed from it.").LineBreak() + .Text("If the app is required by Bench, or as a dependency, it is marked as deactivated.") + .End(BlockType.Paragraph); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to deactivate."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + positionalAppId); + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + var cfg = LoadConfiguration(); + + if (!cfg.ContainsGroup(appId)) + { + WriteError("The app '{0}' was not found.", appId); + return false; + } + + var activationFile = cfg.GetStringValue(PropertyKeys.AppActivationFile); + if (!File.Exists(activationFile)) + { + WriteError("The activation file for apps was not found."); + WriteLine(" " + activationFile); + return false; + } + WriteDetail("Found activation file: " + activationFile); + var deactivationFile = cfg.GetStringValue(PropertyKeys.AppDeactivationFile); + if (!File.Exists(deactivationFile)) + { + WriteError("The deactivation file for apps was not found."); + WriteLine(" " + deactivationFile); + return false; + } + WriteDetail("Found deactivation file: " + deactivationFile); + var activationList = new ActivationFile(activationFile); + var deactivationList = new ActivationFile(deactivationFile); + + var app = cfg.Apps[appId]; + WriteDetail("App ID: " + appId); + if (app.IsDeactivated) + { + WriteDetail("The app is allready deactivated."); + return true; + } + if (app.IsActivated) + { + WriteDetail("Removing the app from the activation file."); + activationList.SignOut(appId); + } + if (app.IsRequired) + { + WriteDetail("The app is required by Bench."); + if (AskForAssurance("Are you sure you want to deactivate an app, which is required by Bench?")) + { + WriteDetail("Adding the app to the deactivation file."); + deactivationList.SignIn(appId); + return true; + } + else return false; + } + if (app.IsDependency) + { + WriteDetail("The app is required by the following apps: " + string.Join(", ", app.Responsibilities)); + if (AskForAssurance("Are you sure you want to deactivate an app, which is required by another app?")) + { + WriteDetail("Adding the app to the deactivation file."); + deactivationList.SignIn(appId); + return true; + } + else return false; + } + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppDownloadCommand.cs b/BenchManager/BenchCLI/Commands/AppDownloadCommand.cs new file mode 100644 index 00000000..ba52c5e3 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppDownloadCommand.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppDownloadCommand : BenchCommand + { + private const string POSITIONAL_APP_ID = "App ID"; + + public override string Name => "download"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command downloads the app resources, in case it is not cached already.") + .End(BlockType.Paragraph); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to download the resource for."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + positionalAppId); + } + + protected override bool ExecuteCommand(string[] args) + { + return RunManagerTask(mgr => mgr.DownloadAppResource( + Arguments.GetPositionalValue(POSITIONAL_APP_ID))); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppExecuteCommand.cs b/BenchManager/BenchCLI/Commands/AppExecuteCommand.cs new file mode 100644 index 00000000..420f0ef9 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppExecuteCommand.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppExecuteCommand : BenchCommand + { + private const string FLAG_DETACHED = "detached"; + private const string POSITIONAL_APP_ID = "App ID"; + + public override string Name => "execute"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command starts the main executable of the specified app.") + .End(BlockType.Paragraph); + + var flagDetached = new FlagArgument(FLAG_DETACHED, 'd', "async"); + flagDetached.Description + .Text("Do not wait for the end of the process."); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to execute."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + flagDetached, + positionalAppId); + } + + protected override bool ExecuteCommand(string[] args) + { + var cfg = LoadConfiguration(); + + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + + if (!cfg.ContainsGroup(appId)) + { + WriteError("The app '{0}' was not found.", appId); + return false; + } + + var app = cfg.Apps[appId]; + if (app.Exe == null) + { + WriteError("The app '{0}' has no main executable.", app.Label); + return false; + } + WriteDetail("Found apps executable: {0}", app.Exe); + + var detached = Arguments.GetFlag(FLAG_DETACHED); + WriteDetail("Starting app '{0}' {1} ...", app.Label, detached ? "detached" : "synchronously"); + + using (var mgr = new DefaultBenchManager(cfg)) + { + mgr.Verbose = Verbose; + if (detached) + { + mgr.ProcessExecutionHost.StartProcess(mgr.Env, + cfg.BenchRootDir, app.Exe, CommandLine.FormatArgumentList(args), + null, ProcessMonitoring.ExitCode); + return true; + } + else + { + var r = mgr.ProcessExecutionHost.RunProcess(mgr.Env, + cfg.BenchRootDir, app.Exe, CommandLine.FormatArgumentList(args), + ProcessMonitoring.ExitCodeAndOutput); + Console.Write(r.Output); + return r.ExitCode == 0; + } + } + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppInfoCommand.cs b/BenchManager/BenchCLI/Commands/AppInfoCommand.cs new file mode 100644 index 00000000..ed08b22b --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppInfoCommand.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppInfoCommand : BenchCommand + { + private const string OPTION_FORMAT = "format"; + private const string POSITIONAL_APP_ID = "App ID"; + + private const DocumentOutputFormat DEF_FORMAT = DocumentOutputFormat.Plain; + + public override string Name => "info"; + + private DocumentOutputFormat Format = DEF_FORMAT; + + private BenchConfiguration config; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command displayes a detailed description for an app in human readable form.") + .End(BlockType.Paragraph); + + var optionFormat = new EnumOptionArgument( + OPTION_FORMAT, 'f', DEF_FORMAT, "fmt"); + optionFormat.Description + .Text("Specify the output format."); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to display the description for."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + optionFormat, + positionalAppId); + } + + protected override bool ValidateArguments() + { + Format = (DocumentOutputFormat)Enum.Parse(typeof(DocumentOutputFormat), + Arguments.GetOptionValue(OPTION_FORMAT, DEF_FORMAT.ToString()), true); + + return true; + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + + config = LoadConfiguration(); + if (!config.Apps.Exists(appId)) + { + WriteError("Unknown app ID: " + appId); + return false; + } + + var app = config.Apps[appId]; + using (var w = DocumentWriterFactory.Create(Format)) + { + WriteAppInfo(app, w); + } + + return true; + } + + private void WriteAppInfo(AppFacade app, DocumentWriter writer) + { + writer.Begin(BlockType.Document); + writer.Title(app.Label); + writer.Headline2("app_" + app.ID + "_description", "Description"); + writer.Begin(BlockType.List); + WriteProperty(writer, "ID", app.ID, InlineType.Keyword); + WriteProperty(writer, "Label", app.Label); + if (app.IsInstalled && app.Launcher != null) + { + WriteProperty(writer, "Launcher", app.Launcher); + } + WriteProperty(writer, "App Type", app.Typ, InlineType.Keyword); + WriteProperty(writer, "Version", app.Version ?? "latest", InlineType.Keyword); + writer.End(BlockType.List); + writer.Headline2("app_" + app.ID + "_state", "State"); + writer.Paragraph(app.LongStatus); + var dependencies = app.Dependencies; + var responsibilities = app.Responsibilities; + if (dependencies.Length > 0 || responsibilities.Length > 0) + { + writer.Headline2("app_" + app.ID + "_relations", "Relationships"); + writer.Begin(BlockType.List); + if (dependencies.Length > 0) + { + Array.Sort(dependencies); + writer.Begin(BlockType.ListItem) + .Text("Dependencies:") + .Begin(BlockType.List); + foreach (var d in dependencies) + { + writer.Begin(BlockType.ListItem) + .Keyword(d) + .End(BlockType.ListItem); + } + writer.End(BlockType.List); + writer.End(BlockType.ListItem); + } + if (responsibilities.Length > 0) + { + Array.Sort(responsibilities); + writer.Begin(BlockType.ListItem) + .Text("Responsibilities:") + .Begin(BlockType.List); + foreach (var r in app.Responsibilities) + { + writer.Begin(BlockType.ListItem) + .Keyword(r) + .End(BlockType.ListItem); + } + writer.End(BlockType.List); + writer.End(BlockType.ListItem); + } + writer.End(BlockType.List); + } + writer.Headline2("app_" + app.ID + "_paths", "Paths and Resources"); + writer.Begin(BlockType.List); + if (app.IsInstalled) + { + WriteProperty(writer, "Installation Dir", app.Dir, InlineType.Keyword); + WriteProperty(writer, "Main Executable", app.Exe, InlineType.Keyword); + } + if (app.IsResourceCached) + { + WriteProperty(writer, "Cached Resource", + Path.Combine(config.GetStringValue(PropertyKeys.DownloadDir), + app.ResourceArchiveName ?? app.ResourceFileName), + InlineType.Keyword); + } + if (app.Url != null) + { + WriteProperty(writer, "Resource URL", app.Url); + } + writer.End(BlockType.List); + + writer.End(BlockType.Document); + } + + private void WriteProperty(DocumentWriter writer, string key, string value) + { + writer + .Begin(BlockType.ListItem) + .Text(key).Text(": ").Text(value) + .End(BlockType.ListItem); + } + private void WriteProperty(DocumentWriter writer, string key, string value, InlineType type) + { + writer.Begin(BlockType.ListItem); + writer.Text(key).Text(": "); + writer.Inline(type, value); + writer.End(BlockType.ListItem); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppInstallCommand.cs b/BenchManager/BenchCLI/Commands/AppInstallCommand.cs new file mode 100644 index 00000000..f0092b8f --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppInstallCommand.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppInstallCommand : BenchCommand + { + private const string POSITIONAL_APP_ID = "App ID"; + + public override string Name => "install"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command installes the specified app, regardless of its activation state.") + .End(BlockType.Paragraph) + .Paragraph("Missing app resources are downloaded automatically."); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to install."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + positionalAppId); + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + var cfg = LoadConfiguration(); + if (!cfg.Apps.Exists(appId)) + { + WriteError("Unknown app ID: " + appId); + return false; + } + return RunManagerTask(mgr => mgr.InstallApp(appId)); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppListPropertiesCommand.cs b/BenchManager/BenchCLI/Commands/AppListPropertiesCommand.cs new file mode 100644 index 00000000..8d44631f --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppListPropertiesCommand.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppListPropertiesCommand : BenchCommand + { + private const string FLAG_RAW = "raw"; + private const string OPTION_FORMAT = "format"; + private const string POSITIONAL_APP_ID = "App ID"; + + private const DataOutputFormat DEF_FORMAT = DataOutputFormat.Plain; + + public override string Name => "list-properties"; + + private bool ShowRaw => Arguments.GetFlag(FLAG_RAW); + + private DataOutputFormat Format = DataOutputFormat.Plain; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command displayes the properties of an app.") + .End(BlockType.Paragraph) + .Paragraph("This command supports different output formats. " + + "And you can choose between the expanded or the raw properties."); + + var flagRaw = new FlagArgument(FLAG_RAW, 'r'); + flagRaw.Description + .Text("Shows the raw properties without expansion and default values."); + + var optionFormat = new EnumOptionArgument( + OPTION_FORMAT, 'f', DEF_FORMAT, "fmt"); + optionFormat.Description + .Text("Specify the output format."); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app of which the properties are to be listed."); + positionalAppId.PossibleValueInfo + .Text("The apps ID, an alphanumeric string without whitespace."); + + parser.RegisterArguments( + flagRaw, + optionFormat, + positionalAppId); + } + + protected override bool ValidateArguments() + { + Format = (DataOutputFormat)Enum.Parse(typeof(DataOutputFormat), + Arguments.GetOptionValue(OPTION_FORMAT, DEF_FORMAT.ToString()), true); + + return true; + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + + var cfg = LoadConfiguration(); + if (!cfg.Apps.Exists(appId)) + { + WriteError("Unknown app ID: " + appId); + return false; + } + + var app = cfg.Apps[appId]; + if (ShowRaw) + PrintRawProperties(cfg, appId); + else + PrintProperties(app); + return true; + } + + private void PrintProperties(AppFacade app) + { + var knownProperties = app.KnownProperties; + var unknownProperties = app.UnknownProperties; + var lookup = new Dictionary(); + var names = new List(); + foreach (var kvp in knownProperties) + { + lookup[kvp.Key] = kvp.Value; + if (!names.Contains(kvp.Key)) names.Add(kvp.Key); + } + foreach (var kvp in unknownProperties) + { + lookup[kvp.Key] = kvp.Value; + if (!names.Contains(kvp.Key)) names.Add(kvp.Key); + } + names.Sort(); + + using (var w = MapWriterFactory.Create(Format)) + { + w.Write("ID", app.ID); + foreach (var p in names) + { + w.Write(p, lookup[p]); + } + } + } + + private void PrintRawProperties(BenchConfiguration cfg, string appId) + { + using (var w = MapWriterFactory.Create(Format)) + { + w.Write("ID", appId); + foreach (var name in cfg.PropertyNames(appId)) + { + w.Write(name, cfg.GetRawGroupValue(appId, name)); + } + } + } + + } +} diff --git a/BenchManager/BenchCLI/Commands/AppPropertyCommand.cs b/BenchManager/BenchCLI/Commands/AppPropertyCommand.cs new file mode 100644 index 00000000..89ee39ee --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppPropertyCommand.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppPropertyCommand : BenchCommand + { + private const string POSITIONAL_APP_ID = "App ID"; + private const string POSITIONAL_PROPERTY_NAME = "Property Name"; + + public override string Name => "property"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command reads the value of an app property.") + .End(BlockType.Paragraph); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to get the property from."); + positionalAppId.PossibleValueInfo + .Text("The apps ID, an alphanumeric string without whitespace."); + + var positionalPropertyName = new PositionalArgument(POSITIONAL_PROPERTY_NAME, + ArgumentValidation.IsIdString, + 2); + positionalPropertyName.Description + .Text("Specifies the property to read."); + positionalPropertyName.PossibleValueInfo + .Text("The property name, an alphanumeric string without whitespace."); + + parser.RegisterArguments( + positionalAppId, + positionalPropertyName); + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + var propertyName = Arguments.GetPositionalValue(POSITIONAL_PROPERTY_NAME); + + var cfg = LoadConfiguration(); + if (!cfg.Apps.Exists(appId)) + { + WriteError("Unknown app ID: " + appId); + return false; + } + WriteDetail("App ID: " + appId); + WriteDetail("Property: " + propertyName); + PropertyWriter.WritePropertyValue(cfg.GetGroupValue(appId, propertyName)); + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppReinstallCommand.cs b/BenchManager/BenchCLI/Commands/AppReinstallCommand.cs new file mode 100644 index 00000000..1a15b897 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppReinstallCommand.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppReinstallCommand : BenchCommand + { + private const string POSITIONAL_APP_ID = "App ID"; + + public override string Name => "reinstall"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command reinstalles the specified app.") + .End(BlockType.Paragraph); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to reinstall."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + positionalAppId); + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + var cfg = LoadConfiguration(); + if (!cfg.Apps.Exists(appId)) + { + WriteError("Unknown app ID: " + appId); + return false; + } + return RunManagerTask(mgr => mgr.ReinstallApp(appId)); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppUninstallCommand.cs b/BenchManager/BenchCLI/Commands/AppUninstallCommand.cs new file mode 100644 index 00000000..35be2687 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppUninstallCommand.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppUninstallCommand : BenchCommand + { + private const string POSITIONAL_APP_ID = "App ID"; + + public override string Name => "uninstall"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command uninstalles the specified app, regardless of its activation state.") + .End(BlockType.Paragraph); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to install."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + positionalAppId); + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + var cfg = LoadConfiguration(); + if (!cfg.Apps.Exists(appId)) + { + WriteError("Unknown app ID: " + appId); + return false; + } + return RunManagerTask(mgr => mgr.UninstallApp(appId)); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AppUpgradeCommand.cs b/BenchManager/BenchCLI/Commands/AppUpgradeCommand.cs new file mode 100644 index 00000000..50f110d7 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AppUpgradeCommand.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AppUpgradeCommand : BenchCommand + { + private const string POSITIONAL_APP_ID = "App ID"; + + public override string Name => "upgrade"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command upgrades the specified app to the most current release.") + .End(BlockType.Paragraph) + .Paragraph("Updates app resources are downloaded automatically."); + + var positionalAppId = new PositionalArgument(POSITIONAL_APP_ID, + ArgumentValidation.IsIdString, + 1); + positionalAppId.Description + .Text("Specifies the app to upgrade."); + positionalAppId.PossibleValueInfo + .Text("An app ID is an alphanumeric string without whitespace."); + + parser.RegisterArguments( + positionalAppId); + } + + protected override bool ExecuteCommand(string[] args) + { + var appId = Arguments.GetPositionalValue(POSITIONAL_APP_ID); + var cfg = LoadConfiguration(); + if (!cfg.Apps.Exists(appId)) + { + WriteError("Unknown app ID: " + appId); + return false; + } + return RunManagerTask(mgr => mgr.UpgradeApp(appId)); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/AutoSetupCommand.cs b/BenchManager/BenchCLI/Commands/AutoSetupCommand.cs new file mode 100644 index 00000000..ac517821 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/AutoSetupCommand.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class AutoSetupCommand : BenchCommand + { + public override string Name => "setup"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command runs the auto-setup for the active Bench apps.") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + => RunManagerTask(mgr => mgr.AutoSetup()); + } +} diff --git a/BenchManager/BenchCLI/Commands/BenchCommand.cs b/BenchManager/BenchCLI/Commands/BenchCommand.cs new file mode 100644 index 00000000..b07b71ad --- /dev/null +++ b/BenchManager/BenchCLI/Commands/BenchCommand.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + abstract class BenchCommand : CommandBase + { + #region Bubbeling Properties + + private string rootPath; + + public string RootPath + { + get { return (Parent as BenchCommand)?.RootPath ?? rootPath; } + set { rootPath = value; } + } + + private string logFile; + + public string LogFile + { + get { return (Parent as BenchCommand)?.LogFile ?? logFile; } + set { logFile = value; } + } + + #endregion + + protected static string BenchBinDirPath() + { + return Path.GetDirectoryName(Program.CliExecutable()); + } + + protected static string DefaultRootPath() + { + var rootPath = Path.GetFullPath(Path.Combine(Path.Combine(BenchBinDirPath(), ".."), "..")); + return BenchConfiguration.IsValidBenchRoot(rootPath) ? rootPath : null; + } + + protected static string DashboardExecutable(string rootDir = null) + { + if (!BenchTasks.IsDashboardSupported) return null; + var path = rootDir != null + ? Path.Combine(Path.Combine(Path.Combine(rootDir, "auto"), "bin"), "BenchDashboard.exe") + : Path.Combine(BenchBinDirPath(), "BenchDashboard.exe"); + return File.Exists(path) ? path : null; + } + + #region Task Helper + + protected BenchConfiguration LoadConfiguration(bool withApps = true) + { + var cfg = new BenchConfiguration(RootPath, withApps, true, true); + if (LogFile != null) cfg.SetValue(PropertyKeys.LogFile, LogFile); + return cfg; + } + + protected DefaultBenchManager CreateManager() + { + return new DefaultBenchManager(LoadConfiguration()) + { + Verbose = Verbose + }; + } + + protected bool RunManagerTask(ManagerTask task) + { + using (var mgr = CreateManager()) + { + return task(mgr); + } + } + + #endregion + } + + delegate bool ManagerTask(DefaultBenchManager mgr); +} diff --git a/BenchManager/BenchCLI/Commands/ConfigCommand.cs b/BenchManager/BenchCLI/Commands/ConfigCommand.cs new file mode 100644 index 00000000..381567b1 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ConfigCommand.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ConfigCommand : BenchCommand + { + public override string Name => "config"; + + private readonly BenchCommand getCommand = new ConfigGetCommand(); + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command gives access to the Bench user configuration.") + .End(BlockType.Paragraph); + + var commandGet = new CommandArgument(getCommand.Name, 'g', "read"); + commandGet.Description + .Text("Reads a configuration value."); + commandGet.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, getCommand); + + parser.RegisterArguments( + commandGet); + } + + public ConfigCommand() + { + RegisterSubCommand(getCommand); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/ConfigGetCommand.cs b/BenchManager/BenchCLI/Commands/ConfigGetCommand.cs new file mode 100644 index 00000000..873d1214 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ConfigGetCommand.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ConfigGetCommand : BenchCommand + { + private static string POSITIONAL_PROPERTY_NAME = "property-name"; + + public override string Name => "get"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command reads a configuration value.") + .End(BlockType.Paragraph); + + var positionalPropertyName = new PositionalArgument(POSITIONAL_PROPERTY_NAME, + null, 1); + positionalPropertyName.Description + .Text("The name of the configuration property to read."); + + parser.RegisterArguments( + positionalPropertyName); + } + + protected override bool ExecuteCommand(string[] args) + { + var propertyName = Arguments.GetPositionalValue(POSITIONAL_PROPERTY_NAME); + + var cfg = LoadConfiguration(false); + if (!cfg.ContainsValue(propertyName)) + { + WriteError("Unknown property name: " + propertyName); + return false; + } + WriteDetail("Property: " + propertyName); + PropertyWriter.WritePropertyValue(cfg.GetValue(propertyName)); + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/DashboardCommand.cs b/BenchManager/BenchCLI/Commands/DashboardCommand.cs new file mode 100644 index 00000000..005bb9f6 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/DashboardCommand.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class DashboardCommand : BenchCommand + { + public override string Name => "dashboard"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command starts the graphical user interface ") + .Emph("Bench Dashboard").Text(".") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + { + var path = DashboardExecutable(RootPath); + if (path == null || !File.Exists(path)) + { + WriteError("Could not find the executable of the Bench Dashboard."); + return false; + } + + System.Diagnostics.Process.Start(path); + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/DownloadsCommand.cs b/BenchManager/BenchCLI/Commands/DownloadsCommand.cs new file mode 100644 index 00000000..762a1365 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/DownloadsCommand.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class DownloadsCommand : BenchCommand + { + private const string COMMAND_CLEAN = "clean"; + private const string COMMAND_PURGE = "purge"; + private const string COMMAND_DOWNLOAD = "download"; + + public override string Name => "downloads"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command manages the cached app resources.") + .End(BlockType.Paragraph); + + var commandClean = new CommandArgument(COMMAND_CLEAN, 'c', "cl"); + commandClean.Description + .Text("Deletes obsolete app resources."); + + var commandPurge = new CommandArgument(COMMAND_PURGE, 'x'); + commandPurge.Description + .Text("Deletes all cached app resources."); + + var commandDownload = new CommandArgument(COMMAND_DOWNLOAD, 'd', "dl"); + commandDownload.Description + .Text("Downloads the app resources for all active apps."); + + parser.RegisterArguments( + commandClean, + commandPurge, + commandDownload); + } + + protected override bool ExecuteUnknownSubCommand(string command, string[] args) + { + switch (command) + { + case COMMAND_CLEAN: + return TaskClean(args); + case COMMAND_PURGE: + return TaskPurge(args); + case COMMAND_DOWNLOAD: + return TaskDownload(args); + + default: + WriteError("Unsupported command: " + command + "."); + return false; + } + } + + private bool TaskClean(string[] args) + => RunManagerTask(mgr => mgr.CleanUpAppResources()); + + private bool TaskPurge(string[] args) + { + if (!AskForAssurance("Are you sure, you want to delete all downloaded app resources?")) + { + return false; + } + return RunManagerTask(mgr => mgr.DeleteAppResources()); + } + + private bool TaskDownload(string[] args) + => RunManagerTask(mgr => mgr.DownloadAppResources()); + } +} diff --git a/BenchManager/BenchCLI/Commands/HelpCommand.cs b/BenchManager/BenchCLI/Commands/HelpCommand.cs new file mode 100644 index 00000000..c4762124 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/HelpCommand.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class HelpCommand : BenchCommand + { + private const string FLAG_NO_TITLE = "no-title"; + private const string FLAG_NO_VERSION = "no-version"; + private const string FLAG_NO_INDEX = "no-index"; + private const string FLAG_APPEND = "append"; + private const string OPTION_TARGET_FILE = "target-file"; + + public override string Name => "help"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command displays the full help for all commands.") + .End(BlockType.Paragraph); + + var flagNoTitle = new FlagArgument(FLAG_NO_TITLE, 't'); + flagNoTitle.Description + .Text("Suppress the output of the tool name as the document title."); + + var flagNoVersion = new FlagArgument(FLAG_NO_VERSION, 'v'); + flagNoVersion.Description + .Text("Suppress the output of the tool version number."); + + var flagNoIndex = new FlagArgument(FLAG_NO_INDEX, 'i'); + flagNoIndex.Description + .Text("Suppress the index of the commands."); + + var flagAppend = new FlagArgument(FLAG_APPEND, 'a'); + flagAppend.Description + .Text("Append to an existing file, in case a target file is specified."); + + var optionTargetFile = new OptionArgument(OPTION_TARGET_FILE, 'o', + ArgumentValidation.IsValidPath, + "out"); + optionTargetFile.Description + .Text("Specifies a target file to write the help content to."); + optionTargetFile.PossibleValueInfo + .Text("A path to a writable file. The target file will be created or overridden."); + optionTargetFile.DefaultValueInfo + .Text("None"); + + parser.RegisterArguments( + flagNoTitle, + flagNoVersion, + flagNoIndex, + flagAppend, + optionTargetFile); + } + + private bool NoTitle => Arguments.GetFlag(FLAG_NO_TITLE); + private bool NoVersion => Arguments.GetFlag(FLAG_NO_VERSION); + private bool NoIndex => Arguments.GetFlag(FLAG_NO_INDEX); + private bool Append => Arguments.GetFlag(FLAG_APPEND); + private string TargetFile => Arguments.GetOptionValue(OPTION_TARGET_FILE); + + protected override bool ExecuteCommand(string[] args) + { + var targetFile = TargetFile; + Stream s = null; + if (targetFile != null) + { + try + { + s = File.Open(targetFile, Append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read); + } + catch (IOException exc) + { + WriteError("Failed to open the target file: " + exc.Message); + return false; + } + } + using (var w = DocumentWriterFactory.Create(HelpFormat, s)) + { + RootCommand.PrintFullHelp(w, !NoTitle, !NoVersion, !NoIndex); + } + if (s != null) s.Close(); + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/InitializeCommand.cs b/BenchManager/BenchCLI/Commands/InitializeCommand.cs new file mode 100644 index 00000000..c1c38c7a --- /dev/null +++ b/BenchManager/BenchCLI/Commands/InitializeCommand.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class InitializeCommand : BenchCommand + { + public override string Name => "initialize"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command initializes the Bench configuration and starts the setup process.") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + { + BenchConfiguration cfgWithSite, cfgWithCoreApps, cfgWithCustom; + + // 1. Initialize the site configuration, possibly with HTTP(S) proxy + cfgWithSite = BenchTasks.InitializeSiteConfiguration(RootPath); + if (cfgWithSite == null) + { + WriteInfo("Initialization canceled."); + return false; + } + + // Create a manager object to get a download manager + using (var mgrWithSite = new DefaultBenchManager(cfgWithSite)) + { + mgrWithSite.Verbose = Verbose; + // 2. Download the app libraries, listed in the Bench system and site configuration + if (!mgrWithSite.LoadAppLibraries()) + { + WriteError("Loading the core app libraries failed."); + return false; + } + } // dispose the manager object + + // Reload the configuration with the core app libraries + cfgWithCoreApps = new BenchConfiguration(RootPath, true, false, true); + cfgWithSite.InjectBenchInitializationProperties(cfgWithCoreApps); + cfgWithSite = null; + + // Create a manager object to get an execution host + using (var mgrWithCoreApps = new DefaultBenchManager(cfgWithCoreApps)) + { + cfgWithCoreApps = null; + mgrWithCoreApps.Verbose = Verbose; + // 3. Download and install required apps from the core app library + if (!mgrWithCoreApps.SetupRequiredApps()) + { + WriteError("Initial app setup failed."); + return false; + } + + // 4. Initialize the user configuration and reload the Bench configuration + cfgWithCustom = BenchTasks.InitializeCustomConfiguration(mgrWithCoreApps); + if (cfgWithCustom == null) + { + WriteInfo("Initialization canceled."); + return false; + } + } // dispose the manager object + + // Create a manager object to get a download manager + using (var mgrWithCustom = new DefaultBenchManager(cfgWithCustom)) + { + mgrWithCustom.Verbose = Verbose; + // 5. Download the app libraries, listed in the custom configuration + if (!mgrWithCustom.LoadAppLibraries()) + { + WriteError("Loading the app libraries failed."); + return false; + } + } + + // Check if the auto setup should be started right now + var autoSetup = cfgWithCustom.GetBooleanValue(PropertyKeys.WizzardStartAutoSetup, true); + + var dashboardPath = DashboardExecutable(); + if (dashboardPath != null) + { + // Kick-off the auto setup with the GUI + var arguments = string.Format("-root \"{0}\"", RootPath); + if (autoSetup) + { + arguments += " -setup"; + } + var pi = new ProcessStartInfo() + { + FileName = dashboardPath, + Arguments = arguments, + UseShellExecute = false + }; + System.Diagnostics.Process.Start(pi); + return true; + } + else if (autoSetup) + { + // Kick-off the auto setup with the CLI + return RunManagerTask(m => m.AutoSetup()); + } + else + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/ListAppLibrariesCommand.cs b/BenchManager/BenchCLI/Commands/ListAppLibrariesCommand.cs new file mode 100644 index 00000000..163256f9 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ListAppLibrariesCommand.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ListAppLibrariesCommand : BenchCommand + { + public override string Name => "applibs"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command lists all loaded app libraries.") + .End(BlockType.Paragraph); + } + + private DataOutputFormat Format => ((ListCommand)Parent).Format; + + private bool OutputAsTable => ((ListCommand)Parent).OutputAsTable; + + protected override bool ExecuteCommand(string[] args) + { + var cfg = LoadConfiguration(withApps: true); + var appLibs = cfg.AppLibraries; + if (OutputAsTable) + { + using (var w = TableWriterFactory.Create(Format)) + { + w.Initialize(new[] { "Order", "ID", "Path", "URL" }); + for (int i = 0; i < appLibs.Length; i++) + { + var l = appLibs[i]; + w.Write((i + 1).ToString().PadLeft(5), l.ID, l.BaseDir, l.Url.OriginalString); + } + } + } + else + { + foreach (var l in appLibs) + { + Console.WriteLine("{0}={1}", l.ID, l.Url); + } + } + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/ListAppsCommand.cs b/BenchManager/BenchCLI/Commands/ListAppsCommand.cs new file mode 100644 index 00000000..5525b99f --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ListAppsCommand.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ListAppsCommand : BenchCommand + { + public override string Name => "apps"; + + public enum AppSet + { + All, + Active, + NotActive, + Activated, + Deactivated, + Installed, + NotInstalled, + Cached, + NotCached, + DefaultApps, + MetaApps, + ManagedPackages, + } + + private const string OPTION_SET = "set"; + private const string OPTION_PROPERTIES = "properties"; + private const string OPTION_FILTER = "filter"; + private const string OPTION_SORT_BY = "sort-by"; + + private static readonly AppSet DEF_SET = AppSet.All; + private static readonly string DEF_PROPERTIES = string.Join(",", + new[] { "ID", PropertyKeys.AppLabel, PropertyKeys.AppVersion, PropertyKeys.AppIsActive }); + private static readonly string DEF_FILTER = string.Empty; + private static readonly string DEF_SORT_BY = "ID"; + + public AppSet Set + => (AppSet)Enum.Parse( + typeof(AppSet), + Arguments.GetOptionValue(OPTION_SET, DEF_SET.ToString()), + true); + + public string[] TableProperties + => Arguments.GetOptionValue(OPTION_PROPERTIES, DEF_PROPERTIES) + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + public string[] Filter + => Arguments.GetOptionValue(OPTION_FILTER, DEF_FILTER) + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + public string SortBy + => Arguments.GetOptionValue(OPTION_SORT_BY, DEF_SORT_BY); + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command lists apps from the app library.") + .End(BlockType.Paragraph) + .Paragraph("You can specify the base set of apps and filter the apps to list."); + + var optionSet = new EnumOptionArgument(OPTION_SET, 's', DEF_SET); + optionSet.Description + .Text("Specifies the set of apps to list."); + + var optionProperties = new OptionArgument(OPTION_PROPERTIES, 'p', null); + optionProperties.Description + .Text("Specifies the properties to display in the listed output.") + .Text(" This option only has an effect, if the flag ") + .Keyword(Parent.Name).Text(" ").Keyword("--table").Text(" is set."); + optionProperties.PossibleValueInfo + .Text("A comma separated list of property names."); + + var optionFilter = new OptionArgument(OPTION_FILTER, 'f', null); + optionFilter.Description + .Text("Specifies a filter to reduce the number of listed apps."); + optionFilter.PossibleValueInfo + .Text("A comma separated list of criteria.") + .Text(" E.g. ").Code("ID=JDK*,!IsInstalled,IsCached").Text("."); + optionFilter.DefaultValueInfo + .Text("no filter"); + + var optionSortBy = new OptionArgument(OPTION_SORT_BY, 'o', null); + optionSortBy.Description + .Text("Specifies a property to sort the apps by."); + optionSortBy.PossibleValueInfo + .Text("The name of an app property."); + optionSortBy.DefaultValueInfo + .Text("ID"); + + parser.RegisterArguments( + optionSet, + optionProperties, + optionFilter, + optionSortBy); + } + + private DataOutputFormat Format => ((ListCommand)Parent).Format; + + private bool OutputAsTable => ((ListCommand)Parent).OutputAsTable; + + protected override bool ExecuteCommand(string[] args) + { + var cfg = LoadConfiguration(); + var apps = new List>(); + var set = Set; + var filter = Filter; + var sortBy = SortBy; + foreach (var app in cfg.Apps) + { + if (!IsIncludedInSet(app, set)) continue; + var props = GetProperties(app); + var match = true; + foreach (var f in filter) + { + if (!MatchesFilter(f, props)) + { + match = false; + break; + } + } + if (match) apps.Add(props); + } + apps.Sort((o1, o2) => + { + object v1 = null; + object v2 = null; + o1.TryGetValue(sortBy, out v1); + o2.TryGetValue(sortBy, out v2); + if (v1 == null && v2 == null) return 0; + if (v1 == null) return -1; + if (v2 == null) return 1; + if (v1 is bool) return ((bool)v1).CompareTo((bool)v2); + if (v1 is string) return ((string)v1).CompareTo((string)v2); + return 0; + }); + if (OutputAsTable) + { + using (var w = TableWriterFactory.Create(Format)) + { + w.Initialize(TableProperties); + foreach (var app in apps) + { + var values = new List(); + foreach (var item in TableProperties) + { + object v; + values.Add(app.TryGetValue(item, out v) ? v : null); + } + w.Write(values.ToArray()); + } + } + } + else + { + foreach (var app in apps) + { + WriteLine((string)app["ID"]); + } + } + return true; + } + + private Dictionary GetProperties(AppFacade app) + { + var result = new Dictionary(); + result["ID"] = app.ID; + foreach (var kvp in app.KnownProperties) + { + result[kvp.Key] = kvp.Value; + } + foreach (var kvp in app.UnknownProperties) + { + result[kvp.Key] = kvp.Value; + } + return result; + } + + private bool IsIncludedInSet(AppFacade app, AppSet set) + { + switch (set) + { + case AppSet.All: return true; + case AppSet.Active: return app.IsActive; + case AppSet.NotActive: return !app.IsActive; + case AppSet.Activated: return app.IsActivated; + case AppSet.Deactivated: return app.IsDeactivated; + case AppSet.Installed: return app.IsInstalled; + case AppSet.NotInstalled: return !app.IsInstalled; + case AppSet.Cached: return app.IsResourceCached; + case AppSet.NotCached: return app.HasResource && !app.IsResourceCached; + case AppSet.DefaultApps: return app.Typ == AppTyps.Default; + case AppSet.MetaApps: return app.Typ == AppTyps.Meta; + case AppSet.ManagedPackages: return app.IsManagedPackage; + default: throw new NotSupportedException(); + } + } + + private Regex CreatePattern(string pattern) + { + var parts = pattern.Split(new[] { '*' }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < parts.Length; i++) + { + parts[i] = Regex.Escape(parts[i]); + } + var pattern2 = string.Join(".*", parts); + if (!pattern.StartsWith("*")) pattern2 = "^" + pattern2; + if (!pattern.EndsWith("*")) pattern2 = pattern2 + "$"; + return new Regex(pattern2); + } + + private bool MatchesFilter(string filter, Dictionary properties) + { + if (filter.Contains("=")) + { + var equalPos = filter.IndexOf("="); + var property = filter.Substring(0, equalPos).Trim(); + var pattern = filter.Substring(equalPos + 1).Trim(); + object v; + if (!properties.TryGetValue(property, out v)) return false; + var text = v as string; + if (string.IsNullOrEmpty(text)) return pattern.Length == 0 || pattern == "*"; + return CreatePattern(pattern).IsMatch(text); + } + else + { + var neg = filter.StartsWith("!"); + if (neg) filter = filter.Substring(1); + object v; + if (properties.TryGetValue(filter, out v)) + { + return (v is bool) && (neg ? !((bool)v) : (bool)v); + } + return false; + } + } + } +} diff --git a/BenchManager/BenchCLI/Commands/ListCommand.cs b/BenchManager/BenchCLI/Commands/ListCommand.cs new file mode 100644 index 00000000..c443c226 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ListCommand.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ListCommand : BenchCommand + { + public override string Name => "list"; + + private const string FLAG_TABLE = "table"; + private const string OPTION_FORMAT = "format"; + + private const DataOutputFormat DEF_FORMAT = DataOutputFormat.Plain; + + private readonly BenchCommand listConfigFilesCommand = new ListConfigFilesCommand(); + private readonly BenchCommand listAppLibsCommand = new ListAppLibrariesCommand(); + private readonly BenchCommand listAppsCommand = new ListAppsCommand(); + + public ListCommand() + { + RegisterSubCommand(listConfigFilesCommand); + RegisterSubCommand(listAppLibsCommand); + RegisterSubCommand(listAppsCommand); + } + + public DataOutputFormat Format + => (DataOutputFormat)Enum.Parse( + typeof(DataOutputFormat), + Arguments.GetOptionValue(OPTION_FORMAT, DEF_FORMAT.ToString()), + true); + + public bool OutputAsTable => Arguments.GetFlag(FLAG_TABLE); + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command lists different kinds of objects from the Bench environment.") + .End(BlockType.Paragraph) + .Begin(BlockType.Paragraph) + .Text("Choose a sub-command to specify the kind of object, you want to list.") + .End(BlockType.Paragraph); + + var flagTable = new FlagArgument(FLAG_TABLE, 't'); + flagTable.Description + .Text("Prints properties of the listed objects as a table.") + .Text(" Otherwise only a short form is printed."); + + var optionFormat = new EnumOptionArgument(OPTION_FORMAT, 'f', DEF_FORMAT); + optionFormat.Description + .Text("Specifies the output format of the listed data."); + + var commandListFiles = new CommandArgument(listConfigFilesCommand.Name, 'f'); + commandListFiles.Description + .Text("List configuration and app library index files."); + + var commandListAppLibs = new CommandArgument(listAppLibsCommand.Name, 'l'); + commandListAppLibs.Description + .Text("List app libraries with ID and URL."); + + var commandListApps = new CommandArgument(listAppsCommand.Name, 'a'); + commandListApps.Description + .Text("List apps from the app library."); + + parser.RegisterArguments( + flagTable, + optionFormat, + commandListFiles, + commandListAppLibs, + commandListApps); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/ListConfigFilesCommand.cs b/BenchManager/BenchCLI/Commands/ListConfigFilesCommand.cs new file mode 100644 index 00000000..be8c0c9f --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ListConfigFilesCommand.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ListConfigFilesCommand : BenchCommand + { + public override string Name => "files"; + + private const string OPTION_TYPE = "type"; + private const ConfigurationFileType DEF_TYPE = ConfigurationFileType.All; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command lists the paths of all loaded configuration files.") + .End(BlockType.Paragraph); + + var optionType = new EnumOptionArgument(OPTION_TYPE, 't', ConfigurationFileType.All); + optionType.Description + .Text("Specify the type of files to show."); + + parser.RegisterArguments( + optionType); + } + + private ConfigurationFileType Type => (ConfigurationFileType)Enum.Parse(typeof(ConfigurationFileType), + Arguments.GetOptionValue(OPTION_TYPE, DEF_TYPE.ToString())); + + private DataOutputFormat Format => ((ListCommand)Parent).Format; + + private bool OutputAsTable => ((ListCommand)Parent).OutputAsTable; + + protected override bool ExecuteCommand(string[] args) + { + var cfg = LoadConfiguration(withApps: true); + var files = cfg.GetConfigurationFiles(Type, actuallyLoaded: true); + if (OutputAsTable) + { + using (var w = TableWriterFactory.Create(Format)) + { + w.Initialize(new[] { "Order", "Type", "Path" }); + foreach (var f in files) + { + w.Write(f.OrderIndex.ToString().PadLeft(5), f.Type.ToString(), f.Path); + } + } + } + else + { + foreach (var f in files) + { + Console.WriteLine(f.Path); + } + } + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/LoadAppLibrariesCommand.cs b/BenchManager/BenchCLI/Commands/LoadAppLibrariesCommand.cs new file mode 100644 index 00000000..d1c4efd7 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/LoadAppLibrariesCommand.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class LoadAppLibraries : BenchCommand + { + private const string FLAG_UPDATE = "update"; + + public override string Name => "load-app-libs"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command loads missing app libraries.") + .End(BlockType.Paragraph); + + var updateFlag = new FlagArgument(FLAG_UPDATE, 'u'); + updateFlag.Description + .Text("Clears the cache and loads the latest version of all app libraries."); + + parser.RegisterArguments( + updateFlag); + } + + protected override bool ExecuteCommand(string[] args) + { + using (var mgr = CreateManager()) + { + BenchTasks.DeleteAppLibraries(mgr.Config); + return mgr.LoadAppLibraries(); + } + } + } +} diff --git a/BenchManager/BenchCLI/Commands/ManageCommand.cs b/BenchManager/BenchCLI/Commands/ManageCommand.cs new file mode 100644 index 00000000..de332bdb --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ManageCommand.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ManageCommand : BenchCommand + { + public override string Name => "manage"; + + private readonly BenchCommand configCommand = new ConfigCommand(); + private readonly BenchCommand initializeCommand = new InitializeCommand(); + private readonly BenchCommand loadAppLibsCommand = new LoadAppLibraries(); + private readonly BenchCommand autoSetupCommand = new AutoSetupCommand(); + private readonly BenchCommand updateEnvCommand = new UpdateEnvironmentCommand(); + private readonly BenchCommand downloadsCommand = new DownloadsCommand(); + private readonly BenchCommand reinstallCommand = new ReinstallCommand(); + private readonly BenchCommand renewCommand = new RenewCommand(); + private readonly BenchCommand updateCommand = new UpdateCommand(); + private readonly BenchCommand upgradeCommand = new UpgradeCommand(); + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command manages the Bench environment via a number of sub-commands.") + .End(BlockType.Paragraph); + + var commandConfig = new CommandArgument(configCommand.Name, 'c', "cfg"); + commandConfig.Description + .Text("Read or write values from the user configuration."); + commandConfig.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, configCommand); + + var commandInitialize = new CommandArgument(initializeCommand.Name, 'i', "init"); + commandInitialize.Description + .Text("Initialize the Bench configuration and start the setup process."); + + var commandLoadAppLibs = new CommandArgument(loadAppLibsCommand.Name, 'l'); + commandLoadAppLibs.Description + .Text("Load the latest app libraries."); + commandLoadAppLibs.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, loadAppLibsCommand); + + var commandSetup = new CommandArgument(autoSetupCommand.Name, 's'); + commandSetup.Description + .Text("Run the auto-setup for the active Bench apps."); + + var commandUpdateEnv = new CommandArgument(updateEnvCommand.Name, 'e'); + commandUpdateEnv.Description + .Text("Update the paths in the Bench environment."); + + var commandDownloads = new CommandArgument(downloadsCommand.Name, 'd', "cache", "dl"); + commandDownloads.Description + .Text("Manage the app resource cache."); + commandDownloads.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, downloadsCommand); + + var commandReinstall = new CommandArgument(reinstallCommand.Name, 'r'); + commandReinstall.Description + .Text("Remove all installed apps, then install all active apps."); + + var commandRenew = new CommandArgument(renewCommand.Name, 'n'); + commandRenew.Description + .Text("Redownload all app resources, remove all installed apps, then install all active apps."); + + var commandUpdate = new CommandArgument(updateCommand.Name, 'u'); + commandUpdate.Description + .Text("Update the app libraries and upgrades all apps."); + + var commandUpgrade = new CommandArgument(upgradeCommand.Name, 'g'); + commandUpgrade.Description + .Text("Download and extract the latest Bench release, then run the auto-setup."); + + parser.RegisterArguments( + commandConfig, + commandInitialize, + commandSetup, + commandLoadAppLibs, + commandUpdateEnv, + commandReinstall, + commandRenew, + commandUpdate, + commandUpgrade); + } + + public ManageCommand() + { + RegisterSubCommand(configCommand); + RegisterSubCommand(initializeCommand); + RegisterSubCommand(loadAppLibsCommand); + RegisterSubCommand(autoSetupCommand); + RegisterSubCommand(updateEnvCommand); + RegisterSubCommand(downloadsCommand); + RegisterSubCommand(reinstallCommand); + RegisterSubCommand(renewCommand); + RegisterSubCommand(updateCommand); + RegisterSubCommand(upgradeCommand); + } + } +} diff --git a/BenchManager/BenchCLI/Commands/ProjectCommand.cs b/BenchManager/BenchCLI/Commands/ProjectCommand.cs new file mode 100644 index 00000000..19af1765 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ProjectCommand.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ProjectCommand : BenchCommand + { + public override string Name => "project"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword("project").Text(" command allows you") + .Text(" to perform certain tasks on projects in the Bench environment.") + .End(BlockType.Paragraph) + .Begin(BlockType.Paragraph) + .Strong("WARNING: This command is not implemented yet.") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + { + WriteError("This command is not implemented yet."); + return false; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/ReinstallCommand.cs b/BenchManager/BenchCLI/Commands/ReinstallCommand.cs new file mode 100644 index 00000000..438d3fe4 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/ReinstallCommand.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class ReinstallCommand : BenchCommand + { + public override string Name => "reinstall"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command removes all installed apps, then installs all active apps.") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + => RunManagerTask(mgr => mgr.ReinstallApps()); + } +} diff --git a/BenchManager/BenchCLI/Commands/RenewCommand.cs b/BenchManager/BenchCLI/Commands/RenewCommand.cs new file mode 100644 index 00000000..c4659826 --- /dev/null +++ b/BenchManager/BenchCLI/Commands/RenewCommand.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class RenewCommand : BenchCommand + { + public override string Name => "renew"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command redownloads all app resources, removes all installed apps, then installs all active apps.") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + => RunManagerTask(mgr => mgr.UpgradeApps()); + } +} diff --git a/BenchManager/BenchCLI/Commands/RootCommand.cs b/BenchManager/BenchCLI/Commands/RootCommand.cs new file mode 100644 index 00000000..fcafe88e --- /dev/null +++ b/BenchManager/BenchCLI/Commands/RootCommand.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class RootCommand : BenchCommand + { + private const string FLAG_VERBOSE = "verbose"; + private const string FLAG_YES = "yes"; + private const string OPTION_HELP_FORMAT = "help-format"; + private const string OPTION_LOGFILE = "logfile"; + private const string OPTION_BENCH_ROOT = "root"; + + private readonly BenchCommand helpCommand = new HelpCommand(); + private readonly BenchCommand listCommand = new ListCommand(); + private readonly BenchCommand dashboardCommand = new DashboardCommand(); + private readonly BenchCommand manageCommand = new ManageCommand(); + private readonly BenchCommand appCommand = new AppCommand(); + private readonly BenchCommand projectCommand = new ProjectCommand(); + + public override string Name + => Assembly.GetExecutingAssembly() + .GetName().Name.ToLowerInvariant(); + + private const DocumentOutputFormat DEF_HELP_FORMAT = DocumentOutputFormat.Plain; + + public RootCommand() + { + ToolName = "Bench CLI"; + ToolVersion = Program.Version(); + ToolDescription + .Begin(BlockType.Paragraph) + .Text("The ").Emph("Bench CLI") + .Text(" allows to interact with a Bench environment on the command line.") + .End(BlockType.Paragraph) + .Begin(BlockType.Paragraph) + .Text("It supports a hierarchy of sub-commands with flags and options,") + .Text(" which can be specified as command line arguments.").LineBreak() + .Text("Additionally it supports an ").Emph("interactive mode") + .Text(" when called without a sub-command specified.").LineBreak() + .Text("Help texts can be displayed for each sub-command") + .Text(" with the ").Keyword("-?").Text(" argument.") + .Text(" The help texts can be printed in ").Emph("different formats").Text(".") + .End(BlockType.Paragraph) + .Begin(BlockType.Paragraph) + .Text("Take a look at ") + .Link("http://mastersign.github.io/bench", "the project website") + .Text(" for a description of the Bench system.") + .End(BlockType.Paragraph); + + RegisterSubCommand(helpCommand); + RegisterSubCommand(listCommand); + RegisterSubCommand(dashboardCommand); + RegisterSubCommand(manageCommand); + RegisterSubCommand(appCommand); + RegisterSubCommand(projectCommand); + } + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name) + .Text(" command is the executable of the Bench CLI.").LineBreak() + .Text("You can call it without a sub-command to enter the ") + .Emph("interactive mode").Text(".") + .End(BlockType.Paragraph); + + var flagVerbose = new FlagArgument(FLAG_VERBOSE, 'v'); + flagVerbose.Description + .Text("Activate verbose output."); + + var flagNoAssurance = new FlagArgument(FLAG_YES, 'y', "force"); + flagNoAssurance.Description + .Text("Suppress all assurance questions."); + + var optionHelpFormat = new EnumOptionArgument( + OPTION_HELP_FORMAT, 'f', DEF_HELP_FORMAT); + optionHelpFormat.Description + .Text("Specify the output format of help texts."); + + var optionLogFile = new OptionArgument(OPTION_LOGFILE, 'l', + ArgumentValidation.IsValidPath, + "log"); + optionLogFile.Description + .Text("Specify a custom location for the log file."); + optionLogFile.PossibleValueInfo + .Text("A path to the log file."); + optionLogFile.DefaultValueInfo + .Text("Auto generated filename in ") + .Variable("bench root") + .Text("\\log\\") + .Text("."); + + var optionBenchRoot = new OptionArgument(OPTION_BENCH_ROOT, 'r', + v => ArgumentValidation.IsValidPath(v) && Directory.Exists(v), + "base"); + optionBenchRoot.Description + .Text("Specify the root directory of the Bench environment."); + optionBenchRoot.PossibleValueInfo + .Text("A path to a valid Bench root directory."); + optionBenchRoot.DefaultValueInfo + .Text("The root directory of the Bench environment, this Bench CLI belongs to."); + + var commandHelp = new CommandArgument(helpCommand.Name, 'h'); + commandHelp.Description + .Text("Display the full help for all commands."); + + var commandList = new CommandArgument(listCommand.Name, 'l'); + commandList.Description + .Text("List different kinds of objects in the Bench environment."); + + var commandDashboard = new CommandArgument(dashboardCommand.Name, 'b', "gui"); + commandDashboard.Description + .Text("Start the ").Emph("Bench Dashboard").Text("."); + + var commandManage = new CommandArgument(manageCommand.Name, 'm'); + commandManage.Description + .Text("Manage the Bench environment and its configuration."); + + var commandApp = new CommandArgument(appCommand.Name, 'a'); + commandApp.Description + .Text("Manage individual apps."); + commandApp.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, appCommand); + + var commandProject = new CommandArgument(projectCommand.Name, 'p', "prj"); + commandProject.Description + .Text("Manage projects in the Bench environment."); + commandProject.SyntaxInfo + .Append(HelpFormatter.CommandSyntax, projectCommand); + + parser.RegisterArguments( + flagVerbose, + flagNoAssurance, + + optionHelpFormat, + optionLogFile, + optionBenchRoot, + + commandHelp, + commandList, + commandManage, + commandDashboard, + commandApp, + commandProject); + } + + private string LogFilePath() + { + var p = Arguments.GetOptionValue(OPTION_LOGFILE); + return p == null || Path.IsPathRooted(p) + ? p : Path.Combine(Environment.CurrentDirectory, p); + } + + protected override bool ValidateArguments() + { + Verbose = Arguments.GetFlag(FLAG_VERBOSE); + NoAssurance = Arguments.GetFlag(FLAG_YES); + HelpFormat = (DocumentOutputFormat)Enum.Parse(typeof(DocumentOutputFormat), + Arguments.GetOptionValue(OPTION_HELP_FORMAT, DEF_HELP_FORMAT.ToString()), true); + + WriteDetail("{0} v{1}: {2}", ToolName, ToolVersion, Program.CliExecutable()); + + var rp = Arguments.GetOptionValue(OPTION_BENCH_ROOT, DefaultRootPath()); + if (rp != null) + { + RootPath = Path.IsPathRooted(rp) + ? rp + : Path.Combine(Environment.CurrentDirectory, rp); + WriteDetail("Bench Root: " + (RootPath ?? "unknown")); + } + else + { + WriteError("No valid Bench root path."); + WriteLine("Try specifying the Bench root directory with the --root option."); + return false; + } + + if (BenchTasks.IsDashboardSupported) + { + WriteDetail("Bench Dashboard: " + (DashboardExecutable(RootPath) ?? "not found")); + } + else + { + WriteDetail("Bench Dashboard: Not Supported. Microsoft .NET Framework 4.5 not installed."); + } + + LogFile = LogFilePath(); + WriteDetail("Log File: " + (LogFile ?? "automatic")); + + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/UpdateCommand.cs b/BenchManager/BenchCLI/Commands/UpdateCommand.cs new file mode 100644 index 00000000..b23afc6f --- /dev/null +++ b/BenchManager/BenchCLI/Commands/UpdateCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class UpdateCommand : BenchCommand + { + public override string Name => "update"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command updates the app libraries and upgrades all apps.") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + { + bool success; + var oldCfg = LoadConfiguration(false); + + // Delete app libraries + BenchTasks.DeleteAppLibraries(oldCfg); + + // Load app libraries + using (var man = new DefaultBenchManager(oldCfg)) + { + man.Verbose = Verbose; + success = man.LoadAppLibraries(); + if (!success) + { + WriteError("Failed to load the latest app libraries."); + return false; + } + } + + // Upgrade apps + var newCfg = LoadConfiguration(true); + using (var man = new DefaultBenchManager(newCfg)) + { + man.Verbose = Verbose; + success = man.UpgradeApps(); + if (!success) + { + WriteError("Failed to upgrade the apps."); + return false; + } + } + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Commands/UpdateEnvironmentCommand.cs b/BenchManager/BenchCLI/Commands/UpdateEnvironmentCommand.cs new file mode 100644 index 00000000..2649e25f --- /dev/null +++ b/BenchManager/BenchCLI/Commands/UpdateEnvironmentCommand.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class UpdateEnvironmentCommand : BenchCommand + { + public override string Name => "update-env"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command updates the paths in the Bench environment.") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + => RunManagerTask(mgr => mgr.UpdateEnvironment()); + } +} diff --git a/BenchManager/BenchCLI/Commands/UpgradeCommand.cs b/BenchManager/BenchCLI/Commands/UpgradeCommand.cs new file mode 100644 index 00000000..0e92726c --- /dev/null +++ b/BenchManager/BenchCLI/Commands/UpgradeCommand.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Mastersign.CliTools; +using Mastersign.Docs; + +namespace Mastersign.Bench.Cli.Commands +{ + class UpgradeCommand : BenchCommand + { + public override string Name => "upgrade"; + + protected override void InitializeArgumentParser(ArgumentParser parser) + { + parser.Description + .Begin(BlockType.Paragraph) + .Text("The ").Keyword(Name).Text(" command checks if a new version of Bench is available and installs it.") + .End(BlockType.Paragraph); + } + + protected override bool ExecuteCommand(string[] args) + { + var cfg = LoadConfiguration(); + var version = cfg.GetStringValue(PropertyKeys.Version); + + WriteDetail("Retrieving the latest version number..."); + var latestVersion = BenchTasks.GetLatestVersion(cfg); + if (latestVersion == null) + { + WriteError("Contacting the distribution site failed."); + return false; + } + + WriteLine("Current version: " + version); + if (string.Equals(version, latestVersion)) + { + WriteLine("No update available."); + if (!AskForAssurance("Do you want to reinstall the Bench system?")) return false; + } + else + { + WriteLine("Latest version: " + latestVersion); + if (!AskForAssurance("Are you sure, you want to upgrade the Bench system?")) return false; + } + + if (!RunManagerTask(mgr => mgr.DownloadBenchUpdate())) return false; + + BenchTasks.InitiateInstallationBootstrap(cfg); + return true; + } + } +} diff --git a/BenchManager/BenchCLI/Docs/BlockType.cs b/BenchManager/BenchCLI/Docs/BlockType.cs new file mode 100644 index 00000000..56bce940 --- /dev/null +++ b/BenchManager/BenchCLI/Docs/BlockType.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Docs +{ + public enum BlockType + { + Document, + Section, + Paragraph, + Title, + Headline1, + Headline2, + Link, + LinkContent, + List, + ListItem, + DefinitionList, + Definition, + DefinitionTopic, + DefinitionContent, + PropertyList, + Property, + PropertyName, + PropertyContent, + Detail, + } +} diff --git a/BenchManager/BenchCLI/Docs/Document.cs b/BenchManager/BenchCLI/Docs/Document.cs new file mode 100644 index 00000000..0e0939c9 --- /dev/null +++ b/BenchManager/BenchCLI/Docs/Document.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Mastersign.Docs +{ + public class Document : DocumentWriter, IDocumentWriter, IDocumentElements + { + #region Element Classes + + public abstract class Element + { + public abstract void WriteTo(IDocumentWriter writer); + } + + public class BlockBeginElement : Element + { + public BlockType BlockType { get; private set; } + + public BlockBeginElement(BlockType type) { BlockType = type; } + + public override void WriteTo(IDocumentWriter writer) + { + writer.Begin(BlockType); + } + } + + public class BlockEndElement : Element + { + public BlockType BlockTyp { get; private set; } + + public BlockEndElement(BlockType typ) { BlockTyp = typ; } + + public override void WriteTo(IDocumentWriter writer) + { + writer.End(BlockTyp); + } + } + + public class InlineElement : Element + { + public InlineType InlineType { get; private set; } + + public string Text { get; private set; } + + public InlineElement(InlineType type, string text) + { + InlineType = type; + Text = text; + } + + public override void WriteTo(IDocumentWriter writer) + { + writer.Inline(InlineType, Text); + } + } + + public class LineBreakElement : Element + { + public override void WriteTo(IDocumentWriter writer) + { + writer.LineBreak(); + } + } + + #endregion + + public Document() { } + + public Document(IDocumentElements elements) + { + elements.WriteTo(this); + } + + public override void Dispose() + { + // nothing + } + + private readonly List elements = new List(); + + private void Record(Element e) + { + elements.Add(e); + } + + public bool IsEmpty { get { return elements.Count == 0; } } + + public void Clear() { elements.Clear(); } + + public void WriteTo(IDocumentWriter writer) + { + foreach (var e in elements) + { + e.WriteTo(writer); + } + } + + public override DocumentWriter Begin(BlockType type) + { + Record(new BlockBeginElement(type)); + return this; + } + + public override DocumentWriter End(BlockType type) + { + Record(new BlockEndElement(type)); + return this; + } + + public override DocumentWriter Inline(InlineType type, string format, params object[] args) + { + Record(new InlineElement(type, string.Format(format, args))); + return this; + } + + public override DocumentWriter LineBreak() + { + Record(new LineBreakElement()); + return this; + } + + public override string ToString() + { + var sb = new StringBuilder(); + using (var w = new StringWriter(sb)) + using (var dw = new PlainTextDocumentWriter(w)) + { + WriteTo(dw); + } + return sb.ToString(); + } + } +} diff --git a/BenchManager/BenchCLI/Docs/DocumentContentGenerator.cs b/BenchManager/BenchCLI/Docs/DocumentContentGenerator.cs new file mode 100644 index 00000000..7e0b81c0 --- /dev/null +++ b/BenchManager/BenchCLI/Docs/DocumentContentGenerator.cs @@ -0,0 +1,8 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Docs +{ + public delegate void DocumentContentGenerator(DocumentWriter w, T a); +} diff --git a/BenchManager/BenchCLI/Docs/DocumentOutputFormat.cs b/BenchManager/BenchCLI/Docs/DocumentOutputFormat.cs new file mode 100644 index 00000000..fb4bd50f --- /dev/null +++ b/BenchManager/BenchCLI/Docs/DocumentOutputFormat.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Docs +{ + public enum DocumentOutputFormat + { + Plain, + Markdown, + // Html, + } +} diff --git a/BenchManager/BenchCLI/Docs/DocumentWriter.cs b/BenchManager/BenchCLI/Docs/DocumentWriter.cs new file mode 100644 index 00000000..d76cf9da --- /dev/null +++ b/BenchManager/BenchCLI/Docs/DocumentWriter.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Docs +{ + public abstract class DocumentWriter : IDocumentWriter + { + #region IDocumentWriter + + void IDocumentWriter.Begin(BlockType type) + { + Begin(type); + } + + void IDocumentWriter.End(BlockType type) + { + End(type); + } + + void IDocumentWriter.Inline(InlineType type, string text) + { + Inline(type, text); + } + + void IDocumentWriter.LineBreak() + { + LineBreak(); + } + + #endregion + + #region Abstract Methods + + public abstract DocumentWriter End(BlockType type); + + public abstract DocumentWriter Begin(BlockType type); + + public abstract DocumentWriter Inline(InlineType type, string format, params object[] args); + + public abstract DocumentWriter LineBreak(); + + public abstract void Dispose(); + + #endregion + + #region Convenience Methods + + public DocumentWriter Append(IDocumentElements elements) + { + elements.WriteTo(this); + return this; + } + + public DocumentWriter Append(DocumentContentGenerator generator, T arg) + { + generator(this, arg); + return this; + } + + public DocumentWriter Block(BlockType type, string format, params object[] args) + { + Begin(type); + Text(format, args); + End(type); + return this; + } + + public DocumentWriter Block(BlockType type, IDocumentElements e) + { + Begin(type); + e.WriteTo(this); + End(type); + return this; + } + + public DocumentWriter Block(BlockType type, DocumentContentGenerator generator, T arg) + { + Begin(type); + generator(this, arg); + End(type); + return this; + } + + public DocumentWriter Title(string format, params object[] args) + { + return Block(BlockType.Title, format, args); + } + + public DocumentWriter Title(IDocumentElements e) + { + return Block(BlockType.Title, e); + } + + public DocumentWriter Title(DocumentContentGenerator generator, T arg) + { + return Block(BlockType.Title, generator, arg); + } + + public DocumentWriter Headline1(string anchor, string format, params object[] args) + { + Begin(BlockType.Headline1); + Anchor(anchor); + Text(format, args); + End(BlockType.Headline1); + return this; + } + + public DocumentWriter Headline1(string anchor, IDocumentElements e) + { + Begin(BlockType.Headline1); + Anchor(anchor); + Append(e); + End(BlockType.Headline1); + return this; + } + + public DocumentWriter Headline1(string anchor, DocumentContentGenerator generator, T arg) + { + Begin(BlockType.Headline1); + Anchor(anchor); + Append(generator, arg); + End(BlockType.Headline1); + return this; + } + + public DocumentWriter Headline2(string anchor, string format, params object[] args) + { + Begin(BlockType.Headline2); + Anchor(anchor); + Text(format, args); + End(BlockType.Headline2); + return this; + } + + public DocumentWriter Headline2(string anchor, IDocumentElements e) + { + Begin(BlockType.Headline2); + Anchor(anchor); + Append(e); + End(BlockType.Headline2); + return this; + } + + public DocumentWriter Headline2(string anchor, DocumentContentGenerator generator, T arg) + { + Begin(BlockType.Headline2); + Anchor(anchor); + Append(generator, arg); + End(BlockType.Headline2); + return this; + } + + public DocumentWriter Paragraph(string format, params object[] args) + { + return Block(BlockType.Paragraph, format, args); + } + + public DocumentWriter Paragraph(IDocumentElements content) + { + return Block(BlockType.Paragraph, content); + } + + public DocumentWriter Paragraph(DocumentContentGenerator generator, T arg) + { + return Block(BlockType.Paragraph, generator, arg); + } + + public DocumentWriter ListItem(string format, params object[] args) + { + return Block(BlockType.ListItem, format, args); + } + + public DocumentWriter ListItem(IDocumentElements content) + { + return Block(BlockType.ListItem, content); + } + + public DocumentWriter ListItem(DocumentContentGenerator generator, T arg) + { + return Block(BlockType.ListItem, generator, arg); + } + + public DocumentWriter DefinitionTopic(string format, params object[] args) + { + return Block(BlockType.DefinitionTopic, format, args); + } + + public DocumentWriter DefinitionTopic(IDocumentElements content) + { + return Block(BlockType.DefinitionTopic, content); + } + + public DocumentWriter DefinitionTopic(DocumentContentGenerator generator, T arg) + { + return Block(BlockType.DefinitionTopic, generator, arg); + } + + public DocumentWriter DefinitionContent(string format, params object[] args) + { + return Block(BlockType.DefinitionContent, format, args); + } + + public DocumentWriter DefinitionContent(IDocumentElements content) + { + return Block(BlockType.DefinitionContent, content); + } + + public DocumentWriter DefinitionContent(DocumentContentGenerator generator, T arg) + { + return Block(BlockType.DefinitionContent, generator, arg); + } + + public DocumentWriter Definition(string topic, string format, params object[] args) + { + Begin(BlockType.Definition); + DefinitionTopic(topic); + DefinitionContent(format, args); + End(BlockType.Definition); + return this; + } + + public DocumentWriter Definition(string topic, IDocumentElements content) + { + Begin(BlockType.Definition); + DefinitionTopic(topic); + DefinitionContent(content); + End(BlockType.Definition); + return this; + } + + public DocumentWriter Definition(string topic, DocumentContentGenerator generator, T arg) + { + Begin(BlockType.Definition); + DefinitionTopic(topic); + DefinitionContent(generator, arg); + End(BlockType.Definition); + return this; + } + + public DocumentWriter PropertyName(string format, params object[] args) + { + return Block(BlockType.PropertyName, format, args); + } + + public DocumentWriter PropertyName(IDocumentElements content) + { + return Block(BlockType.PropertyName, content); + } + + public DocumentWriter PropertyName(DocumentContentGenerator generator, T arg) + { + return Block(BlockType.PropertyName, generator, arg); + } + + public DocumentWriter PropertyContent(string format, params object[] args) + { + return Block(BlockType.PropertyContent, format, args); + } + + public DocumentWriter PropertyContent(IDocumentElements content) + { + return Block(BlockType.PropertyContent, content); + } + + public DocumentWriter PropertyContent(DocumentContentGenerator generator, T arg) + { + return Block(BlockType.PropertyContent, generator, arg); + } + + public DocumentWriter Property(string name, string format, params object[] args) + { + Begin(BlockType.Property); + PropertyName(name); + PropertyContent(format, args); + End(BlockType.Property); + return this; + } + + public DocumentWriter Property(string name, IDocumentElements content) + { + Begin(BlockType.Property); + PropertyName(name); + PropertyContent(content); + End(BlockType.Property); + return this; + } + + public DocumentWriter Property(string name, DocumentContentGenerator generator, T arg) + { + Begin(BlockType.Property); + PropertyName(name); + PropertyContent(generator, arg); + End(BlockType.Property); + return this; + } + + public DocumentWriter Link(string href, string format, params object[] args) + { + Begin(BlockType.Link); + LinkTarget(href); + Block(BlockType.LinkContent, format, args); + End(BlockType.Link); + return this; + } + + public DocumentWriter Link(string href, IDocumentElements content) + { + Begin(BlockType.Link); + LinkTarget(href); + Block(BlockType.LinkContent, content); + End(BlockType.Link); + return this; + } + + public DocumentWriter Link(string href, DocumentContentGenerator generator, T arg) + { + Begin(BlockType.Link); + LinkTarget(href); + Block(BlockType.LinkContent, generator, arg); + End(BlockType.Link); + return this; + } + + public DocumentWriter Text(string format, params object[] args) + => Inline(InlineType.Text, format, args); + + public DocumentWriter Emph(string format, params object[] args) + => Inline(InlineType.Emphasized, format, args); + + public DocumentWriter Strong(string format, params object[] args) + => Inline(InlineType.StronglyEmphasized, format, args); + + public DocumentWriter Code(string format, params object[] args) + => Inline(InlineType.Code, format, args); + + public DocumentWriter Keyword(string format, params object[] args) + => Inline(InlineType.Keyword, format, args); + + public DocumentWriter Variable(string format, params object[] args) + => Inline(InlineType.Variable, format, args); + + public DocumentWriter Anchor(string format, params object[] args) + => Inline(InlineType.Anchor, format, args); + + public DocumentWriter LinkTarget(string format, params object[] args) + => Inline(InlineType.LinkTarget, format, args); + + #endregion + } +} diff --git a/BenchManager/BenchCLI/Docs/DocumentWriterFactory.cs b/BenchManager/BenchCLI/Docs/DocumentWriterFactory.cs new file mode 100644 index 00000000..b463d91f --- /dev/null +++ b/BenchManager/BenchCLI/Docs/DocumentWriterFactory.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Mastersign.Docs +{ + public static class DocumentWriterFactory + { + public static DocumentWriter Create(DocumentOutputFormat format, Stream target = null) + { + switch (format) + { + case DocumentOutputFormat.Plain: + if (target == null) + return new PlainTextDocumentWriter(Console.Out) { UseConsoleColor = true }; + else + return new PlainTextDocumentWriter(target); + case DocumentOutputFormat.Markdown: + return new MarkdownDocumentWriter(target ?? Console.OpenStandardOutput()); + // case DocumentOutputFormat.Html: + // return new HtmlDocumentWriter(target); + default: + throw new NotSupportedException(); + } + } + } +} diff --git a/BenchManager/BenchCLI/Docs/IDocumentElements.cs b/BenchManager/BenchCLI/Docs/IDocumentElements.cs new file mode 100644 index 00000000..bfcfd06f --- /dev/null +++ b/BenchManager/BenchCLI/Docs/IDocumentElements.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Docs +{ + public interface IDocumentElements + { + void WriteTo(IDocumentWriter writer); + } +} diff --git a/BenchManager/BenchCLI/Docs/IDocumentWriter.cs b/BenchManager/BenchCLI/Docs/IDocumentWriter.cs new file mode 100644 index 00000000..e5f1ebb8 --- /dev/null +++ b/BenchManager/BenchCLI/Docs/IDocumentWriter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Docs +{ + + public interface IDocumentWriter : IDisposable + { + void Begin(BlockType typ); + + void End(BlockType typ); + + void Inline(InlineType typ, string text); + + void LineBreak(); + } +} diff --git a/BenchManager/BenchCLI/Docs/InlineType.cs b/BenchManager/BenchCLI/Docs/InlineType.cs new file mode 100644 index 00000000..333528b4 --- /dev/null +++ b/BenchManager/BenchCLI/Docs/InlineType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Docs +{ + public enum InlineType + { + Text, + Emphasized, + StronglyEmphasized, + Code, + Keyword, + Variable, + Anchor, + LinkTarget, + } +} diff --git a/BenchManager/BenchCLI/Docs/MarkdownDocumentWriter.cs b/BenchManager/BenchCLI/Docs/MarkdownDocumentWriter.cs new file mode 100644 index 00000000..4e5f6fb6 --- /dev/null +++ b/BenchManager/BenchCLI/Docs/MarkdownDocumentWriter.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Mastersign.Docs +{ + public class MarkdownDocumentWriter : DocumentWriter, IDocumentWriter + { + private int listDepth = 0; + + public TextWriter writer; + + public MarkdownDocumentWriter(Stream target) + { + writer = new StreamWriter(target, Encoding.Default); + } + + public override void Dispose() + { + if (writer != null) + { + writer.Dispose(); + writer = null; + } + } + + private enum WriteMode + { + Target, + Buffer, + } + + private WriteMode writeMode = WriteMode.Target; + + private StringBuilder buffer = new StringBuilder(); + private string anchor = null; + private string linkTarget = null; + + private void BeginBuffering() + { + writeMode = WriteMode.Buffer; + } + + private string EndBuffering() + { + var text = buffer.ToString(); + writeMode = WriteMode.Target; + buffer.Remove(0, buffer.Length); + bufferIndentFlag = false; + bufferBreakCounter = 0; + return text; + } + + private bool indentFlag = false; + private int breakCounter = 0; + private bool bufferIndentFlag = false; + private int bufferBreakCounter = 0; + + private string Escape(string v) + { + return v + .Replace("<", "<") + .Replace(">", ">") + .Replace("*", "\\*"); + } + + private void W(string format, params object[] args) + { + var t = string.Format(format, args); + switch (writeMode) + { + case WriteMode.Buffer: + bufferBreakCounter = 0; + buffer.Append(t); + break; + default: + breakCounter = 0; + writer.Write(t); + break; + } + } + + private void BR() + { + switch (writeMode) + { + case WriteMode.Buffer: + if (bufferBreakCounter > 1) return; + bufferBreakCounter++; + bufferIndentFlag = false; + buffer.AppendLine(); + break; + default: + if (breakCounter > 1) return; + breakCounter++; + indentFlag = false; + writer.WriteLine(); + break; + } + } + + private readonly List indentStack = new List(); + + private void PushIndent(string i) + { + indentStack.Add(i); + } + + private void PopIndent() + { + if (indentStack.Count > 0) + { + indentStack.RemoveAt(indentStack.Count - 1); + } + } + + private void Indent() + { + switch (writeMode) + { + case WriteMode.Buffer: + if (bufferIndentFlag) return; + bufferIndentFlag = true; + break; + default: + if (indentFlag) return; + indentFlag = true; + break; + } + foreach (var i in indentStack) { W(i); } + } + + private string ListPrefix() + { + if (listDepth < 2) return "* "; + if (listDepth < 3) return "+ "; + return "- "; + } + + public override DocumentWriter Begin(BlockType type) + { + switch (type) + { + // IGNORE + case BlockType.Document: + case BlockType.Section: + break; + // TEXT BLOCK + case BlockType.Paragraph: + case BlockType.Detail: + Indent(); + break; + // TITLE + case BlockType.Title: + BeginBuffering(); + break; + // HEADLINE + case BlockType.Headline1: + BR(); + BR(); + W("## "); + anchor = null; + break; + case BlockType.Headline2: + BR(); + BR(); + W("### "); + anchor = null; + break; + // LINK + case BlockType.Link: + linkTarget = null; + break; + case BlockType.LinkContent: + W("["); + break; + // LIST + case BlockType.List: + BR(); + if (listDepth == 0) BR(); + listDepth++; + break; + case BlockType.ListItem: + Indent(); + var prefix = ListPrefix(); + W(prefix); + PushIndent(string.Empty.PadRight(4)); + break; + // PROPERTY TABLE + case BlockType.PropertyList: + case BlockType.Property: + break; + case BlockType.PropertyName: + case BlockType.PropertyContent: + BeginBuffering(); + break; + // DEFINITION LIST + case BlockType.DefinitionList: + break; + case BlockType.Definition: + break; + case BlockType.DefinitionContent: + break; + case BlockType.DefinitionTopic: + BeginBuffering(); + break; + // UNSUPPORTED + default: + throw new NotSupportedException(); + } + return this; + } + + public override DocumentWriter End(BlockType type) + { + string text; + switch (type) + { + // IGNORE + case BlockType.Document: + break; + // NEWLINE + case BlockType.Section: + BR(); + break; + // EMPTY LINE + case BlockType.Paragraph: + BR(); + BR(); + break; + // TITLE + case BlockType.Title: + text = EndBuffering(); + W(text); + BR(); + W(string.Empty.PadRight(text.Length, '=')); + BR(); + BR(); + break; + // HEADLINE + case BlockType.Headline1: + case BlockType.Headline2: + if (anchor != null) W(" {{#" + anchor + "}}"); + BR(); + break; + // LINK + case BlockType.LinkContent: + W("]"); + break; + case BlockType.Link: + W("({0})", linkTarget); + break; + // LIST + case BlockType.List: + listDepth--; + if (listDepth == 0) BR(); + break; + case BlockType.ListItem: + PopIndent(); + if (indentFlag) BR(); + break; + // PROPERTY TABLE + case BlockType.PropertyName: + W("{0}: ", EndBuffering()); + break; + case BlockType.PropertyContent: + W(EndBuffering()); + break; + case BlockType.Property: + W(" "); + BR(); + break; + case BlockType.PropertyList: + BR(); + break; + // DEFINITION LIST + case BlockType.DefinitionList: + break; + case BlockType.Definition: + BR(); + break; + case BlockType.DefinitionTopic: + text = EndBuffering(); + BR(); + BR(); + W("#### {0}", text); + BR(); + break; + case BlockType.DefinitionContent: + BR(); + break; + // DETAIL + case BlockType.Detail: + BR(); + break; + // UNSUPPORTED + default: + throw new NotSupportedException(); + } + return this; + } + + public override DocumentWriter Inline(InlineType type, string format, params object[] args) + { + if (format == null) return this; + var text = string.Format(format, args); + switch (type) + { + // PASS + case InlineType.Text: + W(Escape(text)); + break; + // ADORN + case InlineType.Emphasized: + W(" _{0}_", Escape(text)); + break; + case InlineType.StronglyEmphasized: + W(" **{0}**", Escape(text)); + break; + case InlineType.Code: + case InlineType.Keyword: + W(" `{0}`", text); + break; + case InlineType.Variable: + W(" _<{0}>_", Escape(text)); + break; + // MOMORIZE + case InlineType.Anchor: + anchor = text; + break; + case InlineType.LinkTarget: + linkTarget = text; + break; + // UNSUPPORTED + default: + throw new NotSupportedException(); + } + return this; + } + + public override DocumentWriter LineBreak() + { + BR(); + Indent(); + return this; + } + + } +} diff --git a/BenchManager/BenchCLI/Docs/PlainTextDocumentWriter.cs b/BenchManager/BenchCLI/Docs/PlainTextDocumentWriter.cs new file mode 100644 index 00000000..9d29caac --- /dev/null +++ b/BenchManager/BenchCLI/Docs/PlainTextDocumentWriter.cs @@ -0,0 +1,512 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace Mastersign.Docs +{ + public class PlainTextDocumentWriter : DocumentWriter, IDocumentWriter + { + private const string INDENT_1 = " "; + private const string INDENT_2 = " "; + + private int listDepth = 0; + + public TextWriter writer; + + public bool UseConsoleColor { get; set; } + + public PlainTextDocumentWriter(Stream target) + { + writer = new StreamWriter(target, Encoding.Default); + } + + public PlainTextDocumentWriter(TextWriter target = null) + { + writer = target ?? Console.Out; + } + + public override void Dispose() + { + if (writer != null) + { + writer.Dispose(); + writer = null; + } + } + + private enum WriteMode + { + Target, + Buffer, + } + + private WriteMode writeMode = WriteMode.Target; + + private StringBuilder buffer = new StringBuilder(); + + private void BeginBuffering() + { + writeMode = WriteMode.Buffer; + } + + private string EndBuffering() + { + var text = buffer.ToString(); + writeMode = WriteMode.Target; + buffer.Remove(0, buffer.Length); + bufferIndentFlag = false; + bufferBreakCounter = 0; + return text; + } + + private readonly List urls = new List(); + private int lastUrl = 0; + + private bool indentFlag = false; + private int breakCounter = 0; + private bool bufferIndentFlag = false; + private int bufferBreakCounter = 0; + + private const ConsoleColor RESET_COLOR = ConsoleColor.Gray; + private const string ESCAPE = "\x12"; + private const char COLOR_OFFSET = 'A'; + private static Regex colorPattern = new Regex(ESCAPE + @"\w"); + + private static string EncodeColor(ConsoleColor color) + { + return ESCAPE + ((char)(COLOR_OFFSET + color)); + } + + private static ConsoleColor DecodeColor(string escapedColor) + { + if (escapedColor.Length != 2) throw new ArgumentException(); + var colorNo = escapedColor[1] - COLOR_OFFSET; + if (Enum.IsDefined(typeof(ConsoleColor), colorNo)) + return (ConsoleColor)colorNo; + else + return ConsoleColor.Gray; + } + + private void WriteWithEscapedColors(string text) + { + var p = 0; + foreach (Match m in colorPattern.Matches(text)) + { + writer.Write(text.Substring(p, m.Index - p)); + if (UseConsoleColor) Console.ForegroundColor = DecodeColor(m.Value); + p = m.Index + m.Length; + } + writer.Write(text.Substring(p)); + } + + private readonly Stack colors = new Stack(); + + private void C(ConsoleColor color) + { + colors.Push(color); + W(EncodeColor(color)); + } + + private void CR() + { + colors.Pop(); + if (colors.Count > 0) + W(EncodeColor(colors.Peek())); + else + W(EncodeColor(RESET_COLOR)); + } + + private void W(string format, params object[] args) + { + switch (writeMode) + { + case WriteMode.Buffer: + bufferBreakCounter = 0; + buffer.Append(string.Format(format, args)); + break; + default: + breakCounter = 0; + WriteWithEscapedColors(string.Format(format, args)); +#if DEBUG + writer.Flush(); +#endif + break; + } + } + + private void BR() + { + switch (writeMode) + { + case WriteMode.Buffer: + if (bufferBreakCounter > 1) return; + bufferBreakCounter++; + bufferIndentFlag = false; + buffer.AppendLine(); + break; + default: + if (breakCounter > 1) return; + breakCounter++; + indentFlag = false; + writer.WriteLine(); +#if DEBUG + writer.Flush(); +#endif + break; + } + } + + private readonly List indentStack = new List(); + + private void PushIndent(string i) + { + indentStack.Add(i); + } + + private void PopIndent() + { + if (indentStack.Count > 0) + { + indentStack.RemoveAt(indentStack.Count - 1); + } + } + + private void Indent() + { + switch (writeMode) + { + case WriteMode.Buffer: + if (bufferIndentFlag) return; + bufferIndentFlag = true; + break; + default: + if (indentFlag) return; + indentFlag = true; + break; + } + foreach (var i in indentStack) { W(i); } + } + + private string ListPrefix() + { + if (listDepth < 2) return " * "; + if (listDepth < 3) return " + "; + return " - "; + } + + public override DocumentWriter Begin(BlockType type) + { + switch (type) + { + // IGNORE + case BlockType.Section: + case BlockType.Property: + break; + // DOCUMENT + case BlockType.Document: + urls.Clear(); + break; + // INDENT + case BlockType.Paragraph: + case BlockType.Definition: + case BlockType.DefinitionTopic: + Indent(); + break; + // CHANGE INDENT + case BlockType.DefinitionList: + PushIndent(INDENT_1); + break; + case BlockType.DefinitionContent: + case BlockType.Detail: + PushIndent(INDENT_2); + Indent(); + break; + // BUFFER + case BlockType.Title: + case BlockType.Headline1: + case BlockType.Headline2: + case BlockType.PropertyName: + case BlockType.PropertyContent: + BeginBuffering(); + break; + // LINK + case BlockType.Link: + lastUrl = -1; + C(ConsoleColor.White); + break; + case BlockType.LinkContent: + break; + // LIST + case BlockType.List: + BR(); + listDepth++; + break; + case BlockType.ListItem: + Indent(); + var prefix = ListPrefix(); + W(prefix); + PushIndent(string.Empty.PadRight(prefix.Length)); + break; + // PROPERTY TABLE + case BlockType.PropertyList: + properties = new List(); + break; + // UNSUPPORTED + default: + throw new NotSupportedException(); + } + return this; + } + + public override DocumentWriter End(BlockType type) + { + string text; + switch (type) + { + // SECTION + case BlockType.Document: + case BlockType.Section: + if (urls.Count > 0) + { + Begin(BlockType.Headline1); + Text("References"); + End(BlockType.Headline1); + for (int i = 0; i < urls.Count; i++) + { + C(ConsoleColor.White); + W("{0,3})", i + 1); + CR(); + W(" {0}", urls[i]); + BR(); + } + urls.Clear(); + } + BR(); + break; + // NEWLINE + case BlockType.Definition: + case BlockType.DefinitionTopic: + case BlockType.Property: + BR(); + break; + // EMPTY LINE + case BlockType.Paragraph: + BR(); + BR(); + break; + // HEADLINES + case BlockType.Title: + text = EndBuffering(); + C(ConsoleColor.Yellow); + W(text); + BR(); + W(string.Empty.PadRight(text.Length, '=')); + CR(); + BR(); + BR(); + break; + case BlockType.Headline1: + text = EndBuffering(); + BR(); + BR(); + C(ConsoleColor.Yellow); + W(text); + BR(); + W(string.Empty.PadRight(text.Length, '-')); + CR(); + BR(); + BR(); + break; + case BlockType.Headline2: + text = EndBuffering(); + BR(); + BR(); + C(ConsoleColor.Yellow); + W(text); + W(":"); + CR(); + BR(); + BR(); + break; + // LINK + case BlockType.LinkContent: + break; + case BlockType.Link: + if (lastUrl >= 0) + { + W(" [{0}]", lastUrl + 1); + } + CR(); + break; + // LIST + case BlockType.List: + listDepth--; + if (listDepth == 0) BR(); + break; + case BlockType.ListItem: + PopIndent(); + if (indentFlag) BR(); + break; + // PROPERTY TABLE + case BlockType.PropertyName: + PushProperty(EndBuffering()); + break; + case BlockType.PropertyContent: + UpdateCurrentProperty(EndBuffering()); + break; + case BlockType.PropertyList: + int maxKeyLength = MaximalPropertyKeyLength(); + foreach (var item in properties) + { + Indent(); + W((item.Key + ":").PadRight(maxKeyLength + 2)); + var lines = item.Description.Split( + new string[] { Environment.NewLine }, StringSplitOptions.None); + for (int i = 0; i < lines.Length; i++) + { + if (i > 0) + { + BR(); + Indent(); + W(string.Empty.PadRight(maxKeyLength + 2)); + } + W(lines[i]); + } + BR(); + } + properties = null; + break; + // INDENT + case BlockType.DefinitionList: + case BlockType.DefinitionContent: + case BlockType.Detail: + PopIndent(); + BR(); + break; + // UNSUPPORTED + default: + throw new NotSupportedException(); + } + return this; + } + + public override DocumentWriter Inline(InlineType type, string format, params object[] args) + { + if (format == null) return this; + var text = string.Format(format, args); + switch (type) + { + // IGNORE + case InlineType.Anchor: + break; + // PASS + case InlineType.Text: + W(text); + break; + // LINK TARGET + case InlineType.LinkTarget: + if (!text.StartsWith("#")) + { + var i = urls.IndexOf(text); + if (i < 0) + { + urls.Add(text); + i = urls.Count - 1; + } + lastUrl = i; + } + break; + // COLORED + case InlineType.Keyword: + C(ConsoleColor.Cyan); + W(text); + CR(); + break; + case InlineType.Emphasized: + C(ConsoleColor.White); + W(text); + CR(); + break; + case InlineType.StronglyEmphasized: + C(ConsoleColor.Red); + W(text); + CR(); + break; + case InlineType.Code: + C(ConsoleColor.Green); + W(text); + CR(); + break; + // ADORN + case InlineType.Variable: + C(ConsoleColor.Magenta); + W("<{0}>", text); + CR(); + break; + // UNSUPPORTED + default: + throw new NotSupportedException(); + } + return this; + } + + public override DocumentWriter LineBreak() + { + BR(); + Indent(); + return this; + } + + private struct PropertyItem + { + public string Key; + public string Description; + } + + private List properties; + + private PropertyItem CurrentProperty + { + get { return properties[properties.Count - 1]; } + set { properties[properties.Count - 1] = value; } + } + + private PropertyItem PopCurrentProperty() + { + var res = CurrentProperty; + properties.RemoveAt(properties.Count - 1); + return res; + } + + public void PushProperty(string text) + { + properties.Add(new PropertyItem { Key = text }); + } + + public void UpdateCurrentProperty(string description) + { + var p = CurrentProperty; + CurrentProperty = new PropertyItem + { + Key = p.Key, + Description = description + }; + } + + private int MaximalPropertyKeyLength() + { + int maxKeyLength = 0; + foreach (var item in properties) + { + if (item.Key.Length > maxKeyLength) + { + maxKeyLength = item.Key.Length; + } + } + + return maxKeyLength; + } + } +} diff --git a/BenchManager/BenchCLI/Program.cs b/BenchManager/BenchCLI/Program.cs new file mode 100644 index 00000000..23ad78c6 --- /dev/null +++ b/BenchManager/BenchCLI/Program.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Mastersign.Bench.Cli.Commands; + +namespace Mastersign.Bench.Cli +{ + class Program + { + static int Main(string[] args) + { + var controller = new RootCommand(); + return controller.Process(args) ? 0 : -1; + } + + public static string CliExecutable() + { + var assemblyName = Assembly.GetExecutingAssembly().GetName(); + return new Uri(assemblyName.CodeBase).LocalPath; + } + + public static string Version() + { + return Assembly.GetExecutingAssembly().GetName().Version.ToString(3); + } + } +} diff --git a/BenchManager/BenchCLI/Properties/AssemblyInfo.cs b/BenchManager/BenchCLI/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..ff352f6d --- /dev/null +++ b/BenchManager/BenchCLI/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("Bench CLI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Tobias Kiertscher")] +[assembly: AssemblyProduct("Bench")] +[assembly: AssemblyCopyright("Copyright © Tobias Kiertscher 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("64e94a41-026f-473c-bc48-70f8d5eb977a")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/BenchManager/BenchCLI/PropertyWriter.cs b/BenchManager/BenchCLI/PropertyWriter.cs new file mode 100644 index 00000000..b0c88be0 --- /dev/null +++ b/BenchManager/BenchCLI/PropertyWriter.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Bench.Cli +{ + public static class PropertyWriter + { + public static void WritePropertyValue(object value) + { + if (value is string[]) + { + foreach (var item in (string[])value) + { + Console.WriteLine(item); + } + } + else + { + Console.Write(value); + } + } + } +} diff --git a/BenchManager/BenchCLI/logo.ico b/BenchManager/BenchCLI/logo.ico new file mode 100644 index 00000000..6e29d201 Binary files /dev/null and b/BenchManager/BenchCLI/logo.ico differ diff --git a/BenchManager/BenchCLI/packages.config b/BenchManager/BenchCLI/packages.config new file mode 100644 index 00000000..09413b22 --- /dev/null +++ b/BenchManager/BenchCLI/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/BenchManager/BenchDashboard/AboutDialog.Designer.cs b/BenchManager/BenchDashboard/AboutDialog.Designer.cs index 1fb1e4cb..42d34c11 100644 --- a/BenchManager/BenchDashboard/AboutDialog.Designer.cs +++ b/BenchManager/BenchDashboard/AboutDialog.Designer.cs @@ -33,17 +33,20 @@ private void InitializeComponent() this.lblLicenses = new System.Windows.Forms.Label(); this.lblAckLabel = new System.Windows.Forms.Label(); this.panelHead = new System.Windows.Forms.Panel(); + this.picVersionState = new System.Windows.Forms.PictureBox(); + this.lblUpdate = new System.Windows.Forms.Label(); + this.lblVersion = new System.Windows.Forms.Label(); this.picLogo = new System.Windows.Forms.PictureBox(); this.lblSubtitle = new System.Windows.Forms.Label(); this.lblTitle = new System.Windows.Forms.Label(); this.panelFooter = new System.Windows.Forms.Panel(); + this.linkAuthor = new System.Windows.Forms.LinkLabel(); this.btnClose = new System.Windows.Forms.Button(); this.lblAcks = new System.Windows.Forms.Label(); this.txtLicenses = new System.Windows.Forms.TextBox(); - this.lblVersion = new System.Windows.Forms.Label(); - this.linkAuthor = new System.Windows.Forms.LinkLabel(); this.table.SuspendLayout(); this.panelHead.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picVersionState)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.picLogo)).BeginInit(); this.panelFooter.SuspendLayout(); this.SuspendLayout(); @@ -96,6 +99,8 @@ private void InitializeComponent() // this.panelHead.BackColor = System.Drawing.SystemColors.Control; this.table.SetColumnSpan(this.panelHead, 2); + this.panelHead.Controls.Add(this.picVersionState); + this.panelHead.Controls.Add(this.lblUpdate); this.panelHead.Controls.Add(this.lblVersion); this.panelHead.Controls.Add(this.picLogo); this.panelHead.Controls.Add(this.lblSubtitle); @@ -107,6 +112,39 @@ private void InitializeComponent() this.panelHead.Size = new System.Drawing.Size(624, 88); this.panelHead.TabIndex = 1; // + // picVersionState + // + this.picVersionState.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.picVersionState.Location = new System.Drawing.Point(518, 15); + this.picVersionState.Name = "picVersionState"; + this.picVersionState.Size = new System.Drawing.Size(16, 16); + this.picVersionState.TabIndex = 6; + this.picVersionState.TabStop = false; + this.picVersionState.DoubleClick += new System.EventHandler(this.VersionDoubleClickHandler); + // + // lblUpdate + // + this.lblUpdate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.lblUpdate.ForeColor = System.Drawing.SystemColors.GrayText; + this.lblUpdate.Location = new System.Drawing.Point(154, 36); + this.lblUpdate.Name = "lblUpdate"; + this.lblUpdate.Size = new System.Drawing.Size(358, 13); + this.lblUpdate.TabIndex = 5; + this.lblUpdate.Text = "version of update"; + this.lblUpdate.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.lblUpdate.DoubleClick += new System.EventHandler(this.VersionDoubleClickHandler); + // + // lblVersion + // + this.lblVersion.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.lblVersion.Location = new System.Drawing.Point(412, 16); + this.lblVersion.Name = "lblVersion"; + this.lblVersion.Size = new System.Drawing.Size(100, 13); + this.lblVersion.TabIndex = 4; + this.lblVersion.Text = "v0.0.0"; + this.lblVersion.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.lblVersion.DoubleClick += new System.EventHandler(this.VersionDoubleClickHandler); + // // picLogo // this.picLogo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); @@ -150,6 +188,18 @@ private void InitializeComponent() this.panelFooter.Size = new System.Drawing.Size(624, 60); this.panelFooter.TabIndex = 2; // + // linkAuthor + // + this.linkAuthor.AutoSize = true; + this.linkAuthor.LinkColor = System.Drawing.SystemColors.HotTrack; + this.linkAuthor.Location = new System.Drawing.Point(12, 24); + this.linkAuthor.Name = "linkAuthor"; + this.linkAuthor.Size = new System.Drawing.Size(92, 13); + this.linkAuthor.TabIndex = 4; + this.linkAuthor.TabStop = true; + this.linkAuthor.Text = "Tobias Kiertscher"; + this.linkAuthor.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkAuthor_LinkClicked); + // // btnClose // this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); @@ -187,28 +237,6 @@ private void InitializeComponent() this.txtLicenses.Size = new System.Drawing.Size(502, 288); this.txtLicenses.TabIndex = 5; // - // lblVersion - // - this.lblVersion.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.lblVersion.Location = new System.Drawing.Point(440, 16); - this.lblVersion.Name = "lblVersion"; - this.lblVersion.Size = new System.Drawing.Size(100, 13); - this.lblVersion.TabIndex = 4; - this.lblVersion.Text = "v0.0.0"; - this.lblVersion.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // linkAuthor - // - this.linkAuthor.AutoSize = true; - this.linkAuthor.LinkColor = System.Drawing.SystemColors.HotTrack; - this.linkAuthor.Location = new System.Drawing.Point(12, 24); - this.linkAuthor.Name = "linkAuthor"; - this.linkAuthor.Size = new System.Drawing.Size(93, 13); - this.linkAuthor.TabIndex = 4; - this.linkAuthor.TabStop = true; - this.linkAuthor.Text = "Tobias Kiertscher"; - this.linkAuthor.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkAuthor_LinkClicked); - // // AboutDialog // this.AcceptButton = this.btnClose; @@ -229,6 +257,7 @@ private void InitializeComponent() this.table.PerformLayout(); this.panelHead.ResumeLayout(false); this.panelHead.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picVersionState)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.picLogo)).EndInit(); this.panelFooter.ResumeLayout(false); this.panelFooter.PerformLayout(); @@ -251,5 +280,7 @@ private void InitializeComponent() private System.Windows.Forms.TextBox txtLicenses; private System.Windows.Forms.Label lblVersion; private System.Windows.Forms.LinkLabel linkAuthor; + private System.Windows.Forms.Label lblUpdate; + private System.Windows.Forms.PictureBox picVersionState; } } \ No newline at end of file diff --git a/BenchManager/BenchDashboard/AboutDialog.cs b/BenchManager/BenchDashboard/AboutDialog.cs index 25efde64..a778dbe0 100644 --- a/BenchManager/BenchDashboard/AboutDialog.cs +++ b/BenchManager/BenchDashboard/AboutDialog.cs @@ -14,8 +14,11 @@ namespace Mastersign.Bench.Dashboard { public partial class AboutDialog : Form { - public AboutDialog() + private readonly Core core; + + public AboutDialog(Core core) { + this.core = core; InitializeComponent(); lblVersion.Text = "Version " + Program.Core.Config.GetStringValue(PropertyKeys.Version); txtLicenses.Text = Resources.licenses; @@ -26,11 +29,49 @@ public AboutDialog() acks.Add(ack.Trim()); } lblAcks.Text = string.Join(" / ", acks); + lblUpdate.Text = string.Empty; + if (core.Config.GetBooleanValue(PropertyKeys.AutoUpdateCheck)) + { + CheckForUpdate(); + } } private void linkAuthor_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { Process.Start("http://www.mastersign.de/"); } + + private void VersionDoubleClickHandler(object sender, EventArgs e) + { + CheckForUpdate(); + } + + private async void CheckForUpdate() + { + var config = core.Config; + lblUpdate.Text = "Checking for update..."; + picVersionState.Image = Resources.progress_16_animation; + var version = await core.GetLatestVersionNumber(); + if (IsDisposed) return; + if (version != null) + { + var currentVersion = config.GetStringValue(PropertyKeys.Version); + if (!string.Equals(currentVersion, version)) + { + lblUpdate.Text = "Update available: v" + version; + picVersionState.Image = Resources.info_16; + } + else + { + lblUpdate.Text = string.Empty; + picVersionState.Image = Resources.ok_16; + } + } + else + { + lblUpdate.Text = "Check for update failed."; + picVersionState.Image = Resources.warning_16; + } + } } } diff --git a/BenchManager/BenchDashboard/AppInfoDialog.Designer.cs b/BenchManager/BenchDashboard/AppInfoDialog.Designer.cs index 4ced2d89..2a423181 100644 --- a/BenchManager/BenchDashboard/AppInfoDialog.Designer.cs +++ b/BenchManager/BenchDashboard/AppInfoDialog.Designer.cs @@ -31,6 +31,8 @@ private void InitializeComponent() System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AppInfoDialog)); this.lblAppId = new System.Windows.Forms.Label(); this.tabControl = new System.Windows.Forms.TabControl(); + this.tabDocumentation = new System.Windows.Forms.TabPage(); + this.mdDocumentation = new Mastersign.Bench.Dashboard.MarkdownControl(); this.tabResolved = new System.Windows.Forms.TabPage(); this.gridResolved = new System.Windows.Forms.DataGridView(); this.colName = new System.Windows.Forms.DataGridViewTextBoxColumn(); @@ -39,26 +41,32 @@ private void InitializeComponent() this.gridRaw = new System.Windows.Forms.DataGridView(); this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.panelHead = new System.Windows.Forms.Panel(); + this.llblLicense = new System.Windows.Forms.LinkLabel(); this.tabControl.SuspendLayout(); + this.tabDocumentation.SuspendLayout(); this.tabResolved.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.gridResolved)).BeginInit(); this.tabRaw.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.gridRaw)).BeginInit(); + this.panelHead.SuspendLayout(); this.SuspendLayout(); // // lblAppId // - this.lblAppId.Dock = System.Windows.Forms.DockStyle.Top; + this.lblAppId.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.lblAppId.Font = new System.Drawing.Font("Segoe UI", 20.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.lblAppId.Location = new System.Drawing.Point(0, 0); this.lblAppId.Name = "lblAppId"; this.lblAppId.Padding = new System.Windows.Forms.Padding(4); - this.lblAppId.Size = new System.Drawing.Size(433, 48); + this.lblAppId.Size = new System.Drawing.Size(366, 48); this.lblAppId.TabIndex = 1; this.lblAppId.Text = ""; // // tabControl // + this.tabControl.Controls.Add(this.tabDocumentation); this.tabControl.Controls.Add(this.tabResolved); this.tabControl.Controls.Add(this.tabRaw); this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill; @@ -68,6 +76,25 @@ private void InitializeComponent() this.tabControl.Size = new System.Drawing.Size(433, 465); this.tabControl.TabIndex = 2; // + // tabDocumentation + // + this.tabDocumentation.Controls.Add(this.mdDocumentation); + this.tabDocumentation.Location = new System.Drawing.Point(4, 22); + this.tabDocumentation.Name = "tabDocumentation"; + this.tabDocumentation.Padding = new System.Windows.Forms.Padding(3); + this.tabDocumentation.Size = new System.Drawing.Size(425, 439); + this.tabDocumentation.TabIndex = 2; + this.tabDocumentation.Text = "Documentation"; + this.tabDocumentation.UseVisualStyleBackColor = true; + // + // mdDocumentation + // + this.mdDocumentation.Dock = System.Windows.Forms.DockStyle.Fill; + this.mdDocumentation.Location = new System.Drawing.Point(3, 3); + this.mdDocumentation.Name = "mdDocumentation"; + this.mdDocumentation.Size = new System.Drawing.Size(419, 433); + this.mdDocumentation.TabIndex = 0; + // // tabResolved // this.tabResolved.Controls.Add(this.gridResolved); @@ -76,7 +103,7 @@ private void InitializeComponent() this.tabResolved.Padding = new System.Windows.Forms.Padding(3); this.tabResolved.Size = new System.Drawing.Size(425, 439); this.tabResolved.TabIndex = 0; - this.tabResolved.Text = "Effective"; + this.tabResolved.Text = "Properties"; this.tabResolved.UseVisualStyleBackColor = true; // // gridResolved @@ -124,7 +151,7 @@ private void InitializeComponent() this.tabRaw.Padding = new System.Windows.Forms.Padding(3); this.tabRaw.Size = new System.Drawing.Size(425, 439); this.tabRaw.TabIndex = 1; - this.tabRaw.Text = "Raw"; + this.tabRaw.Text = "Raw Properties"; this.tabRaw.UseVisualStyleBackColor = true; // // gridRaw @@ -164,23 +191,47 @@ private void InitializeComponent() this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2"; this.dataGridViewTextBoxColumn2.ReadOnly = true; // + // panelHead + // + this.panelHead.Controls.Add(this.llblLicense); + this.panelHead.Controls.Add(this.lblAppId); + this.panelHead.Dock = System.Windows.Forms.DockStyle.Top; + this.panelHead.Location = new System.Drawing.Point(0, 0); + this.panelHead.Name = "panelHead"; + this.panelHead.Size = new System.Drawing.Size(433, 48); + this.panelHead.TabIndex = 3; + // + // llblLicense + // + this.llblLicense.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.llblLicense.Location = new System.Drawing.Point(372, 9); + this.llblLicense.Name = "llblLicense"; + this.llblLicense.Size = new System.Drawing.Size(49, 13); + this.llblLicense.TabIndex = 2; + this.llblLicense.TabStop = true; + this.llblLicense.Text = "License"; + this.llblLicense.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.llblLicense.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LicenseHandler); + // // AppInfoDialog // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(433, 513); this.Controls.Add(this.tabControl); - this.Controls.Add(this.lblAppId); + this.Controls.Add(this.panelHead); this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Name = "AppInfoDialog"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "App Info"; this.tabControl.ResumeLayout(false); + this.tabDocumentation.ResumeLayout(false); this.tabResolved.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.gridResolved)).EndInit(); this.tabRaw.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.gridRaw)).EndInit(); + this.panelHead.ResumeLayout(false); this.ResumeLayout(false); } @@ -196,5 +247,9 @@ private void InitializeComponent() private System.Windows.Forms.DataGridView gridRaw; private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1; private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2; + private System.Windows.Forms.TabPage tabDocumentation; + private MarkdownControl mdDocumentation; + private System.Windows.Forms.Panel panelHead; + private System.Windows.Forms.LinkLabel llblLicense; } } \ No newline at end of file diff --git a/BenchManager/BenchDashboard/AppInfoDialog.cs b/BenchManager/BenchDashboard/AppInfoDialog.cs index 7bc925c4..61b7976d 100644 --- a/BenchManager/BenchDashboard/AppInfoDialog.cs +++ b/BenchManager/BenchDashboard/AppInfoDialog.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; @@ -12,85 +13,30 @@ namespace Mastersign.Bench.Dashboard { internal partial class AppInfoDialog : Form { - private static readonly string[] KnownProperties = new[] - { - PropertyKeys.AppTyp, - PropertyKeys.AppWebsite, - PropertyKeys.AppDocs, - PropertyKeys.AppVersion, - PropertyKeys.AppDependencies, - PropertyKeys.AppForce, - PropertyKeys.AppSetupTestFile, - PropertyKeys.AppPackageName, - PropertyKeys.AppUrl, - PropertyKeys.AppDownloadCookies, - PropertyKeys.AppDownloadHeaders, - PropertyKeys.AppResourceName, - PropertyKeys.AppArchiveName, - PropertyKeys.AppArchiveTyp, - PropertyKeys.AppArchivePath, - PropertyKeys.AppDir, - PropertyKeys.AppExe, - PropertyKeys.AppRegister, - PropertyKeys.AppPath, - PropertyKeys.AppEnvironment, - PropertyKeys.AppAdornedExecutables, - PropertyKeys.AppRegistryKeys, - PropertyKeys.AppLauncher, - PropertyKeys.AppLauncherExecutable, - PropertyKeys.AppLauncherArguments, - PropertyKeys.AppLauncherIcon, - }; - public AppInfoDialog(BenchConfiguration config, AppFacade app) { InitializeComponent(); gridResolved.DoubleBuffered(true); lblAppId.Text = app.Label; LoadProperties(config, app); + LoadLicense(app); + LoadDocumentation(app); } private void LoadProperties(BenchConfiguration config, AppFacade app) { gridResolved.Rows.Clear(); - AddRow(gridResolved, "ID", app.ID); - AddRow(gridResolved, PropertyKeys.AppTyp, app.Typ); - AddRow(gridResolved, PropertyKeys.AppWebsite, app.Website); - AddRow(gridResolved, PropertyKeys.AppDocs, app.Docs); - AddRow(gridResolved, PropertyKeys.AppVersion, app.Version); - AddRow(gridResolved, "Installed Version", app.InstalledVersion); - AddRow(gridResolved, PropertyKeys.AppDependencies, app.Dependencies); - AddRow(gridResolved, PropertyKeys.AppForce, app.Force); - AddRow(gridResolved, PropertyKeys.AppSetupTestFile, app.SetupTestFile); - AddRow(gridResolved, PropertyKeys.AppPackageName, app.PackageName); - AddRow(gridResolved, PropertyKeys.AppUrl, app.Url); - AddRow(gridResolved, PropertyKeys.AppDownloadCookies, app.DownloadCookies); - AddRow(gridResolved, PropertyKeys.AppDownloadHeaders, app.DownloadHeaders); - AddRow(gridResolved, PropertyKeys.AppResourceName, app.ResourceFileName); - AddRow(gridResolved, PropertyKeys.AppArchiveName, app.ResourceArchiveName); - AddRow(gridResolved, PropertyKeys.AppArchivePath, app.ResourceArchivePath); - AddRow(gridResolved, PropertyKeys.AppDir, app.Dir); - AddRow(gridResolved, PropertyKeys.AppExe, app.Exe); - AddRow(gridResolved, PropertyKeys.AppRegister, app.Register); - AddRow(gridResolved, PropertyKeys.AppPath, app.Path); - AddRow(gridResolved, PropertyKeys.AppEnvironment, app.Environment); - AddRow(gridResolved, PropertyKeys.AppAdornedExecutables, app.AdornedExecutables); - AddRow(gridResolved, PropertyKeys.AppRegistryKeys, app.RegistryKeys); - AddRow(gridResolved, PropertyKeys.AppLauncher, app.Launcher); - AddRow(gridResolved, PropertyKeys.AppLauncherExecutable, app.LauncherExecutable); - AddRow(gridResolved, PropertyKeys.AppLauncherArguments, app.LauncherArguments); - AddRow(gridResolved, PropertyKeys.AppLauncherIcon, app.LauncherIcon); - foreach(var key in config.PropertyNames(app.ID)) + foreach (var kvp in app.KnownProperties) { - if (!KnownProperties.Contains(key)) - { - AddRow(gridResolved, key, config.GetGroupValue(app.ID, key)); - } + AddRow(gridResolved, kvp.Key, kvp.Value); + } + foreach (var kvp in app.UnknownProperties) + { + AddRow(gridResolved, kvp.Key, kvp.Value); } gridRaw.Rows.Clear(); - AddRow(gridRaw, "ID", app.ID); - foreach(var key in config.PropertyNames(app.ID)) + foreach (var key in config.PropertyNames(app.ID)) { AddRow(gridRaw, key, config.GetRawGroupValue(app.ID, key)); } @@ -130,5 +76,33 @@ private void AddRow(DataGridView grid, string name, string value) { grid.Rows.Add(name, value); } + + private void LoadLicense(AppFacade app) + { + llblLicense.Tag = app.LicenseUrl; + llblLicense.Visible = llblLicense.Tag != null; + } + + private void LoadDocumentation(AppFacade app) + { + var docText = app.MarkdownDocumentation; + if (!string.IsNullOrWhiteSpace(docText)) + { + mdDocumentation.ShowMarkdownText(docText, app.Label); + } + else + { + tabControl.TabPages.Remove(tabDocumentation); + } + } + + private void LicenseHandler(object sender, LinkLabelLinkClickedEventArgs e) + { + var url = ((Control)sender).Tag as Uri; + if (url != null) + { + Process.Start(url.AbsoluteUri); + } + } } } diff --git a/BenchManager/BenchDashboard/AppLauncherControl.cs b/BenchManager/BenchDashboard/AppLauncherControl.cs index 5195c9fb..7967c227 100644 --- a/BenchManager/BenchDashboard/AppLauncherControl.cs +++ b/BenchManager/BenchDashboard/AppLauncherControl.cs @@ -16,6 +16,17 @@ public partial class AppLauncherControl : UserControl public AppLauncherControl() { InitializeComponent(); + VisibleChanged += VisibleChangedHandler; + } + + private void VisibleChangedHandler(object sender, EventArgs e) + { + if (!Visible) return; + if (listView.Items.Count > 0 && icons32.Images.Count == 0) + { + Application.DoEvents(); + LoadIconImages(); + } } public Core Core { get; set; } @@ -32,10 +43,20 @@ public AppIndexFacade AppIndex } } - private async void BindAppIndex() + private void BindAppIndex() { icons16.Images.Clear(); icons32.Images.Clear(); + listView.Items.Clear(); + var items = from app in appIndex.ActiveApps + where app.Launcher != null + select AppItem(app); + listView.Items.AddRange(items.ToArray()); + if (Visible) LoadIconImages(); + } + + private async void LoadIconImages() + { foreach (var app in appIndex.ActiveApps) { if (app.Launcher != null) @@ -45,11 +66,6 @@ private async void BindAppIndex() icons32.Images.Add(app.ID, icons.Item2); } } - listView.Items.Clear(); - var items = from app in appIndex.ActiveApps - where app.Launcher != null - select AppItem(app); - listView.Items.AddRange(items.ToArray()); } private ListViewItem AppItem(AppFacade app) diff --git a/BenchManager/BenchDashboard/AppWrapper.cs b/BenchManager/BenchDashboard/AppWrapper.cs index a7845231..2ac6446a 100644 --- a/BenchManager/BenchDashboard/AppWrapper.cs +++ b/BenchManager/BenchDashboard/AppWrapper.cs @@ -25,6 +25,14 @@ public AppWrapper(AppFacade app, int no) public string Label { get { return app.Label; } } + public string Name => app.Name; + + public string Namespace => app.Namespace ?? "(default)"; + + public string AppLibrary => app.AppLibrary?.ID ?? "user"; + + public string Category => app.Category; + public string Version { get @@ -35,6 +43,10 @@ public string Version } } + public string License => app.License; + + public Uri LicenseUrl => app.LicenseUrl; + public string Launcher { get { return app.Launcher; } } public int Index { get { return no; } } diff --git a/BenchManager/BenchDashboard/BenchDashboard.csproj b/BenchManager/BenchDashboard/BenchDashboard.csproj index cb8d6b56..8d938125 100644 --- a/BenchManager/BenchDashboard/BenchDashboard.csproj +++ b/BenchManager/BenchDashboard/BenchDashboard.csproj @@ -107,6 +107,12 @@ MainForm.cs + + UserControl + + + MarkdownControl.cs + Form @@ -157,6 +163,9 @@ MainForm.cs + + MarkdownControl.cs + MarkdownViewer.cs @@ -200,13 +209,16 @@ + + + + - @@ -230,6 +242,7 @@ + diff --git a/BenchManager/BenchDashboard/ConEmuExecutionHost.cs b/BenchManager/BenchDashboard/ConEmuExecutionHost.cs index 9986d383..03cc4617 100644 --- a/BenchManager/BenchDashboard/ConEmuExecutionHost.cs +++ b/BenchManager/BenchDashboard/ConEmuExecutionHost.cs @@ -10,49 +10,35 @@ using System.Diagnostics; using System.Windows.Forms; using ConEmu.WinForms; +using Mastersign.Bench.RemoteExecHost; namespace Mastersign.Bench.Dashboard { - class ConEmuExecutionHost : IProcessExecutionHost + class ConEmuExecutionHost : PowerShellExecutionHostBase { - private const string PowerShellHostScript = "PsExecHost.ps1"; - - private const int CONNECTION_TIMEOUT = 5000; - - private const string EXITCODE_LINE_FORMAT = "EXITCODE {0} "; - private const string TRANSCRIPTPATH_LINE_FORMAT = "TRANSCRIPT {0} "; - private readonly ConEmuControl control; private readonly Core core; private readonly string conEmuExe; - private readonly Semaphore hostSemaphore = new Semaphore(1, 1); - private XmlDocument config; - private string currentToken; private ConEmuSession currentSession; - private IProcessExecutionHost backupHost; - - private bool reloadConfigBeforeNextExecution = false; - public ConEmuExecutionHost(Core core, ConEmuControl control, string conEmuExe) + : base(core.Config.BenchRootDir, core.Config.GetStringValue(PropertyKeys.BenchScripts)) { this.core = core; this.control = control; this.conEmuExe = conEmuExe; - backupHost = new DefaultExecutionHost(); config = LoadConfigFromResource(); - StartPowerShellExecutionHost(); this.core.ConfigReloaded += CoreConfigReloadedHandler; } private void CoreConfigReloadedHandler(object sender, EventArgs e) { - reloadConfigBeforeNextExecution = true; + RequestConfigurationReload(); } private XmlDocument LoadConfigFromResource() @@ -96,37 +82,26 @@ private ConEmuSession StartProcess(ConEmuStartInfo startInfo) return control.Start(startInfo); } - private void StartPowerShellExecutionHost() + protected override void StartPowerShellExecutionHost() { - if (!IsConEmuInstalled) - { - return; - } - var config = core.Config; - var hostScript = Path.Combine(config.GetStringValue(PropertyKeys.BenchScripts), PowerShellHostScript); - if (!File.Exists(hostScript)) - { - throw new FileNotFoundException("The PowerShell host script was not found."); - } - currentToken = Guid.NewGuid().ToString("D"); - var cwd = config.GetStringValue(PropertyKeys.BenchRoot); - var startInfo = BuildStartInfo(cwd, PowerShell.Executable, + if (!IsConEmuInstalled) return; + var startInfo = BuildStartInfo(BenchRoot, PowerShell.Executable, "\"" + string.Join(" ", "-NoProfile", "-NoLogo", "-ExecutionPolicy", "Unrestricted", - "-File", "\"" + hostScript + "\"", - "-Token", currentToken)); + "-File", "\"" + PsExecHostScriptFile + "\"", + "-Token", CurrentToken)); currentSession = StartProcess(startInfo); currentSession.ConsoleEmulatorClosed += (s, o) => { - currentToken = null; + CurrentToken = null; currentSession = null; }; } - private bool IsPowerShellExecutionHostRunning => + protected override bool IsPowerShellExecutionHostRunning => currentSession != null; - private void WaitForSessionToEnd() + protected override void WaitForPowerShellExecutionHostToEnd() { while (currentSession != null) { @@ -141,170 +116,9 @@ private void WaitForSessionToEnd() } } - private IEnumerable SendCommand(string command, params string[] arguments) - { - if (!IsPowerShellExecutionHostRunning) throw new InvalidOperationException(); - hostSemaphore.WaitOne(); - try - { - var client = new NamedPipeClientStream(".", currentToken, PipeDirection.InOut); - TextWriter w; - TextReader r; - try - { - client.Connect(CONNECTION_TIMEOUT); - w = new StreamWriter(client, Encoding.UTF8, 4, true); - r = new StreamReader(client, Encoding.UTF8, false, 4, true); - } - catch (TimeoutException) - { - yield break; - } - catch (IOException ioEx) - { - Debug.WriteLine(ioEx); - yield break; - } - w.WriteLine(command); - foreach (var arg in arguments) - { - w.WriteLine(arg); - } - w.Flush(); - while (client.IsConnected) - { - var l = r.ReadLine(); - if (l != null) - { - yield return l; - } - } - r.Dispose(); - client.Dispose(); - } - finally - { - hostSemaphore.Release(); - } - } - - private void ReloadConfiguration() - { - if (!IsPowerShellExecutionHostRunning) return; - SendCommand("reload").Any(l => l == "OK"); - reloadConfigBeforeNextExecution = false; - } - - private void StopPowerShellExecutionHost() - { - if (!IsPowerShellExecutionHostRunning) return; - SendCommand("close").Any(l => l == "OK"); - WaitForSessionToEnd(); - } - - private bool ParseExitCode(string line, ref int exitCode) - { - var exitCodePrefix = string.Format(EXITCODE_LINE_FORMAT, currentToken); - if (line.StartsWith(exitCodePrefix)) - { - var number = line.Substring(exitCodePrefix.Length); - int tmp; - if (int.TryParse(number, out tmp)) exitCode = tmp; - return true; - } - return false; - } - - private bool ParseTranscriptPath(string line, ref string transcriptPath) - { - var exitCodePrefix = string.Format(TRANSCRIPTPATH_LINE_FORMAT, currentToken); - if (line.StartsWith(exitCodePrefix)) - { - transcriptPath = line.Substring(exitCodePrefix.Length); - return true; - } - return false; - } - - public ProcessExecutionResult RunProcess(BenchEnvironment env, - string cwd, string executable, string arguments, - ProcessMonitoring monitoring) - { - if (IsDisposed) - { - throw new ObjectDisposedException(nameof(ConEmuExecutionHost)); - } - if (!IsPowerShellExecutionHostRunning) - { - return backupHost.RunProcess(env, cwd, executable, arguments, monitoring); - } - if (reloadConfigBeforeNextExecution) - { - ReloadConfiguration(); - } - var collectOutput = (monitoring & ProcessMonitoring.Output) == ProcessMonitoring.Output; - var response = SendCommand("exec", cwd, executable, arguments); - var exitCode = 999999; - var transcriptPath = default(string); - foreach (var l in response) - { - ParseExitCode(l, ref exitCode); - ParseTranscriptPath(l, ref transcriptPath); - } - var output = default(string); - if (collectOutput && transcriptPath != null && File.Exists(transcriptPath)) - { - output = File.ReadAllText(transcriptPath, Encoding.Default); - File.Delete(transcriptPath); - } - return new ProcessExecutionResult(exitCode, output); - } - - public void StartProcess(BenchEnvironment env, - string cwd, string executable, string arguments, - ProcessExitCallback cb, ProcessMonitoring monitoring) - { - if (IsDisposed) - { - throw new ObjectDisposedException(nameof(ConEmuExecutionHost)); - } - if (!IsPowerShellExecutionHostRunning) - { - backupHost.StartProcess(env, cwd, executable, arguments, cb, monitoring); - return; - } - var collectOutput = (monitoring & ProcessMonitoring.Output) == ProcessMonitoring.Output; - var response = SendCommand("exec", cwd, executable, arguments); - AsyncManager.StartTask(() => - { - var exitCode = 999999; - var transcriptPath = default(string); - foreach (var l in response) - { - ParseExitCode(l, ref exitCode); - ParseTranscriptPath(l, ref transcriptPath); - } - var output = default(string); - if (collectOutput && transcriptPath != null && File.Exists(transcriptPath)) - { - output = File.ReadAllText(transcriptPath, Encoding.Default); - File.Delete(transcriptPath); - } - var result = new ProcessExecutionResult(exitCode, output); - cb(result); - }); - } - - public bool IsDisposed { get; private set; } - - public void Dispose() + protected override void OnDispose() { - if (IsDisposed) return; - IsDisposed = true; - core.ConfigReloaded -= CoreConfigReloadedHandler; - StopPowerShellExecutionHost(); - backupHost.Dispose(); - backupHost = null; + this.core.ConfigReloaded -= CoreConfigReloadedHandler; } private delegate ConEmuSession ConEmuStarter(ConEmuStartInfo si); diff --git a/BenchManager/BenchDashboard/Core.cs b/BenchManager/BenchDashboard/Core.cs index 600146be..470ef311 100644 --- a/BenchManager/BenchDashboard/Core.cs +++ b/BenchManager/BenchDashboard/Core.cs @@ -22,7 +22,7 @@ public class Core : IDisposable, IBenchManager public Downloader Downloader { get; private set; } - public Control GuiContext { get; set; } + public Form GuiContext { get; set; } public bool SetupOnStartup { get; set; } @@ -30,9 +30,13 @@ public class Core : IDisposable, IBenchManager private bool configReloadNecessary; + private bool activationReloadNecessary; + private readonly object configReloadLockHandle = new object(); - private FileSystemWatcher[] fsWatchers; + private FileSystemWatcher[] configFileWatchers; + + private FileSystemWatcher[] activationFileWatchers; private ActionState actionState; @@ -40,6 +44,8 @@ public class Core : IDisposable, IBenchManager public event EventHandler ConfigReloaded; + public event EventHandler AppActivationChanged; + public event EventHandler AllAppStateChanged; public event EventHandler AppStateChanged; @@ -57,7 +63,7 @@ private Action CatchTaskInfos(Action notify) OnAppStateChanged(info.AppId); } if (info is TaskError) ActionState = ActionState.BusyWithErrors; - if (notify != null) notify(info); + notify?.Invoke(info); }; } @@ -68,20 +74,21 @@ public Core(string benchRoot) Config = new BenchConfiguration(benchRoot, true, true, true); Env = new BenchEnvironment(Config); Downloader = BenchTasks.InitializeDownloader(Config); - ProcessExecutionHost = new DefaultExecutionHost(); + ProcessExecutionHost = new SimpleExecutionHost(); SetupFileWatchers(); } + public void Shutdown() + { + GuiContext.Close(); + } + public void SyncWithGui(ThreadStart task) { if (GuiContext != null && GuiContext.InvokeRequired) - { GuiContext.Invoke(task); - } else - { task(); - } } public bool Busy @@ -92,21 +99,18 @@ public bool Busy if (value == busy) return; busy = value; OnBusyChanged(); - if (!busy && configReloadNecessary) + if (!busy) { - Reload(); + if (configReloadNecessary) + Reload(); + else if (activationReloadNecessary) + ReloadAppActivation(); } } } private void OnBusyChanged() - { - SyncWithGui(() => - { - var handler = BusyChanged; - if (handler != null) handler(this, EventArgs.Empty); - }); - } + => SyncWithGui(() => BusyChanged?.Invoke(this, EventArgs.Empty)); public ActionState ActionState { @@ -120,99 +124,95 @@ public ActionState ActionState } private void OnActionStateChanged() - { - SyncWithGui(() => - { - var handler = ActionStateChanged; - if (handler != null) handler(this, EventArgs.Empty); - }); - } + => SyncWithGui(() => ActionStateChanged?.Invoke(this, EventArgs.Empty)); public Cancelation Cancelation { get { return cancelation; } } private void SetupFileWatchers() { DisposeFileWatchers(); - var paths = Config.Sources; - fsWatchers = paths - .Select(p => new FileSystemWatcher(Path.GetDirectoryName(p)) - { - Filter = Path.GetFileName(p), - //NotifyFilter = NotifyFilters.LastWrite, - IncludeSubdirectories = false, - EnableRaisingEvents = true, - }) + + var configFileSet = ConfigurationFileType.UserConfig + | ConfigurationFileType.SiteConfig + | ConfigurationFileType.UserAppLib; + configFileWatchers = Config + .GetConfigurationFiles(configFileSet, actuallyLoaded: true, mustExist: true) + .Select(p => CreateFileWatcher(p.Path, ConfigFileChangedHandler)) .ToArray(); - foreach (var w in fsWatchers) + + var activationFileSet = ConfigurationFileType.AppSelection; + configFileWatchers = Config + .GetConfigurationFiles(activationFileSet, actuallyLoaded: true, mustExist: true) + .Select(p => CreateFileWatcher(p.Path, ActivationFileChangedHandler)) + .ToArray(); + } + + private FileSystemWatcher CreateFileWatcher(string path, FileSystemEventHandler handler) + { + var watcher = new FileSystemWatcher(Path.GetDirectoryName(path)) { - w.Changed += SourceFileChangedHandler; - } + Filter = Path.GetFileName(path), + IncludeSubdirectories = false, + EnableRaisingEvents = true, + }; + watcher.Changed += handler; + return watcher; } private void DisposeFileWatchers() { - if (fsWatchers != null) + if (configFileWatchers != null) + { + foreach (var w in configFileWatchers) + { + w.Changed -= ConfigFileChangedHandler; + w.Dispose(); + } + configFileWatchers = null; + } + if (activationFileWatchers != null) { - foreach (var w in fsWatchers) + foreach (var w in activationFileWatchers) { - w.Changed -= SourceFileChangedHandler; + w.Changed -= ActivationFileChangedHandler; w.Dispose(); } - fsWatchers = null; + activationFileWatchers = null; } } - private void SourceFileChangedHandler(object sender, FileSystemEventArgs e) + private void ConfigFileChangedHandler(object sender, FileSystemEventArgs e) { if (busy) - { configReloadNecessary = true; - } else - { Task.Run(() => Reload(true)); - } } - private void OnConfigReloaded() + private void ActivationFileChangedHandler(object sender, FileSystemEventArgs e) { - SyncWithGui(() => - { - var handler = ConfigReloaded; - if (handler != null) - { - handler(this, EventArgs.Empty); - } - }); + if (busy) + activationReloadNecessary = true; + else + Task.Run(() => ReloadAppActivation()); } + private void OnConfigReloaded() + => SyncWithGui(() => ConfigReloaded?.Invoke(this, EventArgs.Empty)); + private void OnAllAppStateChanged() - { - SyncWithGui(() => - { - var handler = AllAppStateChanged; - if (handler != null) - { - handler(this, EventArgs.Empty); - } - }); - } + => SyncWithGui(() => AllAppStateChanged?.Invoke(this, EventArgs.Empty)); + + private void OnAppActivationChanged() + => SyncWithGui(() => AppActivationChanged?.Invoke(this, EventArgs.Empty)); private void OnAppStateChanged(string appId) - { - SyncWithGui(() => - { - var handler = AppStateChanged; - if (handler != null) - { - handler(this, new AppEventArgs(appId)); - } - }); - } + => SyncWithGui(() => AppStateChanged?.Invoke(this, new AppEventArgs(appId))); public void Reload(bool configChanged = false) { configReloadNecessary = false; + activationReloadNecessary = false; lock (configReloadLockHandle) { Config = Config.Reload(); @@ -226,49 +226,39 @@ public void Reload(bool configChanged = false) OnConfigReloaded(); } + public void ReloadAppActivation() + { + activationReloadNecessary = false; + lock (configReloadLockHandle) + { + Config.ReloadAppActivation(); + } + OnAppActivationChanged(); + } + public void SetAppActivated(string appId, bool value) { var activationFile = new ActivationFile(Config.GetStringValue(PropertyKeys.AppActivationFile)); if (value) - { activationFile.SignIn(appId); - } else - { activationFile.SignOut(appId); - } } public void SetAppDeactivated(string appId, bool value) { var deactivationFile = new ActivationFile(Config.GetStringValue(PropertyKeys.AppDeactivationFile)); if (value) - { deactivationFile.SignIn(appId); - } else - { deactivationFile.SignOut(appId); - } } - public Task RunTaskAsync(BenchTaskForAll action, - Action notify, Cancelation cancelation) - { - return Task.Run(() => - { - return action(this, CatchTaskInfos(notify), cancelation); - }); - } + public Task RunTaskAsync(BenchTaskForAll action, Action notify, Cancelation cancelation) + => Task.Run(() => action(this, CatchTaskInfos(notify), cancelation)); - public Task RunTaskAsync(BenchTaskForOne action, string appId, - Action notify, Cancelation cancelation) - { - return Task.Run(() => - { - return action(this, appId, CatchTaskInfos(notify), cancelation); - }); - } + public Task RunTaskAsync(BenchTaskForOne action, string appId, Action notify, Cancelation cancelation) + => Task.Run(() => action(this, appId, CatchTaskInfos(notify), cancelation)); private void BeginAction() { @@ -607,6 +597,96 @@ public async Task UpdateEnvironmentAsync(Action notify) return result; } + public async Task UpdateAppLibrariesAsync(Action notify) + { + BeginAction(); + BenchTasks.DeleteAppLibraries(Config); + var result = await RunTaskAsync(BenchTasks.DoLoadAppLibraries, notify, cancelation); + EndAction(result.Success); + if (result.Canceled) + { + UI.ShowWarning("Loading App Libraries", "Canceled"); + EndAction(result.Success); + return result; + } + else if (result.Success) + { + Reload(); + } + else + { + UI.ShowWarning("Loading App Libraries", + "Loading the app libraries failed."); + } + return result; + } + + public async Task UpdateAppsAsync(Action notify) + { + var result = await UpdateAppLibrariesAsync(notify); + if (!result.Success) return result; + return await UpgradeAppsAsync(notify); + } + + private class AsyncVersionNumberResult : IAsyncResult + { + public object AsyncState => null; + + public bool Success { get; private set; } + public string VersionNumber { get; private set; } + private ManualResetEvent handle; + + public AsyncVersionNumberResult() + { + handle = new ManualResetEvent(false); + } + + public WaitHandle AsyncWaitHandle => handle; + + public bool CompletedSynchronously => false; + + public bool IsCompleted => handle == null; + + public void NotifyResult(bool success, string versionNumber) + { + Success = success; + VersionNumber = versionNumber; + handle.Set(); + handle = null; + } + } + + public Task GetLatestVersionNumber() + { + var asyncResult = new AsyncVersionNumberResult(); + BenchTasks.GetLatestVersionAsync(Config, asyncResult.NotifyResult); + return Task.Factory.FromAsync(asyncResult, r => + { + var result = (AsyncVersionNumberResult)r; + return result.Success ? result.VersionNumber : null; + }); + } + + public async Task DownloadBenchUpdateAsync(Action notify) + { + BeginAction(); + var result = await RunTaskAsync(BenchTasks.DoDownloadBenchUpdate, notify, cancelation); + EndAction(result.Success); + if (result.Canceled) + { + UI.ShowWarning("Downloading Bench update", "Canceled"); + } + else if (!result.Success) + { + UI.ShowWarning("Upgrading Bench System", + BuildCombinedErrorMessage( + "Downloading the latest Bench update failed.", + "Downloading the latest Bench update failed.", + null, 1)); + } + return result; + } + private static string BuildCombinedErrorMessage(string infoWithErrors, string infoWithoutErrors, IEnumerable errors, int maxLines) { diff --git a/BenchManager/BenchDashboard/DownloadList.cs b/BenchManager/BenchDashboard/DownloadList.cs index 6e9c5eb0..ad604b19 100644 --- a/BenchManager/BenchDashboard/DownloadList.cs +++ b/BenchManager/BenchDashboard/DownloadList.cs @@ -6,18 +6,25 @@ using System.Text; using System.Windows.Forms; using System.IO; +using System.Diagnostics; namespace Mastersign.Bench.Dashboard { public partial class DownloadList : UserControl { + private readonly Dictionary downloadControls + = new Dictionary(); + public DownloadList() { InitializeComponent(); + Disposed += DisposedHandler; } - private readonly Dictionary downloadControls - = new Dictionary(); + private void DisposedHandler(object sender, EventArgs e) + { + Downloader = null; + } private Downloader downloader; @@ -28,22 +35,47 @@ public Downloader Downloader { if (downloader != null) { - downloader.DownloadStarted -= DownloadStartedHandler; - downloader.DownloadProgress -= DownloadProgressHandler; - downloader.DownloadEnded -= DownloadEndedHandler; - downloadControls.Clear(); - Controls.Clear(); + UnbindDownloader(); } downloader = value; if (downloader != null) { - downloader.DownloadStarted += DownloadStartedHandler; - downloader.DownloadProgress += DownloadProgressHandler; - downloader.DownloadEnded += DownloadEndedHandler; + BindDownloader(); } } } + private void BindDownloader() + { + downloader.IsWorkingChanged += DownloaderIsWorkingChangedHandler; + downloader.DownloadStarted += DownloadStartedHandler; + downloader.DownloadProgress += DownloadProgressHandler; + downloader.DownloadEnded += DownloadEndedHandler; + ClearDownloadTasks(); + // Potential inconsitency ... add already running download tasks + } + + private void UnbindDownloader() + { + downloader.IsWorkingChanged -= DownloaderIsWorkingChangedHandler; + downloader.DownloadStarted -= DownloadStartedHandler; + downloader.DownloadProgress -= DownloadProgressHandler; + downloader.DownloadEnded -= DownloadEndedHandler; + } + + private void DownloaderIsWorkingChangedHandler(object sender, EventArgs e) + { + if (InvokeRequired) + { + Invoke((EventHandler)DownloaderIsWorkingChangedHandler, sender, e); + return; + } + if (downloader.IsWorking) + { + ClearDownloadTasks(); + } + } + private void DownloadStartedHandler(object sender, DownloadEventArgs e) { if (InvokeRequired) @@ -51,15 +83,7 @@ private void DownloadStartedHandler(object sender, DownloadEventArgs e) Invoke((EventHandler)DownloadStartedHandler, sender, e); return; } - var control = new DownloadControl(); - control.Visible = false; - control.Left = ClientRectangle.Left; - control.Width = ClientSize.Width; - control.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; - control.FileName = Path.GetFileName(e.Task.TargetFile); - downloadControls.Add(e.Task, control); - Controls.Add(control); - UpdateLayout(); + AddDownloadTask(e.Task); } private void DownloadProgressHandler(object sender, DownloadProgressEventArgs e) @@ -87,9 +111,7 @@ private void DownloadEndedHandler(object sender, DownloadEventArgs e) } if (e.Task.Success) { - Controls.Remove(downloadControls[e.Task]); - downloadControls.Remove(e.Task); - UpdateLayout(); + RemoveDownloadTask(e.Task); } else { @@ -97,6 +119,32 @@ private void DownloadEndedHandler(object sender, DownloadEventArgs e) } } + private void AddDownloadTask(DownloadTask t) + { + var control = new DownloadControl(); + control.Visible = false; + control.Left = ClientRectangle.Left; + control.Width = ClientSize.Width; + control.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; + control.FileName = Path.GetFileName(t.TargetFile); + downloadControls.Add(t, control); + Controls.Add(control); + UpdateLayout(); + } + + private void RemoveDownloadTask(DownloadTask t) + { + Controls.Remove(downloadControls[t]); + downloadControls.Remove(t); + UpdateLayout(); + } + + private void ClearDownloadTasks() + { + downloadControls.Clear(); + Controls.Clear(); + } + private void UpdateLayout() { SuspendLayout(); diff --git a/BenchManager/BenchDashboard/MainForm.cs b/BenchManager/BenchDashboard/MainForm.cs index 7eae0b1e..08c2e578 100644 --- a/BenchManager/BenchDashboard/MainForm.cs +++ b/BenchManager/BenchDashboard/MainForm.cs @@ -24,9 +24,10 @@ public partial class MainForm : Form public MainForm(Core core) { this.core = core; + core.ConfigReloaded += ConfigReloadedHandler; core.AllAppStateChanged += AppStateChangedHandler; + core.AppActivationChanged += AppStateChangedHandler; core.AppStateChanged += AppStateChangedHandler; - core.ConfigReloaded += ConfigReloadedHandler; core.BusyChanged += CoreBusyChangedHandler; InitializeComponent(); InitializeAppLauncherList(); @@ -86,16 +87,24 @@ private async void InitializeDocsMenu() { var ctxm = new ContextMenuStrip(); - var benchItem = new ToolStripMenuItem("Bench"); + var benchItem = new ToolStripMenuItem("Bench Website"); benchItem.Image = new Icon(Icon, new Size(16, 16)).ToBitmap(); benchItem.Tag = core.Config.GetStringValue(PropertyKeys.Website); benchItem.Click += LinkHandler; ctxm.Items.Add(benchItem); - var appLibItem = new ToolStripMenuItem("Bench App Library"); - appLibItem.Image = Resources.library_16; - appLibItem.Click += AppIndexHandler; - ctxm.Items.Add(appLibItem); + var appLibsItem = new ToolStripMenuItem("App Libraries"); + appLibsItem.Image = Resources.library_16; + ctxm.Items.Add(appLibsItem); + foreach (var lib in core.Config.AppLibraries) + { + var appLibItem = new ToolStripMenuItem("App Library '" + lib.ID + "'"); + appLibItem.Image = Resources.books_16; + appLibItem.Tag = lib; + appLibItem.Click += AppIndexHandler; + appLibsItem.DropDownItems.Add(appLibItem); + } + var userAppLibItem = new ToolStripMenuItem("User App Library"); userAppLibItem.Image = Resources.userlibrary_16; userAppLibItem.Click += CustomAppIndexHandler; @@ -138,15 +147,25 @@ private async void InitializeDocsMenu() private void AppIndexHandler(object sender, EventArgs e) { - var viewer = new MarkdownViewer(core); - viewer.LoadMarkdown(core.Config.GetStringValue(PropertyKeys.AppIndexFile), "Bench App Library"); - viewer.Show(); + var lib = (sender as ToolStripItem)?.Tag as AppLibrary; + if (lib != null) + { + var viewer = new MarkdownViewer(core); + viewer.LoadMarkdown(Path.Combine(lib.BaseDir, + core.Config.GetStringValue(PropertyKeys.AppLibIndexFileName)), + "App Library '" + lib.ID + "'"); + viewer.Show(); + } } private void CustomAppIndexHandler(object sender, EventArgs e) { var viewer = new MarkdownViewer(core); - viewer.LoadMarkdown(core.Config.GetStringValue(PropertyKeys.CustomAppIndexFile), "User App Library"); + viewer.LoadMarkdown( + Path.Combine( + core.Config.GetStringValue(PropertyKeys.CustomConfigDir), + core.Config.GetStringValue(PropertyKeys.AppLibIndexFileName)), + "User App Library"); viewer.Show(); } @@ -244,14 +263,14 @@ private void RootPathClickHandler(object sender, EventArgs e) private void ShellCmdHandler(object sender, EventArgs e) { - new DefaultExecutionHost().StartProcess(core.Env, + new SimpleExecutionHost().StartProcess(core.Env, core.Config.GetStringValue(PropertyKeys.ProjectRootDir), core.CmdPath, "", result => { }, ProcessMonitoring.ExitCode); } private void ShellPowerShellHandler(object sender, EventArgs e) { - new DefaultExecutionHost().StartProcess(core.Env, + new SimpleExecutionHost().StartProcess(core.Env, core.Config.GetStringValue(PropertyKeys.ProjectRootDir), core.PowerShellPath, "", result => { }, ProcessMonitoring.ExitCode); } @@ -261,7 +280,7 @@ private void ShellBashHandler(object sender, EventArgs e) var bashPath = core.BashPath; if (File.Exists(bashPath)) { - new DefaultExecutionHost().StartProcess(core.Env, + new SimpleExecutionHost().StartProcess(core.Env, core.Config.GetStringValue(PropertyKeys.ProjectRootDir), bashPath, "", result => { }, ProcessMonitoring.ExitCode); } @@ -312,7 +331,7 @@ private void AutoSetupHandler(object sender, EventArgs e) private void AboutHandler(object sender, EventArgs e) { - new AboutDialog().ShowDialog(this); + new AboutDialog(core).ShowDialog(this); } private void DocsHandler(object sender, EventArgs e) diff --git a/BenchManager/BenchDashboard/MarkdownControl.Designer.cs b/BenchManager/BenchDashboard/MarkdownControl.Designer.cs new file mode 100644 index 00000000..b5d7944d --- /dev/null +++ b/BenchManager/BenchDashboard/MarkdownControl.Designer.cs @@ -0,0 +1,57 @@ +namespace Mastersign.Bench.Dashboard +{ + partial class MarkdownControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.webBrowser = new System.Windows.Forms.WebBrowser(); + this.SuspendLayout(); + // + // webBrowser + // + this.webBrowser.Dock = System.Windows.Forms.DockStyle.Fill; + this.webBrowser.Location = new System.Drawing.Point(0, 0); + this.webBrowser.MinimumSize = new System.Drawing.Size(20, 20); + this.webBrowser.Name = "webBrowser"; + this.webBrowser.Size = new System.Drawing.Size(150, 150); + this.webBrowser.TabIndex = 1; + // + // MarkdownControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.webBrowser); + this.Name = "MarkdownControl"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.WebBrowser webBrowser; + } +} diff --git a/BenchManager/BenchDashboard/MarkdownControl.cs b/BenchManager/BenchDashboard/MarkdownControl.cs new file mode 100644 index 00000000..742f2905 --- /dev/null +++ b/BenchManager/BenchDashboard/MarkdownControl.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Mastersign.Bench.Dashboard.Properties; +using System.IO; +using Mastersign.Bench.Markdown; +using System.Diagnostics; + +namespace Mastersign.Bench.Dashboard +{ + public partial class MarkdownControl : UserControl + { + private readonly static string template; + + static MarkdownControl() + { + template = Resources.MarkdownViewerTemplate.Replace("$CSS$", Resources.MarkdownViewerStyle); + } + + private string tempFile; + + private Uri TempDocumentUrl { get { return new Uri("file:///" + TempFile); } } + + public MarkdownControl() + { + InitializeComponent(); + Disposed += MarkdownControl_Disposed; + webBrowser.Navigating += WebBrowser_Navigating; + } + + private void WebBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e) + { + var url = e.Url; + if (url.IsAbsoluteUri && !url.IsFile) + { + e.Cancel = true; + Process.Start(url.AbsoluteUri); + } + } + + private void MarkdownControl_Disposed(object sender, EventArgs e) + { + TempFile = null; + } + + private string TempFile + { + get { return tempFile; } + set + { + if (tempFile != null && File.Exists(tempFile)) + { + File.Delete(tempFile); + } + tempFile = value; + } + } + + private string NewTempFilePath() + { + return Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".html"); + } + + public MarkdownToHtmlConverter ShowMarkdownText(string text, string title) + { + TempFile = NewTempFilePath(); + var md2html = new MarkdownToHtmlConverter(); + try + { + string html; + using (var r = new StringReader(text)) + { + html = md2html.ConvertToHtml(r); + } + html = template.Replace("$TITLE$", title).Replace("$CONTENT$", html); + + File.WriteAllText(TempFile, html, Encoding.UTF8); + } + catch (Exception e) + { + Debug.WriteLine(e.ToString()); + TempFile = null; + return null; + } + webBrowser.Navigate(TempDocumentUrl); + return md2html; + } + + public MarkdownToHtmlConverter ShowMarkdownFile(string filePath, string title = null) + { + TempFile = NewTempFilePath(); + title = title ?? Path.GetFileNameWithoutExtension(filePath); + var md2html = new MarkdownToHtmlConverter(); + try + { + string html; + using (var s = File.Open(filePath, FileMode.Open, FileAccess.Read)) + { + html = md2html.ConvertToHtml(s); + } + html = template.Replace("$TITLE$", title).Replace("$CONTENT$", html); + + File.WriteAllText(TempFile, html, Encoding.UTF8); + } + catch (Exception e) + { + Debug.WriteLine(e.ToString()); + TempFile = null; + return null; + } + webBrowser.Navigate(TempDocumentUrl); + return md2html; + } + + public void ScrollToElement(string id) + { + var element = webBrowser.Document.GetElementById(id); + if (element != null) + { + element.ScrollIntoView(true); + } + } + } +} diff --git a/BenchManager/BenchDashboard/MarkdownControl.resx b/BenchManager/BenchDashboard/MarkdownControl.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/BenchManager/BenchDashboard/MarkdownControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BenchManager/BenchDashboard/MarkdownViewer.Designer.cs b/BenchManager/BenchDashboard/MarkdownViewer.Designer.cs index 16c60056..176f15db 100644 --- a/BenchManager/BenchDashboard/MarkdownViewer.Designer.cs +++ b/BenchManager/BenchDashboard/MarkdownViewer.Designer.cs @@ -29,21 +29,11 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MarkdownViewer)); - this.webBrowser = new System.Windows.Forms.WebBrowser(); this.treeView = new System.Windows.Forms.TreeView(); this.splitter = new System.Windows.Forms.Splitter(); + this.markdownControl = new Mastersign.Bench.Dashboard.MarkdownControl(); this.SuspendLayout(); // - // webBrowser - // - this.webBrowser.Dock = System.Windows.Forms.DockStyle.Fill; - this.webBrowser.Location = new System.Drawing.Point(206, 0); - this.webBrowser.MinimumSize = new System.Drawing.Size(20, 20); - this.webBrowser.Name = "webBrowser"; - this.webBrowser.Size = new System.Drawing.Size(418, 441); - this.webBrowser.TabIndex = 0; - this.webBrowser.Navigating += new System.Windows.Forms.WebBrowserNavigatingEventHandler(this.webBrowser_Navigating); - // // treeView // this.treeView.BorderStyle = System.Windows.Forms.BorderStyle.None; @@ -62,12 +52,20 @@ private void InitializeComponent() this.splitter.TabIndex = 2; this.splitter.TabStop = false; // + // markdownControl + // + this.markdownControl.Dock = System.Windows.Forms.DockStyle.Fill; + this.markdownControl.Location = new System.Drawing.Point(206, 0); + this.markdownControl.Name = "markdownControl"; + this.markdownControl.Size = new System.Drawing.Size(418, 441); + this.markdownControl.TabIndex = 3; + // // MarkdownViewer // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(624, 441); - this.Controls.Add(this.webBrowser); + this.Controls.Add(this.markdownControl); this.Controls.Add(this.splitter); this.Controls.Add(this.treeView); this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); @@ -81,9 +79,8 @@ private void InitializeComponent() } #endregion - - private System.Windows.Forms.WebBrowser webBrowser; private System.Windows.Forms.TreeView treeView; private System.Windows.Forms.Splitter splitter; + private MarkdownControl markdownControl; } } \ No newline at end of file diff --git a/BenchManager/BenchDashboard/MarkdownViewer.cs b/BenchManager/BenchDashboard/MarkdownViewer.cs index acc11d01..d83cc0c2 100644 --- a/BenchManager/BenchDashboard/MarkdownViewer.cs +++ b/BenchManager/BenchDashboard/MarkdownViewer.cs @@ -16,16 +16,8 @@ namespace Mastersign.Bench.Dashboard { public partial class MarkdownViewer : Form { - private readonly static string template; - - static MarkdownViewer() - { - template = Resources.MarkdownViewerTemplate.Replace("$CSS$", Resources.MarkdownViewerStyle); - } - private readonly IBenchManager core; private readonly string windowTitle; - private string tempFile; public MarkdownViewer(IBenchManager core) { @@ -41,7 +33,6 @@ private void MarkdownViewer_Load(object sender, EventArgs e) private void MarkdownViewer_FormClosed(object sender, FormClosedEventArgs e) { - TempFile = null; } private void InitializeBounds() @@ -54,52 +45,10 @@ private void InitializeBounds() SetBounds(x, y, w, h); } - private string TempFile - { - get { return tempFile; } - set - { - if (tempFile != null && File.Exists(tempFile)) - { - File.Delete(tempFile); - } - tempFile = value; - } - } - - private Uri TempDocumentUrl { get { return new Uri("file:///" + TempFile); } } - - private string NewTempFilePath() - { - return Path.Combine( - core.Config.GetStringValue(PropertyKeys.TempDir), - Path.GetRandomFileName() + ".html"); - } - public void LoadMarkdown(string file, string title = null) { - TempFile = NewTempFilePath(); - title = title ?? Path.GetFileNameWithoutExtension(file); - Text = windowTitle + " - " + title; - string html; - var md2html = new MarkdownToHtmlConverter(); - try - { - using (var s = File.Open(file, FileMode.Open, FileAccess.Read)) - { - html = md2html.ConvertToHtml(s); - } - html = template.Replace("$TITLE$", title).Replace("$CONTENT$", html); - - File.WriteAllText(TempFile, html, Encoding.UTF8); - } - catch (Exception e) - { - Debug.WriteLine(e.ToString()); - TempFile = null; - return; - } - webBrowser.Navigate(TempDocumentUrl); + Text = windowTitle + " - " + (title ?? Path.GetFileNameWithoutExtension(file)); + var md2html = markdownControl.ShowMarkdownFile(file, title); LoadTree(md2html.Anchors); } @@ -143,11 +92,7 @@ private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs var mdElement = e.Node.Tag as MdHeadline; if (mdElement != null) { - var element = webBrowser.Document.GetElementById(mdElement.Id); - if (element != null) - { - element.ScrollIntoView(true); - } + markdownControl.ScrollToElement(mdElement.Id); } } diff --git a/BenchManager/BenchDashboard/Program.cs b/BenchManager/BenchDashboard/Program.cs index 60e08946..f5bfa2f3 100644 --- a/BenchManager/BenchDashboard/Program.cs +++ b/BenchManager/BenchDashboard/Program.cs @@ -80,7 +80,7 @@ private static string GetBenchRoot(string[] args) var assemblyName = Assembly.GetExecutingAssembly().GetName(); var codeBase = new Uri(assemblyName.CodeBase).LocalPath; var rootPath = Path.GetFullPath(Path.Combine(Path.Combine(Path.GetDirectoryName(codeBase), ".."), "..")); - return File.Exists(Path.Combine(rootPath, @"res\apps.md")) ? rootPath : null; + return BenchConfiguration.IsValidBenchRoot(rootPath) ? rootPath : null; } private static bool IsImmediateSetupRequested(string[] args) diff --git a/BenchManager/BenchDashboard/Properties/AssemblyInfo.cs b/BenchManager/BenchDashboard/Properties/AssemblyInfo.cs index 98c74b13..682e2aed 100644 --- a/BenchManager/BenchDashboard/Properties/AssemblyInfo.cs +++ b/BenchManager/BenchDashboard/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.13.3.0")] -[assembly: AssemblyFileVersion("0.13.3.0")] +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/BenchManager/BenchDashboard/Properties/Resources.Designer.cs b/BenchManager/BenchDashboard/Properties/Resources.Designer.cs index bda89c0b..75181bf1 100644 --- a/BenchManager/BenchDashboard/Properties/Resources.Designer.cs +++ b/BenchManager/BenchDashboard/Properties/Resources.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.42000 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -// der Code erneut generiert wird. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ @@ -13,12 +13,12 @@ namespace Mastersign.Bench.Dashboard.Properties { /// - /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// A strongly-typed resource class, for looking up localized strings, etc. /// - // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert - // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. - // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen - // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -33,7 +33,7 @@ internal Resources() { } /// - /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -47,8 +47,8 @@ internal Resources() { } /// - /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle - /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -61,7 +61,7 @@ internal Resources() { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Microsoft for the OS and .NET ecosystem + /// Looks up a localized string similar to Microsoft for the OS and .NET ecosystem ///Igor Pavlov for 7-Zip ///Scott Willeke for Less Msiérables ///Ariman for Inno Setup Unpacker @@ -71,7 +71,7 @@ internal Resources() { ///Jeff Atwood for MarkdownSharp ///Tsuda Kageyu for the IconExtractor ///Linus Torvalds, Junio C. Hamano, Shawn O. Pearce, et.al. for Git - /// ähnelt. + ///. /// internal static string acknowledgements { get { @@ -80,7 +80,7 @@ internal static string acknowledgements { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap apps { get { @@ -90,7 +90,7 @@ internal static System.Drawing.Bitmap apps { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap blocked_16 { get { @@ -100,7 +100,27 @@ internal static System.Drawing.Bitmap blocked_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap book_16 { + get { + object obj = ResourceManager.GetObject("book_16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap books_16 { + get { + object obj = ResourceManager.GetObject("books_16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap cached_16 { get { @@ -110,7 +130,7 @@ internal static System.Drawing.Bitmap cached_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap cancelled_48 { get { @@ -120,7 +140,7 @@ internal static System.Drawing.Bitmap cancelled_48 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap cleanup_16 { get { @@ -130,7 +150,7 @@ internal static System.Drawing.Bitmap cleanup_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap clear_16 { get { @@ -140,7 +160,7 @@ internal static System.Drawing.Bitmap clear_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap deletedownload_16 { get { @@ -150,7 +170,7 @@ internal static System.Drawing.Bitmap deletedownload_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap do_16 { get { @@ -160,7 +180,7 @@ internal static System.Drawing.Bitmap do_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap do_32 { get { @@ -170,7 +190,7 @@ internal static System.Drawing.Bitmap do_32 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap doc_16 { get { @@ -180,7 +200,7 @@ internal static System.Drawing.Bitmap doc_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap docs_16 { get { @@ -190,7 +210,7 @@ internal static System.Drawing.Bitmap docs_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap download_16 { get { @@ -200,7 +220,7 @@ internal static System.Drawing.Bitmap download_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap downloadall_16 { get { @@ -210,7 +230,7 @@ internal static System.Drawing.Bitmap downloadall_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap error_48 { get { @@ -220,7 +240,7 @@ internal static System.Drawing.Bitmap error_48 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap exclude { get { @@ -230,7 +250,7 @@ internal static System.Drawing.Bitmap exclude { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap include { get { @@ -240,7 +260,7 @@ internal static System.Drawing.Bitmap include { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap info_16 { get { @@ -250,7 +270,7 @@ internal static System.Drawing.Bitmap info_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap install_16 { get { @@ -260,7 +280,7 @@ internal static System.Drawing.Bitmap install_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap library_16 { get { @@ -270,7 +290,7 @@ internal static System.Drawing.Bitmap library_16 { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Bench / Bench Dashboard + /// Looks up a localized string similar to Bench / Bench Dashboard ///The MIT License (MIT) ///Copyright (c) 2016 Tobias Kiertscher <dev@mastersign.de> /// @@ -279,7 +299,7 @@ internal static System.Drawing.Bitmap library_16 { ///in the Software without restriction, including without limitation the rights ///to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ///copies of the Software, and to permit persons to whom the Software is - ///furnished to do so, sub [Rest der Zeichenfolge wurde abgeschnitten]"; ähnelt. + ///furnished to do so, sub [rest of string was truncated]";. /// internal static string licenses { get { @@ -288,7 +308,7 @@ internal static string licenses { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap logo_64 { get { @@ -298,7 +318,7 @@ internal static System.Drawing.Bitmap logo_64 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap markdown_16 { get { @@ -308,7 +328,7 @@ internal static System.Drawing.Bitmap markdown_16 { } /// - /// Sucht eine lokalisierte Zeichenfolge, die abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;color:inherit;font-family:inherit}body{line-height:100%;backg [Rest der Zeichenfolge wurde abgeschnitten]"; ähnelt. + /// Looks up a localized string similar to abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;color:inherit;font-family:inherit}body{line-height:100%;backg [rest of string was truncated]";. /// internal static string MarkdownViewerStyle { get { @@ -317,7 +337,7 @@ internal static string MarkdownViewerStyle { } /// - /// Sucht eine lokalisierte Zeichenfolge, die <!DOCTYPE html> + /// Looks up a localized string similar to <!DOCTYPE html> ///<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ///<head> /// <meta charset="utf-8" /> @@ -331,7 +351,7 @@ internal static string MarkdownViewerStyle { /// $CONTENT$ /// </div> ///</body> - ///</html> ähnelt. + ///</html>. /// internal static string MarkdownViewerTemplate { get { @@ -340,7 +360,7 @@ internal static string MarkdownViewerTemplate { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap missing_app_16 { get { @@ -350,7 +370,7 @@ internal static System.Drawing.Bitmap missing_app_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Icon ähnlich wie (Symbol). + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// internal static System.Drawing.Icon MissingApp { get { @@ -360,7 +380,7 @@ internal static System.Drawing.Icon MissingApp { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap none_16 { get { @@ -370,7 +390,7 @@ internal static System.Drawing.Bitmap none_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap ok_16 { get { @@ -380,7 +400,7 @@ internal static System.Drawing.Bitmap ok_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap ok_48 { get { @@ -390,7 +410,17 @@ internal static System.Drawing.Bitmap ok_48 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap progress_16_animation { + get { + object obj = ResourceManager.GetObject("progress_16_animation", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap progress_36_animation { get { @@ -400,7 +430,7 @@ internal static System.Drawing.Bitmap progress_36_animation { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap properties_16 { get { @@ -410,7 +440,7 @@ internal static System.Drawing.Bitmap properties_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap refresh_16 { get { @@ -420,7 +450,7 @@ internal static System.Drawing.Bitmap refresh_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap reinstall_16 { get { @@ -430,7 +460,7 @@ internal static System.Drawing.Bitmap reinstall_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap setup_16 { get { @@ -440,7 +470,7 @@ internal static System.Drawing.Bitmap setup_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap stop_16 { get { @@ -450,7 +480,7 @@ internal static System.Drawing.Bitmap stop_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap stop_32 { get { @@ -460,7 +490,7 @@ internal static System.Drawing.Bitmap stop_32 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap stop_36_animation { get { @@ -470,7 +500,7 @@ internal static System.Drawing.Bitmap stop_36_animation { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap task_16 { get { @@ -480,7 +510,7 @@ internal static System.Drawing.Bitmap task_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap tolerated_16 { get { @@ -490,7 +520,7 @@ internal static System.Drawing.Bitmap tolerated_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap uninstall_16 { get { @@ -500,7 +530,27 @@ internal static System.Drawing.Bitmap uninstall_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap update_apps_16 { + get { + object obj = ResourceManager.GetObject("update_apps_16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap update_bench_16 { + get { + object obj = ResourceManager.GetObject("update_bench_16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap updateenv_16 { get { @@ -510,7 +560,7 @@ internal static System.Drawing.Bitmap updateenv_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap upgrade_16 { get { @@ -520,7 +570,7 @@ internal static System.Drawing.Bitmap upgrade_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap userconfig_16 { get { @@ -530,7 +580,7 @@ internal static System.Drawing.Bitmap userconfig_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap userlibrary_16 { get { @@ -540,7 +590,7 @@ internal static System.Drawing.Bitmap userlibrary_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap warning_16 { get { @@ -550,7 +600,7 @@ internal static System.Drawing.Bitmap warning_16 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap warning_36_animation { get { @@ -560,7 +610,7 @@ internal static System.Drawing.Bitmap warning_36_animation { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap warning_48 { get { @@ -570,7 +620,7 @@ internal static System.Drawing.Bitmap warning_48 { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// internal static System.Drawing.Bitmap website_16 { get { diff --git a/BenchManager/BenchDashboard/Properties/Resources.resx b/BenchManager/BenchDashboard/Properties/Resources.resx index afddd66d..88878033 100644 --- a/BenchManager/BenchDashboard/Properties/Resources.resx +++ b/BenchManager/BenchDashboard/Properties/Resources.resx @@ -265,4 +265,19 @@ ..\resources\userlibrary_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\progress_16_animation.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\update_bench_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\resources\books_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\resources\book_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\resources\update_apps_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/BenchManager/BenchDashboard/Resources/MarkdownViewerStyle.css b/BenchManager/BenchDashboard/Resources/MarkdownViewerStyle.css index 229098a1..d3f8b0e3 100644 --- a/BenchManager/BenchDashboard/Resources/MarkdownViewerStyle.css +++ b/BenchManager/BenchDashboard/Resources/MarkdownViewerStyle.css @@ -1 +1 @@ -abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;color:inherit;font-family:inherit}body{line-height:100%;background-color:#fff;color:#000;font-family:sans-serif;text-align:left}h1,h2,h3,h4,h5,h6{font-family:serif}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}code{font-family:monospace}ins{text-decoration:underline}mark{font-style:italic;font-weight:700}del{text-decoration:line-through}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-collapse:collapse;border-spacing:0}hr{display:block;height:1px;border:0;border-top:1px solid #888;margin:1em 0;padding:0}input,select{vertical-align:middle}body{font-family:Calibri,Tahoma,Helvetica,Arial,sans-serif;line-height:115%;margin:1em 1.5em 2em;background-color:#fff;color:#000}h1,h2,h3,h4,h5,h6{margin-top:.66em;margin-bottom:.33em;line-height:100%;color:#252525}h1,h2,h3,h4{font-family:Cambria,Times,Times New Roman,serif;font-weight:400}h1{font-size:2.3em}h2{font-size:1.8em}h3{font-size:1.4em}h4{font-size:1.1em}h5,h6{font-family:Calibri,Tahoma,Helvetica,Arial,sans-serif}h5{font-weight:700}h5,h6{font-size:1em}h6{text-decoration:underline;font-weight:400}hr{clear:both}dl,figure,ol,p,pre,summary,table,ul{margin-top:1em}em{font-style:italic;font-weight:400}strong{font-style:normal;font-weight:600}a{color:#1b58b8;text-decoration:none}a:visited{color:#5f8acd}a:hover{color:#00296a}a:active{color:#1faeff}h1 a,h1 a:visited,h2 a,h2 a:visited,h3 a,h3 a:visited,h4 a,h4 a:visited,h5 a,h5 a:visited,h6 a,h6 a:visited{color:inherit}h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{color:#00296a}h1 a:active,h2 a:active,h3 a:active,h4 a:active,h5 a:active,h6 a:active{color:#1faeff}img{border:none}ins{color:#093;text-decoration:none}mark{background-color:#ffc;border:1px solid #ffff4d;border-radius:0;padding:0 .25em;color:#000;font-style:normal;font-weight:400}del{color:#f44;text-decoration:line-through}li{margin-left:2em}li ol,li ul{margin-top:.33em;margin-bottom:.33em}li p:first-of-type{margin-top:0}ul li{list-style:disc outside none}ul li li{list-style:circle outside none}ul li li li{list-style:square outside none}ol li{list-style:decimal outside none}ol li li{list-style:lower-latin outside none}ol li li li{list-style:lower-roman outside none}dl{font-weight:300}dt{font-weight:600;font-style:italic}dd{margin-left:2em;padding-bottom:.5em}nav li,nav ul li,nav ul li li,nav ul li li li{list-style:none outside none;margin-left:0}nav li ol,nav li ul{margin:0}nav li ol li:first-child,nav li ul li:first-child{margin-top:.2em}q{quotes:"\201E" "\201C";font-style:italic}q:before{content:open-quote}q:after{content:close-quote}aside,blockquote,figure,pre{background-color:#f8f8f8}blockquote{border:none;border-radius:0;margin:1em 10%;padding:.5em .75em;font-style:italic}blockquote cite{display:block;text-align:right;margin-top:.5em;font-style:normal;font-size:.8em}blockquote>p:first-child{margin-top:0}blockquote blockquote{margin:1em 0 .5em}pre{border:none;border-radius:0;padding:.25em .75em .55em;white-space:pre;overflow:auto}code{font-family:Consolas,Courier New,Courier,monospace;font-size:.8em;color:#024;background-color:#f2f2f2;padding:0 .2em;border:1px solid #ccd3da;border-radius:0}pre code{background-color:inherit;border:none;border-radius:none;padding:0}code span{font-family:Consolas,Courier New,Courier,monospace;font-weight:400;font-size:1em}code span.kw{font-weight:700;color:#2673ec}code span.fu{color:#004d60}code span.dt{color:#b01e00}code span.bn{color:#006ac1}code span.dv{color:#4617b4}code span.fl{color:#7200ac}code span.ch{color:#199900}code span.st{color:#004a00}code span.co{font-weight:300;font-style:italic}code span.co,code span.ot{color:#6f6666}code span.al,code span.er{color:#ae113d;font-weight:700}code span.re{color:#024}table{border-collapse:collapse}td,th{padding:.125em .5em;border:none}tfoot td,th,thead td{font-weight:700}tfoot,thead{background-color:#f2f2f2}figure{border:none;border-radius:0;margin-left:10%;margin-right:10%;padding:.5em;text-align:center;overflow:auto}figure figcaption{display:block;text-align:center;font-style:normal;font-weight:700;font-size:.8em;margin-top:.5em}figure table{width:100%;margin-top:0}section{border-top:1px solid #aaa;margin-top:1.5em}footer{margin-top:2em;text-align:center;color:#888}aside{float:right;width:25%;border:none;border-radius:0;margin-left:1em;padding:0 .75em 1em}details{color:#545454;padding-left:1em;border-left:1px solid #aaa}details p{margin-top:.5em}details summary{display:block;font-weight:500;color:#000;padding-bottom:.5em}.badge{display:inline-block;font-weight:700;font-size:.9em;margin:1em .5em 0 0;padding:.1em .75em .2em;border:1px solid #696969;border-radius:0}span.badge{display:inline;padding:0 .5em .05em;margin:0}.default{border-color:#696969}.highlight{border-color:#00a4a4}.success{border-color:#78ba00}.info{border-color:#f4b300}.warning{border-color:#e46a19}.error{border-color:#ae113d}.passive{border-color:#d5d5d5}.active{border-color:#2673ec}.default,.fg-default{color:#202020}.fg-highlight,.highlight{color:#003131}.fg-success,.success{color:#243800}.fg-info,.info{color:#493600}.fg-warning,.warning{color:#442008}.error,.fg-error{color:#340512}.fg-passive,.passive{color:#777}.active,.fg-active{color:#0b2347}.bg-default,.default{background-color:#d2d2d2}.bg-highlight,.highlight{background-color:#b3e4e4}.bg-success,.success{background-color:#d7eab3}.bg-info,.info{background-color:#fce8b3}.bg-warning,.warning{background-color:#f7d2ba}.bg-error,.error{background-color:#e7b8c5}.bg-passive,.passive{background-color:#eee}.active,.bg-active{background-color:#bed5f9}body{margin:0;padding:0;background-color:#696969}#frame{text-align:left;padding:1em;background-color:#fff;-chrome-box-shadow:0 0 12px #696969;box-shadow:0 0 12px #696969}header{padding-bottom:1em;margin-left:13em}figure{margin-left:0;margin-right:0}nav ul{list-style:none;margin-top:0;padding-top:0;margin-left:0;padding-left:0}nav .menu-title{font-weight:700}nav.horizontal{border-bottom:1px solid #aaa;width:auto;float:none;padding:.25em;margin-left:13em;margin-right:.5em}nav.horizontal .menu-title,nav.horizontal ul{display:inline-block}nav.horizontal .menu-title{padding:0 .5em 0 .25em}nav.horizontal li{display:inline-block}nav.horizontal li:first-of-type{padding-left:.5em}nav.horizontal li+li{margin-left:0}nav.horizontal li+li:before{content:'\00A0\2022\00A0\00A0';color:#8dacdc}nav.vertical{border-bottom:none;width:10em;float:left;padding:1em 1em 1em .5em}nav.vertical .menu-title,nav.vertical ul{display:block}nav.vertical .menu-title{padding:0 0 .5em}nav.vertical li{display:block;padding:0 0 .25em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}nav.vertical li li{margin-left:.75em;font-size:small;padding:0}nav.fixed{position:fixed;top:0;height:100%;overflow-y:auto;margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0}nav.fixed>ul{margin-top:3em;margin-bottom:2em}#page{margin:1em .5em 1em 12em;padding-left:1em}footer{border-top:1px solid #aaa;clear:both}@media screen and (min-width:960px){#frame{width:90%;margin-left:auto;margin-right:auto}}@media screen and (max-device-height:720px) and (orientation:landscape),screen and (max-width:720px){#frame{max-width:720px}header{margin-left:0}nav.horizontal{margin:0}nav.vertical{border-bottom:1px solid #aaa;width:auto;float:none;padding:.25em;margin:0}nav.vertical>.menu-title{padding:0 .5em 0 .25em}nav.fixed{position:relative}#page{margin:1em .5em;padding:0}}@media screen and (max-device-height:720px) and (orientation:landscape) and (min-width:480px),screen and (max-device-height:720px) and (orientation:landscape) and (orientation:landscape),screen and (max-width:720px) and (min-width:480px),screen and (max-width:720px) and (orientation:landscape){nav.vertical li{margin:.5em;overflow:auto}nav.vertical>ul{white-space:normal}nav.vertical>ul ul{display:block}nav.vertical>ul ul li{display:inline;margin-right:0;padding:0}nav.vertical>ul ul li+li{margin-left:0}nav.vertical>ul ul li+li:before{content:'\00A0\2022\00A0\00A0';color:#8dacdc}}@media screen and (max-width:480px) and (orientation:portrait){#frame{max-width:480px;padding:.25em}header{padding-bottom:0;margin:0}header h1{padding-bottom:.25em;padding-top:.25em;font-size:1.8em;border-bottom:1px solid #aaa}header h1,nav.horizontal{margin:0;text-align:center}nav.horizontal li,nav.horizontal li:first-of-type{display:block;padding:0 0 .15em}nav.horizontal li+li{margin-left:0}nav.horizontal li+li:before{content:none}nav.horizontal,nav.vertical{position:auto;border-bottom:1px solid #aaa;width:auto;float:none;padding:.5em 0}nav.vertical ul{display:block}nav.vertical>ul{margin:.5em}nav.horizontal .menu-title,nav.vertical .menu-title{display:block;padding:0 0 .5em}aside{width:50%}}body{font-family:Segoe UI,Calibri,Arial,Helvetica,sans-serif;font-size:11.5pt;line-height:130%}aside,footer,h1,h2,h3,h4{font-family:Segoe UI,Calibri Light,Arial,Helvetica,sans-serif}h1,h2{font-weight:200}h3,h4{font-weight:300}h5,h6{font-weight:700}h5{text-decoration:underline}h6{text-decoration:none}ins{color:#199900}del{color:#b81b1b}mark{background-color:#ffa;border:none}aside{font-weight:400;font-size:9.5pt}blockquote{border-left:.5em solid #e0e0e0}figure figcaption{font-weight:400;font-size:10.5pt}code{font-family:Consolas,Courier New,Courier,monospace;line-height:110%}table{background-color:#fff}tfoot td,thead td{background-color:#f8f8f8}td,tfoot td,th,thead td{border:1px solid #ccc}.badge{font-weight:300;font-size:12pt;padding:.33em .66em}.active,.default,.error,.highlight,.info,.passive,.success,.warning{border:none;color:#fff}.default{background-color:#696969}.highlight{background-color:#00a4a4}.success{background-color:#78ba00}.info{background-color:#f4b300}.warning{background-color:#e46a19}.error{background-color:#ae113d}.active{background-color:#2673ec}.passive{background-color:#aaa} \ No newline at end of file +abbr,address,article,aside,audio,b,blockquote,body,canvas,caption,cite,code,dd,del,details,dfn,div,dl,dt,em,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,p,pre,q,samp,section,small,span,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,ul,var,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;color:inherit;font-family:inherit}body{line-height:100%;background-color:#fff;color:#000;font-family:sans-serif;text-align:left}h1,h2,h3,h4,h5,h6{font-family:serif}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}code{font-family:monospace}ins{text-decoration:underline}mark{font-style:italic;font-weight:700}del{text-decoration:line-through}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-collapse:collapse;border-spacing:0}hr{display:block;height:1px;border:0;border-top:1px solid #888;margin:1em 0;padding:0}input,select{vertical-align:middle}body{font-family:Calibri,Tahoma,Helvetica,Arial,sans-serif;line-height:115%;margin:1em 1.5em 2em;background-color:#fff;color:#000}h1,h2,h3,h4,h5,h6{margin-top:.66em;margin-bottom:.33em;line-height:100%;color:#252525}h1,h2,h3,h4{font-family:Cambria,Times,Times New Roman,serif;font-weight:400}h1{font-size:2.3em}h2{font-size:1.8em}h3{font-size:1.4em}h4{font-size:1.1em}h5,h6{font-family:Calibri,Tahoma,Helvetica,Arial,sans-serif}h5{font-weight:700}h5,h6{font-size:1em}h6{text-decoration:underline;font-weight:400}hr{clear:both}dl,figure,ol,p,pre,summary,table,ul{margin-top:1em}em{font-style:italic;font-weight:400}strong{font-style:normal;font-weight:600}a{color:#1b58b8;text-decoration:none}a:visited{color:#5f8acd}a:hover{color:#00296a}a:active{color:#1faeff}h1 a,h1 a:visited,h2 a,h2 a:visited,h3 a,h3 a:visited,h4 a,h4 a:visited,h5 a,h5 a:visited,h6 a,h6 a:visited{color:inherit}h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{color:#00296a}h1 a:active,h2 a:active,h3 a:active,h4 a:active,h5 a:active,h6 a:active{color:#1faeff}img{border:none}ins{color:#093;text-decoration:none}mark{background-color:#ffc;border:1px solid #ffff4d;border-radius:0;padding:0 .25em;color:#000;font-style:normal;font-weight:400}del{color:#f44;text-decoration:line-through}li{margin-left:2em}li ol,li ul{margin-top:.33em;margin-bottom:.33em}li p:first-of-type{margin-top:0}ul li{list-style:disc outside none}ul li li{list-style:circle outside none}ul li li li{list-style:square outside none}ol li{list-style:decimal outside none}ol li li{list-style:lower-latin outside none}ol li li li{list-style:lower-roman outside none}dl{font-weight:300}dt{font-weight:600;font-style:italic}dd{margin-left:2em;padding-bottom:.5em}nav li,nav ul li,nav ul li li,nav ul li li li{list-style:none outside none;margin-left:0}nav li ol,nav li ul{margin:0}nav li ol li:first-child,nav li ul li:first-child{margin-top:.2em}q{quotes:"\201E" "\201C";font-style:italic}q:before{content:open-quote}q:after{content:close-quote}aside,blockquote,figure,pre{background-color:#f8f8f8}blockquote{border:none;border-radius:0;margin:1em 10%;padding:.5em .75em;font-style:italic}blockquote cite{display:block;text-align:right;margin-top:.5em;font-style:normal;font-size:.8em}blockquote>p:first-child{margin-top:0}blockquote blockquote{margin:1em 0 .5em}pre{border:none;border-radius:0;padding:.25em .75em .55em;white-space:pre;overflow:auto}code{font-family:Consolas,Courier New,Courier,monospace;font-size:.8em;color:#024;background-color:#f2f2f2;padding:0 .2em;border:1px solid #ccd3da;border-radius:0}pre code{background-color:inherit;border:none;border-radius:none;padding:0}code span{font-family:Consolas,Courier New,Courier,monospace;font-weight:400;font-size:1em}code span.kw{font-weight:700;color:#2673ec}code span.fu{color:#004d60}code span.dt{color:#b01e00}code span.bn{color:#006ac1}code span.dv{color:#4617b4}code span.fl{color:#7200ac}code span.ch{color:#199900}code span.st{color:#004a00}code span.co{font-weight:300;font-style:italic}code span.co,code span.ot{color:#6f6666}code span.al,code span.er{color:#ae113d;font-weight:700}code span.re{color:#024}table{border-collapse:collapse}td,th{padding:.125em .5em;border:none}tfoot td,th,thead td{font-weight:700}tfoot,thead{background-color:#f2f2f2}figure{border:none;border-radius:0;margin-left:10%;margin-right:10%;padding:.5em;text-align:center;overflow:auto}figure figcaption{display:block;text-align:center;font-style:normal;font-weight:700;font-size:.8em;margin-top:.5em}figure table{width:100%;margin-top:0}section{border-top:1px solid #aaa;margin-top:1.5em}footer{margin-top:2em;text-align:center;color:#888}aside{float:right;width:25%;border:none;border-radius:0;margin-left:1em;padding:0 .75em 1em}details{color:#545454;padding-left:1em;border-left:1px solid #aaa}details p{margin-top:.5em}details summary{display:block;font-weight:500;color:#000;padding-bottom:.5em}.badge{display:inline-block;font-weight:700;font-size:.9em;margin:1em .5em 0 0;padding:.1em .75em .2em;border:1px solid #696969;border-radius:0}span.badge{display:inline;padding:0 .5em .05em;margin:0}.default{border-color:#696969}.highlight{border-color:#00a4a4}.success{border-color:#78ba00}.info{border-color:#f4b300}.warning{border-color:#e46a19}.error{border-color:#ae113d}.passive{border-color:#d5d5d5}.active{border-color:#2673ec}.default,.fg-default{color:#202020}.fg-highlight,.highlight{color:#003131}.fg-success,.success{color:#243800}.fg-info,.info{color:#493600}.fg-warning,.warning{color:#442008}.error,.fg-error{color:#340512}.fg-passive,.passive{color:#777}.active,.fg-active{color:#0b2347}.bg-default,.default{background-color:#d2d2d2}.bg-highlight,.highlight{background-color:#b3e4e4}.bg-success,.success{background-color:#d7eab3}.bg-info,.info{background-color:#fce8b3}.bg-warning,.warning{background-color:#f7d2ba}.bg-error,.error{background-color:#e7b8c5}.bg-passive,.passive{background-color:#eee}.active,.bg-active{background-color:#bed5f9}body{margin:0;padding:0;background-color:#fff}#frame{text-align:left;padding:1em;background-color:#fff;-chrome-box-shadow:0 0 12px #696969;box-shadow:0 0 12px #696969}header{padding-bottom:1em;margin-left:13em}figure{margin-left:0;margin-right:0}nav ul{list-style:none;margin-top:0;padding-top:0;margin-left:0;padding-left:0}nav .menu-title{font-weight:700}nav.horizontal{border-bottom:1px solid #aaa;width:auto;float:none;padding:.25em;margin-left:13em;margin-right:.5em}nav.horizontal .menu-title,nav.horizontal ul{display:inline-block}nav.horizontal .menu-title{padding:0 .5em 0 .25em}nav.horizontal li{display:inline-block}nav.horizontal li:first-of-type{padding-left:.5em}nav.horizontal li+li{margin-left:0}nav.horizontal li+li:before{content:'\00A0\2022\00A0\00A0';color:#8dacdc}nav.vertical{border-bottom:none;width:10em;float:left;padding:1em 1em 1em .5em}nav.vertical .menu-title,nav.vertical ul{display:block}nav.vertical .menu-title{padding:0 0 .5em}nav.vertical li{display:block;padding:0 0 .25em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}nav.vertical li li{margin-left:.75em;font-size:small;padding:0}nav.fixed{position:fixed;top:0;height:100%;overflow-y:auto;margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0}nav.fixed>ul{margin-top:3em;margin-bottom:2em}#page{margin:1em .5em 1em 12em;padding-left:1em}footer{border-top:1px solid #aaa;clear:both}@media screen and (min-width:960px){#frame{width:90%;margin-left:auto;margin-right:auto}}@media screen and (max-device-height:720px) and (orientation:landscape),screen and (max-width:720px){#frame{max-width:720px}header{margin-left:0}nav.horizontal{margin:0}nav.vertical{border-bottom:1px solid #aaa;width:auto;float:none;padding:.25em;margin:0}nav.vertical>.menu-title{padding:0 .5em 0 .25em}nav.fixed{position:relative}#page{margin:1em .5em;padding:0}}@media screen and (max-device-height:720px) and (orientation:landscape) and (min-width:480px),screen and (max-device-height:720px) and (orientation:landscape) and (orientation:landscape),screen and (max-width:720px) and (min-width:480px),screen and (max-width:720px) and (orientation:landscape){nav.vertical li{margin:.5em;overflow:auto}nav.vertical>ul{white-space:normal}nav.vertical>ul ul{display:block}nav.vertical>ul ul li{display:inline;margin-right:0;padding:0}nav.vertical>ul ul li+li{margin-left:0}nav.vertical>ul ul li+li:before{content:'\00A0\2022\00A0\00A0';color:#8dacdc}}@media screen and (max-width:480px) and (orientation:portrait){#frame{max-width:480px;padding:.25em}header{padding-bottom:0;margin:0}header h1{padding-bottom:.25em;padding-top:.25em;font-size:1.8em;border-bottom:1px solid #aaa}header h1,nav.horizontal{margin:0;text-align:center}nav.horizontal li,nav.horizontal li:first-of-type{display:block;padding:0 0 .15em}nav.horizontal li+li{margin-left:0}nav.horizontal li+li:before{content:none}nav.horizontal,nav.vertical{position:auto;border-bottom:1px solid #aaa;width:auto;float:none;padding:.5em 0}nav.vertical ul{display:block}nav.vertical>ul{margin:.5em}nav.horizontal .menu-title,nav.vertical .menu-title{display:block;padding:0 0 .5em}aside{width:50%}}body{font-family:Segoe UI,Calibri,Arial,Helvetica,sans-serif;font-size:11.5pt;line-height:130%}aside,footer,h1,h2,h3,h4{font-family:Segoe UI,Calibri Light,Arial,Helvetica,sans-serif}h1,h2{font-weight:200}h3,h4{font-weight:300}h5,h6{font-weight:700}h5{text-decoration:underline}h6{text-decoration:none}ins{color:#199900}del{color:#b81b1b}mark{background-color:#ffa;border:none}aside{font-weight:400;font-size:9.5pt}blockquote{border-left:.5em solid #e0e0e0}figure figcaption{font-weight:400;font-size:10.5pt}code{font-family:Consolas,Courier New,Courier,monospace;line-height:110%}table{background-color:#fff}tfoot td,thead td{background-color:#f8f8f8}td,tfoot td,th,thead td{border:1px solid #ccc}.badge{font-weight:300;font-size:12pt;padding:.33em .66em}.active,.default,.error,.highlight,.info,.passive,.success,.warning{border:none;color:#fff}.default{background-color:#696969}.highlight{background-color:#00a4a4}.success{background-color:#78ba00}.info{background-color:#f4b300}.warning{background-color:#e46a19}.error{background-color:#ae113d}.active{background-color:#2673ec}.passive{background-color:#aaa} \ No newline at end of file diff --git a/BenchManager/BenchDashboard/Resources/book_16.png b/BenchManager/BenchDashboard/Resources/book_16.png new file mode 100644 index 00000000..a6c2e586 Binary files /dev/null and b/BenchManager/BenchDashboard/Resources/book_16.png differ diff --git a/BenchManager/BenchDashboard/Resources/books_16.png b/BenchManager/BenchDashboard/Resources/books_16.png new file mode 100644 index 00000000..f2200eea Binary files /dev/null and b/BenchManager/BenchDashboard/Resources/books_16.png differ diff --git a/BenchManager/BenchDashboard/Resources/progress_16_animation.gif b/BenchManager/BenchDashboard/Resources/progress_16_animation.gif new file mode 100644 index 00000000..f98f2c24 Binary files /dev/null and b/BenchManager/BenchDashboard/Resources/progress_16_animation.gif differ diff --git a/BenchManager/BenchDashboard/Resources/update_apps_16.png b/BenchManager/BenchDashboard/Resources/update_apps_16.png new file mode 100644 index 00000000..22f1b58c Binary files /dev/null and b/BenchManager/BenchDashboard/Resources/update_apps_16.png differ diff --git a/BenchManager/BenchDashboard/Resources/update_bench_16.png b/BenchManager/BenchDashboard/Resources/update_bench_16.png new file mode 100644 index 00000000..e1c97de2 Binary files /dev/null and b/BenchManager/BenchDashboard/Resources/update_bench_16.png differ diff --git a/BenchManager/BenchDashboard/SetupForm.Designer.cs b/BenchManager/BenchDashboard/SetupForm.Designer.cs index 5e83a1a8..65dea6fb 100644 --- a/BenchManager/BenchDashboard/SetupForm.Designer.cs +++ b/BenchManager/BenchDashboard/SetupForm.Designer.cs @@ -29,9 +29,12 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); + System.Windows.Forms.ToolStripSeparator toolStripSeparator4; System.Windows.Forms.ToolStripSeparator toolStripSeparator2; System.Windows.Forms.ToolStripSeparator toolStripSeparator3; System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SetupForm)); this.tsSeparatorDownloads = new System.Windows.Forms.ToolStripSeparator(); this.splitterBottom = new System.Windows.Forms.Splitter(); @@ -49,12 +52,16 @@ private void InitializeComponent() this.gridApps = new System.Windows.Forms.DataGridView(); this.colIcon = new System.Windows.Forms.DataGridViewImageColumn(); this.colIndex = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colLibrary = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colID = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colCategory = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colLabel = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colTyp = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colActivated = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.colExcluded = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.colStatus = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colTyp = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colLicense = new System.Windows.Forms.DataGridViewLinkColumn(); this.colComment = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ctxmAppActions = new System.Windows.Forms.ContextMenuStrip(this.components); this.miAppInfo = new System.Windows.Forms.ToolStripMenuItem(); @@ -74,6 +81,9 @@ private void InitializeComponent() this.tsmSetup = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiAuto = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiUpdateEnvironment = new System.Windows.Forms.ToolStripMenuItem(); + this.tsmiUpdateAppLibs = new System.Windows.Forms.ToolStripMenuItem(); + this.tsmiUpdateBench = new System.Windows.Forms.ToolStripMenuItem(); + this.tsmiUpgradeBench = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiInstallAll = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiReinstallAll = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiUpgradeAll = new System.Windows.Forms.ToolStripMenuItem(); @@ -82,16 +92,20 @@ private void InitializeComponent() this.tsmiDownloadAllResources = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiDownloadAllAppResources = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiDeleteAllResources = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); + this.tsmiClose = new System.Windows.Forms.ToolStripMenuItem(); this.tsmEdit = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiEditCustomConfig = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiEditCustomApps = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiEditActivationList = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiEditDeactivationList = new System.Windows.Forms.ToolStripMenuItem(); + this.tsmiColumns = new System.Windows.Forms.ToolStripMenuItem(); this.tsmView = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiShowAppIndex = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiShowCustomAppIndex = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiAlwaysShowDownloads = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiRefreshView = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); @@ -102,15 +116,20 @@ private void InitializeComponent() this.menuStrip.SuspendLayout(); this.SuspendLayout(); // + // toolStripSeparator4 + // + toolStripSeparator4.Name = "toolStripSeparator4"; + toolStripSeparator4.Size = new System.Drawing.Size(234, 6); + // // toolStripSeparator2 // toolStripSeparator2.Name = "toolStripSeparator2"; - toolStripSeparator2.Size = new System.Drawing.Size(227, 6); + toolStripSeparator2.Size = new System.Drawing.Size(234, 6); // // toolStripSeparator3 // toolStripSeparator3.Name = "toolStripSeparator3"; - toolStripSeparator3.Size = new System.Drawing.Size(227, 6); + toolStripSeparator3.Size = new System.Drawing.Size(234, 6); // // toolStripSeparator1 // @@ -264,12 +283,16 @@ private void InitializeComponent() this.gridApps.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.colIcon, this.colIndex, + this.colLibrary, + this.colID, + this.colCategory, this.colLabel, - this.colTyp, + this.colVersion, this.colActivated, this.colExcluded, this.colStatus, - this.colVersion, + this.colTyp, + this.colLicense, this.colComment}); this.gridApps.Dock = System.Windows.Forms.DockStyle.Fill; this.gridApps.Location = new System.Drawing.Point(0, 133); @@ -307,32 +330,73 @@ private void InitializeComponent() this.colIndex.ToolTipText = "The index number from the app registry."; this.colIndex.Width = 62; // + // colLibrary + // + this.colLibrary.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colLibrary.DataPropertyName = "AppLibrary"; + dataGridViewCellStyle1.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.colLibrary.DefaultCellStyle = dataGridViewCellStyle1; + this.colLibrary.Frozen = true; + this.colLibrary.HeaderText = "Library"; + this.colLibrary.Name = "colLibrary"; + this.colLibrary.ReadOnly = true; + this.colLibrary.ToolTipText = "The ID of the library, this app is defined in."; + this.colLibrary.Width = 66; + // + // colID + // + this.colID.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colID.DataPropertyName = "ID"; + dataGridViewCellStyle2.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.colID.DefaultCellStyle = dataGridViewCellStyle2; + this.colID.Frozen = true; + this.colID.HeaderText = "ID"; + this.colID.Name = "colID"; + this.colID.ReadOnly = true; + this.colID.ToolTipText = "The full ID of the app including the namespace."; + this.colID.Width = 43; + // + // colCategory + // + this.colCategory.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colCategory.DataPropertyName = "Category"; + this.colCategory.Frozen = true; + this.colCategory.HeaderText = "Category"; + this.colCategory.Name = "colCategory"; + this.colCategory.ReadOnly = true; + this.colCategory.ToolTipText = "The category of the app."; + this.colCategory.Width = 78; + // // colLabel // this.colLabel.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colLabel.DataPropertyName = "Label"; - this.colLabel.HeaderText = "Name"; + this.colLabel.Frozen = true; + this.colLabel.HeaderText = "Label"; this.colLabel.Name = "colLabel"; this.colLabel.ReadOnly = true; this.colLabel.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.colLabel.Width = 61; + this.colLabel.ToolTipText = "The user friendly name of the app."; + this.colLabel.Width = 59; // - // colTyp + // colVersion // - this.colTyp.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; - this.colTyp.DataPropertyName = "Typ"; - this.colTyp.HeaderText = "Typ"; - this.colTyp.Name = "colTyp"; - this.colTyp.ReadOnly = true; - this.colTyp.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.colTyp.ToolTipText = "The typ of the app."; - this.colTyp.Width = 48; + this.colVersion.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colVersion.DataPropertyName = "Version"; + this.colVersion.Frozen = true; + this.colVersion.HeaderText = "Version"; + this.colVersion.Name = "colVersion"; + this.colVersion.ReadOnly = true; + this.colVersion.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.colVersion.ToolTipText = "The version number of the app."; + this.colVersion.Width = 70; // // colActivated // this.colActivated.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; this.colActivated.DataPropertyName = "IsActive"; this.colActivated.FalseValue = "inactive"; + this.colActivated.Frozen = true; this.colActivated.HeaderText = "Active"; this.colActivated.IndeterminateValue = "implicit"; this.colActivated.Name = "colActivated"; @@ -346,6 +410,7 @@ private void InitializeComponent() // this.colExcluded.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; this.colExcluded.DataPropertyName = "IsDeactivated"; + this.colExcluded.Frozen = true; this.colExcluded.HeaderText = "Deactivated"; this.colExcluded.Name = "colExcluded"; this.colExcluded.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; @@ -356,28 +421,43 @@ private void InitializeComponent() // this.colStatus.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; this.colStatus.DataPropertyName = "ShortStatus"; + this.colStatus.Frozen = true; this.colStatus.HeaderText = "Status"; this.colStatus.Name = "colStatus"; this.colStatus.ReadOnly = true; + this.colStatus.ToolTipText = "A brief description of the apps status."; this.colStatus.Width = 64; // - // colVersion + // colTyp // - this.colVersion.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; - this.colVersion.DataPropertyName = "Version"; - this.colVersion.HeaderText = "Version"; - this.colVersion.Name = "colVersion"; - this.colVersion.ReadOnly = true; - this.colVersion.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.colVersion.Width = 70; + this.colTyp.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colTyp.DataPropertyName = "Typ"; + this.colTyp.Frozen = true; + this.colTyp.HeaderText = "Typ"; + this.colTyp.Name = "colTyp"; + this.colTyp.ReadOnly = true; + this.colTyp.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.colTyp.ToolTipText = "The typ of the app."; + this.colTyp.Width = 48; + // + // colLicense + // + this.colLicense.DataPropertyName = "License"; + this.colLicense.Frozen = true; + this.colLicense.HeaderText = "License"; + this.colLicense.Name = "colLicense"; + this.colLicense.ReadOnly = true; + this.colLicense.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; // // colComment // this.colComment.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; this.colComment.DataPropertyName = "LongStatus"; this.colComment.HeaderText = "Comment"; + this.colComment.MinimumWidth = 100; this.colComment.Name = "colComment"; this.colComment.ReadOnly = true; + this.colComment.ToolTipText = "A more detailed description of the apps status."; // // ctxmAppActions // @@ -501,6 +581,7 @@ private void InitializeComponent() this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.tsmSetup, this.tsmEdit, + this.tsmiColumns, this.tsmView}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; @@ -514,6 +595,10 @@ private void InitializeComponent() this.tsmSetup.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.tsmiAuto, this.tsmiUpdateEnvironment, + toolStripSeparator4, + this.tsmiUpdateAppLibs, + this.tsmiUpdateBench, + this.tsmiUpgradeBench, toolStripSeparator2, this.tsmiInstallAll, this.tsmiReinstallAll, @@ -523,7 +608,9 @@ private void InitializeComponent() this.tsmiCleanUpObsoleteResources, this.tsmiDownloadAllResources, this.tsmiDownloadAllAppResources, - this.tsmiDeleteAllResources}); + this.tsmiDeleteAllResources, + this.toolStripSeparator5, + this.tsmiClose}); this.tsmSetup.Name = "tsmSetup"; this.tsmSetup.Size = new System.Drawing.Size(49, 20); this.tsmSetup.Text = "&Setup"; @@ -532,82 +619,132 @@ private void InitializeComponent() // this.tsmiAuto.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.do_16; this.tsmiAuto.Name = "tsmiAuto"; - this.tsmiAuto.Size = new System.Drawing.Size(230, 22); + this.tsmiAuto.Size = new System.Drawing.Size(237, 22); this.tsmiAuto.Text = "&Automatic Setup"; + this.tsmiAuto.ToolTipText = "Uninstalls incactive apps, downloades missing resources, installs active but not " + + "installed apps."; this.tsmiAuto.Click += new System.EventHandler(this.AutoHandler); // // tsmiUpdateEnvironment // this.tsmiUpdateEnvironment.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.updateenv_16; this.tsmiUpdateEnvironment.Name = "tsmiUpdateEnvironment"; - this.tsmiUpdateEnvironment.Size = new System.Drawing.Size(230, 22); + this.tsmiUpdateEnvironment.Size = new System.Drawing.Size(237, 22); this.tsmiUpdateEnvironment.Text = "Update &Environment"; - this.tsmiUpdateEnvironment.Click += new System.EventHandler(this.UpdateEnvironment); + this.tsmiUpdateEnvironment.ToolTipText = "Updates the Bench environment file(s) and launchers."; + this.tsmiUpdateEnvironment.Click += new System.EventHandler(this.UpdateEnvironmentHandler); + // + // tsmiUpdateAppLibs + // + this.tsmiUpdateAppLibs.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.update_apps_16; + this.tsmiUpdateAppLibs.Name = "tsmiUpdateAppLibs"; + this.tsmiUpdateAppLibs.Size = new System.Drawing.Size(237, 22); + this.tsmiUpdateAppLibs.Text = "Update App &Libraries"; + this.tsmiUpdateAppLibs.ToolTipText = "Clears the app library cache and re-loads all app libraries."; + this.tsmiUpdateAppLibs.Click += new System.EventHandler(this.UpdateAppLibsHandler); + // + // tsmiUpdateBench + // + this.tsmiUpdateBench.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.update_apps_16; + this.tsmiUpdateBench.Name = "tsmiUpdateBench"; + this.tsmiUpdateBench.Size = new System.Drawing.Size(237, 22); + this.tsmiUpdateBench.Text = "&Update App Libraries and Apps"; + this.tsmiUpdateBench.ToolTipText = "Updates the libraries and upgrades all upgradable apps."; + this.tsmiUpdateBench.Click += new System.EventHandler(this.UpdateBenchHandler); + // + // tsmiUpgradeBench + // + this.tsmiUpgradeBench.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.update_bench_16; + this.tsmiUpgradeBench.Name = "tsmiUpgradeBench"; + this.tsmiUpgradeBench.Size = new System.Drawing.Size(237, 22); + this.tsmiUpgradeBench.Text = "Upgrade &Bench"; + this.tsmiUpgradeBench.ToolTipText = "Upgrades the Bench system."; + this.tsmiUpgradeBench.Click += new System.EventHandler(this.UpgradeBenchSystemHandler); // // tsmiInstallAll // this.tsmiInstallAll.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.install_16; this.tsmiInstallAll.Name = "tsmiInstallAll"; - this.tsmiInstallAll.Size = new System.Drawing.Size(230, 22); + this.tsmiInstallAll.Size = new System.Drawing.Size(237, 22); this.tsmiInstallAll.Text = "&Install Apps"; + this.tsmiInstallAll.ToolTipText = "Installes all active but not installed apps."; this.tsmiInstallAll.Click += new System.EventHandler(this.InstallAllHandler); // // tsmiReinstallAll // this.tsmiReinstallAll.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.reinstall_16; this.tsmiReinstallAll.Name = "tsmiReinstallAll"; - this.tsmiReinstallAll.Size = new System.Drawing.Size(230, 22); + this.tsmiReinstallAll.Size = new System.Drawing.Size(237, 22); this.tsmiReinstallAll.Text = "&Reinstall Apps"; + this.tsmiReinstallAll.ToolTipText = "Uninstalles all installed apps and reinstalls all active apps."; this.tsmiReinstallAll.Click += new System.EventHandler(this.ReinstallAllHandler); // // tsmiUpgradeAll // this.tsmiUpgradeAll.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.upgrade_16; this.tsmiUpgradeAll.Name = "tsmiUpgradeAll"; - this.tsmiUpgradeAll.Size = new System.Drawing.Size(230, 22); - this.tsmiUpgradeAll.Text = "&Upgrade Apps"; + this.tsmiUpgradeAll.Size = new System.Drawing.Size(237, 22); + this.tsmiUpgradeAll.Text = "Up&grade Apps"; + this.tsmiUpgradeAll.ToolTipText = "Upgrades all upgradable apps."; this.tsmiUpgradeAll.Click += new System.EventHandler(this.UpgradeAllHandler); // // tsmiUninstallAll // this.tsmiUninstallAll.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.uninstall_16; this.tsmiUninstallAll.Name = "tsmiUninstallAll"; - this.tsmiUninstallAll.Size = new System.Drawing.Size(230, 22); + this.tsmiUninstallAll.Size = new System.Drawing.Size(237, 22); this.tsmiUninstallAll.Text = "U&ninstall Apps"; + this.tsmiUninstallAll.ToolTipText = "Uninstalls all apps."; this.tsmiUninstallAll.Click += new System.EventHandler(this.UninstallAllHandler); // // tsmiCleanUpObsoleteResources // this.tsmiCleanUpObsoleteResources.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.cleanup_16; this.tsmiCleanUpObsoleteResources.Name = "tsmiCleanUpObsoleteResources"; - this.tsmiCleanUpObsoleteResources.Size = new System.Drawing.Size(230, 22); + this.tsmiCleanUpObsoleteResources.Size = new System.Drawing.Size(237, 22); this.tsmiCleanUpObsoleteResources.Text = "&Clean-Up Obsolete Resources"; + this.tsmiCleanUpObsoleteResources.ToolTipText = "Deletes app resources, which are no longer referenced by any app."; this.tsmiCleanUpObsoleteResources.Click += new System.EventHandler(this.CleanUpResourcesHandler); // // tsmiDownloadAllResources // this.tsmiDownloadAllResources.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.download_16; this.tsmiDownloadAllResources.Name = "tsmiDownloadAllResources"; - this.tsmiDownloadAllResources.Size = new System.Drawing.Size(230, 22); + this.tsmiDownloadAllResources.Size = new System.Drawing.Size(237, 22); this.tsmiDownloadAllResources.Text = "Do&wnload Active Resources"; + this.tsmiDownloadAllResources.ToolTipText = "Download missing app resources of all active apps."; this.tsmiDownloadAllResources.Click += new System.EventHandler(this.DownloadActiveHandler); // // tsmiDownloadAllAppResources // this.tsmiDownloadAllAppResources.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.downloadall_16; this.tsmiDownloadAllAppResources.Name = "tsmiDownloadAllAppResources"; - this.tsmiDownloadAllAppResources.Size = new System.Drawing.Size(230, 22); - this.tsmiDownloadAllAppResources.Text = "Down&load All Resources"; + this.tsmiDownloadAllAppResources.Size = new System.Drawing.Size(237, 22); + this.tsmiDownloadAllAppResources.Text = "D&ownload All Resources"; + this.tsmiDownloadAllAppResources.ToolTipText = "Downloads missing app resources of all apps."; this.tsmiDownloadAllAppResources.Click += new System.EventHandler(this.DownloadAllHandler); // // tsmiDeleteAllResources // this.tsmiDeleteAllResources.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.deletedownload_16; this.tsmiDeleteAllResources.Name = "tsmiDeleteAllResources"; - this.tsmiDeleteAllResources.Size = new System.Drawing.Size(230, 22); + this.tsmiDeleteAllResources.Size = new System.Drawing.Size(237, 22); this.tsmiDeleteAllResources.Text = "&Delete All Resources"; + this.tsmiDeleteAllResources.ToolTipText = "Clears the app resource cache."; this.tsmiDeleteAllResources.Click += new System.EventHandler(this.DeleteAllResourcesHandler); // + // toolStripSeparator5 + // + this.toolStripSeparator5.Name = "toolStripSeparator5"; + this.toolStripSeparator5.Size = new System.Drawing.Size(234, 6); + // + // tsmiClose + // + this.tsmiClose.Name = "tsmiClose"; + this.tsmiClose.Size = new System.Drawing.Size(237, 22); + this.tsmiClose.Text = "Clo&se"; + this.tsmiClose.Click += new System.EventHandler(this.CloseHandler); + // // tsmEdit // this.tsmEdit.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -651,6 +788,12 @@ private void InitializeComponent() this.tsmiEditDeactivationList.Text = "&Deactivated Apps"; this.tsmiEditDeactivationList.Click += new System.EventHandler(this.DeactivationListHandler); // + // tsmiColumns + // + this.tsmiColumns.Name = "tsmiColumns"; + this.tsmiColumns.Size = new System.Drawing.Size(67, 20); + this.tsmiColumns.Text = "&Columns"; + // // tsmView // this.tsmView.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -668,8 +811,7 @@ private void InitializeComponent() this.tsmiShowAppIndex.Image = global::Mastersign.Bench.Dashboard.Properties.Resources.library_16; this.tsmiShowAppIndex.Name = "tsmiShowAppIndex"; this.tsmiShowAppIndex.Size = new System.Drawing.Size(205, 22); - this.tsmiShowAppIndex.Text = "Bench App &Library"; - this.tsmiShowAppIndex.Click += new System.EventHandler(this.ShowAppIndexHandler); + this.tsmiShowAppIndex.Text = "Bench App &Libraries"; // // tsmiShowCustomAppIndex // @@ -777,15 +919,25 @@ private void InitializeComponent() private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolStripMenuItem miWebsite; private System.Windows.Forms.ToolStripSeparator tsSeparatorWebsite; + private System.Windows.Forms.ToolStripMenuItem miAppInfo; + private System.Windows.Forms.ToolStripMenuItem tsmiUpgradeBench; + private System.Windows.Forms.ToolStripMenuItem tsmiColumns; + private System.Windows.Forms.ToolStripMenuItem tsmiUpdateBench; + private System.Windows.Forms.ToolStripMenuItem tsmiUpdateAppLibs; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripMenuItem tsmiClose; private System.Windows.Forms.DataGridViewImageColumn colIcon; private System.Windows.Forms.DataGridViewTextBoxColumn colIndex; + private System.Windows.Forms.DataGridViewTextBoxColumn colLibrary; + private System.Windows.Forms.DataGridViewTextBoxColumn colID; + private System.Windows.Forms.DataGridViewTextBoxColumn colCategory; private System.Windows.Forms.DataGridViewTextBoxColumn colLabel; - private System.Windows.Forms.DataGridViewTextBoxColumn colTyp; + private System.Windows.Forms.DataGridViewTextBoxColumn colVersion; private System.Windows.Forms.DataGridViewCheckBoxColumn colActivated; private System.Windows.Forms.DataGridViewCheckBoxColumn colExcluded; private System.Windows.Forms.DataGridViewTextBoxColumn colStatus; - private System.Windows.Forms.DataGridViewTextBoxColumn colVersion; + private System.Windows.Forms.DataGridViewTextBoxColumn colTyp; + private System.Windows.Forms.DataGridViewLinkColumn colLicense; private System.Windows.Forms.DataGridViewTextBoxColumn colComment; - private System.Windows.Forms.ToolStripMenuItem miAppInfo; } } \ No newline at end of file diff --git a/BenchManager/BenchDashboard/SetupForm.cs b/BenchManager/BenchDashboard/SetupForm.cs index 33bd6650..21ddd18a 100644 --- a/BenchManager/BenchDashboard/SetupForm.cs +++ b/BenchManager/BenchDashboard/SetupForm.cs @@ -10,6 +10,7 @@ using System.Windows.Forms; using ConEmu.WinForms; using Mastersign.Bench.Dashboard.Properties; +using Mastersign.Bench.Markdown; namespace Mastersign.Bench.Dashboard { @@ -21,16 +22,26 @@ public partial class SetupForm : Form private ListSortDirection sortDirection; private AppFacade contextApp; - private readonly Dictionary appLookup = new Dictionary(); + private readonly Dictionary appLookup + = new Dictionary(); + private int firstVisibleRowIndex; private ConEmuExecutionHost conHost; private ConEmuControl conControl; + private readonly List appListColumnLabels = new List(); + private readonly Dictionary appListColumns + = new Dictionary(); + private static string[] defaulAppListColumnLabels + = new[] { "Order", "ID", "Version", "Active", "Deactivated", "Status" }; + private DataGridViewColumn iconColumn; + public SetupForm(Core core) { this.core = core; core.ConfigReloaded += CoreConfigReloadedHandler; core.AllAppStateChanged += CoreAllAppStateChangedHandler; + core.AppActivationChanged += CoreAppActivationChangedHandler; core.AppStateChanged += CoreAppStateChangedHandler; core.BusyChanged += CoreBusyChangedHandler; core.ActionStateChanged += CoreActionStateChangedHandler; @@ -38,12 +49,22 @@ public SetupForm(Core core) gridApps.DoubleBuffered(true); InitializeConsole(); gridApps.AutoGenerateColumns = false; + iconColumn = gridApps.Columns[0]; + foreach (DataGridViewColumn col in gridApps.Columns) + { + if (string.IsNullOrEmpty(col.HeaderText)) continue; + appListColumnLabels.Add(col.HeaderText); + appListColumns.Add(col.HeaderText, col); + } } private void SetupForm_Load(object sender, EventArgs e) { InitializeDownloadList(); InitializeBounds(); + InitializeAppIndexMenu(); + InitializeAppListColumnsMenu(); + InitializeAppListColumns(); InitializeAppList(); UpdatePendingCounts(); @@ -72,8 +93,88 @@ private void InitializeBounds() SetBounds(x, y, w, h); } + private void InitializeAppIndexMenu() + { + tsmiShowAppIndex.DropDownItems.Clear(); + foreach (var lib in core.Config.AppLibraries) + { + var appLibItem = new ToolStripMenuItem("App Library '" + lib.ID + "'"); + appLibItem.Image = Resources.books_16; + appLibItem.Tag = lib; + appLibItem.Click += ShowAppIndexHandler; + tsmiShowAppIndex.DropDownItems.Add(appLibItem); + } + } + + private void InitializeAppListColumnsMenu() + { + var colLabels = core.Config.GetStringListValue( + PropertyKeys.DashboardSetupAppListColumns, + defaulAppListColumnLabels); + foreach (var colLabel in appListColumnLabels) + { + ToolStripMenuItem item = null; + if (tsmiColumns.DropDownItems.Count > 0) + { + foreach (ToolStripMenuItem i in tsmiColumns.DropDownItems) + if (i.Text == colLabel) + item = i; + } + if (item == null) + { + item = new ToolStripMenuItem(colLabel); + item.Click += AppListColumnToggleHandler; + tsmiColumns.DropDownItems.Add(item); + } + item.Checked = colLabels.Contains(colLabel); + } + } + + private void AppListColumnToggleHandler(object sender, EventArgs e) + { + var newColLabels = new List(); + foreach (ToolStripMenuItem item in tsmiColumns.DropDownItems) + { + if (item == sender) item.Checked = !item.Checked; + if (item.Checked) + { + newColLabels.Add(string.Format("`{0}`", item.Text)); + } + } + var configFile = core.Config.GetStringValue(PropertyKeys.CustomConfigFile); + MarkdownPropertyEditor.UpdateFile(configFile, new Dictionary + { { PropertyKeys.DashboardSetupAppListColumns, string.Join(", ", newColLabels) } }); + } + + private void InitializeAppListColumns() + { + gridApps.SuspendLayout(); + gridApps.Columns.Clear(); + var colLabels = core.Config.GetStringListValue( + PropertyKeys.DashboardSetupAppListColumns, + defaulAppListColumnLabels); + iconColumn.DisplayIndex = 0; + gridApps.Columns.Add(iconColumn); + var pos = 1; + foreach (var colLabel in colLabels) + { + DataGridViewColumn col; + if (appListColumns.TryGetValue(colLabel, out col)) + { + col.DisplayIndex = pos++; + gridApps.Columns.Add(col); + } + } + gridApps.ResumeLayout(); + } + private void CoreConfigReloadedHandler(object sender, EventArgs e) { + firstVisibleRowIndex = gridApps.FirstDisplayedScrollingRowIndex; + InitializeDownloadList(); + InitializeAppIndexMenu(); + InitializeAppListColumnsMenu(); + InitializeAppListColumns(); InitializeAppList(); UpdatePendingCounts(); } @@ -82,11 +183,17 @@ private void CoreAllAppStateChangedHandler(object sender, EventArgs e) { foreach (var app in core.Config.Apps) { - NotifyAppStateChange(app.ID); + app.DiscardCachedValues(); } + gridApps.Refresh(); UpdatePendingCounts(); } + private void CoreAppActivationChangedHandler(object sender, EventArgs e) + { + gridApps.Refresh(); + } + private void CoreAppStateChangedHandler(object sender, AppEventArgs e) { NotifyAppStateChange(e.ID); @@ -94,12 +201,20 @@ private void CoreAppStateChangedHandler(object sender, AppEventArgs e) } private void NotifyAppStateChange(string appId) + { + ForAppWrapper(appId, w => + { + w.App.DiscardCachedValues(); + w.NotifyChanges(); + }); + } + + private void ForAppWrapper(string appId, Action action) { AppWrapper wrapper; if (appLookup.TryGetValue(appId, out wrapper)) { - wrapper.App.DiscardCachedValues(); - wrapper.NotifyChanges(); + action(wrapper); } } @@ -217,21 +332,26 @@ private void InitializeConsole() Controls.Add(c); conControl = c; conHost = new ConEmuExecutionHost(core, conControl, core.Config.Apps[AppKeys.ConEmu].Exe); + conHost.StartHost(); core.ProcessExecutionHost = conHost; } private void DisposeConsole() { var oldHost = core.ProcessExecutionHost; - core.ProcessExecutionHost = new DefaultExecutionHost(); + core.ProcessExecutionHost = new SimpleExecutionHost(); oldHost.Dispose(); } private void InitializeDownloadList() { + if (downloadList.Downloader != null) + { + downloadList.Downloader.IsWorkingChanged -= DownloaderIsWorkingChangedHandler; + } downloadList.Downloader = core.Downloader; - core.Downloader.IsWorkingChanged += DownloaderIsWorkingChangedHandler; - IsDownloadListVisible = false; + downloadList.Downloader.IsWorkingChanged += DownloaderIsWorkingChangedHandler; + UpdateDownloadListVisibility(); } private void InitializeAppList() @@ -255,7 +375,6 @@ private void InitializeAppList() BeginInvoke((ThreadStart)(() => { var selectedRow = gridApps.SelectedRows.Count > 0 ? gridApps.SelectedRows[0].Index : -10; - var firstVisibleRow = gridApps.FirstDisplayedScrollingRowIndex; gridApps.SuspendLayout(); gridApps.DataSource = bindingList; if (sortedColumn != null) @@ -266,9 +385,9 @@ private void InitializeAppList() { gridApps.Rows[selectedRow].Selected = true; } - if (firstVisibleRow >= 0) + if (firstVisibleRowIndex >= 0) { - gridApps.FirstDisplayedScrollingRowIndex = firstVisibleRow; + gridApps.FirstDisplayedScrollingRowIndex = firstVisibleRowIndex; } gridApps.ResumeLayout(); })); @@ -314,7 +433,7 @@ private void AlwaysShowDownloadsCheckedChanged(object sender, EventArgs e) UpdateDownloadListVisibility(); } - private void EditTextFile(string name, string path) + private void EditFile(string name, string path, string appId) { if (!File.Exists(path)) { @@ -327,19 +446,35 @@ private void EditTextFile(string name, string path) MessageBoxIcon.Error); return; } - System.Diagnostics.Process.Start(path); + var editorApp = core.Config.Apps[appId]; + if (editorApp.IsInstalled) + { + core.LaunchApp(appId, path); + } + else + { + System.Diagnostics.Process.Start(path); + } } + private void EditTextFile(string name, string path) + => EditFile(name, path, core.Config.GetStringValue(PropertyKeys.TextEditorApp)); + + private void EditMarkdownFile(string name, string path) + => EditFile(name, path, core.Config.GetStringValue(PropertyKeys.MarkdownEditorApp)); + private void EditCustomConfigHandler(object sender, EventArgs e) { - EditTextFile("Custom Configuration", + EditMarkdownFile("User Configuration", core.Config.GetStringValue(PropertyKeys.CustomConfigFile)); } private void EditCustomAppsHandler(object sender, EventArgs e) { - EditTextFile("Custom App Index", - core.Config.GetStringValue(PropertyKeys.CustomAppIndexFile)); + EditMarkdownFile("User App Library", + Path.Combine( + core.Config.GetStringValue(PropertyKeys.CustomConfigDir), + core.Config.GetStringValue(PropertyKeys.AppLibIndexFileName))); } private void ActivationListHandler(object sender, EventArgs e) @@ -441,12 +576,70 @@ private async void UpgradeAllHandler(object sender, EventArgs e) await core.UpgradeAppsAsync(TaskInfoHandler); } - private async void UpdateEnvironment(object sender, EventArgs e) + private async void UpdateEnvironmentHandler(object sender, EventArgs e) { AnnounceTask("Update Environment"); await core.UpdateEnvironmentAsync(TaskInfoHandler); } + private async void UpdateAppLibsHandler(object sender, EventArgs e) + { + AnnounceTask("Update App Libraries"); + await core.UpdateAppLibrariesAsync(TaskInfoHandler); + } + + private async void UpdateBenchHandler(object sender, EventArgs e) + { + AnnounceTask("Update App Libraries and Apps"); + await core.UpdateAppsAsync(TaskInfoHandler); + } + + private async void UpgradeBenchSystemHandler(object sender, EventArgs e) + { + AnnounceTask("Updating Bench System"); + + var version = core.Config.GetStringValue(PropertyKeys.Version); + var latestVersion = await core.GetLatestVersionNumber(); + if (latestVersion == null) + { + MessageBox.Show(this, + "Retrieving the version number of the latest release failed." + + Environment.NewLine + Environment.NewLine + + "The update process was cancelled.", + "Updating Bench System", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + else if (string.Equals(version, latestVersion)) + { + var res = MessageBox.Show(this, + "There is no update available." + + " You are already using the latest release of Bench (v" + version + ")." + Environment.NewLine + + Environment.NewLine + + "Do you want to reinstall the Bench system with a download of the latest release anyway?", + "Updating Bench System", + MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (res != DialogResult.Yes) return; + } + else + { + var res = MessageBox.Show(this, + "Are you sure you want to update the Bench system?" + Environment.NewLine + + Environment.NewLine + + "Current version: " + version + Environment.NewLine + + "Update version: " + latestVersion, + "Updating Bench System", + MessageBoxButtons.OKCancel, MessageBoxIcon.Question); + if (res != DialogResult.OK) return; + } + var result = await core.DownloadBenchUpdateAsync(TaskInfoHandler); + if (result.Success) + { + BenchTasks.InitiateInstallationBootstrap(core.Config); + Program.Core.Shutdown(); + } + } + private void AppInfoHandler(object sender, EventArgs e) { if (contextApp == null) return; @@ -602,6 +795,16 @@ private void gridApps_CellContentClick(object sender, DataGridViewCellEventArgs core.SetAppDeactivated(appWrapper.ID, !appWrapper.App.IsDeactivated); } } + if (col == colLicense) + { + var row = gridApps.Rows[e.RowIndex]; + var appWrapper = row.DataBoundItem as AppWrapper; + var url = appWrapper.LicenseUrl; + if (url != null) + { + System.Diagnostics.Process.Start(url.AbsoluteUri); + } + } } private void gridApps_CellDoubleClick(object sender, DataGridViewCellEventArgs e) @@ -617,15 +820,25 @@ private void gridApps_CellDoubleClick(object sender, DataGridViewCellEventArgs e private void ShowAppIndexHandler(object sender, EventArgs e) { - var viewer = new MarkdownViewer(core); - viewer.LoadMarkdown(core.Config.GetStringValue(PropertyKeys.AppIndexFile), "Bench App Library"); - viewer.Show(); + var lib = (sender as ToolStripItem)?.Tag as AppLibrary; + if (lib != null) + { + var viewer = new MarkdownViewer(core); + viewer.LoadMarkdown(Path.Combine(lib.BaseDir, + core.Config.GetStringValue(PropertyKeys.AppLibIndexFileName)), + "App Library '" + lib.ID + "'"); + viewer.Show(); + } } private void ShowCustomAppIndexHandler(object sender, EventArgs e) { var viewer = new MarkdownViewer(core); - viewer.LoadMarkdown(core.Config.GetStringValue(PropertyKeys.CustomAppIndexFile), "User App Library"); + viewer.LoadMarkdown( + Path.Combine( + core.Config.GetStringValue(PropertyKeys.CustomConfigDir), + core.Config.GetStringValue(PropertyKeys.AppLibIndexFileName)), + "User App Library"); viewer.Show(); } @@ -650,5 +863,10 @@ private void SetupForm_FormClosing(object sender, FormClosingEventArgs e) core.BusyChanged -= CoreBusyChangedHandler; core.ActionStateChanged -= CoreActionStateChangedHandler; } + + private void CloseHandler(object sender, EventArgs e) + { + Close(); + } } } diff --git a/BenchManager/BenchDashboard/SetupForm.resx b/BenchManager/BenchDashboard/SetupForm.resx index dbd9ba8a..52d18c8d 100644 --- a/BenchManager/BenchDashboard/SetupForm.resx +++ b/BenchManager/BenchDashboard/SetupForm.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + False + False @@ -132,10 +135,19 @@ True + + True + + + True + + + True + True - + True @@ -147,7 +159,10 @@ True - + + True + + True diff --git a/BenchManager/BenchDashboard/SortedBindingList.cs b/BenchManager/BenchDashboard/SortedBindingList.cs index beaa71e3..6802a0ca 100644 --- a/BenchManager/BenchDashboard/SortedBindingList.cs +++ b/BenchManager/BenchDashboard/SortedBindingList.cs @@ -21,45 +21,15 @@ public SortedBindingList(IEnumerable enumeration) { } - protected override bool IsSortedCore - { - get - { - return m_isSorted; - } - } + protected override bool IsSortedCore => m_isSorted; - protected override ListSortDirection SortDirectionCore - { - get - { - return m_sortDirection; - } - } + protected override ListSortDirection SortDirectionCore => m_sortDirection; - protected override PropertyDescriptor SortPropertyCore - { - get - { - return m_propertyDescriptor; - } - } + protected override PropertyDescriptor SortPropertyCore => m_propertyDescriptor; - protected override bool SupportsSearchingCore - { - get - { - return true; - } - } + protected override bool SupportsSearchingCore => true; - protected override bool SupportsSortingCore - { - get - { - return true; - } - } + protected override bool SupportsSortingCore => true; protected override void ApplySortCore(PropertyDescriptor propertyDesciptor, ListSortDirection sortDirection) { @@ -73,9 +43,7 @@ protected override void ApplySortCore(PropertyDescriptor propertyDesciptor, List } protected virtual IComparer createComparer(PropertyDescriptor property, ListSortDirection direction) - { - return new PropertyDescriptorComparer(property, direction); - } + => new PropertyDescriptorComparer(property, direction); private void sort(IComparer comparer) { @@ -108,6 +76,11 @@ protected override void RemoveSortCore() OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, NO_ITEM_INDEX)); } + + public void NotifyListChanged() + { + OnListChanged(new ListChangedEventArgs(ListChangedType.PropertyDescriptorChanged, 0)); + } } public class PropertyDescriptorComparer : IComparer diff --git a/BenchManager/BenchDashboard/packages.config b/BenchManager/BenchDashboard/packages.config index 6c090504..d640a679 100644 --- a/BenchManager/BenchDashboard/packages.config +++ b/BenchManager/BenchDashboard/packages.config @@ -2,5 +2,4 @@ - \ No newline at end of file diff --git a/BenchManager/BenchLib.Test/GroupedPropertyCollection.cs b/BenchManager/BenchLib.Test/GroupedPropertyCollection.cs index e60dbc02..a6a47ba5 100644 --- a/BenchManager/BenchLib.Test/GroupedPropertyCollection.cs +++ b/BenchManager/BenchLib.Test/GroupedPropertyCollection.cs @@ -7,15 +7,21 @@ class GroupedPropertyCollection : PropertyCollection, IGroupedPropertyCollection { private IDictionary> groups; private IDictionary groupCategories; + private IDictionary groupMetadata; + private IDictionary groupDocs; public GroupedPropertyCollection( IDictionary> groups = null, IDictionary groupCategories = null, + IDictionary groupMetadata = null, + IDictionary groupDocs = null, IDictionary properties = null) : base(properties) { this.groups = groups ?? new Dictionary>(); this.groupCategories = groupCategories ?? new Dictionary(); + this.groupMetadata = groupMetadata ?? new Dictionary(); + this.groupDocs = groupDocs ?? new Dictionary(); } public bool CanGetGroupValue(string group, string name) @@ -42,6 +48,19 @@ public string GetGroupCategory(string group) ? category : null; } + public object GetGroupMetadata(string group) + { + object metadata; + return groupMetadata.TryGetValue(group, out metadata) + ? metadata : null; + } + + public string GetGroupDocumentation(string group) + { + string docs; + return groupDocs.TryGetValue(group, out docs) ? docs : null; + } + public object GetGroupValue(string group, string name) { if (group == null) return GetValue(name); @@ -86,6 +105,16 @@ public void SetGroupCategory(string group, string category) groupCategories[group] = category; } + public void SetGroupMetadata(string group, object metadata) + { + groupMetadata[group] = metadata; + } + + public void SetGroupDocumentation(string group, string docs) + { + groupDocs[group] = docs; + } + public void SetGroupValue(string group, string name, object value) { if (group == null) @@ -102,5 +131,22 @@ public void SetGroupValue(string group, string name, object value) properties[name] = value; } } + + public void ResetGroupValue(string group, string name) + { + if (group == null) + { + ResetValue(name); + } + else + { + IDictionary properties; + if (!groups.TryGetValue(group, out properties)) + { + properties.Add(group, properties = new Dictionary()); + } + if (properties.ContainsKey(name)) properties.Remove(name); + } + } } } diff --git a/BenchManager/BenchLib.Test/PropertyCollection.cs b/BenchManager/BenchLib.Test/PropertyCollection.cs index 841cbab6..21a5b04e 100644 --- a/BenchManager/BenchLib.Test/PropertyCollection.cs +++ b/BenchManager/BenchLib.Test/PropertyCollection.cs @@ -43,5 +43,10 @@ public void SetValue(string name, object value) { properties[name] = value; } + + public void ResetValue(string name) + { + if (properties.ContainsKey(name)) properties.Remove(name); + } } } diff --git a/BenchManager/BenchLib.Test/packages.config b/BenchManager/BenchLib.Test/packages.config index 83aabd48..f2fd55af 100644 --- a/BenchManager/BenchLib.Test/packages.config +++ b/BenchManager/BenchLib.Test/packages.config @@ -1,5 +1,4 @@  - \ No newline at end of file diff --git a/BenchManager/BenchLib/ActivationFile.cs b/BenchManager/BenchLib/ActivationFile.cs index 08ec52f5..ac9f1d7e 100644 --- a/BenchManager/BenchLib/ActivationFile.cs +++ b/BenchManager/BenchLib/ActivationFile.cs @@ -42,6 +42,8 @@ public class ActivationFile : IEnumerable private static readonly Regex DisabledExp = new Regex(@"^#\s*(?\S+)(?\s+.+?)?\s*$"); + private static readonly Regex EnabledExp = new Regex(@"^\s*(?\S+)(?\s+.+?)?\s*$"); + private readonly string FilePath; /// @@ -91,16 +93,22 @@ private static IEnumerable Activator(IEnumerable lines, string i var found = false; foreach (var l in lines) { - var m = DisabledExp.Match(l); + var line = l.Trim(); + var m = DisabledExp.Match(line); if (m.Success && m.Groups["id"].Value == id) { + if (found) continue; yield return id + m.Groups["comment"].Value; found = true; + continue; } - else + m = EnabledExp.Match(line); + if (m.Success && m.Groups["id"].Value == id) { - yield return l; + if (found) continue; + found = true; } + yield return l; } if (!found) { @@ -110,17 +118,25 @@ private static IEnumerable Activator(IEnumerable lines, string i private static IEnumerable Deactivator(IEnumerable lines, string id) { + var found = false; foreach (var l in lines) { var line = l.Trim(); - if (IsValidLine(line) && CleanLine(line) == id) + var m = EnabledExp.Match(l); + if (m.Success && m.Groups["id"].Value == id) { - yield return "# " + l; + if (found) continue; + yield return "# " + line; + found = true; + continue; } - else + m = DisabledExp.Match(line); + if (m.Success && m.Groups["id"].Value == id) { - yield return l; + if (found) continue; + found = true; } + yield return l; } } diff --git a/BenchManager/BenchLib/AppFacade.cs b/BenchManager/BenchLib/AppFacade.cs index f4b9061c..76998dc9 100644 --- a/BenchManager/BenchLib/AppFacade.cs +++ b/BenchManager/BenchLib/AppFacade.cs @@ -14,6 +14,11 @@ namespace Mastersign.Bench /// public class AppFacade { + /// + /// The namespace separator in an app ID. + /// + public const char NS_SEPARATOR = '.'; + private readonly IConfiguration AppIndex; private readonly string AppName; @@ -91,23 +96,78 @@ private void UpdateValue(string property, object value) /// public string ID { get { return AppName; } } + internal static string NamespaceFromId(string id) + { + var p = id.LastIndexOf(NS_SEPARATOR); + return p < 0 ? string.Empty : id.Substring(0, p); + } + + /// + /// Gets the namespace part of the apps ID. + /// + public string Namespace => NamespaceFromId(ID); + + internal static string PathSegmentFromId(string id) + { + return id.ToLowerInvariant().Replace(NS_SEPARATOR, IOPath.DirectorySeparatorChar); + } + + /// + /// Gets a part for a filesystem path, which represents the id of this app. + /// + public string PathSegment => PathSegmentFromId(ID); + + internal static string NamespacePathSegmentFromId(string id) + { + var ns = NamespaceFromId(id); + return string.IsNullOrEmpty(ns) + ? string.Empty + : ns.ToLowerInvariant().Replace(NS_SEPARATOR, IOPath.DirectorySeparatorChar); + } + + /// + /// Gets a part for a filesystem path, which represents the namespace of this app. + /// + public string NamespacePathSegment => NamespacePathSegmentFromId(ID); + + internal static string NameFromId(string id) + { + var p = id.LastIndexOf(NS_SEPARATOR); + return p < 0 ? id : id.Substring(p + 1); + } + + /// + /// Gets the name part of the apps ID. + /// + public string Name => NameFromId(ID); + + /// + /// Gets the app library, this app is defined in. + /// + public AppLibrary AppLibrary => AppIndex.GetGroupMetadata(AppName) as AppLibrary; + + /// + /// Gets the documentation text of this app in Markdown syntax. + /// + public string MarkdownDocumentation => AppIndex.GetGroupDocumentation(AppName); + /// /// Gets the label of the app. /// - public string Label { get { return StringValue(PropertyKeys.AppLabel); } } + public string Label => StringValue(PropertyKeys.AppLabel); /// /// Gets the category, this app belongs to. /// E.g. there are Required and Optional apps. /// /// - public string Category { get { return AppIndex.GetGroupCategory(AppName); } } + public string Category => AppIndex.GetGroupCategory(AppName); /// /// The typ of this app. /// See for to compare and list the app typs. /// - public string Typ { get { return StringValue(PropertyKeys.AppTyp); } } + public string Typ => StringValue(PropertyKeys.AppTyp); /// /// Checks, if this app is a packaged managed by some kind of package manager. @@ -133,18 +193,13 @@ public bool IsManagedPackage /// If the app has the version "latest" it is considered to have no specified version. /// /// - public string Version { get { return StringValue(PropertyKeys.AppVersion); } } + public string Version => StringValue(PropertyKeys.AppVersion); /// /// Checks, if this app has a specified version. /// public bool IsVersioned - { - get - { - return Version != null && !Version.Equals("latest", StringComparison.InvariantCultureIgnoreCase); - } - } + => Version != null && !Version.Equals("latest", StringComparison.InvariantCultureIgnoreCase); private static Regex simpleVersionPattern = new Regex(@"^\d+(\.\d)*$"); @@ -153,20 +208,45 @@ public bool IsVersioned /// like 1.12.5.000 or 3.4. /// public bool IsSimpleVersion - { - get { return IsVersioned && simpleVersionPattern.Match(Version).Success; } - } + => IsVersioned && simpleVersionPattern.Match(Version).Success; /// /// Gets the URL of the project or vendor website of this app, or null if no website was specified. /// - public string Website { get { return StringValue(PropertyKeys.AppWebsite); } } + public string Website => StringValue(PropertyKeys.AppWebsite); /// /// Gets a dictionary with labels and URLs for help and documentation. /// If an URL is relative, it is considered to be relative to the apps . /// - public IDictionary Docs { get { return Value(PropertyKeys.AppDocs) as IDictionary; } } + public IDictionary Docs + => (Value(PropertyKeys.AppDocs) as IDictionary) + ?? new Dictionary(); + + /// + /// Gets the short name of the apps license. + /// + public string License => StringValue(PropertyKeys.AppLicense); + + /// + /// Gets the absolute URL of the apps license document. + /// + public Uri LicenseUrl + { + get + { + Uri result; + var licenseUrl = StringValue(PropertyKeys.AppLicenseUrl); + if (!Uri.TryCreate(licenseUrl, UriKind.RelativeOrAbsolute, out result)) + { + return null; + } + if (!result.IsAbsoluteUri) + return new Uri(new Uri(Dir + "/"), result); + else + return result; + } + } /// /// An array with app IDs which are necessary to be installed for this app to work. @@ -190,70 +270,68 @@ public string[] Responsibilities /// Checks, whether this app is marked as activated by the user, or not. /// /// true if the apps ID is marked as activated by the user; otherwise false. - public bool IsActivated { get { return BoolValue(PropertyKeys.AppIsActivated); } } + public bool IsActivated => BoolValue(PropertyKeys.AppIsActivated); /// /// Checks, whether this app is marked as deactivated by the user, or not. /// /// true if the apps ID is marked as deactivated by the user; otherwise false. - public bool IsDeactivated { get { return BoolValue(PropertyKeys.AppIsDeactivated); } } + public bool IsDeactivated => BoolValue(PropertyKeys.AppIsDeactivated); /// /// Checks, whether this app is required by the Bench system, or not. /// /// true if the app is required by Bench; otherwise false. - public bool IsRequired { get { return BoolValue(PropertyKeys.AppIsRequired); } } + public bool IsRequired => BoolValue(PropertyKeys.AppIsRequired); /// /// Checks, whether this app is dependency of another app. /// /// true if the app is required by another app; otherwise false. - public bool IsDependency { get { return BoolValue(PropertyKeys.AppIsDependency); } } + public bool IsDependency => BoolValue(PropertyKeys.AppIsDependency); /// /// Gets the URL of the apps resource, or null if the app has no downloadable resource. /// - public string Url { get { return StringValue(PropertyKeys.AppUrl); } } + public string Url => StringValue(PropertyKeys.AppUrl); /// /// Gets a dictionary with HTTP header fields for the download request. /// public IDictionary DownloadHeaders - { - get { return Value(PropertyKeys.AppDownloadHeaders) as IDictionary; } - } + => (Value(PropertyKeys.AppDownloadHeaders) as IDictionary) + ?? new Dictionary(); /// /// Gets a dictionary with HTTP cookies for the download request. /// public IDictionary DownloadCookies - { - get { return Value(PropertyKeys.AppDownloadCookies) as IDictionary; } - } + => (Value(PropertyKeys.AppDownloadCookies) as IDictionary) + ?? new Dictionary(); /// /// Gets the name of the apps file resource, or null /// in case the app has an archive resource or no downloadable resource at all. /// - public string ResourceFileName { get { return StringValue(PropertyKeys.AppResourceName); } } + public string ResourceFileName => StringValue(PropertyKeys.AppResourceName); /// /// Gets the name of the apps archive resource, or null /// in case the app has a file resource or no downloadable resource at all. /// - public string ResourceArchiveName { get { return StringValue(PropertyKeys.AppArchiveName); } } + public string ResourceArchiveName => StringValue(PropertyKeys.AppArchiveName); /// /// Gets the sub path inside of the resource archive, or null /// in case the whole archive can be extracted or the app has no archive resource. /// - public string ResourceArchivePath { get { return StringValue(PropertyKeys.AppArchivePath); } } + public string ResourceArchivePath => StringValue(PropertyKeys.AppArchivePath); /// /// Gets the typ of the resource archive, or null if the app has no archive resource. /// See to compare or list the possible typs of an archive resource. /// - public string ResourceArchiveTyp { get { return StringValue(PropertyKeys.AppArchiveTyp); } } + public string ResourceArchiveTyp => StringValue(PropertyKeys.AppArchiveTyp); /// /// Gets a value, which specifies if the app will be installed even if it is already installed. @@ -268,27 +346,38 @@ public bool Force /// The name of the package represented by this app, or null in case /// is false. /// - public string PackageName { get { return StringValue(PropertyKeys.AppPackageName); } } + public string PackageName => StringValue(PropertyKeys.AppPackageName); /// /// The name of the target directory for this app. /// The target directory is the directory where the app resources are installed. /// - public string Dir { get { return StringValue(PropertyKeys.AppDir); } } + public string Dir => StringValue(PropertyKeys.AppDir); /// /// The relative path of the main executable file of the app, or null /// in case the app has no executable (e.g. the app is just a group). /// The path is relative to the target of this app. /// - public string Exe { get { return StringValue(PropertyKeys.AppExe); } } + public string Exe => StringValue(PropertyKeys.AppExe); + + /// + /// A flag to control whether the main executable of this app can be tested + /// by executing it with the and checking the exit code for 0. + /// + public bool ExeTest => BoolValue(PropertyKeys.AppExeTest); + + /// + /// A command line argument string to pass to the main executable, when testing it for propery installation. + /// + public string ExeTestArguments => StringValue(PropertyKeys.AppExeTestArguments) ?? string.Empty; /// /// The relative path to a file, which existence can be used to check if the app is installed, /// or null e.g. in case the app is a package managed by a package manager. /// The path is relative to the target of this app. /// - public string SetupTestFile { get { return StringValue(PropertyKeys.AppSetupTestFile); } } + public string SetupTestFile => StringValue(PropertyKeys.AppSetupTestFile); /// /// An array with relative or absolute paths, @@ -306,15 +395,14 @@ public string[] Path /// A flag to control if the s of this app will be added /// to the environment variable PATH. /// - public bool Register { get { return BoolValue(PropertyKeys.AppRegister); } } + public bool Register => BoolValue(PropertyKeys.AppRegister); /// /// A dictionary with additional environment variables to setup, when this app is activated. /// public IDictionary Environment - { - get { return Value(PropertyKeys.AppEnvironment) as IDictionary; } - } + => (Value(PropertyKeys.AppEnvironment) as IDictionary) + ?? new Dictionary(); /// /// An array with paths to executables, which must be adorned. @@ -339,14 +427,9 @@ internal void RemoveAdornedExecutable(string path) /// Gets the base path of the directory containing the adornmend proxy scripts for the executables of this app. /// public string AdornmentProxyBasePath - { - get - { - return IOPath.Combine( + => IOPath.Combine( AppIndex.GetStringValue(PropertyKeys.AppAdornmentBaseDir), ID.ToLowerInvariant()); - } - } /// /// Checks, whether an executable of this app is marked as adorned, or not. @@ -376,40 +459,33 @@ public bool IsExecutableAdorned(string exePath) /// The path to the executable. /// The path to the adornment script. public string GetExecutableProxy(string exePath) - { - return IOPath.Combine(AdornmentProxyBasePath, + => IOPath.Combine(AdornmentProxyBasePath, IOPath.GetFileNameWithoutExtension(exePath) + ".cmd"); - } /// /// Checks, whether execution adornment proxies are required for this app, or not. /// public bool IsAdornmentRequired - { - get - { - return (RegistryKeys.Length > 0 && AppIndex.GetBooleanValue(PropertyKeys.UseRegistryIsolation)) - || File.Exists(GetCustomScriptFile("pre-run")) - || File.Exists(GetCustomScriptFile("post-run")); - } - } + => (RegistryKeys.Length > 0 && AppIndex.GetBooleanValue(PropertyKeys.UseRegistryIsolation)) + || File.Exists(GetCustomScript("pre-run")) + || File.Exists(GetCustomScript("post-run")); /// /// An array with registry paths relative to the HKCU (current user) hive, /// which must be considered for registry isolation. /// - public string[] RegistryKeys { get { return ListValue(PropertyKeys.AppRegistryKeys); } } + public string[] RegistryKeys => ListValue(PropertyKeys.AppRegistryKeys); /// /// The label for the apps launcher, or null if the app has no launcher. /// - public string Launcher { get { return StringValue(PropertyKeys.AppLauncher); } } + public string Launcher => StringValue(PropertyKeys.AppLauncher); /// /// The path to the main executable, to be started by the apps launcher, /// or null if the app has no launcher. /// - public string LauncherExecutable { get { return StringValue(PropertyKeys.AppLauncherExecutable); } } + public string LauncherExecutable => StringValue(PropertyKeys.AppLauncherExecutable); /// /// An array with command line arguments to be sent to the @@ -418,13 +494,13 @@ public bool IsAdornmentRequired /// from the launcher to the executable. This is also necessary for drag-and-drop of files /// onto the launcher to work. /// - public string[] LauncherArguments { get { return ListValue(PropertyKeys.AppLauncherArguments); } } + public string[] LauncherArguments => ListValue(PropertyKeys.AppLauncherArguments); /// /// A path to an *.ico or *.exe file with the icon for the apps launcher, /// or null if the app has no launcher. /// - public string LauncherIcon { get { return StringValue(PropertyKeys.AppLauncherIcon); } } + public string LauncherIcon => StringValue(PropertyKeys.AppLauncherIcon); #region Recursive Discovery @@ -483,16 +559,58 @@ internal string GetLauncherScriptFile() ID.ToLowerInvariant() + ".cmd"); } - internal string GetCustomScriptFile(string typ) + /// + /// Gets a path to a custom script file for this app. + /// + /// The typ of the custom script (e.g. setup). + /// A path to the script file or null if no custom script exists. + public string GetCustomScript(string typ) { + var relativePath = IOPath.Combine( + AppIndex.GetStringValue(PropertyKeys.AppLibCustomScriptDirName), + NamespacePathSegment); + var scriptName = string.Format("{0}.{1}.ps1", Name.ToLowerInvariant(), typ); + var userPath = IOPath.Combine( - IOPath.Combine(AppIndex.GetStringValue(PropertyKeys.CustomConfigDir), "apps"), - ID.ToLowerInvariant() + "." + typ + ".ps1"); + IOPath.Combine(AppIndex.GetStringValue(PropertyKeys.CustomConfigDir), relativePath), + scriptName); if (File.Exists(userPath)) return userPath; - var integratedPath = IOPath.Combine( - IOPath.Combine(AppIndex.GetStringValue(PropertyKeys.BenchAuto), "apps"), - ID.ToLowerInvariant() + "." + typ + ".ps1"); - if (File.Exists(integratedPath)) return integratedPath; + + if (AppLibrary != null) + { + var libraryPath = IOPath.Combine( + IOPath.Combine(AppLibrary.BaseDir, relativePath), + scriptName); + if (File.Exists(libraryPath)) return libraryPath; + } + return null; + } + + /// + /// Gets a path to a setup resource file or directory. + /// + /// A relative path to a setup resource. + /// An absolute path to the resource or null, if the resource does not exists. + public string GetSetupResource(string relativeResourcePath) + { + var relativeDirPath = IOPath.Combine( + AppIndex.GetStringValue(PropertyKeys.AppLibResourceDirName), + PathSegment); + + var userPath = IOPath.Combine( + IOPath.Combine( + AppIndex.GetStringValue(PropertyKeys.CustomConfigDir), + relativeDirPath), + relativeResourcePath); + if (File.Exists(userPath) || Directory.Exists(userPath)) return userPath; + + if (AppLibrary != null) + { + var libraryPath = IOPath.Combine( + IOPath.Combine(AppLibrary.BaseDir, relativeDirPath), + relativeResourcePath); + if (File.Exists(libraryPath) || Directory.Exists(libraryPath)) return libraryPath; + } return null; } @@ -581,13 +699,13 @@ private bool GetIsInstalled() var pip2PackageDir = IOPath.Combine( IOPath.Combine(python2Dir, "lib"), IOPath.Combine("site-packages", PackageName)); - return Directory.Exists(pip2PackageDir); + return Directory.Exists(pip2PackageDir) || File.Exists(SetupTestFile); case AppTyps.Python3Package: var python3Dir = AppIndex.GetStringGroupValue(AppKeys.Python3, PropertyKeys.AppDir); var pip3PackageDir = IOPath.Combine( IOPath.Combine(python3Dir, "lib"), IOPath.Combine("site-packages", PackageName)); - return Directory.Exists(pip3PackageDir); + return Directory.Exists(pip3PackageDir) || File.Exists(SetupTestFile); default: return File.Exists(SetupTestFile); } @@ -986,52 +1104,37 @@ public AppStatusIcon StatusIcon /// /// Checks, whether the app has a resource and the resource is not cached. /// - public bool CanDownloadResource { get { return HasResource && !IsResourceCached; } } + public bool CanDownloadResource => HasResource && !IsResourceCached; /// /// Checks, whether the app has cached resource. /// - public bool CanDeleteResource { get { return HasResource && IsResourceCached; } } + public bool CanDeleteResource => HasResource && IsResourceCached; /// /// Checks, whether this app can be installed. /// public bool CanInstall - { - get - { - return CanCheckInstallation && (!IsInstalled || Force) - || !CanCheckInstallation && GetCustomScriptFile("setup") != null; - } - } + => CanCheckInstallation && (!IsInstalled || Force) + || !CanCheckInstallation && GetCustomScript("setup") != null; /// /// Checks, whether this app can be uninstalled. /// public bool CanUninstall - { - get - { - return CanCheckInstallation && IsInstalled - || !CanCheckInstallation && GetCustomScriptFile("remove") != null; - } - } + => CanCheckInstallation && IsInstalled + || !CanCheckInstallation && GetCustomScript("remove") != null; /// /// Checks, whether this app can be reinstalled. /// public bool CanReinstall - { - get - { - return CanCheckInstallation && IsInstalled - && (!HasResource || IsResourceCached) - && !IsManagedPackage - || !CanCheckInstallation - && GetCustomScriptFile("remove") != null - && GetCustomScriptFile("setup") != null; - } - } + => CanCheckInstallation && IsInstalled + && (!HasResource || IsResourceCached) + && !IsManagedPackage + || !CanCheckInstallation + && GetCustomScript("remove") != null + && GetCustomScript("setup") != null; /// /// Checks, whether this app can be upgraded to a more recent version. @@ -1039,48 +1142,41 @@ public bool CanReinstall /// /// This method does not check if there really is a more recent version of this app. /// - public bool CanUpgrade - { - get - { - return - // Default app with no version or version difference - CanCheckInstallation && IsInstalled - && !IsManagedPackage - && (!IsVersioned || !IsVersionUpToDate) - // Default app with custom setup and remove - || !CanCheckInstallation - && !IsManagedPackage - && GetCustomScriptFile("remove") != null - && GetCustomScriptFile("setup") != null; - } - } + public bool CanUpgrade => + // App with no version or version difference + CanCheckInstallation && IsInstalled + && !IsManagedPackage + && (!IsVersioned || !IsVersionUpToDate) + // App with custom setup and remove + || !CanCheckInstallation + && !IsManagedPackage + && GetCustomScript("remove") != null + && GetCustomScript("setup") != null; + + /// + /// Checks, whether this app can be tested or not. + /// + public bool CanTest => + IsManagedPackage + || Typ == AppTyps.Default + || Exe != null + || GetCustomScript("setup") != null && GetCustomScript("remove") != null + || GetCustomScript("test") != null; /// /// Checks, whether this app is active (activated or required) but not installed. /// public bool ShouldBeInstalled - { - get - { - return IsActive - && (CanCheckInstallation && !IsInstalled - || !CanCheckInstallation && GetCustomScriptFile("setup") != null); - } - } + => IsActive && (CanCheckInstallation && !IsInstalled + || !CanCheckInstallation && GetCustomScript("setup") != null); /// /// Checks, whether this app is not activated or even deactivated but installed. /// public bool ShouldBeRemoved - { - get - { - return !IsActive - && (CanCheckInstallation && IsInstalled - || !CanCheckInstallation && GetCustomScriptFile("remove") != null); - } - } + => !IsActive + && (CanCheckInstallation && IsInstalled + || !CanCheckInstallation && GetCustomScript("remove") != null); #endregion @@ -1175,6 +1271,31 @@ internal void TrackResponsibilities() } } + /// + /// Resets the following properties: + /// + /// + /// IsActivated + /// + /// + /// IsDeactivated + /// + /// + /// IsRequired + /// + /// + /// IsDependency + /// + /// + /// + internal void ResetActivation() + { + AppIndex.ResetGroupValue(AppName, PropertyKeys.AppIsActivated); + AppIndex.ResetGroupValue(AppName, PropertyKeys.AppIsDeactivated); + AppIndex.ResetGroupValue(AppName, PropertyKeys.AppIsRequired); + AppIndex.ResetGroupValue(AppName, PropertyKeys.AppIsDependency); + } + private void SetupAdornmentForRegistryIsolation() { if (RegistryKeys.Length > 0 && AdornedExecutables.Length == 0) @@ -1232,13 +1353,145 @@ private static string[] RemoveFromSet(string[] list, string value) #endregion + #region PropertyListing + /// - /// Returns a string, containing the apps typ and ID. + /// An array with all property keys, which are known by the Bench system. /// - /// A short string representation of the app. - public override string ToString() + public static readonly string[] KnownPropertyKeys = new[] + { + "ID", + PropertyKeys.AppTyp, + PropertyKeys.AppLabel, + PropertyKeys.AppWebsite, + PropertyKeys.AppDocs, + PropertyKeys.AppVersion, + PropertyKeys.AppLicense, + PropertyKeys.AppLicenseUrl, + PropertyKeys.AppIsActive, + PropertyKeys.AppIsRequired, + PropertyKeys.AppIsActivated, + PropertyKeys.AppIsDeactivated, + PropertyKeys.AppHasResource, + PropertyKeys.AppIsResourceCached, + PropertyKeys.AppInstalledVersion, + PropertyKeys.AppDependencies, + PropertyKeys.AppIsDependency, + PropertyKeys.AppForce, + PropertyKeys.AppSetupTestFile, + PropertyKeys.AppPackageName, + PropertyKeys.AppUrl, + PropertyKeys.AppDownloadCookies, + PropertyKeys.AppDownloadHeaders, + PropertyKeys.AppResourceName, + PropertyKeys.AppArchiveName, + PropertyKeys.AppArchiveTyp, + PropertyKeys.AppArchivePath, + PropertyKeys.AppDir, + PropertyKeys.AppExe, + PropertyKeys.AppRegister, + PropertyKeys.AppPath, + PropertyKeys.AppEnvironment, + PropertyKeys.AppAdornedExecutables, + PropertyKeys.AppRegistryKeys, + PropertyKeys.AppExeTest, + PropertyKeys.AppExeTestArguments, + PropertyKeys.AppLauncher, + PropertyKeys.AppLauncherExecutable, + PropertyKeys.AppLauncherArguments, + PropertyKeys.AppLauncherIcon, + }; + + /// + /// Checks whether a property name is known to the Bench system or not. + /// + /// The name of the app property. + /// true if the property is known; otherwise false. + public static bool IsKnownProperty(string propertyName) + { + foreach (var name in KnownPropertyKeys) + { + if (name.Equals(propertyName)) return true; + } + return false; + } + + /// + /// Returns all known properties. + /// + /// An array with key/value pairs. + public KeyValuePair[] KnownProperties { - return string.Format("App[{0}] {1}", Typ, ID); + get + { + var result = new List>(); + result.Add(new KeyValuePair("ID", this.ID)); + result.Add(new KeyValuePair(PropertyKeys.AppTyp, this.Typ)); + result.Add(new KeyValuePair(PropertyKeys.AppLabel, this.Label)); + result.Add(new KeyValuePair(PropertyKeys.AppWebsite, this.Website)); + result.Add(new KeyValuePair(PropertyKeys.AppDocs, this.Docs)); + result.Add(new KeyValuePair(PropertyKeys.AppVersion, this.Version)); + result.Add(new KeyValuePair(PropertyKeys.AppLicense, this.License)); + result.Add(new KeyValuePair(PropertyKeys.AppLicenseUrl, this.LicenseUrl?.AbsoluteUri)); + result.Add(new KeyValuePair(PropertyKeys.AppIsActive, this.IsActive)); + result.Add(new KeyValuePair(PropertyKeys.AppIsRequired, this.IsRequired)); + result.Add(new KeyValuePair(PropertyKeys.AppIsActivated, this.IsActivated)); + result.Add(new KeyValuePair(PropertyKeys.AppIsDeactivated, this.IsDeactivated)); + result.Add(new KeyValuePair(PropertyKeys.AppHasResource, this.HasResource)); + result.Add(new KeyValuePair(PropertyKeys.AppIsResourceCached, this.IsResourceCached)); + result.Add(new KeyValuePair(PropertyKeys.AppInstalledVersion, this.InstalledVersion)); + result.Add(new KeyValuePair(PropertyKeys.AppDependencies, this.Dependencies)); + result.Add(new KeyValuePair(PropertyKeys.AppIsDependency, this.IsDependency)); + result.Add(new KeyValuePair(PropertyKeys.AppForce, this.Force)); + result.Add(new KeyValuePair(PropertyKeys.AppSetupTestFile, this.SetupTestFile)); + result.Add(new KeyValuePair(PropertyKeys.AppPackageName, this.PackageName)); + result.Add(new KeyValuePair(PropertyKeys.AppUrl, this.Url)); + result.Add(new KeyValuePair(PropertyKeys.AppDownloadCookies, this.DownloadCookies)); + result.Add(new KeyValuePair(PropertyKeys.AppDownloadHeaders, this.DownloadHeaders)); + result.Add(new KeyValuePair(PropertyKeys.AppResourceName, this.ResourceFileName)); + result.Add(new KeyValuePair(PropertyKeys.AppArchiveName, this.ResourceArchiveName)); + result.Add(new KeyValuePair(PropertyKeys.AppArchivePath, this.ResourceArchivePath)); + result.Add(new KeyValuePair(PropertyKeys.AppDir, this.Dir)); + result.Add(new KeyValuePair(PropertyKeys.AppExe, this.Exe)); + result.Add(new KeyValuePair(PropertyKeys.AppRegister, this.Register)); + result.Add(new KeyValuePair(PropertyKeys.AppPath, this.Path)); + result.Add(new KeyValuePair(PropertyKeys.AppEnvironment, this.Environment)); + result.Add(new KeyValuePair(PropertyKeys.AppAdornedExecutables, this.AdornedExecutables)); + result.Add(new KeyValuePair(PropertyKeys.AppRegistryKeys, this.RegistryKeys)); + result.Add(new KeyValuePair(PropertyKeys.AppExeTest, this.ExeTest)); + result.Add(new KeyValuePair(PropertyKeys.AppExeTestArguments, this.ExeTestArguments)); + result.Add(new KeyValuePair(PropertyKeys.AppLauncher, this.Launcher)); + result.Add(new KeyValuePair(PropertyKeys.AppLauncherExecutable, this.LauncherExecutable)); + result.Add(new KeyValuePair(PropertyKeys.AppLauncherArguments, this.LauncherArguments)); + result.Add(new KeyValuePair(PropertyKeys.AppLauncherIcon, this.LauncherIcon)); + return result.ToArray(); + } + } + + /// + /// Returns all unknown properties. + /// + /// An array with key/value pairs. + public KeyValuePair[] UnknownProperties + { + get + { + var result = new List>(); + foreach (var name in AppIndex.PropertyNames(ID)) + { + if (IsKnownProperty(name)) continue; + result.Add(new KeyValuePair(name, Value(name))); + } + return result.ToArray(); + } } + + #endregion + + /// + /// Returns a string, containing the apps typ and ID. + /// + /// A short string representation of the app. + public override string ToString() => string.Format("App[{0}] {1}", Typ, ID); } } diff --git a/BenchManager/BenchLib/AppIndexDefaultValueSource.cs b/BenchManager/BenchLib/AppIndexDefaultValueSource.cs index 4f8fe0f8..345fd059 100644 --- a/BenchManager/BenchLib/AppIndexDefaultValueSource.cs +++ b/BenchManager/BenchLib/AppIndexDefaultValueSource.cs @@ -23,21 +23,46 @@ public string GetGroupCategory(string group) throw new NotSupportedException(); } - public object GetGroupValue(string appName, string key) + public object GetGroupMetadata(string group) + { + throw new NotSupportedException(); + } + + public string GetGroupDocumentation(string group) + { + throw new NotSupportedException(); + } + + public object GetGroupValue(string appId, string key) { string appTyp; switch (key) { case PropertyKeys.AppLabel: - return appName; + return AppFacade.NameFromId(appId); case PropertyKeys.AppTyp: return AppTyps.Default; + case PropertyKeys.AppLicense: + return "unknown"; + case PropertyKeys.AppLicenseUrl: + var knownUrls = AppIndex.GetGroupValue(null, PropertyKeys.KnownLicenses) as IDictionary; + if (knownUrls == null) return null; + var license = AppIndex.GetGroupValue(appId, PropertyKeys.AppLicense) as string; + if (string.IsNullOrEmpty(license)) return null; + string knownUrl; + return knownUrls.TryGetValue(license, out knownUrl) ? knownUrl : null; case PropertyKeys.AppArchiveTyp: return AppArchiveTyps.Auto; + case PropertyKeys.AppArchivePath: + return string.Equals( + AppIndex.GetGroupValue(appId, PropertyKeys.AppArchiveTyp) as string, + AppArchiveTyps.InnoSetup, + StringComparison.InvariantCultureIgnoreCase) + ? "{app}" : null; case PropertyKeys.AppPackageName: - return appName.ToLowerInvariant(); + return AppFacade.NameFromId(appId).ToLowerInvariant(); case PropertyKeys.AppDir: - appTyp = AppIndex.GetGroupValue(appName, PropertyKeys.AppTyp) as string; + appTyp = AppIndex.GetGroupValue(appId, PropertyKeys.AppTyp) as string; switch (appTyp) { case AppTyps.NodePackage: @@ -51,10 +76,10 @@ public object GetGroupValue(string appName, string key) case AppTyps.Meta: return null; default: - return appName.ToLowerInvariant(); + return AppFacade.PathSegmentFromId(appId); } case PropertyKeys.AppPath: - appTyp = AppIndex.GetGroupValue(appName, PropertyKeys.AppTyp) as string; + appTyp = AppIndex.GetGroupValue(appId, PropertyKeys.AppTyp) as string; switch (appTyp) { case AppTyps.NodePackage: @@ -72,41 +97,43 @@ public object GetGroupValue(string appName, string key) case AppTyps.NuGetPackage: return Path.Combine( Path.Combine( - AppIndex.GetGroupValue(appName, PropertyKeys.AppDir) as string, - AppIndex.GetGroupValue(appName, PropertyKeys.AppPackageName) as string), + AppIndex.GetGroupValue(appId, PropertyKeys.AppDir) as string, + AppIndex.GetGroupValue(appId, PropertyKeys.AppPackageName) as string), "tools"); default: - return AppIndex.GetGroupValue(appName, PropertyKeys.AppDir); + return AppIndex.GetGroupValue(appId, PropertyKeys.AppDir); } case PropertyKeys.AppExe: - appTyp = AppIndex.GetGroupValue(appName, PropertyKeys.AppTyp) as string; + appTyp = AppIndex.GetGroupValue(appId, PropertyKeys.AppTyp) as string; if (appTyp == AppTyps.Default) { return Path.Combine( - AppIndex.GetGroupValue(appName, PropertyKeys.AppDir) as string, - appName.ToLowerInvariant() + ".exe"); + AppIndex.GetGroupValue(appId, PropertyKeys.AppDir) as string, + AppFacade.NameFromId(appId).ToLowerInvariant() + ".exe"); } return null; + case PropertyKeys.AppExeTest: + return true; case PropertyKeys.AppRegister: return true; case PropertyKeys.AppLauncherExecutable: - return AppIndex.GetGroupValue(appName, PropertyKeys.AppExe); + return AppIndex.GetGroupValue(appId, PropertyKeys.AppExe); case PropertyKeys.AppLauncherArguments: return new[] { "%*" }; case PropertyKeys.AppLauncherIcon: - return AppIndex.GetGroupValue(appName, PropertyKeys.AppLauncherExecutable); + return AppIndex.GetGroupValue(appId, PropertyKeys.AppLauncherExecutable); case PropertyKeys.AppSetupTestFile: - appTyp = AppIndex.GetGroupValue(appName, PropertyKeys.AppTyp) as string; + appTyp = AppIndex.GetGroupValue(appId, PropertyKeys.AppTyp) as string; switch (appTyp) { case AppTyps.NuGetPackage: return Path.Combine( Path.Combine( - AppIndex.GetGroupValue(appName, PropertyKeys.AppDir) as string, - AppIndex.GetGroupValue(appName, PropertyKeys.AppPackageName) as string), - AppIndex.GetGroupValue(appName, PropertyKeys.AppPackageName) + ".nupkg"); + AppIndex.GetGroupValue(appId, PropertyKeys.AppDir) as string, + AppIndex.GetGroupValue(appId, PropertyKeys.AppPackageName) as string), + AppIndex.GetGroupValue(appId, PropertyKeys.AppPackageName) + ".nupkg"); default: - return AppIndex.GetGroupValue(appName, PropertyKeys.AppExe); + return AppIndex.GetGroupValue(appId, PropertyKeys.AppExe); } default: throw new NotSupportedException(); @@ -117,11 +144,15 @@ public bool CanGetGroupValue(string group, string name) { return name == PropertyKeys.AppTyp || name == PropertyKeys.AppLabel + || name == PropertyKeys.AppLicense + || name == PropertyKeys.AppLicenseUrl || name == PropertyKeys.AppArchiveTyp + || name == PropertyKeys.AppArchivePath || name == PropertyKeys.AppPackageName || name == PropertyKeys.AppDir || name == PropertyKeys.AppPath || name == PropertyKeys.AppExe + || name == PropertyKeys.AppExeTest || name == PropertyKeys.AppRegister || name == PropertyKeys.AppLauncherExecutable || name == PropertyKeys.AppLauncherArguments diff --git a/BenchManager/BenchLib/AppIndexFacade.cs b/BenchManager/BenchLib/AppIndexFacade.cs index 19b13023..a91b2bed 100644 --- a/BenchManager/BenchLib/AppIndexFacade.cs +++ b/BenchManager/BenchLib/AppIndexFacade.cs @@ -73,6 +73,22 @@ public bool Exists(string appName) return AppIndex.ContainsGroup(appName); } + /// + /// Gets an array with facades for all apps. + /// + public AppFacade[] All + { + get + { + var result = new List(); + foreach (var appName in AppIndex.Groups()) + { + result.Add(GetAppFacade(appName)); + } + return result.ToArray(); + } + } + /// /// Gets all apps of a given category. /// diff --git a/BenchManager/BenchLib/AppKeys.cs b/BenchManager/BenchLib/AppKeys.cs index 279f3533..b21c9a02 100644 --- a/BenchManager/BenchLib/AppKeys.cs +++ b/BenchManager/BenchLib/AppKeys.cs @@ -10,39 +10,39 @@ namespace Mastersign.Bench public static class AppKeys { /// The app ID of 7-Zip. - public const string SevenZip = "7z"; + public const string SevenZip = "Bench.7z"; /// The app ID of Less MSI. - public const string LessMSI = "LessMsi"; + public const string LessMSI = "Bench.LessMsi"; /// The app ID of Inno Setup Unpacker. - public const string InnoSetupUnpacker = "InnoUnp"; + public const string InnoSetupUnpacker = "Bench.InnoUnp"; /// The app ID of ConEmu. - public const string ConEmu = "ConEmu"; + public const string ConEmu = "Bench.ConEmu"; /// The app ID of Git. - public const string Git = "Git"; + public const string Git = "Bench.Git"; /// The app ID of Node.js. - public const string NodeJS = "Node"; + public const string NodeJS = "Bench.Node"; /// The app ID of the Node.js package manager. - public const string Npm = "Npm"; + public const string Npm = "Bench.Npm"; /// The app ID of Ruby. - public const string Ruby = "Ruby"; + public const string Ruby = "Bench.Ruby"; /// The app ID of RubyGems. - public const string RubyGems = "RubyGems"; + public const string RubyGems = "Bench.RubyGems"; /// The app ID of Python 2. - public const string Python2 = "Python2"; + public const string Python2 = "Bench.Python2"; /// The app ID of Python 3. - public const string Python3 = "Python3"; + public const string Python3 = "Bench.Python3"; /// The app ID of NuGet. - public const string NuGet = "NuGet"; + public const string NuGet = "Bench.NuGet"; } } diff --git a/BenchManager/BenchLib/AppLibrary.cs b/BenchManager/BenchLib/AppLibrary.cs new file mode 100644 index 00000000..48188e25 --- /dev/null +++ b/BenchManager/BenchLib/AppLibrary.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Mastersign.Bench +{ + /// + /// This class represents an app library. + /// It is initialized with an ID and an URL, and holds a reference to the . + /// + public class AppLibrary + { + private readonly BenchConfiguration config; + + /// + /// The ID of this app library. + /// The ID is unique in the Bench environment. + /// + public string ID { get; private set; } + + /// + /// The absolute URL to the app library. + /// The URL must have a HTTP, HTTPS or FILE scheme. + /// The referred resource must be a ZIP file, containing the apps.md + /// and optionally the custom scripts. + /// + public Uri Url { get; private set; } + + /// + /// Initializes a new instance of . + /// + /// The Bench configuration. + /// The uniqe ID of the app library. + /// The URL of the app library. + public AppLibrary(BenchConfiguration config, string id, Uri url) + { + if (config == null) throw new ArgumentNullException(nameof(config)); + if (id == null) throw new ArgumentNullException(nameof(id)); + if (url == null) throw new ArgumentNullException(nameof(url)); + if (!string.Equals("http", url.Scheme, StringComparison.InvariantCultureIgnoreCase) && + !string.Equals("https", url.Scheme, StringComparison.InvariantCultureIgnoreCase) && + !string.Equals("file", url.Scheme, StringComparison.InvariantCultureIgnoreCase)) + { + throw new ArgumentException("The URL scheme is not supported.", nameof(url)); + } + this.config = config; + ID = id; + Url = url; + } + + /// + /// Gets an absolute path to the base directory of the app library. + /// + public string BaseDir => Path.Combine( + config.GetStringValue(PropertyKeys.AppLibsDir), + ID.ToLowerInvariant()); + } +} diff --git a/BenchManager/BenchLib/BenchConfiguration.cs b/BenchManager/BenchLib/BenchConfiguration.cs index ce10cd14..91415b06 100644 --- a/BenchManager/BenchLib/BenchConfiguration.cs +++ b/BenchManager/BenchLib/BenchConfiguration.cs @@ -27,7 +27,7 @@ namespace Mastersign.Bench /// /// /// site - /// bench-site.md files (filename can be changed via default/custom config) + /// bench-site.md files (filename can be changed via custom config) /// /// /// @@ -35,8 +35,8 @@ namespace Mastersign.Bench /// /// /// - /// default - /// res\apps.md + /// external + /// lib\_applibs\*\apps.md /// /// /// custom @@ -46,9 +46,46 @@ namespace Mastersign.Bench /// public class BenchConfiguration : ResolvingPropertyCollection { - private const string AutoDir = @"auto"; - private const string ScriptsDir = @"auto\lib"; - private const string ConfigFile = @"res\config.md"; + private const string AUTO_DIR = @"auto"; + private const string BIN_DIR = AUTO_DIR + @"\bin"; + private const string SCRIPTS_DIR = AUTO_DIR + @"\lib"; + + /// + /// The relative path of the Bench configuration file. + /// + public const string CONFIG_FILE = @"res\config.md"; + + /// + /// The relative path of the PowerShell API library file. + /// + public const string MAIN_PS_LIB_FILE = @"auto\lib\bench.lib.ps1"; + + private static readonly string[] BENCH_CHECK_FILES = new[] + { + CONFIG_FILE, + MAIN_PS_LIB_FILE, + }; + + /// + /// Checks if the given path is a valid root path of a Bench environment. + /// + /// The absolute path to a possible Bench environment. + /// true if the given path is a path to a valid Bench environment. + public static bool IsValidBenchRoot(string possibleBenchRootPath) + { + if (possibleBenchRootPath == null) + throw new ArgumentNullException(nameof(possibleBenchRootPath)); + if (!Path.IsPathRooted(possibleBenchRootPath)) + throw new ArgumentException("The given path is not absolute."); + if (!Directory.Exists(possibleBenchRootPath)) + return false; + foreach (var path in BENCH_CHECK_FILES) + { + var absolutePath = Path.Combine(possibleBenchRootPath, path); + if (!File.Exists(absolutePath)) return false; + } + return true; + } /// /// The property group category, which contains app definitions of required apps. @@ -112,9 +149,10 @@ public BenchConfiguration(string benchRootDir, bool loadAppIndex, bool loadCusto Target = this, GroupBeginCue = new Regex("^[\\*\\+-]\\s+ID:\\s*(`?)(?\\S+?)\\1$"), GroupEndCue = new Regex("^\\s*$"), + CollectGroupDocs = true, }; - var configFile = Path.Combine(benchRootDir, ConfigFile); + var configFile = Path.Combine(benchRootDir, CONFIG_FILE); Debug.WriteLine("Looking for default configuration: " + configFile); if (!File.Exists(configFile)) { @@ -146,7 +184,7 @@ public BenchConfiguration(string benchRootDir, bool loadAppIndex, bool loadCusto { Debug.WriteLine("Looking for site config file(s): " + siteConfigFileName); var siteConfigFiles = FindSiteConfigFiles(benchRootDir, siteConfigFileName); - foreach(var file in siteConfigFiles) + foreach (var file in siteConfigFiles) { using (var siteConfigStream = File.OpenRead(file)) { @@ -158,72 +196,50 @@ public BenchConfiguration(string benchRootDir, bool loadAppIndex, bool loadCusto if (loadAppIndex) { - var appIndexFile = GetStringValue(PropertyKeys.AppIndexFile); - Debug.WriteLine("Looking for application index: " + appIndexFile); - if (!File.Exists(appIndexFile)) + foreach (var l in AppLibraries) { - throw new FileNotFoundException("The default app index for Bench was not found.", appIndexFile); - } - using (var appIndexStream = File.OpenRead(appIndexFile)) - { - Debug.WriteLine("Reading default application index ..."); - parser.Parse(appIndexStream); + var appIndexFile = Path.Combine(l.BaseDir, GetStringValue(PropertyKeys.AppLibIndexFileName)); + Debug.WriteLine("Looking for app library index: " + appIndexFile); + if (File.Exists(appIndexFile)) + { + parser.CurrentGroupMetadata = l; + using (var appIndexStream = File.OpenRead(appIndexFile)) + { + Debug.WriteLine("Reading index of app library '{0}' ...", l.ID); + parser.Parse(appIndexStream); + } + parser.CurrentGroupMetadata = null; + } + else + { + Debug.WriteLine("Index file of app library '{0}' not found.", l.ID); + } } if (loadCustomConfiguration) { - var customAppIndexFile = GetStringValue(PropertyKeys.CustomAppIndexFile); - Debug.WriteLine("Looking for custom application index: " + customAppIndexFile); + var customAppIndexFile = Path.Combine( + GetStringValue(PropertyKeys.CustomConfigDir), + GetStringValue(PropertyKeys.AppLibIndexFileName)); + Debug.WriteLine("Looking for custom app library index: " + customAppIndexFile); if (File.Exists(customAppIndexFile)) { using (var customAppIndexStream = File.OpenRead(customAppIndexFile)) { - Debug.WriteLine("Reading custom application index ..."); + Debug.WriteLine("Reading custom app library index ..."); parser.Parse(customAppIndexStream); } } } } - AddResolver(new AppIndexValueResolver(this)); + AddResolver(new DictionaryValueResolver(this)); GroupedDefaultValueSource = new AppIndexDefaultValueSource(this); appIndexFacade = new AppIndexFacade(this); AutomaticConfiguration(); - AutomaticActivation(loadCustomConfiguration); - RecordResponsibilities(); - } - - /// - /// Gets an array with absolute paths for all configuration files - /// used to compile this configuration. - /// - public string[] Sources - { - get - { - var paths = new List(); - paths.Add(Path.Combine(BenchRootDir, ConfigFile)); - if (WithCustomConfiguration) - { - paths.Add(GetStringValue(PropertyKeys.CustomConfigFile)); - } - if (WithSiteConfiguration) - { - paths.AddRange(FindSiteConfigFiles(BenchRootDir, siteConfigFileName)); - } - if (WithAppIndex) - { - paths.Add(GetStringValue(PropertyKeys.AppIndexFile)); - if (WithCustomConfiguration) - { - paths.Add(GetStringValue(PropertyKeys.CustomAppIndexFile)); - paths.Add(GetStringValue(PropertyKeys.AppActivationFile)); - paths.Add(GetStringValue(PropertyKeys.AppDeactivationFile)); - } - } - return paths.ToArray(); - } + RecordAppResponsibilities(); + LoadAppActivation(); } private static string[] FindSiteConfigFiles(string benchRootDir, string fileName) @@ -250,6 +266,121 @@ public string[] FindSiteConfigFiles() return FindSiteConfigFiles(BenchRootDir, siteConfigFileName); } + /// + /// Lists the configuration files of the Bench environment. + /// + /// The kind of files to list. + /// If true, only files which are actually loaded + /// by this instance are listed. + /// If true, only existing files are listed; + /// otherwise optional and non existing files are listed to. + /// A list with configuration file descriptors. + public ConfigurationFile[] GetConfigurationFiles( + ConfigurationFileType type = ConfigurationFileType.All, + bool actuallyLoaded = false, bool mustExist = true) + { + if (actuallyLoaded) mustExist = true; + var files = new List(); + if ((type & ConfigurationFileType.BenchConfig) == ConfigurationFileType.BenchConfig) + { + files.Add(new ConfigurationFile(ConfigurationFileType.BenchConfig, 0, + Path.Combine(BenchRootDir, CONFIG_FILE))); + } + if (!actuallyLoaded || WithCustomConfiguration) + { + if ((type & ConfigurationFileType.UserConfig) == ConfigurationFileType.UserConfig) + { + + var userConfigFile = GetStringValue(PropertyKeys.CustomConfigFile); + if (!mustExist || File.Exists(userConfigFile)) + { + files.Add(new ConfigurationFile(ConfigurationFileType.UserConfig, 1, + userConfigFile)); + } + } + } + if (!actuallyLoaded || WithSiteConfiguration) + { + if ((type & ConfigurationFileType.SiteConfig) == ConfigurationFileType.SiteConfig) + { + var siteConfigFiles = FindSiteConfigFiles(); + for (int i = 0; i < siteConfigFiles.Length; i++) + { + files.Add(new ConfigurationFile(ConfigurationFileType.SiteConfig, 10 + i, + siteConfigFiles[i])); + } + } + } + if (!actuallyLoaded || WithAppIndex) + { + if ((type & ConfigurationFileType.BenchAppLib) == ConfigurationFileType.BenchAppLib) + { + var appLibraries = AppLibraries; + for (var i = 0; i < appLibraries.Length; i++) + { + files.Add(new ConfigurationFile(ConfigurationFileType.BenchAppLib, 100 + i, + Path.Combine( + appLibraries[i].BaseDir, + GetStringValue(PropertyKeys.AppLibIndexFileName)))); + } + } + } + if (!actuallyLoaded || (WithAppIndex && WithCustomConfiguration)) + { + if ((type & ConfigurationFileType.UserAppLib) == ConfigurationFileType.UserAppLib) + { + var userAppLib = Path.Combine( + GetStringValue(PropertyKeys.CustomConfigDir), + GetStringValue(PropertyKeys.AppLibIndexFileName)); + if (!mustExist || File.Exists(userAppLib)) + { + files.Add(new ConfigurationFile(ConfigurationFileType.UserAppLib, 999, + userAppLib)); + } + } + } + if (!actuallyLoaded || (WithAppIndex && WithCustomConfiguration)) + { + if ((type & ConfigurationFileType.Activation) == ConfigurationFileType.Activation) + { + var activationFile = GetStringValue(PropertyKeys.AppActivationFile); + if (!mustExist || File.Exists(activationFile)) + { + files.Add(new ConfigurationFile(ConfigurationFileType.Activation, 1000, + activationFile)); + } + } + if ((type & ConfigurationFileType.Deactivation) == ConfigurationFileType.Deactivation) + { + var deactivationFile = GetStringValue(PropertyKeys.AppDeactivationFile); + if (!mustExist || File.Exists(deactivationFile)) + { + files.Add(new ConfigurationFile(ConfigurationFileType.Deactivation, 1001, + deactivationFile)); + } + } + } + return files.ToArray(); + } + + /// + /// Gets an array with absolute paths for all configuration files + /// used to compile this configuration. + /// + public string[] Sources + { + get + { + var files = GetConfigurationFiles(actuallyLoaded: true, mustExist: true); + var result = new string[files.Length]; + for (int i = 0; i < files.Length; i++) + { + result[i] = files[i].Path; + } + return result; + } + } + private string GetVolatileEnvironmentVariable(string name) { using (var key = Registry.CurrentUser.OpenSubKey("Volatile Environment", false)) @@ -262,11 +393,12 @@ private void AutomaticConfiguration() { SetValue(PropertyKeys.BenchRoot, BenchRootDir); SetValue(PropertyKeys.BenchDrive, Path.GetPathRoot(BenchRootDir)); - SetValue(PropertyKeys.BenchAuto, Path.Combine(BenchRootDir, AutoDir)); - SetValue(PropertyKeys.BenchScripts, Path.Combine(BenchRootDir, ScriptsDir)); + SetValue(PropertyKeys.BenchAuto, Path.Combine(BenchRootDir, AUTO_DIR)); + SetValue(PropertyKeys.BenchBin, Path.Combine(BenchRootDir, BIN_DIR)); + SetValue(PropertyKeys.BenchScripts, Path.Combine(BenchRootDir, SCRIPTS_DIR)); var versionFile = GetValue(PropertyKeys.VersionFile) as string; - var version = File.Exists(versionFile) ? File.ReadAllText(versionFile, Encoding.UTF8) : "0.0.0"; + var version = File.Exists(versionFile) ? File.ReadAllText(versionFile, Encoding.UTF8).Trim() : "0.0.0"; SetValue(PropertyKeys.Version, version); if (!GetBooleanValue(PropertyKeys.OverrideHome)) @@ -286,7 +418,15 @@ private void AutomaticConfiguration() } } - private void AutomaticActivation(bool withCustomConfiguration) + private void RecordAppResponsibilities() + { + foreach (var app in new List(Apps)) + { + app.TrackResponsibilities(); + } + } + + private void LoadAppActivation() { // activate required apps @@ -296,7 +436,7 @@ private void AutomaticActivation(bool withCustomConfiguration) app.ActivateAsRequired(); } - if (withCustomConfiguration) + if (WithCustomConfiguration) { // activate manually activated apps @@ -324,14 +464,24 @@ private void AutomaticActivation(bool withCustomConfiguration) } } - private void RecordResponsibilities() + private void ResetAppActivation() { - foreach(var app in new List(Apps)) + foreach (var app in Apps) { - app.TrackResponsibilities(); + app.ResetActivation(); } } + /// + /// Reads the activation and deactivation files, + /// and reloads the app activation and dependency structure. + /// + public void ReloadAppActivation() + { + ResetAppActivation(); + LoadAppActivation(); + } + private bool IsPathProperty(string app, string property) { if (string.IsNullOrEmpty(app)) @@ -364,11 +514,98 @@ private string GetBaseForPathProperty(string app, string property) } } + /// + /// Transfers a couple of temporary properties, needed during the initialization + /// of a Bench environment, to a new instance of the configuration. + /// + /// The new configuration instance. + public void InjectBenchInitializationProperties(BenchConfiguration targetCfg) + { + foreach (var key in new[] + { + PropertyKeys.CustomConfigRepository, + PropertyKeys.WizzardStartAutoSetup, + PropertyKeys.WizzardSelectedApps, + }) + { + targetCfg.SetValue(key, this.GetValue(key)); + } + + if (targetCfg.GetValue(PropertyKeys.CustomConfigRepository) != null) + { + targetCfg.SetGroupCategory(AppKeys.Git, BenchConfiguration.DefaultAppCategory); + targetCfg.Apps[AppKeys.Git].ActivateAsRequired(); + } + } + /// /// The merged definition of the Bench apps as a . /// public AppIndexFacade Apps { get { return appIndexFacade; } } + /// + /// The app libraries defined in the configuration property AppLibs. + /// + public AppLibrary[] AppLibraries + { + get + { + var result = new List(); + foreach (var item in GetStringListValue(PropertyKeys.AppLibs)) + { + var kvp = DictionaryValueResolver.ParseKeyValuePair(item); + if (string.IsNullOrEmpty(kvp.Key)) continue; + var id = kvp.Key; + Uri url; + if (!Uri.TryCreate(ExpandAppLibraryUrl(kvp.Value), UriKind.Absolute, out url) || + (!string.Equals("http", url.Scheme, StringComparison.InvariantCultureIgnoreCase) && + !string.Equals("https", url.Scheme, StringComparison.InvariantCultureIgnoreCase) && + !string.Equals("file", url.Scheme, StringComparison.InvariantCultureIgnoreCase))) + { + continue; + } + for (int i = 0; i < result.Count; i++) + { + if (string.Equals(result[i].ID, id)) + { + result.RemoveAt(i); + break; + } + } + result.Add(new AppLibrary(this, id, url)); + } + return result.ToArray(); + } + } + + /// + /// Gets an app library by its ID. + /// + /// The ID of the app library. + /// An object or null if the ID was not found. + public AppLibrary GetAppLibrary(string id) + { + foreach (var l in AppLibraries) + { + if (l.ID == id) return l; + } + return null; + } + + private static readonly Regex GitHubUrlPattern = new Regex(@"^github:(?[\dA-Za-z-_]+)/(?[\dA-Za-z-_]+)$"); + private static readonly string GitHubUrlTemplate = "https://github.com/{0}/{1}/archive/master.zip"; + + private static string ExpandAppLibraryUrl(string url) + { + var m = GitHubUrlPattern.Match(url); + if (m.Success) + { + return string.Format(GitHubUrlTemplate, + m.Groups["ns"].Value, m.Groups["name"].Value); + } + return url; + } + /// /// Reloads the set of configuration files, specified during construction. /// Call this method to create an updated instance of diff --git a/BenchManager/BenchLib/BenchEnvironment.cs b/BenchManager/BenchLib/BenchEnvironment.cs index 7e4fa5f4..944bf0a6 100644 --- a/BenchManager/BenchLib/BenchEnvironment.cs +++ b/BenchManager/BenchLib/BenchEnvironment.cs @@ -85,7 +85,7 @@ public void Load(DictionaryEntryHandler set) } var paths = new List(); - paths.Add(Config.GetStringValue(PropertyKeys.BenchAuto)); + paths.Add(Config.GetStringValue(PropertyKeys.BenchBin)); paths.AddRange(Config.GetStringListValue(PropertyKeys.CustomPath)); paths.AddRange(Config.Apps.EnvironmentPath); if (Config.GetBooleanValue(PropertyKeys.IgnoreSystemPath)) @@ -157,10 +157,11 @@ public void WriteEnvironmentFile() if (!string.IsNullOrEmpty(userEmail)) w.WriteLine("SET USEREMAIL={0}", userEmail); } - w.WriteLine("SET BENCH_AUTO=%~dp0auto"); - w.WriteLine("CALL :SET_BENCH_HOME \"%BENCH_AUTO%\\..\""); + w.WriteLine("SET BENCH_DRIVE=%~d0"); + w.WriteLine("SET BENCH_HOME=%~dp0"); + w.WriteLine("CALL :CLEAN_BENCH_HOME"); + w.WriteLine("SET BENCH_BIN=" + TryUseVar(Config.GetStringValue(PropertyKeys.BenchBin))); w.WriteLine("SET /P BENCH_VERSION=<\"%BENCH_HOME%\\res\\version.txt\""); - w.WriteLine("CALL :SET_BENCH_DRIVE \"%BENCH_AUTO%\""); w.WriteLine("SET BENCH_APPS={0}", TryUseVar(Config.GetStringValue(PropertyKeys.LibDir), true)); @@ -198,7 +199,7 @@ public void WriteEnvironmentFile() if (Config.GetBooleanValue(PropertyKeys.IgnoreSystemPath)) { w.WriteLine("SET PATH={0}", PathList( - "%BENCH_AUTO%", + "%BENCH_BIN%", "%BENCH_PATH%", "%SystemRoot%", @"%SystemRoot%\System32", @@ -207,7 +208,7 @@ public void WriteEnvironmentFile() else if (!Config.GetBooleanValue(PropertyKeys.RegisterInUserProfile)) { w.WriteLine("SET PATH={0}", PathList( - "%BENCH_AUTO%", + "%BENCH_BIN%", "%BENCH_PATH%", "%PATH%")); } @@ -229,12 +230,10 @@ public void WriteEnvironmentFile() w.WriteLine("GOTO:EOF"); w.WriteLine(); - w.WriteLine(":SET_BENCH_HOME"); - w.WriteLine("SET BENCH_HOME=%~dpfn1"); - w.WriteLine("GOTO:EOF"); - w.WriteLine(); - w.WriteLine(":SET_BENCH_DRIVE"); - w.WriteLine("SET BENCH_DRIVE=%~d1"); + w.WriteLine(":CLEAN_BENCH_HOME"); + w.WriteLine("SET BENCH_HOME=%BENCH_HOME%#+#"); + w.WriteLine("SET BENCH_HOME=%BENCH_HOME:\\#+#=#+#%"); + w.WriteLine("SET BENCH_HOME=%BENCH_HOME:#+#=%"); w.WriteLine("GOTO:EOF"); if (Config.GetBooleanValue(PropertyKeys.OverrideHome)) { @@ -298,6 +297,7 @@ public void RegisterInUserProfile() SetEnvironmentVar("BENCH_APPS", libDir); var benchPath = new List(); + benchPath.Add(Config.GetStringValue(PropertyKeys.BenchBin)); benchPath.AddRange(Config.GetStringListValue(PropertyKeys.CustomPath)); benchPath.AddRange(Config.Apps.EnvironmentPath); SetEnvironmentVar("BENCH_PATH", PathList(benchPath.ToArray())); @@ -339,6 +339,13 @@ public void RegisterInUserProfile() /// public void UnregisterFromUserProfile() { + var currentEnvRootPath = Environment.GetEnvironmentVariable("BENCH_HOME"); + if (!string.Equals(currentEnvRootPath, Config.BenchRootDir, + StringComparison.InvariantCultureIgnoreCase)) + { + return; + } + DeleteEnvironmentVar("BENCH_VERSION"); DeleteEnvironmentVar("BENCH_HOME"); DeleteEnvironmentVar("BENCH_APPS"); diff --git a/BenchManager/BenchLib/BenchLib.csproj b/BenchManager/BenchLib/BenchLib.csproj index e97e72c5..08217f94 100644 --- a/BenchManager/BenchLib/BenchLib.csproj +++ b/BenchManager/BenchLib/BenchLib.csproj @@ -43,9 +43,14 @@ ..\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll True + + ..\packages\Mastersign.Sequence.1.1.0\lib\net20\Mastersign.Sequence.dll + True + + @@ -55,7 +60,10 @@ - + + + + @@ -67,7 +75,7 @@ - + @@ -84,7 +92,16 @@ + + + + + + + + + @@ -92,6 +109,12 @@ + + UserControl + + + AppSelectionStepControl.cs + UserControl @@ -169,6 +192,9 @@ + + AppSelectionStepControl.cs + AdvancedStepControl.cs diff --git a/BenchManager/BenchLib/BenchTasks.cs b/BenchManager/BenchLib/BenchTasks.cs index 9636a4c0..bf63a7ee 100644 --- a/BenchManager/BenchLib/BenchTasks.cs +++ b/BenchManager/BenchLib/BenchTasks.cs @@ -63,29 +63,14 @@ public static BenchConfiguration InitializeSiteConfiguration(string benchRootDir } } - var resultCfg = new BenchConfiguration(benchRootDir, true, false, true); - - // transfer intermediate results from wizzard to following initialization steps - foreach (var key in new[] - { - PropertyKeys.CustomConfigRepository, - PropertyKeys.WizzardStartAutoSetup - }) - { - resultCfg.SetValue(key, cfg.GetValue(key)); - } - - if (resultCfg.GetValue(PropertyKeys.CustomConfigRepository) != null) - { - resultCfg.SetGroupCategory(AppKeys.Git, BenchConfiguration.DefaultAppCategory); - resultCfg.Apps[AppKeys.Git].ActivateAsRequired(); - } + var resultCfg = new BenchConfiguration(benchRootDir, false, false, true); + cfg.InjectBenchInitializationProperties(resultCfg); return resultCfg; } /// - /// This method is the second step for initializing or upgrading a Bench installation. + /// This method is the last fourth for initializing or upgrading a Bench installation. /// /// /// @@ -148,6 +133,7 @@ public static BenchConfiguration InitializeCustomConfiguration(IBenchManager man } var cfg = new BenchConfiguration(man.Config.BenchRootDir, false, true, true); + man.Config.InjectBenchInitializationProperties(cfg); var homeDir = cfg.GetStringValue(PropertyKeys.HomeDir); FileSystem.AsureDir(homeDir); @@ -160,7 +146,9 @@ public static BenchConfiguration InitializeCustomConfiguration(IBenchManager man FileSystem.AsureDir(cfg.GetStringValue(PropertyKeys.LibDir)); FileSystem.AsureDir(cfg.GetStringValue(PropertyKeys.ProjectRootDir)); - var customAppIndexFile = cfg.GetStringValue(PropertyKeys.CustomAppIndexFile); + var customAppIndexFile = Path.Combine( + cfg.GetStringValue(PropertyKeys.CustomConfigDir), + cfg.GetStringValue(PropertyKeys.AppLibIndexFileName)); if (!File.Exists(customAppIndexFile)) { var customAppIndexTemplateFile = cfg.GetStringValue(PropertyKeys.CustomAppIndexTemplateFile); @@ -185,7 +173,16 @@ public static BenchConfiguration InitializeCustomConfiguration(IBenchManager man File.Copy(conEmuConfigTemplateFile, conEmuConfigFile, false); } - return new BenchConfiguration(man.Config.BenchRootDir, true, true, true); + var selectedApps = cfg.GetStringListValue(PropertyKeys.WizzardSelectedApps); + var activationFileEditor = new ActivationFile(activationFile); + foreach (var appId in selectedApps) + { + activationFileEditor.SignIn(appId); + } + + var resultCfg = new BenchConfiguration(man.Config.BenchRootDir, true, true, true); + cfg.InjectBenchInitializationProperties(resultCfg); + return resultCfg; } /// @@ -239,6 +236,128 @@ public static Downloader InitializeDownloader(BenchConfiguration config) }), new Regex(@"\]*class=""direct-link""[^\>]*\>(.*?)\")); + private static WebClient InitializeWebClient(BenchConfiguration config) + { + var useProxy = config.GetBooleanValue(PropertyKeys.UseProxy); + var httpProxy = config.GetStringValue(PropertyKeys.HttpProxy); + var httpsProxy = config.GetStringValue(PropertyKeys.HttpsProxy); + var proxyBypass = config.GetStringListValue(PropertyKeys.ProxyBypass); + var webClient = new WebClient(); + webClient.Proxy = useProxy + ? new SchemeDispatchProxy(new Dictionary + { + {"http", new WebProxy(httpProxy, true, proxyBypass)}, + {"https", new WebProxy(httpsProxy, true, proxyBypass)} + }) + : null; + return webClient; + } + + /// + /// Downloads a string via HTTP(S) asynchronously. + /// + /// The Bench configuration. + /// The URL of the HTTP resource. + /// The handler to process the download result. + public static void DownloadStringAsync(BenchConfiguration config, Uri url, StringDownloadResultHandler resultHandler) + { + var wc = InitializeWebClient(config); + wc.DownloadStringCompleted += (sender, eventArgs) => + { + resultHandler(eventArgs.Error == null && !eventArgs.Cancelled, eventArgs.Result); + wc.Dispose(); + }; + wc.DownloadStringAsync(url); + } + + + /// + /// Downloads a string via HTTP(S). + /// + /// The Bench configuration. + /// The URL of the HTTP resource. + /// The resources content or null if the download failed. + public static string DownloadString(BenchConfiguration config, Uri url) + { + using (var wc = InitializeWebClient(config)) + { + try + { + return wc.DownloadString(url); + } + catch (Exception) + { + return null; + } + } + } + + /// + /// Downloads a file via HTTP(S) asynchronously. + /// + /// The Bench configuration. + /// The URL of the HTTP resource. + /// A path to the target file. + /// The handler to process the download result. + public static void DownloadFileAsync(BenchConfiguration config, Uri url, string targetFile, + FileDownloadResultHandler resultHandler) + { + var wc = InitializeWebClient(config); + wc.DownloadFileCompleted += (sender, eventArgs) => + { + resultHandler(eventArgs.Error == null && !eventArgs.Cancelled); + wc.Dispose(); + }; + wc.DownloadStringAsync(url); + } + + /// + /// Downloads a file via HTTP(S). + /// + /// The Bench configuration. + /// The URL of the HTTP resource. + /// A path to the target file. + /// true if the download was successful; otherwise false. + public static bool DownloadFile(BenchConfiguration config, Uri url, string targetFile) + { + using (var wc = InitializeWebClient(config)) + { + try + { + wc.DownloadFile(url, targetFile); + return true; + } + catch (Exception) + { + return false; + } + } + } + + /// + /// Downloads the version number of the latest Bench release asynchronously. + /// + /// The Bench configuration. + /// The handler for the download result. + public static void GetLatestVersionAsync(BenchConfiguration config, StringDownloadResultHandler resultHandler) + { + var uri = new Uri(config.GetStringValue(PropertyKeys.VersionUrl)); + DownloadStringAsync(config, uri, + (success, content) => resultHandler(success, content != null ? content.Trim() : null)); + } + + /// + /// Downloads the version number of the latest Bench release. + /// + /// The Bench configuration. + /// The version number or null if the download failed. + public static string GetLatestVersion(BenchConfiguration config) + { + var uri = new Uri(config.GetStringValue(PropertyKeys.VersionUrl)); + var result = DownloadString(config, uri); + return result != null ? result.Trim() : null; + } + private static string RunCustomScript(BenchConfiguration config, IProcessExecutionHost execHost, string appId, string path, params string[] args) { @@ -385,6 +504,22 @@ private static string PipExe(BenchConfiguration config, PythonVersion pyVer) #region Higher Order Actions + /// + /// Runs the Bench task of downloading and extracting the app libraries. + /// + /// The Bench manager. + /// The notification handler. + /// A cancelation token. + /// The result of running the task, in shape of an object. + public static ActionResult DoLoadAppLibraries(IBenchManager man, + Action notify, Cancelation cancelation) + { + return RunTasks(man, + new AppFacade[0], + notify, cancelation, + LoadAppLibraries); + } + /// /// Runs the Bench task of setting up only the apps, required of the Bench system itself. /// @@ -440,7 +575,6 @@ public static ActionResult DoAutoSetup(IBenchManager man, man.Config.Apps.ActiveApps }, notify, cancelation, - UninstallApps, DownloadAppResources, InstallApps, @@ -753,6 +887,22 @@ public static ActionResult DoUpdateEnvironment(IBenchManager man, UpdateEnvironment); } + /// + /// Runs the Bench task of downloading the latest Bench binary and bootstrap file. + /// + /// The Bench manager. + /// The notification handler. + /// A cancelation token. + /// The result of running the task, in shape of an object. + public static ActionResult DoDownloadBenchUpdate(IBenchManager man, + Action notify, Cancelation cancelation) + { + return RunTasks(man, + new AppFacade[0], + notify, cancelation, + DownloadBenchUpdate); + } + #endregion #region Task Composition @@ -768,9 +918,15 @@ private static ActionResult RunTasks(IBenchManager man, TaskInfoLogger logger = null; if (logLevel != LogLevels.None) { - logger = new TaskInfoLogger( - man.Config.GetStringValue(PropertyKeys.LogDir), - logLevel == LogLevels.Error); + var file = man.Config.GetStringValue(PropertyKeys.LogFile, + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + "_setup.txt"); + if (!Path.IsPathRooted(file)) + { + var logDir = man.Config.GetStringValue(PropertyKeys.LogDir); + FileSystem.AsureDir(logDir); + file = Path.Combine(logDir, file); + } + logger = new TaskInfoLogger(file, logLevel == LogLevels.Error); } var infos = new List(); @@ -842,6 +998,263 @@ private static T FindLastNotNull(T[] a, int p) #endregion + #region Load App Libraries + + private static void UnwrapSubDir(string targetDir) + { + var content = Directory.GetFileSystemEntries(targetDir); + // Check if the only content of the source folder is one directory + if (content != null && content.Length == 1 && Directory.Exists(content[0])) + { + // Then move all further content one directory up + // (helps with ZIP files which contain the whole app library in a sub-dir) + FileSystem.MoveContent(content[0], targetDir); + Directory.Delete(content[0]); + } + } + + private static bool IsAppLibrary(BenchConfiguration config, string directory) + { + var appIndexFileName = config.GetStringValue(PropertyKeys.AppLibIndexFileName); + return File.Exists(Path.Combine(directory, appIndexFileName)); + } + + private static void ExtractAppLibrary(BenchConfiguration config, string source, string targetDir) + { + if (!ZipFile.IsZipFile(source)) throw new InvalidOperationException( + "The app library does not appear to be a ZIP file: " + source); + FileSystem.EmptyDir(targetDir); + using (var zf = new ZipFile(source)) + { + zf.ExtractExistingFile = ExtractExistingFileAction.OverwriteSilently; + zf.FlattenFoldersOnExtract = false; + zf.ExtractAll(targetDir); + } + if (!IsAppLibrary(config, targetDir)) UnwrapSubDir(targetDir); + } + + private static void CopyAppLibrary(BenchConfiguration config, string source, string targetDir) + { + if (File.Exists(source)) + { + ExtractAppLibrary(config, source, targetDir); + } + else if (Directory.Exists(source)) + { + FileSystem.CopyDir(source, targetDir, true); + if (!IsAppLibrary(config, targetDir)) UnwrapSubDir(targetDir); + } + else + { + throw new ArgumentException("Source of app library not found: " + source); + } + } + + private static string AppLibDirectory(BenchConfiguration config, string appLibId) + { + return Path.Combine(config.GetStringValue(PropertyKeys.AppLibsDir), appLibId); + } + + /// + /// Deletes the loaded and cached app libraries + /// in preparation for re-loading them for an update or a repair. + /// + /// The Bench configuration + public static void DeleteAppLibraries(BenchConfiguration cfg) + { + var appLibDir = cfg.GetStringValue(PropertyKeys.AppLibsDir); + FileSystem.EmptyDir(appLibDir); + var cacheDir = cfg.GetStringValue(PropertyKeys.AppLibsDownloadDir); + FileSystem.EmptyDir(cacheDir); + } + + private static void LoadAppLibraries(IBenchManager man, + ICollection _, + Action notify, Cancelation cancelation) + { + var appLibsDir = man.Config.GetStringValue(PropertyKeys.AppLibsDir); + FileSystem.AsureDir(appLibsDir); + var cacheDir = man.Config.GetStringValue(PropertyKeys.AppLibsDownloadDir); + FileSystem.AsureDir(cacheDir); + + var appLibs = man.Config.AppLibraries; + + // Clean unconfigured app library directories + foreach (var d in Directory.GetDirectories(appLibsDir)) + { + var n = Path.GetFileName(d); + var found = false; + foreach (var l in appLibs) + { + if (string.Equals(l.ID, n, StringComparison.InvariantCultureIgnoreCase)) + { + found = true; + } + } + if (!found) + { + FileSystem.PurgeDir(d); + notify(new TaskInfo(string.Format("Deleted unknown app library '{0}'.", n))); + } + } + + var finished = 0; + var errorCnt = 0; + var taskCnt = appLibs.Length; + var tasks = new List(); + var endEvent = new ManualResetEvent(false); + + EventHandler downloadStartedHandler = (o, e) => + { + notify(new TaskProgress( + string.Format("Started download for app library '{0}' ...", e.Task.Id), + progress: (float)finished / taskCnt, + detailedMessage: e.Task.Url.ToString())); + }; + + EventHandler downloadEndedHandler = (o, e) => + { + finished++; + if (!e.Task.Success) + { + errorCnt++; + notify(new TaskError(e.Task.ErrorMessage)); + } + else + { + try + { + ExtractAppLibrary(man.Config, e.Task.TargetFile, AppLibDirectory(man.Config, e.Task.Id)); + notify(new TaskProgress( + string.Format("Finished download for app library '{0}'.", e.Task.Id), + progress: (float)finished / taskCnt)); + } + catch (Exception exc) + { + errorCnt++; + notify(new TaskError( + string.Format("Extracting the archive of app library '{0}' failed.", e.Task.Id), + exception: exc)); + } + } + }; + + EventHandler workFinishedHandler = null; + workFinishedHandler = (o, e) => + { + man.Downloader.DownloadEnded -= downloadEndedHandler; + man.Downloader.WorkFinished -= workFinishedHandler; + endEvent.Set(); + }; + man.Downloader.DownloadStarted += downloadStartedHandler; + man.Downloader.DownloadEnded += downloadEndedHandler; + man.Downloader.WorkFinished += workFinishedHandler; + + cancelation.Canceled += (s, e) => man.Downloader.CancelAll(); + + notify(new TaskProgress("Loading app libraries...", + progress: 0f)); + + foreach (var l in appLibs) + { + var appLibDir = AppLibDirectory(man.Config, l.ID); + + // Skip app library if is already loaded + if (File.Exists(Path.Combine(appLibDir, + man.Config.GetStringValue(PropertyKeys.AppLibIndexFileName)))) + { + notify(new TaskProgress( + string.Format("App library '{0}' already loaded.", l.ID), + progress: (float)finished / taskCnt)); + continue; + } + else + { + // Clean the app library directory, if the directory is not valid + notify(new TaskInfo( + string.Format("Cleaning invalid app library '{0}'.", l.ID))); + FileSystem.PurgeDir(appLibDir); + } + + var appLibArchive = l.ID + ".zip"; + + if ("file".Equals(l.Url.Scheme.ToLowerInvariant())) + { + var sourcePath = l.Url.LocalPath; + notify(new TaskInfo( + string.Format("Loading app libary '{0}' from file system...", l.ID))); + finished++; + try + { + CopyAppLibrary(man.Config, sourcePath, appLibDir); + notify(new TaskProgress( + string.Format("Successfully loaded app library '{0}'.", l.ID), + progress: (float)finished / taskCnt)); + } + catch (Exception e) + { + errorCnt++; + notify(new TaskError( + string.Format("Loading app library '{0}' failed.", l.ID), + exception: e)); + } + } + else + { + var appLibArchivePath = Path.Combine(cacheDir, appLibArchive); + // Check if app library is cached + if (File.Exists(appLibArchivePath)) + { + finished++; + // Extract if it is cached + try + { + ExtractAppLibrary(man.Config, appLibArchivePath, appLibDir); + notify(new TaskProgress( + string.Format("Extracted app library '{0}' from cache.", l.ID), + progress: (float)finished / taskCnt)); + } + catch (Exception exc) + { + errorCnt++; + notify(new TaskError( + string.Format("Extracting the archive of app library '{0}' failed.", l.ID), + exception: exc)); + } + } + else + { + // Queue download task if not + var task = new DownloadTask(l.ID, l.Url, appLibArchivePath); + tasks.Add(task); + man.Downloader.Enqueue(task); + } + } + } + + if (tasks.Count == 0) + { + man.Downloader.DownloadEnded -= downloadEndedHandler; + man.Downloader.WorkFinished -= workFinishedHandler; + notify(new TaskProgress("Nothing to download.", + progress: 1f)); + } + else + { + notify(new TaskProgress(string.Format("Queued {0} downloads.", tasks.Count), + progress: 0f)); + endEvent.WaitOne(); + } + + if (!cancelation.IsCanceled) + { + notify(new TaskProgress("Finished loading app libraries.", + progress: 1f)); + } + } + + #endregion + #region Download App Resources private static void DownloadAppResources(IBenchManager man, ICollection apps, @@ -973,7 +1386,7 @@ private static void DeleteAppResources(IBenchManager man, } catch (Exception e) { - notify(new TaskError(e.Message, app.ID, null, e)); + notify(new TaskError(e.Message, appId: app.ID, exception: e)); continue; } notify(new TaskProgress( @@ -1028,7 +1441,7 @@ private static void CleanUpAppResources(IBenchManager man, } catch (Exception e) { - notify(new TaskError(e.Message, null, null, e)); + notify(new TaskError(e.Message, exception: e)); continue; } notify(new TaskProgress( @@ -1090,14 +1503,14 @@ private static void CreateBenchDashboardLauncher(BenchConfiguration config) File.Copy(benchDashboardShortcut, Path.Combine(config.BenchRootDir, Path.GetFileName(benchDashboardShortcut)), true); } - private static void CreateActionLauncher(BenchConfiguration config, string label, string action, string icon, + private static void CreateActionLauncher(BenchConfiguration config, string label, string binFile, string icon = null, string targetDir = null) { var launcherDir = targetDir ?? config.GetStringValue(PropertyKeys.LauncherDir); - var actionDir = config.GetStringValue(PropertyKeys.ActionDir); + var binDir = config.GetStringValue(PropertyKeys.BenchBin); var shortcut = Path.Combine(launcherDir, label + ".lnk"); - var target = Path.Combine(actionDir, action + ".cmd"); - FileSystem.CreateShortcut(shortcut, target, null, config.BenchRootDir, icon); + var target = Path.Combine(binDir, binFile); + FileSystem.CreateShortcut(shortcut, target, null, config.BenchRootDir, icon ?? target); } private static void CreateActionLaunchers(BenchConfiguration config) @@ -1106,21 +1519,20 @@ private static void CreateActionLaunchers(BenchConfiguration config) if (!IsDashboardSupported) { - CreateActionLauncher(config, "Bench Control", "bench-ctl", @"%SystemRoot%\System32\imageres.dll,109"); - CreateActionLauncher(config, "Bench Control", "bench-ctl", @"%SystemRoot%\System32\imageres.dll,109", - config.BenchRootDir); + CreateActionLauncher(config, "Bench CLI", "bench.exe"); + CreateActionLauncher(config, "Bench CLI", "bench.exe", null, config.BenchRootDir); } if (config.GetBooleanValue(PropertyKeys.QuickAccessCmd, true)) { - CreateActionLauncher(config, "Command Line", "bench-cmd", @"%SystemRoot%\System32\cmd.exe"); + CreateActionLauncher(config, "Command Line", "bench-cmd.cmd", @"%SystemRoot%\System32\cmd.exe"); } if (config.GetBooleanValue(PropertyKeys.QuickAccessPowerShell, false)) { - CreateActionLauncher(config, "PowerShell", "bench-ps", @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"); + CreateActionLauncher(config, "PowerShell", "bench-ps.cmd", @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"); } if (config.GetBooleanValue(PropertyKeys.QuickAccessBash, false)) { - CreateActionLauncher(config, "Bash", "bench-bash", @"%SystemRoot%\System32\imageres.dll,95"); + CreateActionLauncher(config, "Bash", "bench-bash.cmd", @"%SystemRoot%\System32\imageres.dll,95"); } } @@ -1141,7 +1553,7 @@ private static void CreateLauncher(BenchConfiguration config, AppFacade app) code.AppendLine($"CALL \"{rootDir}\\env.cmd\""); if (app.IsExecutableAdorned(executable) && app.IsAdornmentRequired) { - code.AppendLine($"\"{autoDir}\\runps.cmd\" Run-Adorned {app.ID} \"{executable}\" {args}"); + code.AppendLine($"\"{autoDir}\\bin\\runps.cmd\" Run-Adorned {app.ID} \"{executable}\" {args}"); } else { @@ -1166,7 +1578,7 @@ private static void UpdateEnvironment(IBenchManager man, { notify(new TaskError( string.Format("Writing the environment file failed: {0}", e.Message), - null, null, e)); + exception: e)); return; } try @@ -1184,7 +1596,7 @@ private static void UpdateEnvironment(IBenchManager man, { notify(new TaskError( string.Format("Registering the environment in the user profile failed: {0}", e.Message), - null, null, e)); + exception: e)); return; } try @@ -1195,7 +1607,7 @@ private static void UpdateEnvironment(IBenchManager man, { notify(new TaskError( string.Format("Cleaning execution proxies failed: {0}", e.Message), - null, null, e)); + exception: e)); return; } try @@ -1206,7 +1618,7 @@ private static void UpdateEnvironment(IBenchManager man, { notify(new TaskError( string.Format("Cleaning launchers failed: {0}", e.Message), - null, null, e)); + exception: e)); return; } try @@ -1217,7 +1629,7 @@ private static void UpdateEnvironment(IBenchManager man, { notify(new TaskError( string.Format("Creating bench action launchers failed: {0}", e.Message), - null, null, e)); + exception: e)); return; } var selectedApps = man.Config.Apps.ActiveApps; @@ -1237,7 +1649,7 @@ private static void UpdateEnvironment(IBenchManager man, { notify(new TaskError( string.Format("Creating execution proxy for {0} failed: {1}", app.ID, e.Message), - app.ID, null, e)); + appId: app.ID, exception: e)); continue; } try @@ -1248,15 +1660,15 @@ private static void UpdateEnvironment(IBenchManager man, { notify(new TaskError( string.Format("Creating launcher for {0} failed: {1}", app.ID, e.Message), - app.ID, null, e)); + appId: app.ID, exception: e)); continue; } - var envScript = app.GetCustomScriptFile("env"); + var envScript = app.GetCustomScript("env"); if (envScript != null) { notify(new TaskProgress( - string.Format("Running custom environment script for {0}.", app.ID), - progress, app.ID)); + string.Format("Running custom environment script for {0}.", app.ID), progress, + appId: app.ID)); string scriptOutput = null; try { @@ -1266,14 +1678,14 @@ private static void UpdateEnvironment(IBenchManager man, { notify(new TaskError( string.Format("Running custom environment script for {0} failed: {1}", app.ID, e.Message), - app.ID, e.ProcessOutput, e)); + appId: app.ID, exception: e)); continue; } if (!string.IsNullOrEmpty(scriptOutput)) { notify(new TaskInfo( string.Format("Running custom environment script for {0} finished.", app.ID), - app.ID, scriptOutput)); + appId: app.ID, consoleOutput: scriptOutput)); } } notify(new TaskProgress( @@ -1284,7 +1696,7 @@ private static void UpdateEnvironment(IBenchManager man, var globalEnvScript = GetGlobalCustomScriptFile(man.Config, "env"); if (globalEnvScript != null) { - notify(new TaskProgress("Executing global environment script.", 0.9f)); + notify(new TaskProgress("Running global environment script.", 0.9f)); string scriptOutput = null; try { @@ -1292,12 +1704,14 @@ private static void UpdateEnvironment(IBenchManager man, } catch (ProcessExecutionFailedException e) { - notify(new TaskError("Executing global environment script failed.", - null, e.ProcessOutput, e)); + notify(new TaskError("Running global environment script failed.", + consoleOutput: e.ProcessOutput, exception: e)); } if (!string.IsNullOrEmpty(scriptOutput)) { - notify(new TaskInfo("Executing global environment script finished.", null, scriptOutput)); + notify(new TaskInfo( + "Running global environment script finished.", + consoleOutput: scriptOutput)); } } @@ -1309,6 +1723,104 @@ private static void UpdateEnvironment(IBenchManager man, #endregion + #region Bench Update + + private static void DownloadBenchUpdate(IBenchManager man, + ICollection _, + Action notify, Cancelation cancelation) + { + notify(new TaskInfo("Retrieving latest Bench version number...")); + + var version = GetLatestVersion(man.Config); + if (version == null) + { + notify(new TaskError("Retrieving the latest Bench version number failed.")); + return; + } + notify(new TaskInfo("Found Bench v" + version)); + + var taskCount = 2; + var finished = 0; + var errorCnt = 0; + var endEvent = new ManualResetEvent(false); + + EventHandler downloadStartedHandler = (o, e) => + { + notify(new TaskProgress( + string.Format("Started download for {0} ...", e.Task.Id), + (float)finished / taskCount, e.Task.Id, e.Task.Url.ToString())); + }; + + EventHandler downloadEndedHandler = (o, e) => + { + finished++; + if (!e.Task.Success) + { + errorCnt++; + notify(new TaskError(e.Task.ErrorMessage, e.Task.Id)); + } + else + { + notify(new TaskProgress( + string.Format("Finished download for {0}.", e.Task.Id), + (float)finished / taskCount, e.Task.Id)); + } + }; + + EventHandler workFinishedHandler = null; + workFinishedHandler = (EventHandler)((o, e) => + { + man.Downloader.DownloadEnded -= downloadEndedHandler; + man.Downloader.WorkFinished -= workFinishedHandler; + endEvent.Set(); + }); + man.Downloader.DownloadStarted += downloadStartedHandler; + man.Downloader.DownloadEnded += downloadEndedHandler; + man.Downloader.WorkFinished += workFinishedHandler; + + cancelation.Canceled += (s, e) => + { + man.Downloader.CancelAll(); + }; + + notify(new TaskProgress("Downloading Bench update...", 0f)); + + var binaryUrl = man.Config.GetStringValue(PropertyKeys.UpdateUrlTemplate, string.Empty) + .Replace("#VERSION#", version); + var binaryFile = Path.Combine(man.Config.BenchRootDir, "Bench.zip"); + var bootstrapUrl = man.Config.GetStringValue(PropertyKeys.BootstrapUrlTemplate, string.Empty) + .Replace("#VERSION#", version); + var bootstrapFile = Path.Combine(man.Config.BenchRootDir, "bench-install.bat"); + + man.Downloader.Enqueue(new DownloadTask("Bench Binary", new Uri(binaryUrl), binaryFile)); + man.Downloader.Enqueue(new DownloadTask("Bootstrap File", new Uri(bootstrapUrl), bootstrapFile)); + endEvent.WaitOne(); + + if (!cancelation.IsCanceled) + { + notify(new TaskProgress("Finished downloading the Bench update.", 1f)); + } + } + + /// + /// Starts the boostrap script for (re)installing the Bench system. + /// This requires the bench-install.bat and the bench.zip + /// to be stored in the Bench root directory. + /// + /// The Bench configuration. + public static void InitiateInstallationBootstrap(BenchConfiguration config) + { + var rootPath = config.BenchRootDir; + var si = new ProcessStartInfo("cmd", + "/D \"@ECHO.Starting Bench Installation... && @ECHO. && @ECHO.Make sure, all programs in the Bench environment are closed. && @PAUSE && CALL ^\"" + + Path.Combine(rootPath, "bench-install.bat") + "^\"\""); + si.UseShellExecute = true; + si.WorkingDirectory = rootPath; + Process.Start(si); + } + + #endregion + #region Install Apps private static void CopyAppResourceFile(BenchConfiguration config, AppFacade app) @@ -1335,7 +1847,7 @@ private static void ExtractAppArchive(BenchConfiguration config, IProcessExecuti var targetDir = Path.Combine(config.GetStringValue(PropertyKeys.LibDir), app.Dir); var extractDir = app.ResourceArchivePath != null ? tmpDir : targetDir; FileSystem.AsureDir(extractDir); - var customExtractScript = app.GetCustomScriptFile("extract"); + var customExtractScript = app.GetCustomScript("extract"); switch (app.ResourceArchiveTyp) { case AppArchiveTyps.Auto: @@ -1599,16 +2111,8 @@ private static void InstallNuGetPackage(BenchConfiguration config, IProcessExecu } } - private static void InstallApps(IBenchManager man, - ICollection apps, - Action notify, Cancelation cancelation) + private static bool PrepareDirectories(IBenchManager man, Action notify) { - var selectedApps = new List(); - foreach (var app in apps) - { - if (app.CanInstall) selectedApps.Add(app); - } - try { FileSystem.AsureDir( @@ -1622,155 +2126,180 @@ private static void InstallApps(IBenchManager man, } catch (Exception e) { - notify(new TaskError("Preparing directories failed.", null, null, e)); - return; + notify(new TaskError("Preparing directories failed.", + exception: e)); + return false; } + return true; + } - var cnt = 0; - foreach (var app in selectedApps) + private static bool InstallApp(IBenchManager man, AppFacade app, Action notify) + { + // 1. Extraction / Installation + try { - if (cancelation.IsCanceled) break; - cnt++; - var progress = 0.9f * cnt / selectedApps.Count; + switch (app.Typ) + { + case AppTyps.Meta: + // no resource extraction + break; + case AppTyps.Default: + if (app.ResourceFileName != null) + { + CopyAppResourceFile(man.Config, app); + } + else if (app.ResourceArchiveName != null) + { + ExtractAppArchive(man.Config, man.ProcessExecutionHost, app); + } + break; + case AppTyps.NodePackage: + InstallNodePackage(man.Config, man.ProcessExecutionHost, app); + break; + case AppTyps.RubyPackage: + InstallRubyPackage(man.Config, man.ProcessExecutionHost, app); + break; + case AppTyps.Python2Package: + InstallPythonPackage(man.Config, man.ProcessExecutionHost, PythonVersion.Python2, app); + break; + case AppTyps.Python3Package: + InstallPythonPackage(man.Config, man.ProcessExecutionHost, PythonVersion.Python3, app); + break; + case AppTyps.NuGetPackage: + InstallNuGetPackage(man.Config, man.ProcessExecutionHost, app); + break; + default: + throw new ArgumentOutOfRangeException("Invalid app typ '" + app.Typ + "' for app " + app.ID + "."); + } + } + catch (ProcessExecutionFailedException e) + { + notify(new TaskError( + string.Format("Installing app {0} failed: {1}", app.ID, e.Message), + appId: app.ID, consoleOutput: e.ProcessOutput, exception: e)); + return false; + } + catch (Exception e) + { + notify(new TaskError( + string.Format("Installing app {0} failed: {1}", app.ID, e.Message), + appId: app.ID, exception: e)); + return false; + } - // 1. Extraction / Installation - notify(new TaskProgress(string.Format("Installing app {0}.", app.ID), progress, app.ID)); + // 2. Custom Setup-Script + var customSetupScript = app.GetCustomScript("setup"); + if (customSetupScript != null) + { + notify(new TaskInfo( + string.Format("Executing custom setup script for {0}.", app.ID), + appId: app.ID)); + string scriptOutput = null; try { - switch (app.Typ) - { - case AppTyps.Meta: - // no resource extraction - break; - case AppTyps.Default: - if (app.ResourceFileName != null) - { - CopyAppResourceFile(man.Config, app); - } - else if (app.ResourceArchiveName != null) - { - ExtractAppArchive(man.Config, man.ProcessExecutionHost, app); - } - break; - case AppTyps.NodePackage: - InstallNodePackage(man.Config, man.ProcessExecutionHost, app); - break; - case AppTyps.RubyPackage: - InstallRubyPackage(man.Config, man.ProcessExecutionHost, app); - break; - case AppTyps.Python2Package: - InstallPythonPackage(man.Config, man.ProcessExecutionHost, PythonVersion.Python2, app); - break; - case AppTyps.Python3Package: - InstallPythonPackage(man.Config, man.ProcessExecutionHost, PythonVersion.Python3, app); - break; - case AppTyps.NuGetPackage: - InstallNuGetPackage(man.Config, man.ProcessExecutionHost, app); - break; - default: - throw new ArgumentOutOfRangeException("Invalid app typ '" + app.Typ + "' for app " + app.ID + "."); - } + scriptOutput = RunCustomScript(man.Config, man.ProcessExecutionHost, app.ID, customSetupScript).Trim(); } catch (ProcessExecutionFailedException e) { notify(new TaskError( - string.Format("Installing app {0} failed: {1}", app.ID, e.Message), - app.ID, e.ProcessOutput, e)); - continue; + string.Format("Execution of custom setup script for {0} failed.", app.ID), + appId: app.ID, consoleOutput: e.ProcessOutput, exception: e)); + return false; } - catch (Exception e) + if (!string.IsNullOrEmpty(scriptOutput)) { - notify(new TaskError( - string.Format("Installing app {0} failed: {1}", app.ID, e.Message), - app.ID, null, e)); - continue; + notify(new TaskInfo( + string.Format("Execution custom setup script for {0} finished.", app.ID), + appId: app.ID, consoleOutput: scriptOutput)); } + } - // 2. Custom Setup-Script - var customSetupScript = app.GetCustomScriptFile("setup"); - if (customSetupScript != null) - { - notify(new TaskProgress( - string.Format("Executing custom setup script for {0}.", app.ID), - progress, app.ID)); - string scriptOutput = null; - try - { - scriptOutput = RunCustomScript(man.Config, man.ProcessExecutionHost, app.ID, customSetupScript).Trim(); - } - catch (ProcessExecutionFailedException e) - { - notify(new TaskError( - string.Format("Execution of custom setup script for {0} failed.", app.ID), - app.ID, e.ProcessOutput, e)); - continue; - } - if (!string.IsNullOrEmpty(scriptOutput)) - { - notify(new TaskInfo( - string.Format("Execution custom setup script for {0} finished.", app.ID), - app.ID, scriptOutput)); - } - } + // 3. Create Execution Proxy + try + { + CreateExecutionProxies(man.Config, app); + } + catch (Exception e) + { + notify(new TaskError( + string.Format("Creating the execution proxy for {0} failed: {1}", app.ID, e.Message), + appId: app.ID, exception: e)); + return false; + } - // 3. Create Execution Proxy - try - { - CreateExecutionProxies(man.Config, app); - } - catch (Exception e) - { - notify(new TaskError( - string.Format("Creating the execution proxy for {0} failed: {1}", app.ID, e.Message), - app.ID, null, e)); - continue; - } + // 4. Create Launcher + try + { + CreateLauncher(man.Config, app); + } + catch (Exception e) + { + notify(new TaskError( + string.Format("Creating the launcher for {0} failed: {1}", app.ID, e.Message), + appId: app.ID, exception: e)); + return false; + } - // 4. Create Launcher + // 5. Run Custom Environment Script + var envScript = app.GetCustomScript("env"); + if (envScript != null) + { + notify(new TaskInfo( + string.Format("Running custom environment script for {0}.", app.ID), + appId: app.ID)); + string scriptOutput = null; try { - CreateLauncher(man.Config, app); + scriptOutput = RunCustomScript(man.Config, man.ProcessExecutionHost, app.ID, envScript).Trim(); } - catch (Exception e) + catch (ProcessExecutionFailedException e) { notify(new TaskError( - string.Format("Creating the launcher for {0} failed: {1}", app.ID, e.Message), - app.ID, null, e)); - continue; + string.Format("Running the custom environment script for {0} failed.", app.ID), + appId: app.ID, consoleOutput: e.ProcessOutput, exception: e)); + return false; } - - // 5. Run Custom Environment Script - var envScript = app.GetCustomScriptFile("env"); - if (envScript != null) + if (!string.IsNullOrEmpty(scriptOutput)) { - notify(new TaskProgress( - string.Format("Running custom environment script for {0}.", app.ID), - progress, app.ID)); - string scriptOutput = null; - try - { - scriptOutput = RunCustomScript(man.Config, man.ProcessExecutionHost, app.ID, envScript).Trim(); - } - catch (ProcessExecutionFailedException e) - { - notify(new TaskError( - string.Format("Running the custom environment script for {0} failed.", app.ID), - app.ID, e.ProcessOutput, e)); - continue; - } - if (!string.IsNullOrEmpty(scriptOutput)) - { - notify(new TaskInfo( - string.Format("Running custom environment script for {0} finished.", app.ID), - app.ID, scriptOutput)); - } + notify(new TaskInfo( + string.Format("Running custom environment script for {0} finished.", app.ID), + appId: app.ID, consoleOutput: scriptOutput)); } + } - // 6. Store installed version - app.InstalledVersion = app.Version; + // 6. Store installed version + app.InstalledVersion = app.Version; - notify(new TaskProgress(string.Format("Finished installing app {0}.", app.ID), progress, app.ID)); - app.DiscardCachedValues(); + app.DiscardCachedValues(); + + return true; + } + + private static void InstallApps(IBenchManager man, + ICollection apps, + Action notify, Cancelation cancelation) + { + var selectedApps = new List(); + foreach (var app in apps) + { + if (app.CanInstall) selectedApps.Add(app); + } + + if (!PrepareDirectories(man, notify)) return; + + var cnt = 0; + foreach (var app in selectedApps) + { + if (cancelation.IsCanceled) break; + cnt++; + var progress = 0.9f * cnt / selectedApps.Count; + notify(new TaskProgress(string.Format("Installing app {0}.", app.ID), progress, app.ID)); + + if (InstallApp(man, app, notify)) + { + notify(new TaskProgress(string.Format("Finished installing app {0}.", app.ID), progress, + appId: app.ID)); + } } var globalCustomSetupScript = GetGlobalCustomScriptFile(man.Config, "setup"); @@ -1786,11 +2315,12 @@ private static void InstallApps(IBenchManager man, { notify(new TaskError( "Execution of global custom setup script failed.", - null, e.ProcessOutput, e)); + consoleOutput: e.ProcessOutput, exception: e)); } if (!string.IsNullOrEmpty(scriptOutput)) { - notify(new TaskInfo("Executing global custom setup script finished.", null, scriptOutput)); + notify(new TaskInfo("Executing global custom setup script finished.", + consoleOutput: scriptOutput)); } } @@ -1802,6 +2332,106 @@ private static void InstallApps(IBenchManager man, #endregion + #region Test Apps + + private static bool InstallAppsDependencies(IBenchManager man, AppFacade app, + Action notify, Cancelation cancelation) + { + var dependencyIds = app.FindAllDependencies(); + if (dependencyIds.Contains(app.ID)) dependencyIds.Remove(app.ID); + + var dependencies = man.Config.Apps.GetApps(dependencyIds); + var result = RunTasks(man, + dependencies, + notify, cancelation, + DownloadAppResources, + InstallApps); + return result.Success; + } + + private static void TestApps(IBenchManager man, + ICollection apps, + Action notify, Cancelation cancelation) + { + var selectedApps = new List(); + foreach (var app in apps) + { + if (app.CanTest) selectedApps.Add(app); + } + + if (!PrepareDirectories(man, notify)) return; + + var cnt = 0; + foreach (var app in selectedApps) + { + if (cancelation.IsCanceled) break; + cnt++; + var progress = 0.9f * cnt / selectedApps.Count; + + // TODO Property checks + + // Installing dependencies + notify(new TaskInfo( + string.Format("Installing dependencies for app {0}", app.ID), + appId: app.ID)); + if (!InstallAppsDependencies(man, app, notify, cancelation)) + { + notify(new TaskError( + string.Format("Installing dependencies for app {0} failed.", app.ID), + appId: app.ID)); + continue; + } + + // TODO Download check + + // TODO Install check + + // TODO Post install check + + // TODO ExeTest check + + // TOTO Custom test script check + + //notify(new TaskProgress(string.Format("Testing app {0}.", app.ID), progress, app.ID)); + //try + //{ + // switch (app.Typ) + // { + // case AppTyps.Meta: + // break; + // case AppTyps.Default: + // break; + // case AppTyps.NodePackage: + // break; + // case AppTyps.RubyPackage: + // break; + // case AppTyps.Python2Package: + // break; + // case AppTyps.Python3Package: + // break; + // case AppTyps.NuGetPackage: + // break; + // } + //} + //catch (ProcessExecutionFailedException e) + //{ + // notify(new TaskError( + // string.Format("Testing app {0} failed: {1}", app.ID, e.Message), + // appId: app.ID, consoleOutput: e.ProcessOutput, exception: e)); + // continue; + //} + //catch (Exception e) + //{ + // notify(new TaskError( + // string.Format("Testing app {0} failed: {1}", app.ID, e.Message), + // appId: app.ID, exception: e)); + // continue; + //} + } + } + + #endregion + #region Uninstall App private static void UninstallGeneric(BenchConfiguration config, AppFacade app) @@ -1874,7 +2504,7 @@ private static void UninstallPythonPackage(BenchConfiguration config, IProcessEx private static bool CanOmitUninstall(ICollection selectedApps, AppFacade app) { var parentAppId = default(string); - switch(app.Typ) + switch (app.Typ) { case AppTyps.NodePackage: parentAppId = AppKeys.NodeJS; @@ -1891,11 +2521,11 @@ private static bool CanOmitUninstall(ICollection selectedApps, AppFac } if (parentAppId != null) { - foreach(var selectedApp in selectedApps) + foreach (var selectedApp in selectedApps) { if (selectedApp.ID == parentAppId) { - return app.GetCustomScriptFile("remove") == null; + return app.GetCustomScript("remove") == null; } } } @@ -1929,20 +2559,20 @@ private static void UninstallApps(IBenchManager man, string.Format("Uninstalling app {0}.", app.ID), progress, app.ID)); - var customScript = app.GetCustomScriptFile("remove"); + var customScript = app.GetCustomScript("remove"); try { if (customScript != null) { notify(new TaskProgress( - string.Format("Executing custom uninstall script for {0}.", app.ID), - progress, app.ID)); + string.Format("Running custom uninstall script for {0}.", app.ID), progress, + appId: app.ID)); var scriptOutput = RunCustomScript(man.Config, man.ProcessExecutionHost, app.ID, customScript).Trim(); if (!string.IsNullOrEmpty(scriptOutput)) { notify(new TaskInfo( - string.Format("Execution of custom uninstall script for {0} finished.", app.ID), - app.ID, scriptOutput)); + string.Format("Running of custom uninstall script for {0} finished.", app.ID), + appId: app.ID, consoleOutput: scriptOutput)); } } else @@ -1981,7 +2611,7 @@ private static void UninstallApps(IBenchManager man, { notify(new TaskError( string.Format("Uninstalling the app {0} failed.", app.ID), - app.ID, null, e)); + appId: app.ID, exception: e)); continue; } @@ -2010,7 +2640,7 @@ private static void UninstallApps(IBenchManager man, } catch (Exception e) { - notify(new TaskError("Uninstalling apps failed.", null, null, e)); + notify(new TaskError("Uninstalling apps failed.", exception: e)); } if (success) { diff --git a/BenchManager/BenchLib/ConfigurationFile.cs b/BenchManager/BenchLib/ConfigurationFile.cs new file mode 100644 index 00000000..cec43ea4 --- /dev/null +++ b/BenchManager/BenchLib/ConfigurationFile.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Bench +{ + /// + /// A descriptor of a configuration or app library file. + /// + public class ConfigurationFile + { + /// + /// The kind of file. + /// + public ConfigurationFileType Type { get; private set; } + + /// + /// A number describing the load order of the configuration files. + /// + public int OrderIndex { get; private set; } + + /// + /// The absolute path of the configuration file. + /// + public string Path { get; private set; } + + /// + /// Initializes a new instance of . + /// + /// The kind of file + /// A number describing the load order of the configuration files + /// The absolute path of the configuration file + public ConfigurationFile(ConfigurationFileType type, int orderIndex, string path) + { + Type = type; + OrderIndex = orderIndex; + Path = path; + } + } +} diff --git a/BenchManager/BenchLib/ConfigurationFileType.cs b/BenchManager/BenchLib/ConfigurationFileType.cs new file mode 100644 index 00000000..4dd13b1b --- /dev/null +++ b/BenchManager/BenchLib/ConfigurationFileType.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Bench +{ + /// + /// The different kinds of configuration and app library files. + /// + [Flags] + public enum ConfigurationFileType : int + { + #region File Groups + + /// All kind of configuration and app library files + All = 0xFFFF, + + /// All configuration files (Bench, User, Site) + Config = 0x000F, + + /// All app library index files + AppLib = 0x00F0, + + /// All app selection lists (Activation, Deactivation) + AppSelection = 0x0F00, + + #endregion + + #region Specific File Types + + /// The built-in Bench configuration file + BenchConfig = 0x0001, + + /// The user configuration file + UserConfig = 0x0002, + + /// A site configuration file + SiteConfig = 0x0004, + + /// An index file of a loaded app library + BenchAppLib = 0x0010, + + /// The index file of the user app library + UserAppLib = 0x0020, + + /// The app activation list file + Activation = 0x0100, + + /// The app deactivation list file + Deactivation = 0x0200, + + #endregion + } +} diff --git a/BenchManager/BenchLib/DefaultBenchManager.cs b/BenchManager/BenchLib/DefaultBenchManager.cs index 51dcd2c6..f0cd960f 100644 --- a/BenchManager/BenchLib/DefaultBenchManager.cs +++ b/BenchManager/BenchLib/DefaultBenchManager.cs @@ -41,7 +41,7 @@ public class DefaultBenchManager : IBenchManager /// /// Initializes a new instance of - /// with a and a . + /// with a and a . /// /// The Bench configuration. public DefaultBenchManager(BenchConfiguration config) @@ -49,10 +49,42 @@ public DefaultBenchManager(BenchConfiguration config) Config = config; Downloader = BenchTasks.InitializeDownloader(config); Env = new BenchEnvironment(Config); - ProcessExecutionHost = new DefaultExecutionHost(); + //ProcessExecutionHost = new DefaultExecutionHost(); + var execHost = new PowerShellExecutionHost(config); + execHost.StartHost(); + ProcessExecutionHost = execHost; UI = new ConsoleUserInterface(); } + /// + /// Returns a value, indicating of this instance was already disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Disposes all disposable child objects. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + var d = Downloader; + if (d != null) + { + Downloader = null; + d.Dispose(); + } + var peh = ProcessExecutionHost; + if (peh != null) + { + ProcessExecutionHost = null; + peh.Dispose(); + } + Config = null; + Env = null; + UI = null; + } + /// /// A flag, controlling if non error messages should be displayed to the user. /// If it is set to true, all messages are displayed; otherwise only @@ -78,7 +110,7 @@ private void NotificationHandler(TaskInfo info) info.AppId ?? "global", info.Message); - if (info.DetailedMessage != null) + if (!string.IsNullOrEmpty(info.DetailedMessage)) { Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine(info.DetailedMessage); @@ -93,65 +125,121 @@ private void NotificationHandler(TaskInfo info) } private bool RunAction(BenchTaskForAll action) - { - return action(this, NotificationHandler, new Cancelation()).Success; - } - + => action(this, NotificationHandler, new Cancelation()).Success; + + private bool RunAction(BenchTaskForOne action, string appId) + => action(this, appId, NotificationHandler, new Cancelation()).Success; + + /// + /// Loads the app libraries, configured in the configuration. + /// + /// true if the execution of the task was successful; otherwise false. + public bool LoadAppLibraries() => RunAction(BenchTasks.DoLoadAppLibraries); + /// /// Sets up only the apps required by Bench. /// /// true if the execution of the task was successful; otherwise false. - public bool SetupRequiredApps() { return RunAction(BenchTasks.DoSetupRequiredApps); } + public bool SetupRequiredApps() => RunAction(BenchTasks.DoSetupRequiredApps); /// /// Runs a full automatic setup, including app resource download, app installation of all active apps /// and setup of the environment. /// /// true if the execution of the task was successful; otherwise false. - public bool AutoSetup() { return RunAction(BenchTasks.DoAutoSetup); } + public bool AutoSetup() => RunAction(BenchTasks.DoAutoSetup); /// /// Sets up the environment, including the env.cmd, the launcher scripts and launcher shortcuts. /// It also runs all custom environment scripts. /// /// true if the execution of the task was successful; otherwise false. - public bool UpdateEnvironment() { return RunAction(BenchTasks.DoUpdateEnvironment); } + public bool UpdateEnvironment() => RunAction(BenchTasks.DoUpdateEnvironment); /// /// Downloads the app resources for all active apps, in case they are not already cached. /// /// true if the execution of the task was successful; otherwise false. - public bool DownloadAppResources() { return RunAction(BenchTasks.DoDownloadAppResources); } + public bool DownloadAppResources() => RunAction(BenchTasks.DoDownloadAppResources); + + /// + /// Downloads the resource for the specified app, in case it is not already cached. + /// + /// true if the execution of the task was successful; otherwise false. + public bool DownloadAppResource(string appId) => RunAction(BenchTasks.DoDownloadAppResources, appId); /// /// Deletes all cached app resources. /// /// true if the execution of the task was successful; otherwise false. - public bool DeleteAppResources() { return RunAction(BenchTasks.DoDeleteAppResources); } + public bool DeleteAppResources() => RunAction(BenchTasks.DoDeleteAppResources); + + /// + /// Deletes the cached resource of the specified app. + /// + /// true if the execution of the task was successful; otherwise false. + public bool DeleteAppResource(string appId) => RunAction(BenchTasks.DoDeleteAppResources, appId); + + /// + /// Deletes all obsolete app resources from the cache. + /// + /// true if the execution of the task was successful; otherwise false. + public bool CleanUpAppResources() => RunAction(BenchTasks.DoCleanUpAppResources); /// /// Installs all active apps, in case they are not already installed. /// This also downloads missing app resources. /// /// true if the execution of the task was successful; otherwise false. - public bool InstallApps() { return RunAction(BenchTasks.DoInstallApps); } + public bool InstallApps() => RunAction(BenchTasks.DoInstallApps); + + /// + /// Installs the specified app, in case it is not already installed. + /// This also downloads missing app resources. + /// + /// true if the execution of the task was successful; otherwise false. + public bool InstallApp(string appId) => RunAction(BenchTasks.DoInstallApps, appId); /// /// Uninstalls all installed apps. /// /// true if the execution of the task was successful; otherwise false. - public bool UninstallApps() { return RunAction(BenchTasks.DoUninstallApps); } + public bool UninstallApps() => RunAction(BenchTasks.DoUninstallApps); + + /// + /// Uninstalls the specified app, in case it is installed. + /// + /// true if the execution of the task was successful; otherwise false. + public bool UninstallApp(string appId) => RunAction(BenchTasks.DoUninstallApps, appId); /// /// Uninstalls all installed apps, and then installs all active apps again. /// /// true if the execution of the task was successful; otherwise false. - public bool ReinstallApps() { return RunAction(BenchTasks.DoReinstallApps); } + public bool ReinstallApps() => RunAction(BenchTasks.DoReinstallApps); + + /// + /// Uninstalls the specified app, and then installs it again. + /// + /// true if the execution of the task was successful; otherwise false. + public bool ReinstallApp(string appId) => RunAction(BenchTasks.DoReinstallApps, appId); /// /// Upgrades all active apps, which can be upgraded. /// /// true if the execution of the task was successful; otherwise false. - public bool UpgradeApps() { return RunAction(BenchTasks.DoUpgradeApps); } + public bool UpgradeApps() => RunAction(BenchTasks.DoUpgradeApps); + + /// + /// Upgrades the specified app, if it can be upgraded. + /// + /// true if the execution of the task was successful; otherwise false. + public bool UpgradeApp(string appId) => RunAction(BenchTasks.DoUpgradeApps, appId); + + /// + /// Downloads the latest Bench binary and bootstrap file. + /// + /// true if the execution of the task was successful; otherwise false. + public bool DownloadBenchUpdate() => RunAction(BenchTasks.DoDownloadBenchUpdate); } } diff --git a/BenchManager/BenchLib/Delegates.cs b/BenchManager/BenchLib/Delegates.cs index f3747aa9..659c5089 100644 --- a/BenchManager/BenchLib/Delegates.cs +++ b/BenchManager/BenchLib/Delegates.cs @@ -14,6 +14,19 @@ namespace Mastersign.Bench internal delegate void TextFileEditor(string prompt, string filePath); + /// + /// The type for a handler, called when the download of string finished. + /// + /// true if the download was successful; otherwise false. + /// The content of the download or null if the download failed. + public delegate void StringDownloadResultHandler(bool success, string content); + + /// + /// The type for a handler, called when the download of a file finished. + /// + /// true if the download was successfull; otherwise false. + public delegate void FileDownloadResultHandler(bool success); + /// /// The type for a method which is called, to process a key value pair of strings. /// diff --git a/BenchManager/BenchLib/AppIndexValueResolver.cs b/BenchManager/BenchLib/DictionaryValueResolver.cs similarity index 83% rename from BenchManager/BenchLib/AppIndexValueResolver.cs rename to BenchManager/BenchLib/DictionaryValueResolver.cs index de515de3..7e648178 100644 --- a/BenchManager/BenchLib/AppIndexValueResolver.cs +++ b/BenchManager/BenchLib/DictionaryValueResolver.cs @@ -4,17 +4,17 @@ namespace Mastersign.Bench { - internal class AppIndexValueResolver : IGroupedValueResolver + internal class DictionaryValueResolver : IGroupedValueResolver { private static readonly char[] KeyValueSeparator = new char[] { ':' }; public IGroupedPropertySource AppIndex { get; set; } - public AppIndexValueResolver() + public DictionaryValueResolver() { } - public AppIndexValueResolver(IGroupedPropertySource appIndex) + public DictionaryValueResolver(IGroupedPropertySource appIndex) : this() { AppIndex = appIndex; @@ -26,6 +26,8 @@ public object ResolveGroupValue(string group, string name, object value) { switch (name) { + case PropertyKeys.KnownLicenses: + return ParseKeyValuePairs(value); case PropertyKeys.CustomEnvironment: return ParseKeyValuePairs(value); default: @@ -76,11 +78,11 @@ private Dictionary ParseKeyValuePairs(object value) return d; } - private KeyValuePair ParseKeyValuePair(string header) + public static KeyValuePair ParseKeyValuePair(string value) { - if (header != null && header.Contains(":")) + if (value != null && value.Contains(":")) { - var p = header.Split(KeyValueSeparator, 2); + var p = value.Split(KeyValueSeparator, 2); return new KeyValuePair(p[0].Trim(), p[1].Trim()); } else diff --git a/BenchManager/BenchLib/Downloader.cs b/BenchManager/BenchLib/Downloader.cs index 31b82a86..33336e2d 100644 --- a/BenchManager/BenchLib/Downloader.cs +++ b/BenchManager/BenchLib/Downloader.cs @@ -124,50 +124,30 @@ public Downloader(int parallelDownloads) private void OnDownloadStarted(DownloadTask task) { Debug.WriteLine("Raising event DownloadStarted for " + task.Id + ", Url=" + task.Url); - var handler = DownloadStarted; - if (handler != null) - { - handler(this, new DownloadEventArgs(task)); - } + DownloadStarted?.Invoke(this, new DownloadEventArgs(task)); } private void OnDownloadProgress(DownloadTask task, long bytesDownloaded, long totalBytes, int percentage) { - var handler = DownloadProgress; - if (handler != null) - { - handler(this, new DownloadProgressEventArgs(task, bytesDownloaded, totalBytes, percentage)); - } + DownloadProgress?.Invoke(this, new DownloadProgressEventArgs(task, bytesDownloaded, totalBytes, percentage)); } private void OnDownloadEnded(DownloadTask task) { Debug.WriteLine("Raising event DownloadEnded for " + task.Id + ", Error=" + task.ErrorMessage ?? "None"); - var handler = DownloadEnded; - if (handler != null) - { - handler(this, new DownloadEventArgs(task)); - } + DownloadEnded?.Invoke(this, new DownloadEventArgs(task)); } private void OnIsWorkingChanged() { Debug.WriteLine("Raising event IsWorkingChanged"); - var handler = IsWorkingChanged; - if (handler != null) - { - handler(this, EventArgs.Empty); - } + IsWorkingChanged?.Invoke(this, EventArgs.Empty); } private void OnWorkFinished() { Debug.WriteLine("Raising event WorkFinished"); - var handler = WorkFinished; - if (handler != null) - { - handler(this, EventArgs.Empty); - } + WorkFinished?.Invoke(this, EventArgs.Empty); } /// @@ -195,11 +175,11 @@ public void Enqueue(DownloadTask task) notify = true; } } - availableTasks.Release(); if (notify) { OnIsWorkingChanged(); } + availableTasks.Release(); } private void Worker(int no) diff --git a/BenchManager/BenchLib/FileSystem.cs b/BenchManager/BenchLib/FileSystem.cs index 0537f94a..613fa39c 100644 --- a/BenchManager/BenchLib/FileSystem.cs +++ b/BenchManager/BenchLib/FileSystem.cs @@ -140,6 +140,50 @@ public static void MoveContent(string sourceDir, string targetDir) } } + /// + /// Copies a directory with all its content to another location. + /// + /// A path to the source directory. + /// A path to the target directory. + /// true if subdirectories are copied recursively; otherwise false. + public static void CopyDir(string sourceDir, string targetDir, bool subDirs) + { + // Get the subdirectories for the specified directory. + DirectoryInfo dir = new DirectoryInfo(sourceDir); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException( + "Source directory does not exist or could not be found: " + + sourceDir); + } + + DirectoryInfo[] dirs = dir.GetDirectories(); + // If the destination directory doesn't exist, create it. + if (!Directory.Exists(targetDir)) + { + Directory.CreateDirectory(targetDir); + } + + // Get the files in the directory and copy them to the new location. + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string temppath = Path.Combine(targetDir, file.Name); + file.CopyTo(temppath, false); + } + + // If copying subdirectories, copy them and their contents to new location. + if (subDirs) + { + foreach (DirectoryInfo subdir in dirs) + { + string temppath = Path.Combine(targetDir, subdir.Name); + CopyDir(subdir.FullName, temppath, subDirs); + } + } + } + /// /// Creates a Windows shortcut, or link respectively. /// diff --git a/BenchManager/BenchLib/GroupedPropertyCollection.cs b/BenchManager/BenchLib/GroupedPropertyCollection.cs index 9d6db977..ff048cee 100644 --- a/BenchManager/BenchLib/GroupedPropertyCollection.cs +++ b/BenchManager/BenchLib/GroupedPropertyCollection.cs @@ -16,6 +16,8 @@ public class GroupedPropertyCollection : IConfiguration { private readonly List groupNames = new List(); // ordered list for group names private readonly Dictionary groupCategories = new Dictionary(); + private readonly Dictionary groupMetadata = new Dictionary(); + private readonly Dictionary groupDocumentation = new Dictionary(); private readonly Dictionary> groupKeys = new Dictionary>(); // ordered lists for property names private readonly Dictionary> groups = new Dictionary>(); @@ -59,7 +61,53 @@ public void SetGroupCategory(string group, string category) public string GetGroupCategory(string group) { string category; - return groupCategories.TryGetValue(group, out category) ? category : null; + return groupCategories.TryGetValue(group ?? string.Empty, out category) ? category : null; + } + + /// + /// Attaches a metadata object to a group. + /// + /// The group to attach the metadata to. + /// The metadata object. + public void SetGroupMetadata(string group, object metadata) + { + group = group ?? string.Empty; + groupMetadata[group] = metadata; + } + + /// + /// Gets the metadata object, attached to the specified group, + /// or null if the group has no metadata attached. + /// + /// The group in question. + /// The metadata object attached to the given group, or null. + public object GetGroupMetadata(string group) + { + object metadata; + return groupMetadata.TryGetValue(group ?? string.Empty, out metadata) ? metadata : null; + } + + /// + /// attaches documentation to a group. + /// + /// The group to attach the documentation to. + /// The documentation text. + public void SetGroupDocumentation(string group, string docs) + { + group = group ?? string.Empty; + groupDocumentation[group] = docs; + } + + /// + /// Gets the documentation text, attached to the specified group, + /// or null if the group has no documentation attached. + /// + /// The group in question. + /// A string with the Markdown documentation text, or null. + public string GetGroupDocumentation(string group) + { + string docs; + return groupDocumentation.TryGetValue(group ?? string.Empty, out docs) ? docs : null; } /// @@ -169,6 +217,12 @@ public bool CanGetGroupValue(string group, string name) /// The new value of the property. public void SetValue(string name, object value) { SetGroupValue(null, name, value); } + /// + /// Resets the specified property. + /// + /// The name of the property. + public void ResetValue(string name) { ResetGroupValue(null, name); } + /// /// Sets a string value for the specified group property. /// @@ -213,6 +267,35 @@ public void SetGroupValue(string group, string name, object value) InternalSetValue(group, name, value); } + /// + /// Resets the specified group property. + /// + /// The group of the property. + /// The name of the property. + public void ResetGroupValue(string group, string name) + { + group = group ?? string.Empty; + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentOutOfRangeException("propertyName", "The property name must not be null or empty."); + } + List keys; + Dictionary groupDict; + if (groups.ContainsKey(group)) + { + keys = groupKeys[group]; + groupDict = groups[group]; + } + else + { + return; + } + if (groupDict.ContainsKey(name)) + { + groupDict.Remove(name); + } + } + private void InternalSetValue(string groupName, string propertyName, object value) { groupName = groupName ?? string.Empty; @@ -655,47 +738,6 @@ public IEnumerable PropertyNames(string group) return groupKeys.TryGetValue(group, out keys) ? keys : (IEnumerable)new string[0]; } - private string FormatValue(string group, string name, object val, bool resolve = true) - { - if (resolve) - { - val = ResolveGroupValue(group, name, val); - } - if (val == null) - { - return "null"; - } - if (val is bool) - { - return (bool)val ? "`true`" : "`false`"; - } - if (val is string) - { - return string.Format("`{0}`", val); - } - if (val.GetType().IsArray) - { - var a = (Array)val; - var f = new string[a.Length]; - for (int i = 0; i < a.Length; i++) - { - f[i] = FormatValue(group, name, a.GetValue(i), false); - } - return "List( " + string.Join(", ", f) + " )"; - } - if (val is IDictionary) - { - var d = (IDictionary)val; - var l = new List(d.Count); - foreach (var k in d.Keys) - { - l.Add(string.Format("`{0}: {1}`", k, d[k])); - } - return "Dict( " + string.Join(", ", l.ToArray()) + " )"; - } - return "Object( " + val.ToString() + " )"; - } - /// /// Returns a string represenation of this property collection. /// @@ -741,5 +783,46 @@ public string ToString(bool resolve) } return sb.ToString().TrimStart(); } + + private string FormatValue(string group, string name, object val, bool resolve = true) + { + if (resolve) + { + val = ResolveGroupValue(group, name, val); + } + if (val == null) + { + return "null"; + } + if (val is bool) + { + return (bool)val ? "`true`" : "`false`"; + } + if (val is string) + { + return string.Format("`{0}`", val); + } + if (val.GetType().IsArray) + { + var a = (Array)val; + var f = new string[a.Length]; + for (int i = 0; i < a.Length; i++) + { + f[i] = FormatValue(group, name, a.GetValue(i), false); + } + return "List( " + string.Join(", ", f) + " )"; + } + if (val is IDictionary) + { + var d = (IDictionary)val; + var l = new List(d.Count); + foreach (var k in d.Keys) + { + l.Add(string.Format("`{0}: {1}`", k, d[k])); + } + return "Dict( " + string.Join(", ", l.ToArray()) + " )"; + } + return "Object( " + val.ToString() + " )"; + } } } diff --git a/BenchManager/BenchLib/IBenchManager.cs b/BenchManager/BenchLib/IBenchManager.cs index 28a86c3e..357ef9e8 100644 --- a/BenchManager/BenchLib/IBenchManager.cs +++ b/BenchManager/BenchLib/IBenchManager.cs @@ -7,7 +7,7 @@ namespace Mastersign.Bench /// /// A Bench manager is an object which knows the most important components of a Bench system. /// - public interface IBenchManager + public interface IBenchManager : IDisposable { /// /// The configuration of the Bench system. diff --git a/BenchManager/BenchLib/IGroupedPropertySource.cs b/BenchManager/BenchLib/IGroupedPropertySource.cs index 0f2148a5..4fc55a68 100644 --- a/BenchManager/BenchLib/IGroupedPropertySource.cs +++ b/BenchManager/BenchLib/IGroupedPropertySource.cs @@ -17,6 +17,22 @@ public interface IGroupedPropertySource /// The category name of the given group, or null. string GetGroupCategory(string group); + /// + /// Gets the metadata object, attached to the specified group, + /// or null if the group has no metadata attached. + /// + /// The group in question. + /// The metadata object attached to the given group, or null. + object GetGroupMetadata(string group); + + /// + /// Gets the documentation text, attached to the specified group, + /// or null if the group has no documentation attached. + /// + /// The group in question. + /// A string with the Markdown documentation text, or null. + string GetGroupDocumentation(string group); + /// /// Gets the value of the specified property. /// diff --git a/BenchManager/BenchLib/IGroupedPropertyTarget.cs b/BenchManager/BenchLib/IGroupedPropertyTarget.cs index d1207075..5f1695ae 100644 --- a/BenchManager/BenchLib/IGroupedPropertyTarget.cs +++ b/BenchManager/BenchLib/IGroupedPropertyTarget.cs @@ -17,6 +17,20 @@ public interface IGroupedPropertyTarget /// The new category for the group. void SetGroupCategory(string group, string category); + /// + /// Attaches a metadata object to a group. + /// + /// The group to attach the metadata to. + /// The metadata object. + void SetGroupMetadata(string group, object metadata); + + /// + /// attaches documentation to a group. + /// + /// The group to attach the documentation to. + /// The documentation text. + void SetGroupDocumentation(string group, string docs); + /// /// Sets the value of the specified property. /// If the property did exist until now, it is created. @@ -25,5 +39,12 @@ public interface IGroupedPropertyTarget /// The name of the property. /// The new value for the property. void SetGroupValue(string group, string name, object value); + + /// + /// Resets the specified group property. + /// + /// The group of the property. + /// The name of the property. + void ResetGroupValue(string group, string name); } } diff --git a/BenchManager/BenchLib/IPropertyTarget.cs b/BenchManager/BenchLib/IPropertyTarget.cs index 2286c6c5..965ecffe 100644 --- a/BenchManager/BenchLib/IPropertyTarget.cs +++ b/BenchManager/BenchLib/IPropertyTarget.cs @@ -17,5 +17,11 @@ public interface IPropertyTarget /// The name of the property. /// The new value of the property. void SetValue(string name, object value); + + /// + /// Resets the specified property. + /// + /// The name of the property. + void ResetValue(string name); } } diff --git a/BenchManager/BenchLib/Markdown/MarkdownPropertyEditor.cs b/BenchManager/BenchLib/Markdown/MarkdownPropertyEditor.cs index d98b1f86..edf2b3d0 100644 --- a/BenchManager/BenchLib/Markdown/MarkdownPropertyEditor.cs +++ b/BenchManager/BenchLib/Markdown/MarkdownPropertyEditor.cs @@ -6,12 +6,20 @@ namespace Mastersign.Bench.Markdown { - internal static class MarkdownPropertyEditor + /// + /// A class with the capability to update a Markdown property file. + /// + public static class MarkdownPropertyEditor { private const string PatternTemplate = @"^{0}\s+((?:~~)?)\s*{1}\s*:\s*{2}\s*\1\s*$"; private const string PropertyTemplate = @"* {0}: `{1}`"; private const string UnquotedPropertyTemplate = @"* {0}: {1}"; + /// + /// Sets a property value in a Markdown property file. + /// + /// The target file. + /// The dictionary with the properties to set. public static void UpdateFile(string file, IDictionary dict) { var lines = new List(File.ReadAllLines(file, Encoding.UTF8)); diff --git a/BenchManager/BenchLib/Markdown/MarkdownPropertyParser.cs b/BenchManager/BenchLib/Markdown/MarkdownPropertyParser.cs index 76ad034a..515f9d7f 100644 --- a/BenchManager/BenchLib/Markdown/MarkdownPropertyParser.cs +++ b/BenchManager/BenchLib/Markdown/MarkdownPropertyParser.cs @@ -24,6 +24,8 @@ public class MarkdownPropertyParser private static readonly Regex ListElementExp = new Regex("^(?:\t| +)[\\*\\+-]\\s+(?.*?)\\s*$"); private static readonly string ListElementFormat = "`{0}`"; + private static readonly Regex DefaultGroupDocsBeginCue = new Regex("^#{3}\\s+\\S"); + private static readonly Regex DefaultGroupDocsEndCue = new Regex("^#{1,3}\\s+\\S"); private static readonly Regex DefaultGroupBeginCue = new Regex("^###\\s+(?.+?)\\s*(?:\\{.*?\\})?\\s*(?:###)?$"); private static readonly Regex DefaultGroupEndCue = new Regex("^\\s*$"); @@ -128,6 +130,13 @@ private static bool IsQuoted(string value) /// public Regex GroupBeginCue { get; set; } + /// + /// This regular expression is used to detect the beginning of the documentation + /// of a group. All lines, which are not recognized as properties or another cue, + /// are collected as the documentation for the group. + /// + public Regex GroupDocsBeginCue { get; set; } + /// /// This regular expression is used to detect the end of a property group. /// Properties, which are recognized after this expression matches a line, @@ -135,11 +144,34 @@ private static bool IsQuoted(string value) /// public Regex GroupEndCue { get; set; } + /// + /// This regular expression is used to detect the end of the documentation + /// of a group. When this cue is triggered, the collected documentation + /// is attached to all groups, which where detected by + /// since the last . + /// + public Regex GroupDocsEndCue { get; set; } + /// /// Gets or sets the property target, where recognized properties are stored in. /// public IGroupedPropertyTarget Target { get; set; } + /// + /// Gets or sets a metadata object, which is going to be attached to every group, + /// properties are recognized for. + /// + public object CurrentGroupMetadata { get; set; } + + private readonly List collectedGroupDocs = new List(); + private readonly List docGroups = new List(); + + /// + /// A switch to control, whether non-property lines are collected + /// as the documentation for a group. + /// + public bool CollectGroupDocs { get; set; } + /// /// Initializes a new instance of . /// @@ -150,6 +182,8 @@ public MarkdownPropertyParser() CategoryCue = DefaultCategoryCue; GroupBeginCue = DefaultGroupBeginCue; GroupEndCue = DefaultGroupEndCue; + GroupDocsBeginCue = DefaultGroupDocsBeginCue; + GroupDocsEndCue = DefaultGroupDocsEndCue; } /// @@ -168,6 +202,10 @@ private void OnPropertyValue(string name, object value) if (target != null) { target.SetGroupValue(CurrentGroup, name, value); + if (CurrentGroupMetadata != null) + { + target.SetGroupMetadata(CurrentGroup, CurrentGroupMetadata); + } } } @@ -179,6 +217,7 @@ private void OnPropertyValue(string name, object value) private List ListItems = new List(); private MdContext Context; + private bool collectingGroupDocsActive; /// /// Parses the data in the given stream as UTF8 encoded Markdown text @@ -231,24 +270,34 @@ private void ProcessLine(string line) case MdContext.HtmlComment: if (MdSyntax.IsHtmlCommentEnd(line)) Context = MdContext.Text; + ProcessPossibleGroupDocsLine(line); break; case MdContext.CodeBlock: if (MdSyntax.IsCodeBlockEnd(line, ref CodePreamble)) Context = MdContext.Text; + ProcessPossibleGroupDocsLine(line); break; case MdContext.Text: if (MdSyntax.IsYamlHeaderStart(LineNo, line)) Context = MdContext.YamlHeader; else if (MdSyntax.IsHtmlCommentStart(line)) + { Context = MdContext.HtmlComment; + ProcessPossibleGroupDocsLine(line); + } else if (MdSyntax.IsCodeBlockStart(line, ref CodePreamble)) + { Context = MdContext.CodeBlock; + ProcessPossibleGroupDocsLine(line); + } else if (IsDeactivationCue(line)) Context = MdContext.Inactive; else if (IsListStart(line, ref ListKey)) Context = MdContext.List; else + { ProcessTextLine(line); + } break; case MdContext.List: if (!IsListElement(line, ListItems)) @@ -284,7 +333,14 @@ private void ProcessTextLine(string line) if (cm.Success) { CurrentCategory = cm.Groups["category"].Value; - return; + } + } + if (GroupEndCue != null) + { + var gm = GroupEndCue.Match(line); + if (gm.Success) + { + CurrentGroup = null; } } if (GroupBeginCue != null) @@ -297,16 +353,10 @@ private void ProcessTextLine(string line) { Target.SetGroupCategory(CurrentGroup, CurrentCategory); } - return; - } - } - if (GroupEndCue != null) - { - var gm = GroupEndCue.Match(line); - if (gm.Success) - { - CurrentGroup = null; - return; + if (collectingGroupDocsActive) + { + docGroups.Add(CurrentGroup); + } } } var m = MdListExp.Match(line); @@ -329,6 +379,44 @@ private void ProcessTextLine(string line) OnPropertyValue(key, value); } } + else + { + ProcessPossibleGroupDocsLine(line); + } + } + + private void CheckForGroupDocsBegin(string line) + { + if (CollectGroupDocs && GroupDocsBeginCue.IsMatch(line)) + { + collectingGroupDocsActive = true; + } + } + + private void CheckForGroupDocsEnd(string line) + { + if (CollectGroupDocs && GroupDocsEndCue.IsMatch(line)) + { + var docText = string.Join(Environment.NewLine, collectedGroupDocs.ToArray()).Trim(); + foreach (var g in docGroups) + { + Target.SetGroupDocumentation(g, docText); + } + collectedGroupDocs.Clear(); + docGroups.Clear(); + collectingGroupDocsActive = false; + } } + + private void ProcessPossibleGroupDocsLine(string line) + { + CheckForGroupDocsEnd(line); + if (collectingGroupDocsActive) + { + collectedGroupDocs.Add(line); + } + CheckForGroupDocsBegin(line); + } + } } diff --git a/BenchManager/BenchLib/PowerShellExecutionHost.cs b/BenchManager/BenchLib/PowerShellExecutionHost.cs new file mode 100644 index 00000000..10cb7667 --- /dev/null +++ b/BenchManager/BenchLib/PowerShellExecutionHost.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; +using Mastersign.Bench.RemoteExecHost; + +namespace Mastersign.Bench +{ + /// + /// This launches a PowerShell process + /// with the PsExecHost.ps1 script and uses + /// to request process executions. + /// + public class PowerShellExecutionHost : PowerShellExecutionHostBase + { + private Process currentPsProcess; + + /// + /// Initializes a new instance of . + /// + /// The Bench configuration. + public PowerShellExecutionHost(BenchConfiguration config) + : base(config.BenchRootDir, config.GetStringValue(PropertyKeys.BenchScripts)) + { + } + + private ProcessStartInfo BuildStartInfo(string cwd, string executable, string arguments) + { + var cmdLine = CommandLine.EscapeArgument(executable) + " " + arguments; + var si = new ProcessStartInfo(); + si.FileName = PowerShell.Executable; + si.Arguments = arguments; + si.WorkingDirectory = cwd; + si.UseShellExecute = false; + return si; + } + + /// + /// Starts the PowerShell process and runs the PsExecHost.ps1. + /// + protected override void StartPowerShellExecutionHost() + { + var startInfo = BuildStartInfo(BenchRoot, PowerShell.Executable, + string.Join(" ", new[] { + "-NoProfile", "-NoLogo", + "-ExecutionPolicy", "Unrestricted", + "-File", "\"" + PsExecHostScriptFile + "\"", + "-Token", CurrentToken, "-WaitMessage", "\"\"" })); + currentPsProcess = Process.Start(startInfo); + currentPsProcess.Exited += (s, o) => + { + CurrentToken = null; + currentPsProcess = null; + }; + } + + /// + /// Checks is the PowerShell process is running. + /// + protected override bool IsPowerShellExecutionHostRunning => + currentPsProcess != null; + + /// + /// Waits for the PowerShell process to end. + /// Is called after the shut down request was send. + /// + protected override void WaitForPowerShellExecutionHostToEnd() + { + while (!currentPsProcess.HasExited) + { + currentPsProcess.WaitForExit(); + } + } + } +} diff --git a/BenchManager/BenchLib/PowerShellExecutionHostBase.cs b/BenchManager/BenchLib/PowerShellExecutionHostBase.cs new file mode 100644 index 00000000..cb94fe17 --- /dev/null +++ b/BenchManager/BenchLib/PowerShellExecutionHostBase.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using Mastersign.Bench.RemoteExecHost; + +namespace Mastersign.Bench +{ + /// + /// THe base class for implementations of , + /// which uses the PsExecHost.ps1 PowerShell script as a remote execution host. + /// + public abstract class PowerShellExecutionHostBase : IProcessExecutionHost + { + private const string HOST_SCRIPT_FILE = "PsExecHost.ps1"; + private const int RETRY_INTERVAL = 250; + private const int START_UP_TIMEOUT = 10000; + + private readonly string benchRoot; + + private readonly string scriptsDirectory; + + private IProcessExecutionHost backupHost; + + private readonly Semaphore hostSemaphore = new Semaphore(1, 1); + + private bool calledSuccessfully = false; + + private bool reloadConfigBeforeNextExecution = false; + + /// + /// Initializes a new instance of . + /// + /// The root directory of the Bench environment. + /// The directory where to find the PsExecHost.ps1 script file. + protected PowerShellExecutionHostBase(string benchRoot, string scriptsDirectory) + { + this.benchRoot = benchRoot; + this.scriptsDirectory = scriptsDirectory; + if (!File.Exists(PsExecHostScriptFile)) + { + throw new FileNotFoundException("The PowerShell host script was not found."); + } + } + + /// + /// Starts the remote host and prepares for first execution request. + /// + public void StartHost() + { + if (CurrentToken != null) throw new InvalidOperationException("Host is already started."); + backupHost = new SimpleExecutionHost(); + CurrentToken = Guid.NewGuid().ToString("D"); + StartPowerShellExecutionHost(); + } + + /// + /// Checks whether the host is started, or not. + /// + public bool IsStarted => CurrentToken != null; + + /// + /// The root directory of the Bench environment. + /// + protected string BenchRoot => benchRoot; + + /// + /// An absolute path to the PsExecHost.ps1 script file. + /// + protected string PsExecHostScriptFile => Path.Combine(scriptsDirectory, HOST_SCRIPT_FILE); + + /// + /// The current token to identify the IPC connection. + /// + protected string CurrentToken { get; set; } + + /// + /// Call this to request a reload of the Bench configuration + /// in the remote execution host, before the next execution request is processed. + /// + protected void RequestConfigurationReload() + { + reloadConfigBeforeNextExecution = true; + } + + /// + /// Starts the PowerShell process and runs the PsExecHost.ps1. + /// + /// Needs to be implemented in a derived class. + protected abstract void StartPowerShellExecutionHost(); + + /// + /// Checks is the PowerShell process is running. + /// + /// Needs to be implemented in a derived class. + protected abstract bool IsPowerShellExecutionHostRunning { get; } + + /// + /// Waits for the PowerShell process to end. + /// Is called after the shut down request was send. + /// + /// Needs to be implemented in a derived class. + protected abstract void WaitForPowerShellExecutionHostToEnd(); + + private void RemoteCall(Action task) + { + if (!IsPowerShellExecutionHostRunning) throw new InvalidOperationException(); + hostSemaphore.WaitOne(); + try + { + using (var client = new RemoteExecHostClient(CurrentToken)) + { + task(client.ExecHost); + } + } + finally + { + hostSemaphore.Release(); + } + } + + private void WaitForPowerShellExecutionHost() + { + if (calledSuccessfully) return; + var available = false; + var t0 = DateTime.Now; + while (!available && (DateTime.Now - t0).TotalMilliseconds < START_UP_TIMEOUT) + { + try + { + string response = null; + RemoteCall(h => response = h.Ping()); + available = response != null; + } + catch (Exception) + { + Debug.WriteLine("Attempt to reach the remote execution host failed."); + Thread.Sleep(RETRY_INTERVAL); + } + } + calledSuccessfully = available; + } + + private static string CleanUpPowerShellTranscript(string transcript) + { + transcript = transcript.Trim(); + var lines = transcript.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + if (lines.Length == 0) return transcript; + // check if first line contains a sequence of the same character + if (lines[0].Length > 2 && lines[0] == new string(lines[0][0], lines[0].Length)) + { + var separator = lines[0]; + var outputLines = new List(); + var p = 1; + // search for the end of the header + for (int i = p; i < lines.Length; i++) + { + p++; + if (lines[i] == separator) break; + } + // collect lines until the start of the footer + for (int i = p; i < lines.Length; i++) + { + if (lines[i] == separator) break; + outputLines.Add(lines[i]); + } + return string.Join(Environment.NewLine, outputLines.ToArray()).Trim(); + } + return transcript; + } + + private void ReloadConfiguration() + { + if (!IsPowerShellExecutionHostRunning) return; + WaitForPowerShellExecutionHost(); + RemoteCall(h => h.Reload()); + reloadConfigBeforeNextExecution = false; + } + + private void StopPowerShellExecutionHost() + { + if (!IsPowerShellExecutionHostRunning) return; + WaitForPowerShellExecutionHost(); + RemoteCall(h => h.Shutdown()); + WaitForPowerShellExecutionHostToEnd(); + } + + /// + /// Starts a Windows process in a synchronous fashion. + /// + /// The environment variables of Bench. + /// The working directory, to start the process in. + /// The path to the executable. + /// The string with the command line arguments. + /// A flag to control the level of monitoring. + /// An instance of with the exit code + /// and optionally the output of the process. + /// + public ProcessExecutionResult RunProcess(BenchEnvironment env, + string cwd, string executable, string arguments, + ProcessMonitoring monitoring) + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(PowerShellExecutionHost)); + } + if (!IsStarted) + { + throw new InvalidOperationException("The host is not started yet."); + } + if (!IsPowerShellExecutionHostRunning) + { + return backupHost.RunProcess(env, cwd, executable, arguments, monitoring); + } + WaitForPowerShellExecutionHost(); + if (reloadConfigBeforeNextExecution) + { + ReloadConfiguration(); + } + + ExecutionResult result = null; + RemoteCall(h => result = h.Execute(new ExecutionRequest(cwd, executable, arguments))); + + var collectOutput = (monitoring & ProcessMonitoring.Output) == ProcessMonitoring.Output; + if (result != null) + { + var transcriptPath = result.TranscriptPath; + var output = default(string); + if (collectOutput && transcriptPath != null && File.Exists(transcriptPath)) + { + output = File.ReadAllText(transcriptPath, Encoding.Default); + output = CleanUpPowerShellTranscript(output); + File.Delete(transcriptPath); + } + return new ProcessExecutionResult(result.ExitCode, output); + } + else + { + return new ProcessExecutionResult(99999, null); + } + } + + /// + /// Starts a Windows process in an asynchronous fashion. + /// + /// The environment variables of Bench. + /// The working directory, to start the process in. + /// The path to the executable. + /// The string with the command line arguments. + /// The handler method to call when the execution of the process finishes. + /// A flag to control the level of monitoring. + /// + public void StartProcess(BenchEnvironment env, + string cwd, string executable, string arguments, + ProcessExitCallback cb, ProcessMonitoring monitoring) + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(PowerShellExecutionHost)); + } + if (!IsStarted) + { + throw new InvalidOperationException("The host is not started yet."); + } + if (!IsPowerShellExecutionHostRunning) + { + backupHost.StartProcess(env, cwd, executable, arguments, cb, monitoring); + return; + } + WaitForPowerShellExecutionHost(); + if (reloadConfigBeforeNextExecution) + { + ReloadConfiguration(); + } + + var collectOutput = (monitoring & ProcessMonitoring.Output) == ProcessMonitoring.Output; + AsyncManager.StartTask(() => + { + ExecutionResult remoteExecResult = null; + RemoteCall(h => remoteExecResult = h.Execute(new ExecutionRequest(cwd, executable, arguments))); + ProcessExecutionResult result; + if (remoteExecResult != null) + { + var transcriptPath = remoteExecResult.TranscriptPath; + var output = default(string); + if (collectOutput && transcriptPath != null && File.Exists(transcriptPath)) + { + output = File.ReadAllText(transcriptPath, Encoding.Default); + output = CleanUpPowerShellTranscript(output); + File.Delete(transcriptPath); + } + result = new ProcessExecutionResult(remoteExecResult.ExitCode, output); + } + else + { + result = new ProcessExecutionResult(99999, null); + } + cb(result); + }); + } + + /// + /// Checks if this instance of already disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Shuts down this execution host and kills the attached PowerShell process. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + OnDispose(); + StopPowerShellExecutionHost(); + backupHost.Dispose(); + backupHost = null; + } + + /// + /// Is called when this instance is disposed. + /// + protected virtual void OnDispose() { } + } +} diff --git a/BenchManager/BenchLib/Properties/AssemblyInfo.cs b/BenchManager/BenchLib/Properties/AssemblyInfo.cs index 893b136a..b67b06b2 100644 --- a/BenchManager/BenchLib/Properties/AssemblyInfo.cs +++ b/BenchManager/BenchLib/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.13.3.0")] -[assembly: AssemblyFileVersion("0.13.3.0")] +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/BenchManager/BenchLib/PropertyKeys.cs b/BenchManager/BenchLib/PropertyKeys.cs index 392167dc..7e3b2a98 100644 --- a/BenchManager/BenchLib/PropertyKeys.cs +++ b/BenchManager/BenchLib/PropertyKeys.cs @@ -13,6 +13,10 @@ public static class PropertyKeys public const string Version = "Version"; public const string VersionFile = "VersionFile"; + public const string VersionUrl = "VersionUrl"; + public const string AutoUpdateCheck = "AutoUpdateCheck"; + public const string UpdateUrlTemplate = "UpdateUrlTemplate"; + public const string BootstrapUrlTemplate = "BootstrapUrlTemplate"; public const string Website = "Website"; public const string UserName = "UserName"; @@ -21,6 +25,7 @@ public static class PropertyKeys public const string BenchDrive = "BenchDrive"; public const string BenchRoot = "BenchRoot"; public const string BenchAuto = "BenchAuto"; + public const string BenchBin = "BenchBin"; public const string BenchScripts = "BenchScripts"; public const string CustomConfigDir = "CustomConfigDir"; @@ -30,8 +35,6 @@ public static class PropertyKeys public const string SiteConfigFileName = "SiteConfigFileName"; public const string SiteConfigTemplateFile = "SiteConfigTemplateFile"; - public const string AppIndexFile = "AppIndexFile"; - public const string CustomAppIndexFile = "CustomAppIndexFile"; public const string CustomAppIndexTemplateFile = "CustomAppIndexTemplateFile"; public const string AppActivationFile = "AppActivationFile"; public const string AppActivationTemplateFile = "AppActivationTemplateFile"; @@ -40,22 +43,32 @@ public static class PropertyKeys public const string AppVersionIndexDir = "AppVersionIndexDir"; public const string WizzardStartAutoSetup = "WizzardStartAutoSetup"; + public const string WizzardApps = "WizzardApps"; + public const string WizzardSelectedApps = "WizzardSelectedApps"; public const string QuickAccessCmd = "QuickAccessCmd"; public const string QuickAccessPowerShell = "QuickAccessPowerShell"; public const string QuickAccessBash = "QuickAccessBash"; + public const string DashboardSetupAppListColumns = "DashboardSetupAppListColumns"; + public const string ConEmuConfigFile = "ConEmuConfigFile"; public const string ConEmuConfigTemplateFile = "ConEmuConfigTemplateFile"; public const string TempDir = "TempDir"; public const string DownloadDir = "DownloadDir"; public const string LibDir = "LibDir"; + public const string AppLibs = "AppLibs"; + public const string AppLibsDir = "AppLibsDir"; + public const string AppLibsDownloadDir = "AppLibsDownloadDir"; + public const string AppLibIndexFileName = "AppLibIndexFileName"; + public const string AppLibCustomScriptDirName = "AppLibCustomScriptDirName"; + public const string AppLibResourceDirName = "AppLibResourceDirName"; + public const string KnownLicenses = "KnownLicenses"; public const string LogDir = "LogDir"; + public const string LogFile = "LogFile"; public const string LogLevel = "LogLevel"; - public const string AppResourceBaseDir = "AppResourceBaseDir"; public const string AppAdornmentBaseDir = "AppAdornmentBaseDir"; public const string AppRegistryBaseDir = "AppRegistryBaseDir"; - public const string ActionDir = "ActionDir"; public const string LauncherDir = "LauncherDir"; public const string LauncherScriptDir = "LauncherScriptDir"; public const string HomeDir = "HomeDir"; @@ -80,20 +93,24 @@ public static class PropertyKeys public const string DownloadAttempts = "DownloadAttempts"; public const string ParallelDownloads = "ParallelDownloads"; - public const string EditorApp = "EditorApp"; + public const string TextEditorApp = "TextEditorApp"; + public const string MarkdownEditorApp = "MarkdownEditorApp"; // Common App Properties + public const string AppIsActive = "IsActive"; public const string AppIsActivated = "IsActivated"; public const string AppIsDeactivated = "IsDeactivated"; public const string AppIsRequired = "IsRequired"; public const string AppIsDependency = "IsDependency"; - public const string AppLabel = "Label"; public const string AppTyp = "Typ"; public const string AppVersion = "Version"; + public const string AppInstalledVersion = "InstalledVersion"; public const string AppWebsite = "Website"; public const string AppDocs = "Docs"; + public const string AppLicense = "License"; + public const string AppLicenseUrl = "LicenseUrl"; public const string AppDependencies = "Dependencies"; public const string AppResponsibilities = "Responsibilities"; public const string AppForce = "Force"; @@ -104,6 +121,8 @@ public static class PropertyKeys public const string AppExe = "Exe"; public const string AppAdornedExecutables = "AdornedExecutables"; public const string AppRegistryKeys = "RegistryKeys"; + public const string AppExeTest = "ExeTest"; + public const string AppExeTestArguments = "ExeTestArguments"; public const string AppLauncher = "Launcher"; public const string AppLauncherExecutable = "LauncherExecutable"; public const string AppLauncherArguments = "LauncherArguments"; @@ -111,6 +130,8 @@ public static class PropertyKeys // Default App Properties + public const string AppHasResource = "HasResource"; + public const string AppIsResourceCached = "IsResourceCached"; public const string AppUrl = "Url"; public const string AppDownloadHeaders = "DownloadHeaders"; public const string AppDownloadCookies = "DownloadCookies"; diff --git a/BenchManager/BenchLib/RemoteExecHost/ExecutionRequest.cs b/BenchManager/BenchLib/RemoteExecHost/ExecutionRequest.cs new file mode 100644 index 00000000..8b91c328 --- /dev/null +++ b/BenchManager/BenchLib/RemoteExecHost/ExecutionRequest.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Bench.RemoteExecHost +{ + /// + /// A serializable execution request for remote execution hosts. + /// + [Serializable] + public class ExecutionRequest + { + /// + /// The directory in which the execution shell take place. + /// + public string WorkingDirectory; + + /// + /// An absolute path to the executable to run. + /// + public string Executable; + + /// + /// The command line argument string to pass to the executable. + /// + public string Arguments; + + /// + /// Initializes a new instance of . + /// + /// An absolute path to the working directory for the execution. + /// An absolute path to the executable. + /// The command line argument string. + public ExecutionRequest(string wd, string cmd, string cmdArgs) + { + WorkingDirectory = wd; + Executable = cmd; + Arguments = cmdArgs; + } + } +} diff --git a/BenchManager/BenchLib/RemoteExecHost/ExecutionResult.cs b/BenchManager/BenchLib/RemoteExecHost/ExecutionResult.cs new file mode 100644 index 00000000..38f6efd7 --- /dev/null +++ b/BenchManager/BenchLib/RemoteExecHost/ExecutionResult.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Bench.RemoteExecHost +{ + /// + /// A serializable execution result for remote execution hosts. + /// + [Serializable] + public class ExecutionResult + { + /// + /// The exit code of the executed process. + /// + public readonly int ExitCode; + + /// + /// An absolute path to a text file, containing the transcript of the process output. + /// + public readonly string TranscriptPath; + + /// + /// Initializes a new instance of . + /// + /// The exit code of the process. + /// An absolute path to the transcript file. + public ExecutionResult(int exitCode, string transcriptPath) + { + ExitCode = exitCode; + TranscriptPath = transcriptPath; + } + } +} diff --git a/BenchManager/BenchLib/RemoteExecHost/IRemoteExecHost.cs b/BenchManager/BenchLib/RemoteExecHost/IRemoteExecHost.cs new file mode 100644 index 00000000..39a3f267 --- /dev/null +++ b/BenchManager/BenchLib/RemoteExecHost/IRemoteExecHost.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mastersign.Bench.RemoteExecHost +{ + /// + /// The interface for a remote execution host. + /// + public interface IRemoteExecHost + { + /// + /// Does nothing but checking the communication roundtrip. + /// + /// Some string, not null. + string Ping(); + + /// + /// Requests the execution of a program. + /// + /// The request parameter. + /// The execution result. + ExecutionResult Execute(ExecutionRequest requ); + + /// + /// Requests the execution host to reload the Bench configuration. + /// + void Reload(); + + /// + /// Requests the execution host to shut down. + /// + void Shutdown(); + } +} diff --git a/BenchManager/BenchLib/RemoteExecHost/RemoteCommand.cs b/BenchManager/BenchLib/RemoteExecHost/RemoteCommand.cs new file mode 100644 index 00000000..e16e889a --- /dev/null +++ b/BenchManager/BenchLib/RemoteExecHost/RemoteCommand.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Mastersign.Bench.RemoteExecHost +{ + /// + /// A class to represent a remotely requested command. + /// + public class RemoteCommand + { + /// + /// The type of this command. + /// + public RemoteCommandType Type; + + /// + /// Initializes a new instance of . + /// + /// The command type. + public RemoteCommand(RemoteCommandType type) + { + Type = type; + } + } + + /// + /// A class to represent a remotely requested test message. + /// + public class PingRequest : RemoteCommand + { + private string message; + + private ManualResetEvent e = new ManualResetEvent(false); + + /// + /// Initializes a new instance of . + /// + public PingRequest() + : base(RemoteCommandType.Ping) + { + } + + /// + /// Hands the response message to who ever calls . + /// + /// The response message. + public void NotifyResult(string message) + { + this.message = message; + e.Set(); + } + + /// + /// Gets the response message. + /// Blocks until the response was notified with . + /// + /// The notified message. + public string WaitForResult() + { + e.WaitOne(); + return message; + } + } + + /// + /// A class to represent a remotely requested process execution . + /// + public class RemoteExecutionRequest : RemoteCommand + { + /// + /// The parameter describing the execution. + /// + public ExecutionRequest Parameter; + + private ManualResetEvent e = new ManualResetEvent(false); + + private ExecutionResult result; + + /// + /// Initializes a new instance of . + /// + /// The parameter describing the execution. + public RemoteExecutionRequest(ExecutionRequest parameter) + : base(RemoteCommandType.Execution) + { + Parameter = parameter; + } + + /// + /// Hands the results of the execution to who ever calls . + /// + /// The execution result. + public void NotifyResult(ExecutionResult result) + { + this.result = result; + e.Set(); + } + + /// + /// Gets the result of the execution. + /// Blocks until the result was notified with . + /// + /// + public ExecutionResult WaitForResult() + { + e.WaitOne(); + return result; + } + } + + /// + /// The different remote command types. + /// + public enum RemoteCommandType + { + /// + /// A check request to test the communication (). + /// + Ping, + + /// + /// A process execution request (). + /// + Execution, + + /// + /// A request to reload the Bench configuration (). + /// + Reload, + + /// + /// A request to shutdown the remote execution host (). + /// + Shutdown, + } +} diff --git a/BenchManager/BenchLib/RemoteExecHost/RemoteExecHostClient.cs b/BenchManager/BenchLib/RemoteExecHost/RemoteExecHostClient.cs new file mode 100644 index 00000000..36511d8f --- /dev/null +++ b/BenchManager/BenchLib/RemoteExecHost/RemoteExecHostClient.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Remoting.Channels; +using System.Runtime.Remoting.Channels.Ipc; +using System.Text; +using System.Threading; + +namespace Mastersign.Bench.RemoteExecHost +{ + /// + /// A client to communicate via IPC with a remote execution host. + /// + public class RemoteExecHostClient : IDisposable + { + private IpcChannel ipcChannel; + + private IRemoteExecHost execHost; + + /// + /// Initialize a new instance of . + /// + /// A unique string to identify the server. + public RemoteExecHostClient(string token) + { + ipcChannel = new IpcChannel("Bench_ExecHost_Client_" + token); + ChannelServices.RegisterChannel(ipcChannel, false); + + execHost = (IRemoteExecHost)Activator.GetObject( + typeof(IRemoteExecHost), + "ipc://Bench_ExecHost_" + token + "/RemoteExecHost"); + } + + /// + /// The proxy object to communicate with the server. + /// + public IRemoteExecHost ExecHost => execHost; + + /// + /// Checks if this instance is already disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Disposes this instance. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + + execHost = null; + ChannelServices.UnregisterChannel(ipcChannel); + ipcChannel = null; + } + } +} diff --git a/BenchManager/BenchLib/RemoteExecHost/RemoteExecHostServer.cs b/BenchManager/BenchLib/RemoteExecHost/RemoteExecHostServer.cs new file mode 100644 index 00000000..65d65515 --- /dev/null +++ b/BenchManager/BenchLib/RemoteExecHost/RemoteExecHostServer.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Remoting; +using System.Runtime.Remoting.Channels; +using System.Runtime.Remoting.Channels.Ipc; +using System.Text; + +namespace Mastersign.Bench.RemoteExecHost +{ + /// + /// A server to host a remote execution host via IPC. + /// The command requests are queued and served statically in . + /// + public class RemoteExecHostServer : IDisposable + { + private IpcChannel ipcChannel; + + /// + /// Initializes a new instance of . + /// + /// A unique string to identify this server. + public RemoteExecHostServer(string token) + { + ipcChannel = new IpcChannel("Bench_ExecHost_" + token); + ChannelServices.RegisterChannel(ipcChannel, false); + RemotingConfiguration.RegisterWellKnownServiceType( + typeof(RemoteExecutionFacade), "RemoteExecHost", + WellKnownObjectMode.SingleCall); + } + + /// + /// Checks if this instance is already disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Shuts down the IPC server. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + + ChannelServices.UnregisterChannel(ipcChannel); + ipcChannel = null; + } + } +} diff --git a/BenchManager/BenchLib/RemoteExecHost/RemoteExecutionFacade.cs b/BenchManager/BenchLib/RemoteExecHost/RemoteExecutionFacade.cs new file mode 100644 index 00000000..4bcac095 --- /dev/null +++ b/BenchManager/BenchLib/RemoteExecHost/RemoteExecutionFacade.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Mastersign.Bench.RemoteExecHost +{ + /// + /// This class provides statically the queued remote commands and + /// dynamically serves as the SingleCall remote object + /// for . + /// + public class RemoteExecutionFacade : MarshalByRefObject, IRemoteExecHost + { + private static readonly Queue cmdQueue = new Queue(); + + private static readonly ManualResetEvent queueEvent = new ManualResetEvent(false); + + private static readonly object queueLock = new object(); + + /// + /// Waits for the next command which is received remotely. + /// Blocks until a command arrives. + /// + /// A remote command object. + public static RemoteCommand WaitForCommand() + { + var wait = false; + lock (queueLock) + { + if (cmdQueue.Count == 0) + { + queueEvent.Reset(); + wait = true; + } + } + if (wait) queueEvent.WaitOne(); + lock (queueLock) + { + return cmdQueue.Dequeue(); + } + } + + private static void EnqueueCommand(RemoteCommand cmd) + { + lock (queueLock) + { + cmdQueue.Enqueue(cmd); + queueEvent.Set(); + } + } + + /// + /// Does nothing but checking the communication roundtrip. + /// + /// Some string, not null. + public string Ping() + { + var cmd = new PingRequest(); + EnqueueCommand(cmd); + return cmd.WaitForResult(); + } + + /// + /// Requests the execution of a program. + /// + /// The request parameter. + /// The execution result. + public ExecutionResult Execute(ExecutionRequest requ) + { + var cmd = new RemoteExecutionRequest(requ); + EnqueueCommand(cmd); + return cmd.WaitForResult(); + } + + /// + /// Requests the execution host to reload the Bench configuration. + /// + public void Reload() + { + EnqueueCommand(new RemoteCommand(RemoteCommandType.Reload)); + } + + /// + /// Requests the execution host to shut down. + /// + public void Shutdown() + { + EnqueueCommand(new RemoteCommand(RemoteCommandType.Shutdown)); + } + } +} diff --git a/BenchManager/BenchLib/DefaultExecutionHost.cs b/BenchManager/BenchLib/SimpleExecutionHost.cs similarity index 85% rename from BenchManager/BenchLib/DefaultExecutionHost.cs rename to BenchManager/BenchLib/SimpleExecutionHost.cs index d33569f3..dfe8bc51 100644 --- a/BenchManager/BenchLib/DefaultExecutionHost.cs +++ b/BenchManager/BenchLib/SimpleExecutionHost.cs @@ -9,11 +9,11 @@ namespace Mastersign.Bench { /// - /// This class is the default implementation of . + /// This class is a simple implementation of . /// It starts processes invisible in the background /// and allows no user interaction during the process execution. /// - public class DefaultExecutionHost : IProcessExecutionHost + public class SimpleExecutionHost : IProcessExecutionHost { /// /// Starts a Windows process in an asynchronous fashion. @@ -31,12 +31,20 @@ public void StartProcess(BenchEnvironment env, { if (IsDisposed) { - throw new ObjectDisposedException(nameof(DefaultExecutionHost)); + throw new ObjectDisposedException(nameof(SimpleExecutionHost)); } - AsyncManager.StartTask(() => + if (cb != null) { - cb(RunProcess(env, cwd, exe, arguments, monitoring)); - }); + AsyncManager.StartTask(() => + { + cb(RunProcess(env, cwd, exe, arguments, monitoring)); + }); + } + else + { + StringBuilder sbStd, sbErr; + StartProcess(env, cwd, exe, arguments, false, out sbStd, out sbErr); + } } /// @@ -56,8 +64,36 @@ public ProcessExecutionResult RunProcess(BenchEnvironment env, { if (IsDisposed) { - throw new ObjectDisposedException(nameof(DefaultExecutionHost)); + throw new ObjectDisposedException(nameof(SimpleExecutionHost)); + } + var collectOutput = (monitoring & ProcessMonitoring.Output) == ProcessMonitoring.Output; + StringBuilder sbStd, sbErr; + var p = StartProcess(env, cwd, exe, arguments, collectOutput, out sbStd, out sbErr); + p.WaitForExit(); + if (collectOutput) + { + string output; + try + { + output = FormatOutput(sbStd.ToString()) + + Environment.NewLine + + FormatOutput(sbErr.ToString()); + } + catch (Exception e) + { + output = e.ToString(); + } + return new ProcessExecutionResult(p.ExitCode, output); + } + else + { + return new ProcessExecutionResult(p.ExitCode); } + } + + private Process StartProcess(BenchEnvironment env, string cwd, string exe, string arguments, + bool collectOutput, out StringBuilder stdOut, out StringBuilder errOut) + { var p = new Process(); if (!File.Exists(exe)) { @@ -68,7 +104,6 @@ public ProcessExecutionResult RunProcess(BenchEnvironment env, throw new DirectoryNotFoundException("The working directory could not be found: " + cwd); } PreparePowerShellScriptExecution(ref exe, ref arguments); - var collectOutput = (monitoring & ProcessMonitoring.Output) == ProcessMonitoring.Output; StringBuilder sbStd = null; StringBuilder sbErr = null; var si = new ProcessStartInfo(exe, arguments); @@ -92,26 +127,9 @@ public ProcessExecutionResult RunProcess(BenchEnvironment env, p.BeginOutputReadLine(); p.BeginErrorReadLine(); } - p.WaitForExit(); - if (collectOutput) - { - string output; - try - { - output = FormatOutput(sbStd.ToString()) - + Environment.NewLine - + FormatOutput(sbErr.ToString()); - } - catch (Exception e) - { - output = e.ToString(); - } - return new ProcessExecutionResult(p.ExitCode, output); - } - else - { - return new ProcessExecutionResult(p.ExitCode); - } + stdOut = sbStd; + errOut = sbErr; + return p; } /// diff --git a/BenchManager/BenchLib/TaskInfo.cs b/BenchManager/BenchLib/TaskInfo.cs index a408e102..d14d1a3f 100644 --- a/BenchManager/BenchLib/TaskInfo.cs +++ b/BenchManager/BenchLib/TaskInfo.cs @@ -42,19 +42,26 @@ public class TaskInfo /// public string DetailedMessage { get; private set; } + /// + /// The output of executed processes, or null. + /// + public string ConsoleOutput { get; private set; } + /// /// Initializes a new instance of . /// /// A short message desccribing the event. /// The ID of the afected app or null. + /// The output of executed programs or null. /// A detailed message describing the event or null. - public TaskInfo(string message, string appId = null, string detailedMessage = null) + public TaskInfo(string message, string appId = null, string detailedMessage = null, string consoleOutput = null) { if (message == null) throw new ArgumentNullException("message"); Timestamp = DateTime.Now; AppId = appId; Message = message; DetailedMessage = detailedMessage; + ConsoleOutput = consoleOutput; } } @@ -113,9 +120,11 @@ public class TaskError : TaskInfo /// A short message describing the error. /// Th ID of the affected app or null. /// A detailed message describing the error or null. + /// The output of executed programs or null. /// The execption that caused the error or null. - public TaskError(string message, string appId = null, string detailedMessage = null, Exception exception = null) - : base(message, appId, detailedMessage) + public TaskError(string message, string appId = null, string detailedMessage = null, + string consoleOutput = null, Exception exception = null) + : base(message, appId, detailedMessage, consoleOutput) { Exception = exception; } diff --git a/BenchManager/BenchLib/TaskInfoLogger.cs b/BenchManager/BenchLib/TaskInfoLogger.cs index 6b3a99c6..6ed814d2 100644 --- a/BenchManager/BenchLib/TaskInfoLogger.cs +++ b/BenchManager/BenchLib/TaskInfoLogger.cs @@ -10,11 +10,9 @@ internal class TaskInfoLogger : IDisposable private bool onlyErrors; private TextWriter writer; - public TaskInfoLogger(string logDir, bool onlyErrors) + public TaskInfoLogger(string file, bool onlyErrors) { this.onlyErrors = onlyErrors; - FileSystem.AsureDir(logDir); - var file = Path.Combine(logDir, DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + "_setup.txt"); writer = new StreamWriter(file, false, Encoding.UTF8); } @@ -36,12 +34,18 @@ public void Log(TaskInfo info) info is TaskError ? "ERROR" : "INFO", info.AppId ?? "global", info.Message); - if (info.DetailedMessage != null) + if (!string.IsNullOrEmpty(info.DetailedMessage)) { writer.WriteLine("[{0}] DETAIL:", info.Timestamp.ToString("yyyy-MM-dd HH-mm-ss")); writer.WriteLine(info.DetailedMessage); } + if (!string.IsNullOrEmpty(info.ConsoleOutput)) + { + writer.WriteLine("[{0}] CONSOLE:", + info.Timestamp.ToString("yyyy-MM-dd HH-mm-ss")); + writer.WriteLine(info.ConsoleOutput); + } var err = info as TaskError; if (err != null) { diff --git a/BenchManager/BenchLib/UI/AppSelectionStepControl.Designer.cs b/BenchManager/BenchLib/UI/AppSelectionStepControl.Designer.cs new file mode 100644 index 00000000..908bf978 --- /dev/null +++ b/BenchManager/BenchLib/UI/AppSelectionStepControl.Designer.cs @@ -0,0 +1,60 @@ +namespace Mastersign.Bench.UI +{ + partial class AppSelectionStepControl + { + /// + /// Erforderliche Designervariable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Verwendete Ressourcen bereinigen. + /// + /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Vom Komponenten-Designer generierter Code + + /// + /// Erforderliche Methode für die Designerunterstützung. + /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. + /// + private void InitializeComponent() + { + this.clstAppSelection = new System.Windows.Forms.CheckedListBox(); + this.SuspendLayout(); + // + // clstAppSelection + // + this.clstAppSelection.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.clstAppSelection.CheckOnClick = true; + this.clstAppSelection.FormattingEnabled = true; + this.clstAppSelection.Location = new System.Drawing.Point(15, 15); + this.clstAppSelection.Name = "clstAppSelection"; + this.clstAppSelection.Size = new System.Drawing.Size(434, 139); + this.clstAppSelection.TabIndex = 0; + // + // AppSelectionStepControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.clstAppSelection); + this.Name = "AppSelectionStepControl"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.CheckedListBox clstAppSelection; + } +} diff --git a/BenchManager/BenchLib/UI/AppSelectionStepControl.cs b/BenchManager/BenchLib/UI/AppSelectionStepControl.cs new file mode 100644 index 00000000..86e0232b --- /dev/null +++ b/BenchManager/BenchLib/UI/AppSelectionStepControl.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Text; +using System.Windows.Forms; +using static Mastersign.Sequence.Sequence; + +namespace Mastersign.Bench.UI +{ + /// + /// A wizzard control, which allows the user to pre-select apps before the setup. + /// + internal partial class AppSelectionStepControl : WizzardStepControl + { + public AppSelectionStepControl() + { + Description = "App/Group Selection..."; + InitializeComponent(); + } + + private Dictionary appLookup; + + public void InitializeStepControl(KeyValuePair[] apps) + { + appLookup = Seq(apps).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + clstAppSelection.Items.Clear(); + Seq(apps).ForEach(kvp => clstAppSelection.Items.Add(kvp.Key)); + } + + public string[] SelectedApps + { + get + { + return Seq(clstAppSelection.CheckedItems) + .Map((key, i) => appLookup[key]) + .ToArray(); + } + } + } +} diff --git a/BenchManager/BenchLib/UI/AppSelectionStepControl.resx b/BenchManager/BenchLib/UI/AppSelectionStepControl.resx new file mode 100644 index 00000000..7080a7d1 --- /dev/null +++ b/BenchManager/BenchLib/UI/AppSelectionStepControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BenchManager/BenchLib/UI/InitializeConfigTask.cs b/BenchManager/BenchLib/UI/InitializeConfigTask.cs index 477806b1..7e8556d5 100644 --- a/BenchManager/BenchLib/UI/InitializeConfigTask.cs +++ b/BenchManager/BenchLib/UI/InitializeConfigTask.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Text; +using static Mastersign.Sequence.Sequence; using Mastersign.Bench.Markdown; namespace Mastersign.Bench.UI @@ -11,6 +12,7 @@ internal class InitializeConfigTask : WizzardTask private ProxyStepControl stepProxy; private UserIdentificationStepControl stepUserIdentification; private ExistingConfigStepControl stepExistingConfig; + private AppSelectionStepControl stepAppSeletion; private AdvancedStepControl stepAdvanced; private readonly BenchConfiguration config; @@ -40,6 +42,7 @@ public override WizzardStepControl[] StepControls if (InitCustomConfig) { steps.Add(stepExistingConfig); + steps.Add(stepAppSeletion); } steps.Add(stepAdvanced); return steps.ToArray(); @@ -67,6 +70,12 @@ public override void Before() { stepExistingConfig = new ExistingConfigStepControl(); stepExistingConfig.IsConfigGitRepoExisting = false; + + stepAppSeletion = new AppSelectionStepControl(); + stepAppSeletion.InitializeStepControl( + Seq(config.GetStringListValue(PropertyKeys.WizzardApps)) + .Map(DictionaryValueResolver.ParseKeyValuePair) + .ToArray()); } stepAdvanced = new AdvancedStepControl(); stepAdvanced.StartAutoSetup = config.GetBooleanValue( @@ -81,7 +90,7 @@ public override void After() foreach (var e in stepProxy.ProxyBypass) bypassList.Add("`" + e + "`"); var updates = new Dictionary { - {PropertyKeys.UserName, stepUserIdentification.UserName }, + { PropertyKeys.UserName, stepUserIdentification.UserName }, { PropertyKeys.UserEmail, stepUserIdentification.UserEmail }, { PropertyKeys.UseProxy, stepProxy.UseProxy ? "true" : "false" }, { PropertyKeys.HttpProxy, stepProxy.HttpProxy }, @@ -102,7 +111,6 @@ public override void After() File.Copy(siteConfigTemplateFile, defaultSiteConfigFile, false); MarkdownPropertyEditor.UpdateFile(defaultSiteConfigFile, updates); } - if (InitCustomConfig) { if (stepExistingConfig.IsConfigGitRepoExisting) @@ -113,6 +121,7 @@ public override void After() { config.SetValue(PropertyKeys.CustomConfigRepository, (object)null); } + config.SetValue(PropertyKeys.WizzardSelectedApps, stepAppSeletion.SelectedApps); } config.SetValue(PropertyKeys.WizzardStartAutoSetup, stepAdvanced.StartAutoSetup); diff --git a/BenchManager/BenchLib/packages.config b/BenchManager/BenchLib/packages.config index 91662575..40e31675 100644 --- a/BenchManager/BenchLib/packages.config +++ b/BenchManager/BenchLib/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/BenchManager/BenchManager.sln b/BenchManager/BenchManager.sln index ec6ac068..280015a2 100644 --- a/BenchManager/BenchManager.sln +++ b/BenchManager/BenchManager.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchLib.Test", "BenchLib.T EndProject Project("{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}") = "BenchLibDocs", "BenchLibDocs\BenchLibDocs.shfbproj", "{64100396-695D-4424-8A39-9BA1849C49C7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchCLI", "BenchCLI\BenchCLI.csproj", "{64E94A41-026F-473C-BC48-70F8D5EB977A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +33,10 @@ Global {B3109E41-23AA-4B9A-B701-4D2463B1EF1E}.Release|Any CPU.Build.0 = Release|Any CPU {64100396-695D-4424-8A39-9BA1849C49C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {64100396-695D-4424-8A39-9BA1849C49C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64E94A41-026F-473C-BC48-70F8D5EB977A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64E94A41-026F-473C-BC48-70F8D5EB977A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64E94A41-026F-473C-BC48-70F8D5EB977A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64E94A41-026F-473C-BC48-70F8D5EB977A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/BenchManager/scripts/bench-bash.cmd b/BenchManager/scripts/bench-bash.cmd new file mode 100644 index 00000000..e315bc44 --- /dev/null +++ b/BenchManager/scripts/bench-bash.cmd @@ -0,0 +1,12 @@ +@ECHO OFF +CALL "%~dp0\..\..\env.cmd" + +IF "_%1_" == "__" ( + ECHO.BENCH v%BENCH_VERSION% BASH +) + +IF EXIST "%BENCH_APPS%\git\bin\bash.exe" ( + CALL "%BENCH_APPS%\git\bin\bash.exe" %* +) ELSE ( + ECHO.No Bash executable found. Is Git installed? +) diff --git a/actions/bench-cmd.cmd b/BenchManager/scripts/bench-cmd.cmd similarity index 71% rename from actions/bench-cmd.cmd rename to BenchManager/scripts/bench-cmd.cmd index 1824925b..d8c033b6 100644 --- a/actions/bench-cmd.cmd +++ b/BenchManager/scripts/bench-cmd.cmd @@ -1,9 +1,11 @@ @ECHO OFF -CALL "%~dp0\..\env.cmd" -CD /D "%~dp0\.." +CALL "%~dp0\..\..\env.cmd" + IF "_%1_" == "__" ( ECHO.BENCH v%BENCH_VERSION% CMD ECHO. CMD /D + GOTO:EOF ) + CALL CMD /D /C %* diff --git a/BenchManager/scripts/bench-ps.cmd b/BenchManager/scripts/bench-ps.cmd new file mode 100644 index 00000000..69f205b5 --- /dev/null +++ b/BenchManager/scripts/bench-ps.cmd @@ -0,0 +1,11 @@ +@ECHO OFF +CALL "%~dp0\..\..\env.cmd" + +IF "_%1_" == "__" ( + ECHO.BENCH v%BENCH_VERSION% PowerShell + ECHO. + powershell -NoLogo -NoProfile -ExecutionPolicy Unrestricted -NoExit + GOTO:EOF +) + +powershell -NoLogo -NoProfile -ExecutionPolicy Unrestricted %* diff --git a/auto/runps.cmd b/BenchManager/scripts/runps.cmd similarity index 90% rename from auto/runps.cmd rename to BenchManager/scripts/runps.cmd index e53a80aa..8d2f724e 100644 --- a/auto/runps.cmd +++ b/BenchManager/scripts/runps.cmd @@ -1,6 +1,6 @@ @ECHO OFF Setlocal EnableDelayedExpansion -SET SCRIPT=%~dp0lib\%1 +SET SCRIPT=%~dp0..\lib\%1 SHIFT SET "args=" @@ -22,4 +22,4 @@ IF DEFINED args SET "args=%args:~1%" REM ECHO.%SCRIPT% REM ECHO.%args% -powershell -NoLogo -NoProfile -ExecutionPolicy Unrestricted "& ('%SCRIPT%')" %args% +powershell -NoLogo -NoProfile -ExecutionPolicy Unrestricted "& ('%SCRIPT%')" %args% \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d7731390..72f90365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,15 +18,82 @@ This document follows the guidelines in http://keepachangelog.md. Use the following change groups: Added, Changed, Deprecated, Removed, Fixed, Security Add a link to the GitHub diff like -[Full Changelog](https://github.com/mastersign/bench/compare/v...v) +[]: https://github.com/mastersign/bench/compare/v...v --> ## [Unreleased] -[Dev Changes](https://github.com/mastersign/bench/compare/master...dev), -[App Changes](https://github.com/mastersign/bench/compare/master...apps) + +[Unreleased]: https://github.com/mastersign/bench/compare/master...dev + +## [0.14.0] - 2017-01-27 + +[0.14.0]: https://github.com/mastersign/bench/compare/v0.13.3...v0.14.0 + +### Added +- Bench CLI + ([#87](https://github.com/mastersign/bench/issues/87)) +- Update check in the About dialog of _BenchDashboard_ +- _Upgrade Bench_ entry in the _Setup_ menu of the setup dialog of _BenchDashboard_ +- Configurable columns to the Setup dialog of _BenchDashboard_ +- Config properties + + `VersionUrl` + + `UpdateUrlTemplate` + + `BootstrapUrlTemplate` + + `AutoUpdateCheck` + + `KnownLicenses` +- Support for multiple app libraries + ([#90](https://github.com/mastersign/bench/issues/90)) + Support for license infos + ([#91](https://github.com/mastersign/bench/issues/91)) +- Namespaces for app IDs +- Config properties: + + `AppLibs` + (this property must be overriden in the user or site config, to load more than the core app library) + + `AppLibsDownloadDir` + + `AppLibsDir` + + `AppLibIndexFileName` + + `AppLibCustomScriptDirName` + + `AppLibResourceDirName` + + `License` + + `LicenseUrl` +- _Update App Libraries and Apps_ entry in the _Setup_ menu of the setup dialog of _BenchDashboard_ +- Markdown info text in the app info dialog +- License link to the app info dialog +- Additional columns to the setup dialog + + Category + + Library + + License + +### Changed +- Upgrade process is using the _Bench CLI_ now + ([#84](https://github.com/mastersign/bench/issues/84)) +- Directory for custom scripts in the user app library + was moved from `config\apps` to `config\scripts` +- Moved app definitions into their own Git repositories + + + + +- In the future, all app related issues are attended at the app library repositories on GitHub +- Improved performance for app selection in the Setup Dialog of BenchDashboard + +### Fixed +- Proxy setup for Maven + ([#89](https://github.com/mastersign/bench/issues/89)) +- Download view in the Setup Dialog of the BenchDashboard + ([#81](https://github.com/mastersign/bench/issues/81)) + +### Removed +- Script based actions +- Embedded app library + **(the `AppLibs` property must be overriden in the user or site config now, to load more than the core app library)** +- Config properties + + `ActionDir` + + `AppIndexFile` + + `CustomAppIndexFile` + + `AppResourceBaseDir` ## [0.13.3] - 2016-11-19 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.13.2...v0.13.3) + +[0.13.3]: https://github.com/mastersign/bench/compare/v0.13.2...v0.13.3 ### Added - Support for second argument `verbose` in `bench-ctl` @@ -41,7 +108,8 @@ Add a link to the GitHub diff like ([#86](https://github.com/mastersign/bench/issues/86)) ## [0.13.2] - 2016-10-22 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.13.1...v0.13.2) + +[0.13.2]: https://github.com/mastersign/bench/compare/v0.13.1...v0.13.2 ### Fixed - Switched from expanded strings to simple strings when setting @@ -49,7 +117,8 @@ Add a link to the GitHub diff like ([#82](https://github.com/mastersign/bench/issues/82)) ## [0.13.1] - 2016-10-19 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.13.0...v0.13.1) + +[0.13.1]: https://github.com/mastersign/bench/compare/v0.13.0...v0.13.1 ### Added - Added Scribus @@ -71,7 +140,8 @@ Add a link to the GitHub diff like - Update: Blender from 2.77a to 2.78 ## [0.13.0] - 2016-08-16 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.12.1...v0.13.0) + +[0.13.0]: https://github.com/mastersign/bench/compare/v0.12.1...v0.13.0 ### Added - Configuration property `UseRegistryIsolation` to provide a way @@ -125,14 +195,16 @@ Add a link to the GitHub diff like ([#67](https://github.com/mastersign/bench/issues/67)) ## [0.12.1] - 2016-07-29 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.12.0...v0.12.1) + +[0.12.1]: https://github.com/mastersign/bench/compare/v0.12.0...v0.12.1 ### Fixed - Environment setup fails if the user has no `PATH` environment variable ([#76](https://github.com/mastersign/bench/issues/76)) ## [0.12.0] - 2016-07-27 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.11.4...v0.12.0) + +[0.12.0]: https://github.com/mastersign/bench/compare/v0.11.4...v0.12.0 ### Added - Support for custom scripts in `config\apps` @@ -153,7 +225,8 @@ Add a link to the GitHub diff like ([#73](https://github.com/mastersign/bench/issues/73)) ## [0.11.4] - 2016-07-04 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.11.3...v0.11.4) + +[0.11.4]: https://github.com/mastersign/bench/compare/v0.11.3...v0.11.4 ### Changed - Update: Node.js from 4.4.6 to 6.2.2 @@ -162,13 +235,15 @@ Add a link to the GitHub diff like - UI performance when activating/deactivating apps ## [0.11.3] - 2016-07-02 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.11.2...v0.11.3) + +[0.11.3]: https://github.com/mastersign/bench/compare/v0.11.2...v0.11.3 ### Fixed - Fixed execution policy for running `PsExecHost.ps1` ## [0.11.2] - 2016-06-30 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.11.1...v0.11.2) + +[0.11.2]: https://github.com/mastersign/bench/compare/v0.11.1...v0.11.2 ### Changed - Update: ConEmu from 16.03.13 to 16.06.19 @@ -179,7 +254,8 @@ Add a link to the GitHub diff like - Focus flicker of GUI app ## [0.11.1] - 2016-06-25 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.11.0...v0.11.1) + +[0.11.1]: https://github.com/mastersign/bench/compare/v0.11.0...v0.11.1 ### Added - Bench configuration property `Website` @@ -199,7 +275,8 @@ Add a link to the GitHub diff like - Custom setup script of Leiningen ## [0.11.0] - 2016-06-18 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.8...v0.11.0) + +[0.11.0]: https://github.com/mastersign/bench/compare/v0.10.8...v0.11.0 ### Added - Custom config dictionary property `Environment` to add environment variables @@ -243,14 +320,16 @@ Add a link to the GitHub diff like ([#61](https://github.com/mastersign/bench/issues/61)) ## [0.10.8] - 2016-05-27 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.7...v0.10.8) + +[0.10.8]: https://github.com/mastersign/bench/compare/v0.10.7...v0.10.8 ### Fixed - Mark Git as required, if an existing custom config needs to be cloned ([#55](https://github.com/mastersign/bench/issues/55)) ## [0.10.7] - 2016-05-27 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.6...v0.10.7) + +[0.10.7]: https://github.com/mastersign/bench/compare/v0.10.6...v0.10.7 ### Added - Setup action for downloading all app resources @@ -275,7 +354,8 @@ Add a link to the GitHub diff like ([#53](https://github.com/mastersign/bench/issues/53)) ## [0.10.6] - 2016-05-24 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.5...v0.10.6) + +[0.10.6]: https://github.com/mastersign/bench/compare/v0.10.5...v0.10.6 ### Added - Launcher for 7-Zip file manager @@ -291,7 +371,8 @@ Add a link to the GitHub diff like - Initializing/Upgrading the Bench environment ## [0.10.5] - 2016-05-17 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.4...v0.10.5) + +[0.10.5]: https://github.com/mastersign/bench/compare/v0.10.4...v0.10.5 ### Added - Configuration properties to control the appearance of shell launchers @@ -306,7 +387,8 @@ Add a link to the GitHub diff like ([#44](https://github.com/mastersign/bench/issues/44)) ## [0.10.4] - 2016-05-14 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.3...v0.10.4) + +[0.10.4]: https://github.com/mastersign/bench/compare/v0.10.3...v0.10.4 ### Added - Content tree in Markdown viewer @@ -330,7 +412,8 @@ Add a link to the GitHub diff like - GitKraken Resource Url ## [0.10.3] - 2016-05-13 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.2...v0.10.3) + +[0.10.3]: https://github.com/mastersign/bench/compare/v0.10.2...v0.10.3 ### Added - Added GitKraken (latest) @@ -342,7 +425,8 @@ Add a link to the GitHub diff like ([#42](https://github.com/mastersign/bench/issues/42)) ## [0.10.2] - 2016-05-09 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.1...v0.10.2) + +[0.10.2]: https://github.com/mastersign/bench/compare/v0.10.1...v0.10.2 ### Added - Support for Ruby Gems (app typ `ruby-package`) @@ -350,7 +434,8 @@ Add a link to the GitHub diff like - Added SASS (latest) ## [0.10.1] - 2016-05-08 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.10.0...v0.10.1) + +[0.10.1]: https://github.com/mastersign/bench/compare/v0.10.0...v0.10.1 ### Fixed - Installing NodeJS and Python packages via _Bench Dashboard_ @@ -362,7 +447,8 @@ Add a link to the GitHub diff like + `Maven` properties ## [0.10.0] - 2016-05-07 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.9.3...v0.10.0) + +[0.10.0]: https://github.com/mastersign/bench/compare/v0.9.3...v0.10.0 For this release a clean install is required and the configuration must be migrated to the new format. @@ -401,7 +487,8 @@ must be migrated to the new format. - Spacemacs under path with whitespaces ## [0.9.3] - 2016-04-13 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.9.2...v0.9.3) + +[0.9.3]: https://github.com/mastersign/bench/compare/v0.9.2...v0.9.3 ### Added - App: Maven @@ -423,7 +510,8 @@ must be migrated to the new format. ([#34](https://github.com/mastersign/bench/issues/34)) ## [0.9.2] 2016-03-07 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.9.1...v0.9.2) + +[0.9.2]: https://github.com/mastersign/bench/compare/v0.9.1...v0.9.2 ### Added - App: JRE 7 @@ -435,7 +523,8 @@ must be migrated to the new format. - MSYS tools from MinGW are not in PATH ## [0.9.1] - 2016-03-03 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.9.0...v0.9.1) + +[0.9.1]: https://github.com/mastersign/bench/compare/v0.9.0...v0.9.1 ### Added - support for Windows 7 with PowerShell 2 @@ -460,7 +549,8 @@ must be migrated to the new format. ([#31](https://github.com/mastersign/bench/issues/31)) ## [0.9.0] - 2016-03-02 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.8.0...v0.9.0) + +[0.9.0]: https://github.com/mastersign/bench/compare/v0.8.0...v0.9.0 ### Added - support for unpacking `*.tar.*` archives with two steps @@ -498,7 +588,8 @@ must be migrated to the new format. - cancel downloads if one download failes ## [0.8.0] - 2016-02-15 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.7.2...v0.8.0) + +[0.8.0]: https://github.com/mastersign/bench/compare/v0.7.2...v0.8.0 ### Added - `auto\lib\profile.ps1` for customization of the PowerShell environment @@ -526,7 +617,8 @@ must be migrated to the new format. - expansion of config values `AppAdornmentBaseDir` and `AppRegistryBaseDir` ## [0.7.2] - 2016-02-12 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.7.1...v0.7.2) + +[0.7.2]: https://github.com/mastersign/bench/compare/v0.7.1...v0.7.2 ### Added - Passing arguments to _Command Line_ launcher with `/C`, to allow @@ -538,13 +630,15 @@ must be migrated to the new format. - Update: Visual Studio Code Archive Pattern ## [0.7.1] - 2016-02-12 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.7.0...v0.7.1) + +[0.7.1]: https://github.com/mastersign/bench/compare/v0.7.0...v0.7.1 ### Fixed - FFmpeg exe path ## [0.7.0] - 2016-02-12 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.6.1...v0.7.0) + +[0.7.0]: https://github.com/mastersign/bench/compare/v0.6.1...v0.7.0 ### Added - `CHANGELOG.md` @@ -579,7 +673,8 @@ must be migrated to the new format. + `auto\bench-upgrade.cmd` ## [0.6.1] - 2016-02-07 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.6.0...v0.6.1) + +[0.6.1]: https://github.com/mastersign/bench/compare/v0.6.0...v0.6.1 ### Added - Automatic update of PIP to most recent version after installing Python 2 and Python 3 @@ -591,7 +686,8 @@ must be migrated to the new format. ([#15](https://github.com/mastersign/bench/issues/15)) ## [0.6.0] - 2016-01-27 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.5.4...v0.6.0) + +[0.6.0]: https://github.com/mastersign/bench/compare/v0.5.4...v0.6.0 ### Added - actions scripts for _CMD_, _PowerShell_, and _Bash_ @@ -602,7 +698,8 @@ must be migrated to the new format. - updated documentation of overridden environment variables ## [0.5.4] - 2016-01-27 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.5.3...v0.5.4) + +[0.5.4]: https://github.com/mastersign/bench/compare/v0.5.3...v0.5.4 ### Change - Removed home directory from Git version control to prevent data loss @@ -611,19 +708,22 @@ must be migrated to the new format. to `res\apps\vscode` ## [0.5.3] - 2016-01-27 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.5.2...v0.5.3) + +[0.5.3]: https://github.com/mastersign/bench/compare/v0.5.2...v0.5.3 ### Fixed - Building relative paths in `env.cmd` ## [0.5.2] - 2016-01-27 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.5.2...v0.5.2) + +[0.5.2]: https://github.com/mastersign/bench/compare/v0.5.2...v0.5.2 ### Fixed - Incorrect quotes for path strings in `config.template.ps1` ## [0.5.1] - 2016-01-27 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.5.0...v0.5.1) + +[0.5.1]: https://github.com/mastersign/bench/compare/v0.5.0...v0.5.1 ### Changed - Moved Spacemacs config file from `%HOME%\.spacemacs` to `%HOME%\.spacemacs.d\init.el` @@ -639,7 +739,8 @@ must be migrated to the new format. - Update: LightTable from 0.8.0 to 0.8.1 ## [0.5.0] - 2016-01-22 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.4.0...v0.5.0) + +[0.5.0]: https://github.com/mastersign/bench/compare/v0.4.0...v0.5.0 ### Added - Support for launcher shortcuts @@ -659,7 +760,8 @@ must be migrated to the new format. - Do not empty the temp directory while loading `env.lib.ps1` ## [0.4.0] - 2016-01-20 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.3.4...v0.4.0) + +[0.4.0]: https://github.com/mastersign/bench/compare/v0.3.4...v0.4.0 ### Added - App: IPython (in app group `DevPython2` and `DevPython3`) @@ -672,7 +774,8 @@ must be migrated to the new format. - Restricted download of app resources to apps of type `default` ## [0.3.4] - 2016-01-20 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.3.3...v0.3.4) + +[0.3.4]: https://github.com/mastersign/bench/compare/v0.3.3...v0.3.4 ### Added - App: GnuTLS @@ -684,7 +787,8 @@ must be migrated to the new format. - Custom environment variables and setup scripts for meta apps ## [0.3.3] - 2016-01-18 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.3.2...v0.3.3) + +[0.3.3]: https://github.com/mastersign/bench/compare/v0.3.2...v0.3.3 ### Added - Support for NPM version ranges @@ -697,7 +801,8 @@ must be migrated to the new format. - Incorrect version patterns for NPM packages ## [0.3.2] - 2016-01-11 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.3.1...v0.3.2) + +[0.3.2]: https://github.com/mastersign/bench/compare/v0.3.1...v0.3.2 ### Added - Override `HOME` in addition to `USERPROFILE` @@ -723,13 +828,15 @@ must be migrated to the new format. - Incorrect warnings for meta apps during environment update ## [0.3.1] - 2016-01-08 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.3.3...v0.3.1) + +[0.3.1]: https://github.com/mastersign/bench/compare/v0.3.3...v0.3.1 ### Added - Documentation for groups in `apps.md` ## [0.3.0] - 2016-01-08 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.2.2...v0.3.0) + +[0.3.0]: https://github.com/mastersign/bench/compare/v0.2.2...v0.3.0 ### Added - Support for app typ `meta` for custom apps and app groups @@ -748,7 +855,8 @@ must be migrated to the new format. - Update: Eclipse from 4.4.2 to 4.5 ## [0.2.2] - 2016-01-07 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.2.1...v0.2.2) + +[0.2.2]: https://github.com/mastersign/bench/compare/v0.2.1...v0.2.2 ### Added - App: Apache Web Server @@ -766,7 +874,8 @@ must be migrated to the new format. - Switched `lib\git\bin` to `lib\git\cmd` in `PATH` ## [0.2.1] - 2016-01-06 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.2.0...v0.2.1) + +[0.2.1]: https://github.com/mastersign/bench/compare/v0.2.0...v0.2.1 ### Added - App: Sift @@ -784,7 +893,8 @@ must be migrated to the new format. - File name extraction from download URL ## [0.2.0] - 2016-01-06 -[Full Changelog](https://github.com/mastersign/bench/compare/v0.1.0...v0.2.0) + +[0.2.0]: https://github.com/mastersign/bench/compare/v0.1.0...v0.2.0 ### Added - App: Oracel JDK 7 and 8 @@ -807,6 +917,6 @@ must be migrated to the new format. - location for `AppDataDir` - incorrect proxy template in `res\config.template.ps1` -## [0.1.0] - 2016-01-04 +## 0.1.0 - 2016-01-04 First public release on GitHub. diff --git a/LICENSE.md b/LICENSE.md index 4f0992ad..84a46aa5 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,5 @@ The MIT License (MIT) ---------------------- +===================== Copyright (c) 2016 Tobias Kiertscher diff --git a/README.md b/README.md index 007ca3b3..74f802a0 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,21 @@ > Portable Environment for Software Development on Windows -[Bench Website][bench-website] (currently under construction) +[**Bench Website**][bench-website] -Take a look at the [**About Page**][About] if you want to know why Bench exists. -Browse through the [**Usage Scenarios**][Scenarios] to learn what Bench can do for you. -And if you want to dive right in, read the [**Quickstart**][Quickstart]. +Take a look at the [_About Page_][About] if you want to know why Bench exists. +Browse through the [_Usage Scenarios_][Scenarios] to learn what Bench can do for you. +Dive right in, by reading the [_Quickstart_][Quickstart]. +And take a look at the [_App Libraries_][Apps] if you wonder if your favorite tool is supported by Bench. ![Bench Dashboard](docs/static/img/teaser.png) +## Related Repositories + +* [Core App Library](https://github.com/mastersign/bench-apps-core/) +* [Default App Library](https://github.com/mastersign/bench-apps-default/) +* [Development Configuration](https://github.com/mastersign/bench-dev-config) + ## License This project is released under the MIT license. @@ -22,3 +29,4 @@ Copyright © by Tobias Kiertscher . [About]: http://mastersign.github.io/bench/about/ [Scenarios]: http://mastersign.github.io/bench/scenarios/ [Quickstart]: http://mastersign.github.io/bench/start/ +[Apps]: http://mastersign.github.io/bench/apps/ diff --git a/actions/bench-bash.cmd b/actions/bench-bash.cmd deleted file mode 100644 index 0e4d27cc..00000000 --- a/actions/bench-bash.cmd +++ /dev/null @@ -1,7 +0,0 @@ -@ECHO OFF -CALL "%~dp0\..\env.cmd" -CD /D "%~dp0\.." -IF "_%1_" == "__" ( - ECHO.BENCH v%BENCH_VERSION% BASH -) -CALL "%BENCH_APPS%\git\bin\bash.exe" %* diff --git a/actions/bench-ctl.cmd b/actions/bench-ctl.cmd deleted file mode 100644 index 462fabb7..00000000 --- a/actions/bench-ctl.cmd +++ /dev/null @@ -1,221 +0,0 @@ -@ECHO OFF -SETLOCAL EnableDelayedExpansion - -SET ROOT_DIR=%~dp0\.. -SET AUTO_DIR=%ROOT_DIR%\auto -SET /P BENCH_VERSION=<"%ROOT_DIR%\res\version.txt" -SET RUN_SHELL=0 -SET VERBOSE=0 -SET SURE=0 - -CD /D "%ROOT_DIR%" - -REM If no argument is given, start interactive mode -IF "_%1_" == "__" GOTO:interactive -REM If the first argument is /?, show help -IF "_%1_" == "_/?_" GOTO:help - -REM Check if the first given argument is a valid action, else show help -SET i=0 -FOR %%a IN (initialize, download, setup, reinstall, renew, upgrade, update-env) DO ( - SET /A i+=1 - SET arg[!i!]=%%a -) -FOR /L %%i IN (1,1,7) DO ( - IF /I "%1" == "!arg[%%i]!" ( - SET ACTION=!arg[%%i]! - GOTO:silent - EXIT /B 0 - ) -) -ECHO.Unknown action: %1 -ECHO. -GOTO:help - -GOTO:EOF - -:switch_verbose - IF %VERBOSE% == 0 ( - SET VERBOSE=1 - ) ELSE ( - SET VERBOSE=0 - ) -GOTO:interactive - -:help - ECHO.Bench Control - Usage - ECHO.--------------------- - ECHO.bench-clt - ECHO. Interactive mode - ECHO.bench-ctl ^ - ECHO. Run one of the following actions: - ECHO. update-env, setup, download, reinstall, renew, upgrade - ECHO.bench-ctl /? - ECHO. Display this help -GOTO:EOF - -:silent - SET SILENT=1 - 2>NUL CALL :action_%ACTION% %* - IF ERRORLEVEL 1 ( - ECHO.Error during execution of action %ACTION% - EXIT /B 1 - ) -GOTO:EOF - -:interactive - SET SILENT=0 - CLS - ECHO.Bench Control v%BENCH_VERSION% - ECHO.--------------------- - IF %VERBOSE% == 1 ECHO.(Verbose) - ECHO. - ECHO.The following actions are available: - ECHO. E: Update environment after moving Bench (update-env) - ECHO. S: Download and install selected apps (setup) - ECHO. D: Download missing app resources (download) - ECHO. R: Download and reinstall selected apps (reinstall) - ECHO. N: Redownload and reinstall selected apps (renew) - ECHO. I: Initialize and setup Bench (initialize) - ECHO. U: Update Bench and renew (upgrade) - IF %VERBOSE% == 0 ( - ECHO. V: Activate verbose messages - ) ELSE ( - ECHO. V: Deactivate verbose messages - ) - ECHO. Q: Quit - ECHO. - CHOICE /C DSRNUEIVQ /M "Select Action" - IF ERRORLEVEL 9 SET ACTION=quit - IF ERRORLEVEL 8 GOTO:switch_verbose - IF ERRORLEVEL 7 SET ACTION=initialize - IF ERRORLEVEL 6 SET ACTION=update-env - IF ERRORLEVEL 5 SET ACTION=upgrade - IF ERRORLEVEL 4 SET ACTION=renew - IF ERRORLEVEL 3 SET ACTION=reinstall - IF ERRORLEVEL 2 SET ACTION=setup - IF ERRORLEVEL 1 SET ACTION=download - IF "%ACTION%" == "quit" GOTO:EOF - ECHO. - CLS - 2>NUL CALL :action_%ACTION% %* - PAUSE - IF %RUN_SHELL% == 1 ( - runps Shell.ps1 - ) -GOTO:EOF - -:reasure - CHOICE /C NY /M "Are you shure?" - IF ERRORLEVEL 2 ( - SET SURE=1 - ) ELSE ( - SET SURE=0 - ) -GOTO:EOF - -:action_initialize - IF %SILENT% == 0 ( - ECHO.Initializing Bench environment... - ECHO. - ) - CALL "%AUTO_DIR%\init.cmd" - CALL :runps Initialize-Bench.ps1 -GOTO:EOF - -:action_update-env - IF %SILENT% == 0 ( - ECHO.Update Bench environment paths and launchers... - ECHO. - ) - CALL "%AUTO_DIR%\init.cmd" - CALL :runsetup update-env -GOTO:EOF - -:action_setup - IF %SILENT% == 0 ( - ECHO.Download and install selected apps... - ECHO. - ) - CALL "%AUTO_DIR%\init.cmd" - CALL :runsetup setup - CD /D "%ROOT_DIR%" - CALL ".\env.cmd" - SET RUN_SHELL=1 -GOTO:EOF - -:action_download - IF %SILENT% == 0 ( - ECHO.Download missing app resources... - ECHO. - ) - CALL "%AUTO_DIR%\init.cmd" - CALL :runsetup download - CD /D "%ROOT_DIR%" -GOTO:EOF - -:action_reinstall - IF %SILENT% == 0 ( - ECHO.This will first uninstall and then install all active apps. - CALL :reasure - IF !SURE! == 0 GOTO:EOF - ECHO. - ECHO.Download and reinstall selected apps... - ECHO. - ) - CALL "%AUTO_DIR%\init.cmd" - CALL :runsetup reinstall - CD /D "%ROOT_DIR%" - CALL ".\env.cmd" - SET RUN_SHELL=1 -GOTO:EOF - -:action_renew - IF %SILENT% == 0 ( - ECHO.This will first delete and redownload all app resources and then uninstall and reinstall all active apps. - CALL :reasure - IF !SURE! == 0 GOTO:EOF - ECHO. - ECHO.Redownload and reinstall selected apps... - ECHO. - ) - CALL "%AUTO_DIR%\init.cmd" - CALL :runsetup renew - CD /D "%ROOT_DIR%" - CALL ".\env.cmd" - SET RUN_SHELL=1 -GOTO:EOF - -:action_upgrade - IF %SILENT% == 0 ( - ECHO.This will first upgrade Bench including the predefined app index, then download missing app resources, and finally uninstall and reinstall all active apps. - CALL :reasure - IF !SURE! == 0 GOTO:EOF - ECHO. - ECHO.Update Bench, download, and reinstall selected apps... - ECHO. - ) - CALL "%AUTO_DIR%\init.cmd" - IF %SILENT% == 0 ( - CALL runps Upgrade-Bench.ps1 - ) ELSE ( - CALL runps Upgrade-Bench.ps1 -Silent - ) - CD /D "%ROOT_DIR%" -GOTO:EOF - -:runsetup - IF %VERBOSE% == 1 ( - CALL runps Setup-Bench.ps1 -Action %1 -WithInfo - ) ELSE ( - CALL runps Setup-Bench.ps1 -Action %1 - ) -GOTO:EOF - -:runps - IF %VERBOSE% == 1 ( - CALL runps %1 -WithInfo "%~2" - ) ELSE ( - CALL runps %1 "%~2" - ) -GOTO:EOF diff --git a/actions/bench-ps.cmd b/actions/bench-ps.cmd deleted file mode 100644 index b16aa46a..00000000 --- a/actions/bench-ps.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@ECHO OFF -CD /D "%~dp0\.." -CALL .\env.cmd -.\auto\runps.cmd Shell.ps1 'BENCH ROOT' %* diff --git a/actions/clone-git-project.cmd b/actions/clone-git-project.cmd deleted file mode 100644 index 2695b1f3..00000000 --- a/actions/clone-git-project.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF -CALL "%~dp0\..\env.cmd" -runps Clone-GitProject.ps1 %* diff --git a/actions/new-project.cmd b/actions/new-project.cmd deleted file mode 100644 index bb54d2f9..00000000 --- a/actions/new-project.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF -CALL "%~dp0\..\env.cmd" -runps New-Project.ps1 %* diff --git a/actions/open-editor.cmd b/actions/open-editor.cmd deleted file mode 100644 index c62e7f87..00000000 --- a/actions/open-editor.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO ON -CALL "%~dp0..\env.cmd" -runps Start-Editor.ps1 %* diff --git a/actions/project-backup.cmd b/actions/project-backup.cmd deleted file mode 100644 index 2451394e..00000000 --- a/actions/project-backup.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF -CALL "%~dp0\..\env.cmd" -runps Archive-Project.ps1 %* diff --git a/actions/project-editor.cmd b/actions/project-editor.cmd deleted file mode 100644 index e46fd72f..00000000 --- a/actions/project-editor.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF -CALL "%~dp0\..\env.cmd" -runps Edit-Project.ps1 %* diff --git a/actions/project-ps.cmd b/actions/project-ps.cmd deleted file mode 100644 index 2165c902..00000000 --- a/actions/project-ps.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF -CALL "%~dp0\..\env.cmd" -runps Shell-Project.ps1 %* diff --git a/actions/project-watch.cmd b/actions/project-watch.cmd deleted file mode 100644 index 938c7ea3..00000000 --- a/actions/project-watch.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF -CALL "%~dp0\..\env.cmd" -runps Watch-Project.ps1 %* diff --git a/auto/apps/apache.env.ps1 b/auto/apps/apache.env.ps1 deleted file mode 100644 index da288aed..00000000 --- a/auto/apps/apache.env.ps1 +++ /dev/null @@ -1,120 +0,0 @@ -$httpdDir = App-Dir Apache - -$confDir = Resolve-Path "$httpdDir\conf" -$confFile = "$confDir\httpd.conf" -$confBackupFile = [IO.Path]::ChangeExtension($confFile, ".conf.bak") - -# Load configuration value - -$wwwDir = Safe-Dir (Get-AppConfigValue Apache HttpdDocumentRoot) -Debug "DocumentRoot = '$wwwDir'" -$wwwListen = Get-AppConfigValue Apache HttpdListen -Debug "Listen = '$wwwListen'" - -# Replay backup if no configuration found - -if ((Test-Path $confBackupFile) -and !(Test-Path $confFile)) { - cp $confBackupFile $confFile -} - -# Make backup of configuration - -if (!(Test-Path $confBackupFile)) { - cp $confFile $confBackupFile -} - -# Helper functions - -function ApacheConformPath([string]$p) { - return $p.Replace('\', '/') -} - -function regex([string]$pattern, [Text.RegularExpressions.RegexOptions]$options) { - return New-Object System.Text.RegularExpressions.Regex ($pattern, $options) -} - -function Clean-Whitespace([string]$txt) { - return ([regex]'(\r?\n){2,}').Replace($txt, '$1$1') -} - -function Remove-PatternLine([string]$txt, [string]$pattern) { - [regex]$r = regex $pattern Multiline - return $r.Replace($txt, '') -} - -function Asure-PatternLine([string]$txt, [string]$pattern, [string]$replacement) { - $txt = Remove-PatternLine $txt $pattern - $txt = Clean-Whitespace $txt - $txt = $txt.TrimEnd() + "`n" + $replacement - return $txt -} - -# Load configuration - -$txt = [IO.File]::ReadAllText($confFile, [Text.Encoding]::UTF8) - -# Modify configuration - -$serverRootP = regex '^ServerRoot\s+"(.*?)"' Multiline - -$txt = $serverRootP.Replace($txt, "ServerRoot `"$(ApacheConformPath $httpdDir)`"") - -$docRootP = regex '^DocumentRoot\s+"(?.*?)"' Multiline - -$docRootM = $docRootP.Match($txt) -if ($docRootM.Success) { - $txt = $docRootP.Replace($txt, "DocumentRoot `"$(ApacheConformPath $wwwDir)`"") - $oldDocRootPath = $docRootM.Groups['path'].Value - $oldDocRootPath = [regex]::Escape((ApacheConformPath $oldDocRootPath)) - $docRootDirP = regex "^\" "Multiline, IgnoreCase, CultureInvariant" - $txt = $docRootDirP.Replace($txt, "") -} - -$listenP = regex '^Listen\s+(\S*)' Multiline -$txt = $listenP.Replace($txt, "Listen $wwwListen") - -# Install PHP - -# Try PHP 7 - -$php7 = App-Exe PHP7 -if ($php7) { - $php7Dir = App-Dir PHP7 - $php7Module = "$(App-Dir PHP7)\php7apache2_4.dll" - - $txt = Asure-PatternLine $txt ` - '^LoadModule\s+php\d_module\s+"(.*?)"' ` - "LoadModule php7_module `"$(ApacheConformPath $php7Module)`"" - - $txt = Asure-PatternLine $txt ` - '^PHPIniDir\s+"(.*?)"' ` - "PHPIniDir `"$(ApacheConformPath $php7Dir)`"" - - $txt = Asure-PatternLine $txt ` - '^AddType\s+application/x-httpd-php\s+(.*?)$' ` - "AddType application/x-httpd-php php php7" -} - -# Try PHP 5 - -$php5 = App-Exe PHP5 -if ($php5 -and !$php7) { - $php5Dir = App-Dir PHP5 - $php5Module = "$(App-Dir PHP5)\php5apache2_4.dll" - - $txt = Asure-PatternLine $txt ` - '^LoadModule\s+php\d_module\s+"(.*?)"' ` - "LoadModule php5_module `"$(ApacheConformPath $php5Module)`"" - - $txt = Asure-PatternLine $txt ` - '^PHPIniDir\s+"(.*?)"' ` - "PHPIniDir `"$(ApacheConformPath $php5Dir)`"" - - $txt = Asure-PatternLine $txt ` - '^AddType\s+application/x-httpd-php\s+(.*?)$' ` - "AddType application/x-httpd-php php php5" -} - -# Write configuration - -[IO.File]::WriteAllText($confFile, $txt, [Text.Encoding]::UTF8) diff --git a/auto/apps/erlang.env.ps1 b/auto/apps/erlang.env.ps1 deleted file mode 100644 index cfb25879..00000000 --- a/auto/apps/erlang.env.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -$erlangDir = App-Dir Erlang -$binDir = "$erlangDir\bin" -$ertsVersion = Get-AppConfigValue Erlang ErtsVersion -$ertsDir = "$erlangDir\erts-${ertsVersion}" - -$bindirPath = $binDir.TrimEnd("\").Replace("\", "\\") -$rootdirPath = $erlangDir.TrimEnd("\").Replace("\", "\\") - -$iniText = @("[erlang]") -$iniText += "Bindir=${bindirPath}" -$iniText += "Progname=erl" -$iniText += "Rootdir=${rootdirPath}" - -$iniText | Out-File "$binDir\erl.ini" -Encoding Default -Force diff --git a/auto/apps/erlang.setup.ps1 b/auto/apps/erlang.setup.ps1 deleted file mode 100644 index 18fd9aba..00000000 --- a/auto/apps/erlang.setup.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -$erlangDir = App-Dir Erlang - -$majorVersion = Get-AppConfigValue Erlang VersionMajor -$ertsVersion = Get-AppConfigValue Erlang ErtsVersion - -$binDir = "$erlangDir\bin" -$ertsDir = "$erlangDir\erts-${ertsVersion}" -$releaseDir = "$erlangDir\releases\${majorVersion}" - -if (!(Test-Path $binDir)) -{ - $_ = mkdir $binDir - cp "$ertsDir\bin\ct_run.exe" $binDir - cp "$ertsDir\bin\dialyzer.exe" $binDir - cp "$ertsDir\bin\erl.exe" $binDir - cp "$ertsDir\bin\erlc.exe" $binDir - cp "$ertsDir\bin\escript.exe" $binDir - cp "$ertsDir\bin\typer.exe" $binDir - cp "$ertsDir\bin\werl.exe" $binDir - cp "$releaseDir\no_dot_erlang.boot" $binDir - cp "$releaseDir\start.boot" $binDir - cp "$releaseDir\start_clean.boot" $binDir - cp "$releaseDir\start_sasl.boot" $binDir -} - -Purge-Dir "$erlangDir\`$PLUGINDIR" -del "$erlangDir\Install.*" -del "$erlangDir\Uninstall.*" -del "$erlangDir\*.template" diff --git a/auto/apps/git.env.ps1 b/auto/apps/git.env.ps1 deleted file mode 100644 index d740f0d3..00000000 --- a/auto/apps/git.env.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -$gitDir = App-Dir Git -$git = App-Exe Git - -pushd $gitDir -Write-Host "Running post-install script for Git ..." -copy "post-install.bat.bak" "post-install.bat" -try -{ - cmd /C "post-install.bat" | Out-Null -} -catch { } -popd - -if (Get-ConfigBooleanValue UseProxy) -{ - & $git config --global "http.proxy" $(Get-ConfigValue HttpProxy) - & $git config --global "https.proxy" $(Get-ConfigValue HttpsProxy) - & $git config --global "url.https://.insteadof" "git://" -} -else -{ - & $git config --global --unset "http.proxy" - & $git config --global --unset "https.proxy" - & $git config --global --unset "url.https://.insteadof" -} diff --git a/auto/apps/git.setup.ps1 b/auto/apps/git.setup.ps1 deleted file mode 100644 index b5a5cd23..00000000 --- a/auto/apps/git.setup.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -$gitDir = App-Dir Git -$git = App-Exe Git -if (!$git) { throw "Git not found" } - -if (Test-Path "$gitDir\post-install.bat") -{ - Write-Host "Renaming Git post-install script to prevent deletion" - mv "$gitDir\post-install.bat" "$gitDir\post-install.bat.bak" -} - -pushd $gitDir -Write-Host "Running post-install script for Git ..." -copy "post-install.bat.bak" "post-install.bat" -try -{ - cmd /C "post-install.bat" | Out-Null -} -catch { } -popd - -$autocrlf = & $git config --global core.autocrlf -$pushDefault = & $git config --global push.default -$user = & $git config --global user.name -$email = & $git config --global user.email - -if (!$autocrlf) -{ - & $git config --global "core.autocrlf" "true" -} - -if (!$pushDefault) -{ - & $git config --global "push.default" "simple" -} - -if (Get-ConfigBooleanValue UseProxy) -{ - & $git config --global "http.proxy" $(Get-ConfigValue HttpProxy) - & $git config --global "https.proxy" $(Get-ConfigValue HttpsProxy) - & $git config --global "url.https://.insteadof" "git://" -} -else -{ - & $git config --global --unset "http.proxy" - & $git config --global --unset "https.proxy" - & $git config --global --unset "url.https://.insteadof" -} - -if (!$user -or !$email) -{ - Write-Host "Configuring your GIT identity ..." - if (!$user) - { - $user = Get-ConfigValue UserName - Write-Host "User Name: $user" - & $git config --global "user.name" $user - } - if (!$email) - { - $email = Get-ConfigValue UserEmail - Write-Host "Email: $email" - & $git config --global "user.email" $email - } -} diff --git a/auto/apps/gitkraken.env.ps1 b/auto/apps/gitkraken.env.ps1 deleted file mode 100644 index c6a400d5..00000000 --- a/auto/apps/gitkraken.env.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -$appData = Get-ConfigValue AppDataDir -$gitKrakenConfig = Resolve-Path "$appData\.gitkraken\config" - -if ($gitKrakenConfig) -{ - $projectsDir = Get-ConfigValue ProjectRootDir - $projectsDir = $projectsDir.Replace("\", "\\") - $enc = New-Object System.Text.UTF8Encoding ($False) - $json = [IO.File]::ReadAllText($gitKrakenConfig, $enc) - [regex]$p = "`"projectsDirectory`"\s*:\s*`".*?`"" - $json = $p.Replace($json, "`"projectsDirectory`":`"$projectsDir`"") - [IO.File]::WriteAllText($gitKrakenConfig, $json, $enc) -} diff --git a/auto/apps/gitkraken.extract.ps1 b/auto/apps/gitkraken.extract.ps1 deleted file mode 100644 index 1d8b0a29..00000000 --- a/auto/apps/gitkraken.extract.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -param ($archive, $targetDir) - -$extractDir = Empty-Dir "$(Get-ConfigValue TempDir)\gitkraken" - -$7z = App-Exe 7z - -& $7z x "-o$extractDir" "$archive" | Out-Null - -if (!(Test-Path "$extractDir\Update.exe")) { - throw "Did not find the expected content in the setup archive" -} - -$nupkg = gci "$extractDir\gitkraken-*-full.nupkg" - -& $7z x "-o$targetDir" $nupkg.FullName | Out-Null - -Purge-Dir $extractDir diff --git a/auto/apps/gitkraken.setup.ps1 b/auto/apps/gitkraken.setup.ps1 deleted file mode 100644 index f3fd3a79..00000000 --- a/auto/apps/gitkraken.setup.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -$configDir = [IO.Path]::Combine((Get-ConfigValue AppDataDir), ".gitkraken") -$configFile = [IO.Path]::Combine($configDir, "config") -$templateFile = [IO.Path]::Combine((Get-ConfigValue AppResourceBaseDir), "gitkraken\config") - -if (!(Test-Path $configDir)) -{ - $_ = mkdir $configDir -} -if (!(Test-Path $configFile)) -{ - copy $templateFile $configFile -} diff --git a/auto/apps/jdk7.extract.ps1 b/auto/apps/jdk7.extract.ps1 deleted file mode 100644 index d49a8a82..00000000 --- a/auto/apps/jdk7.extract.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -param ($archive, $targetDir) - -$jdkexDir = Empty-Dir "$(Get-ConfigValue TempDir)\jdk8ex" - -$7z = App-Exe 7z - -& $7z x "-o$jdkexDir" "$archive" | Out-Null - -if (!(Test-Path "$jdkexDir\tools.zip")) { - throw "Did not find the expected content in the JDK archive" -} - -& $7z x "-o$targetDir" "-x!lib\missioncontrol*" "-x!bin\jmc.exe" "-x!javafx-src.zip" "$jdkexDir\tools.zip" | Out-Null - -Purge-Dir $jdkexDir - -foreach ($f in (Get-ChildItem $targetDir -Include "*.pack" -Recurse)) { - & "$targetDir\bin\unpack200.exe" -r $f.FullName ([IO.Path]::ChangeExtension($f.FullName, ".jar")) | Out-Null -} diff --git a/auto/apps/jdk8.extract.ps1 b/auto/apps/jdk8.extract.ps1 deleted file mode 100644 index d49a8a82..00000000 --- a/auto/apps/jdk8.extract.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -param ($archive, $targetDir) - -$jdkexDir = Empty-Dir "$(Get-ConfigValue TempDir)\jdk8ex" - -$7z = App-Exe 7z - -& $7z x "-o$jdkexDir" "$archive" | Out-Null - -if (!(Test-Path "$jdkexDir\tools.zip")) { - throw "Did not find the expected content in the JDK archive" -} - -& $7z x "-o$targetDir" "-x!lib\missioncontrol*" "-x!bin\jmc.exe" "-x!javafx-src.zip" "$jdkexDir\tools.zip" | Out-Null - -Purge-Dir $jdkexDir - -foreach ($f in (Get-ChildItem $targetDir -Include "*.pack" -Recurse)) { - & "$targetDir\bin\unpack200.exe" -r $f.FullName ([IO.Path]::ChangeExtension($f.FullName, ".jar")) | Out-Null -} diff --git a/auto/apps/leiningen.setup.ps1 b/auto/apps/leiningen.setup.ps1 deleted file mode 100644 index dcb5f689..00000000 --- a/auto/apps/leiningen.setup.ps1 +++ /dev/null @@ -1,24 +0,0 @@ -$lein = App-Exe Leiningen -$leinResourceDir = "$(Get-ConfigValue AppResourceBaseDir)\leiningen" -$leinProfilesTemplate = "$leinResourceDir\profiles.clj" -$leinProfilesDir = "$(Get-ConfigValue HomeDir)\.lein" -$leinProfiles = [IO.Path]::Combine($leinProfilesDir, "profiles.clj") - -$leinEnv = Get-AppConfigValue Leiningen Environment -$leinJar = $leinEnv["LEIN_JAR"] - -if (!(Test-Path $leinJar)) -{ - $env:LEIN_JAR = $leinJar - Write-Host "Installing Leiningen to: $leinJar" - & $lein self-install -} - -if (!(Test-Path $leinProfilesDir)) -{ - $_ = mkdir $leinProfilesDir -} -if (!(Test-Path $leinProfiles)) -{ - copy $leinProfilesTemplate $leinProfiles -} diff --git a/auto/apps/maven.env.ps1 b/auto/apps/maven.env.ps1 deleted file mode 100644 index 26e1974a..00000000 --- a/auto/apps/maven.env.ps1 +++ /dev/null @@ -1,62 +0,0 @@ -$settingsFile = "$(App-Dir Maven)\conf\settings.xml" - -function AppendValueElement([Xml.XmlElement]$e, [string]$name, [string]$value) { - $dom = $e.OwnerDocument - $ns = $dom.DocumentElement.NamespaceURI - $vE = $dom.CreateElement($name, $ns) - $vE.InnerText = $value - $_ = $e.AppendChild($vE) -} - -function AddProxy([Xml.XmlElement]$proxiesE, [string]$protocol, [Uri]$uri) { - $dom = $proxiesE.OwnerDocument - $ns = $dom.DocumentElement.NamespaceURI - $proxyE = $dom.CreateElement("proxy", $ns) - AppendValueElement $proxyE "id" "bench_$protocol" - AppendValueElement $proxyE "active" "true" - AppendValueElement $proxyE "protocol" $protocol - AppendValueElement $proxyE "host" $uri.Host - AppendValueElement $proxyE "port" $uri.Port - $_ = $proxiesE.AppendChild($proxyE) -} - -if (Test-Path $settingsFile) { - Debug "Configuring Maven settings ..." - $dom = [xml](Get-Content $settingsFile -Encoding UTF8) - $nameTable = New-Object System.Xml.NameTable - $nsMgr = New-Object System.Xml.XmlNamespaceManager($nameTable) - $doc = $dom.DocumentElement - $ns = $doc.NamespaceURI - $nsMgr.AddNamespace("m", $ns) - - # Update location of local repository - - $repoE = $doc.SelectSingleNode("m:localRepository", $nsMgr) - if (!$repoE) { - $repoE = $dom.CreateElement("localRepository", $ns) - $_ = $doc.AppendChild($repoE) - } - $repoE.InnerText = "`${env.HOME}\m2_repo" - - # Update proxy configuration - - $proxiesE = $doc.SelectSingleNode("m:proxies", $nsMgr) - if (!$proxiesE) { - $proxiesE = $dom.CreateElement("proxies", $ns) - $_ = $doc.AppendChild($proxiesE) - } - $proxiesE.RemoveAll() - - if (Get-ConfigValue UseProxy) { - [Uri]$httpProxyUri = Get-ConfigValue HttpProxy - if ($httpProxyUri) { - AddProxy $proxiesE "http" $httpProxyUri - } - [Uri]$httpsProxyUri = Get-ConfigValue HttpsProxy - if ($httpsProxyUri) { - AddProxy $proxiesE "https" $httpsProxyUri - } - } - - $dom.Save($settingsFile) -} diff --git a/auto/apps/miktex.setup.ps1 b/auto/apps/miktex.setup.ps1 deleted file mode 100644 index fbcebf42..00000000 --- a/auto/apps/miktex.setup.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -$mpm = "$(App-Path MiKTeX)\mpm.exe" -if (!(Test-Path $mpm)) { - throw "MiKTeX Package Manager not found" -} - -$packages = @( - "koma-script", - "upquote", - "mathspec", - "etoolbox", - "l3kernel", - "l3packages", - "tipa", - "xetex-def", - "realscripts", - "metalogo", - "microtype", - "url", - "polyglossia", - "makecmds", - "fancyvrb", - "booktabs" -) - -function Extract-InstalledPackageNames() { - begin { - [regex]$ex = "\S+$" - } - process { - if ($_.StartsWith("i ")) { - $m = $ex.Match($_) - if ($m.Success) { - return $m.Value - } - } - } -} - -Write-Host "Installing missing LaTeX packages" - -$installed = & $mpm --list | Extract-InstalledPackageNames - -foreach ($package in $packages) { - if (!($installed -contains $package)) { - & $mpm "--install=$package" - $installed = & $mpm --list | Extract-InstalledPackageNames - } -} diff --git a/auto/apps/mingw.setup.ps1 b/auto/apps/mingw.setup.ps1 deleted file mode 100644 index be6b3e10..00000000 --- a/auto/apps/mingw.setup.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -$mingwGet = App-Exe MinGwGet - -$mingwPackages = Get-AppConfigListValue MinGW Packages - -$ErrorActionPreference = "SilentlyContinue" -foreach ($p in $mingwPackages) -{ - Write-Host "Setting up MinGW package $p ..." - & $mingwGet install $p -} diff --git a/auto/apps/mingwget.setup.ps1 b/auto/apps/mingwget.setup.ps1 deleted file mode 100644 index 875ced62..00000000 --- a/auto/apps/mingwget.setup.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -$mingwDir = App-Dir MinGwGet -$mingwGet = App-Exe MinGwGet - -if (!(Test-Path "$mingwDir\var\cache")) -{ - pushd $mingwDir - Write-Host "Updating MinGW catalog ..." - & $mingwGet update - popd -} diff --git a/auto/apps/mysql.setup.ps1 b/auto/apps/mysql.setup.ps1 deleted file mode 100644 index 1bf961a9..00000000 --- a/auto/apps/mysql.setup.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -$mysqlResourceDir = "$(Get-ConfigValue AppResourceBaseDir)\mysql" -$mysqlDir = App-Dir MySQL -$mysqlPath = App-Path MySQL -$dataDir = Get-AppConfigValue MySQL MySqlDataDir - -if (!(Test-Path $dataDir -PathType Container)) { - $_ = mkdir $dataDir - $logFile = "$dataDir\$env:COMPUTERNAME.err" - if (Test-Path $logFile) { - del $logFile - } - & "$mysqlPath\mysqld.exe" --initialize --init-file "$mysqlResourceDir\init.sql" --log_syslog=0 "--basedir=$mysqlDir" "--datadir=$dataDir" -} - -if (!(Test-Path "$mysqlPath\mysql_start.cmd")) { - cp "$mysqlResourceDir\mysql_start.cmd" $mysqlPath - Write-Host "Run 'mysql_start' on the Bench shell to start the MySQL server." -} -if (!(Test-Path "$mysqlPath\mysql_stop.cmd")) { - cp "$mysqlResourceDir\mysql_stop.cmd" $mysqlPath - Write-Host "Run 'mysql_stop' on the Bench shell to stop a running MySQL server." -} -if (!(Test-Path "$mysqlPath\mysql_log.cmd")) { - cp "$mysqlResourceDir\mysql_log.cmd" $mysqlPath - Write-Host "Run 'mysql_log' to open the MySQL log file in the system editor." -} diff --git a/auto/apps/npm.env.ps1 b/auto/apps/npm.env.ps1 deleted file mode 100644 index d2ba5c63..00000000 --- a/auto/apps/npm.env.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -$node = App-Exe Node -$nodeDir = App-Dir Node -if (!$node) { throw "NodeJS not found" } -$npm = App-Exe Npm -if (!$npm) { throw "Node Package Manager not found" } - -& $npm config set registry "http://registry.npmjs.org/" -if (Get-ConfigBooleanValue UseProxy) { - & $npm config set "proxy" $(Get-ConfigValue HttpProxy) - & $npm config set "https-proxy" $(Get-ConfigValue HttpsProxy) -} else { - & $npm config delete "proxy" - & $npm config delete "https-proxy" -} diff --git a/auto/apps/npm.remove.ps1 b/auto/apps/npm.remove.ps1 deleted file mode 100644 index e7ab3105..00000000 --- a/auto/apps/npm.remove.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -$node = App-Exe Node -$nodeDir = App-Dir Node -if (!$node) { throw "NodeJS not found" } -$npm = App-Exe Npm -if (!$npm) { throw "Node Package Manager not found" } - -& $node "$nodeDir\node_modules\npm\bin\npm-cli.js" remove --global "npm" diff --git a/auto/apps/npm.setup.ps1 b/auto/apps/npm.setup.ps1 deleted file mode 100644 index e887247b..00000000 --- a/auto/apps/npm.setup.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -$node = App-Exe Node -$nodeDir = App-Dir Node -if (!$node) { throw "NodeJS not found" } -$npm = App-Exe Npm -if (!$npm) { throw "Node Package Manager not found" } - -$currentNpmVersion = & $npm --version -if ($currentNpmVersion.Trim() -eq "1.4.12") { - $targetNpmVersion = App-Version Npm - & $node "$nodeDir\node_modules\npm\bin\npm-cli.js" install --global "`"npm@$targetNpmVersion`"" -} diff --git a/auto/apps/nuget.env.ps1 b/auto/apps/nuget.env.ps1 deleted file mode 100644 index a3d5384d..00000000 --- a/auto/apps/nuget.env.ps1 +++ /dev/null @@ -1,8 +0,0 @@ -$nugetExe = App-Exe NuGet -if (!$nugetExe) { throw "NuGet executable not found" } - -if (Get-ConfigBooleanValue UseProxy) { - & $nugetExe config -Set "HTTP_PROXY=$(Get-ConfigValue HttpProxy)" -} else { - & $nugetExe config -Set "HTTP_PROXY=" -} diff --git a/auto/apps/php5.setup.ps1 b/auto/apps/php5.setup.ps1 deleted file mode 100644 index e5d07a8f..00000000 --- a/auto/apps/php5.setup.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -$phpDir = App-Dir PHP5 - -cp "$phpDir\php.ini-development" "$phpDir\php.ini" diff --git a/auto/apps/php7.setup.ps1 b/auto/apps/php7.setup.ps1 deleted file mode 100644 index f48459fb..00000000 --- a/auto/apps/php7.setup.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -$phpDir = App-Dir PHP7 - -cp "$phpDir\php.ini-development" "$phpDir\php.ini" diff --git a/auto/apps/postgresql.setup.ps1 b/auto/apps/postgresql.setup.ps1 deleted file mode 100644 index 8fe06e28..00000000 --- a/auto/apps/postgresql.setup.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -$pgResourceDir = "$(Get-ConfigValue AppResourceBaseDir)\postgresql" -$pgPath = App-Path PostgreSQL - -$dataDir = Get-AppConfigValue PostgreSQL PostgreSqlDataDir -$logFile = Get-AppConfigValue PostgreSQL PostgreSqlLogFile - -if (!(Test-Path $dataDir -PathType Container)) { - Write-Host "Initializing PostgreSQL database in $dataDir" - pushd $pgPath - .\initdb.exe "--pgdata=$dataDir" "--username=postgres" "--pwfile=$pgResourceDir\defaultpw.txt" | Out-File $logFile -Encoding OEM - popd - Write-Host "Login to PostgreSQL with user 'postgres' and password 'bench'." - if ($LASTEXITCODE -ne 0) { - throw "Error during initialization of the PostgreSQL data directory: Exit Code = $LASTEXITCODE." - } -} - -$regFile = Get-AppRegistryFileName PostgreSQL bench -if (!(Test-Path $regFile)) { - cp "$pgResourceDir\default.reg" $regFile - Write-Host "Initialize default registry backup for pgAdmin III." -} - -if (!(Test-Path "$pgPath\postgresql_start.cmd")) { - cp "$pgResourceDir\postgresql_start.cmd" $pgPath - Write-Host "Run 'postgresql_start' on the Bench shell to start the PostgreSQL server." -} -if (!(Test-Path "$pgPath\postgresql_stop.cmd")) { - cp "$pgResourceDir\postgresql_stop.cmd" $pgPath - Write-Host "Run 'postgresql_stop' on the Bench shell to stop a running PostgreSQL server." -} -if (!(Test-Path "$pgPath\postgresql_log.cmd")) { - cp "$pgResourceDir\postgresql_log.cmd" $pgPath - Write-Host "Run 'postgresql_log' to open the PostgreSQL log file in the system editor." -} diff --git a/auto/apps/python2.setup.ps1 b/auto/apps/python2.setup.ps1 deleted file mode 100644 index 7300c725..00000000 --- a/auto/apps/python2.setup.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -$python = App-Exe Python2 -if (!$python) { throw "Python2 not found" } - -$pythonDir = App-Dir Python2 - -$pythonWrapper = [IO.Path]::Combine($pythonDir, "python2.cmd") -if (!(Test-Path $pythonWrapper)) { - Write-Host "Creating wrapper to call Python 2 via 'python2' ..." - "@CALL `"%~dp0\python.exe`" %*" | Out-File $pythonWrapper -Encoding default -} - -$pipPackageDir = [IO.Path]::Combine($pythonDir, "lib\site-packages\pip") -if (!(Test-Path $pipPackageDir -PathType Container)) { - Write-Host "Setting up PIP ..." - & $python -m ensurepip - pushd $pythonDir - & $python -m pip install --upgrade setuptools - & $python -m pip install --upgrade pip - popd -} diff --git a/auto/apps/python3.setup.ps1 b/auto/apps/python3.setup.ps1 deleted file mode 100644 index 2e2f966f..00000000 --- a/auto/apps/python3.setup.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -$python = App-Exe Python3 -if (!$python) { throw "Python3 not found" } - -$pythonDir = App-Dir Python3 - -$pythonWrapper = [IO.Path]::Combine($pythonDir, "python3.cmd") -if (!(Test-Path $pythonWrapper -PathType Leaf)) { - Write-Host "Creating wrapper to call Python 3 via 'python3' ..." - "@CALL `"%~dp0\python.exe`" %*" | Out-File $pythonWrapper -Encoding default -} - -$pipPackageDir = [IO.Path]::Combine($pythonDir, "lib\site-packages\pip") -if (!(Test-Path $pipPackageDir -PathType Container)) { - Write-Host "Setting up PIP ..." - & $python -m ensurepip - pushd $pythonDir - & $python -m pip install --upgrade setuptools - & $python -m pip install --upgrade pip - popd -} diff --git a/auto/apps/rabbitmq.setup.ps1 b/auto/apps/rabbitmq.setup.ps1 deleted file mode 100644 index 6a74e39a..00000000 --- a/auto/apps/rabbitmq.setup.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -Start-Process rabbitmq-server - -echo "Waiting 10s for the broker to boot ..." -[Threading.Thread]::Sleep(10000) - -echo "Activating Web UI ..." -rabbitmq-plugins enable rabbitmq_management - -echo "Killing the broker ..." -rabbitmqctl stop diff --git a/auto/apps/rubygems.setup.ps1 b/auto/apps/rubygems.setup.ps1 deleted file mode 100644 index 0ca65cf0..00000000 --- a/auto/apps/rubygems.setup.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -$ruby = App-Exe Ruby -$gemsTmpDir = App-Dir RubyGems - -$packageDir = gci "$gemsTmpDir\rubygems-*" | Sort-Object -Descending -cd $packageDir -ruby setup.rb diff --git a/auto/apps/scribus.setup.ps1 b/auto/apps/scribus.setup.ps1 deleted file mode 100644 index e9ae972d..00000000 --- a/auto/apps/scribus.setup.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -$scribusDir = App-Dir Scribus - -$nsisDirs = "$scribusDir\`$PLUGINSDIR", "$scribusDir\`$TEMP" -foreach ($d in $nsisDirs) -{ - if (Test-Path $d) - { - Purge-Dir $d - } -} - -$uninst = "$scribusDir\uninst.exe" -if (Test-Path $uninst) -{ - del $uninst -Force -} diff --git a/auto/apps/spacemacs.env.ps1 b/auto/apps/spacemacs.env.ps1 deleted file mode 100644 index e8e8e129..00000000 --- a/auto/apps/spacemacs.env.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -$homeDir = Get-ConfigValue HomeDir -$emacsInitFile = [IO.Path]::Combine($homeDir, ".emacs") -$emacsUserDir = [IO.Path]::Combine($homeDir, ".emacs.d") - -$nl = [Environment]::NewLine -$preamble = ";; BENCH SPACEMACS" - -if (Test-Path $emacsInitFile -PathType Leaf) { - [string]$oldCode = [IO.File]::ReadAllText($emacsInitFile, [Text.Encoding]::UTF8) - if (!$oldCode.StartsWith($preamble)) { - Write-Warning "Could not setup Spacemacs initialization, because an unknown .emacs file exists in the home directory." - return - } -} - -function formatPath ([string]$path) { - return $path.Replace('\', '/') -} - -$initCode = "$preamble$nl$nl" -$initCode += ";; Set path to Spacemacs explicitly, because the recognition$nl" -$initCode += ";; of the directory '%HOME%\.emacs.d' does not work correctly$nl" -$initCode += ";; with overridden HOME environment variable.$nl$nl" -$initCode += "(setq user-emacs-directory `"$(formatPath $emacsUserDir)/`")$nl" -$initCode += "(setq user-init-file `"$(formatPath $emacsUserDir)/init.el`")$nl" -$initCode += "(load user-init-file)$nl" - -Debug "Updating Emacs init file: $emacsInitFile" - -[IO.File]::WriteAllText($emacsInitFile, $initCode, [Text.Encoding]::UTF8) diff --git a/auto/apps/spacemacs.remove.ps1 b/auto/apps/spacemacs.remove.ps1 deleted file mode 100644 index d54b5188..00000000 --- a/auto/apps/spacemacs.remove.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -$homeDir = Get-ConfigValue HomeDir -$spacemacsConfig = [IO.Path]::Combine($homeDir, ".spacemacs") -$spacemacsConfigDir = [IO.Path]::Combine($homeDir, ".spacemacs.d") -$spacemacsDir = [IO.Path]::Combine($homeDir, ".emacs.d") - -if ((Test-Path $spacemacsConfig) -or (Test-Path $spacemacsConfigDir)) -{ - if (Test-Path $spacemacsDir) - { - Purge-Dir $spacemacsDir - } -} diff --git a/auto/apps/spacemacs.setup.ps1 b/auto/apps/spacemacs.setup.ps1 deleted file mode 100644 index 2b996e6b..00000000 --- a/auto/apps/spacemacs.setup.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -$spacemacsResourceDir = "$(Get-ConfigValue AppResourceBaseDir)\spacemacs" -$emacsDir = App-Path Emacs -$git = App-Exe Git - -if (!$git) { throw "Git not found" } - -function Run-Git ($arguments) { - Start-Process $git -Wait -NoNewWindow $arguments -} - -$homeDir = Get-ConfigValue HomeDir -$spacemacsConfig = [IO.Path]::Combine($homeDir, ".spacemacs") -$spacemacsConfigDir = [IO.Path]::Combine($homeDir, ".spacemacs.d") -$spacemacsInitFile = [IO.Path]::Combine($spacemacsConfigDir, "init.el") -$spacemacsInitTemplate = Resolve-Path "$spacemacsResourceDir\init.el" - -if (!(Test-Path $spacemacsConfig -PathType Leaf) -and !(Test-Path $spacemacsConfigDir -PathType Container)) { - Write-Host "Initializing Spacemacs configuration ..." - mkdir $spacemacsConfigDir | Out-Null - cp $spacemacsInitTemplate $spacemacsInitFile - pushd $spacemacsConfigDir | Out-Null - Run-Git @("init") - Run-Git @("add", "-A", ":/") - Run-Git @("commit", "-m", '"Default Spacemacs configuration from Bench template"') - popd | Out-Null -} - -$spacemacsDir = [IO.Path]::Combine($homeDir, ".emacs.d") - -if (!(Test-Path $spacemacsDir -PathType Container)) { - Write-Host "Cloning Spacemacs ..." - Run-Git @("clone", "https://github.com/syl20bnr/spacemacs.git", "`"$spacemacsDir`"") - Write-Host "" - Write-Host "Run 'emacs' once to initialize and start Spacemacs." -} diff --git a/auto/apps/vscode.setup.ps1 b/auto/apps/vscode.setup.ps1 deleted file mode 100644 index 2c3dd897..00000000 --- a/auto/apps/vscode.setup.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -$codeResourceDir = "$(Get-ConfigValue AppResourceBaseDir)\vscode" -$codeAppData = Safe-Dir "$(Get-ConfigValue AppDataDir)\Code\User" - -$snippetSourceDir = "$codeResourceDir\snippets" -$snippetTargetDir = Safe-Dir "$codeAppData\snippets" - -foreach ($f in (Get-ChildItem "$codeResourceDir\*.json")) { - if (!(Test-Path "$codeAppData\$($f.Name)")) { - cp $f "$codeAppData\" - } -} -foreach ($f in (Get-ChildItem "$snippetSourceDir\*.json")) { - if (!(Test-Path "$snippetTargetDir\$($f.Name)")) { - cp $f "$snippetTargetDir\" - } -} diff --git a/auto/archive.cmd b/auto/archive.cmd deleted file mode 100644 index b9cb5766..00000000 --- a/auto/archive.cmd +++ /dev/null @@ -1,5 +0,0 @@ -@ECHO OFF -FOR %%f in ("%CD%") DO ( - SET PROJECT=%%~nf -) -runps Archive-Project.ps1 "%PROJECT%" diff --git a/auto/editor.cmd b/auto/editor.cmd deleted file mode 100644 index 6e909148..00000000 --- a/auto/editor.cmd +++ /dev/null @@ -1,5 +0,0 @@ -@ECHO OFF -FOR %%f in ("%CD%") DO ( - SET PROJECT=%%~nf -) -runps Edit-Project.ps1 "%PROJECT%" diff --git a/auto/init.cmd b/auto/init.cmd deleted file mode 100644 index c57af2c7..00000000 --- a/auto/init.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHo OFF -SET PATH=%~dp0;%SystemRoot%;%SystemRoot%\System32;%SystemRoot%\System32\WindowsPowerShell\v1.0 -CD "%~dp0.." diff --git a/auto/lib/Archive-Project.ps1 b/auto/lib/Archive-Project.ps1 deleted file mode 100644 index 0d8b02fb..00000000 --- a/auto/lib/Archive-Project.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -param ( - $projectName = $(& ([IO.Path]::Combine([IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition), "Select-Project.ps1"))), - [switch]$debug -) - -if (!$projectName) { return } - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\bench.lib.ps1" - -$7z = App-Exe 7z - -trap { Write-TrapError $_ } -Set-Debugging $debug - -$projectPath = Get-ProjectPath $projectName -$projectName = Get-ProjectName $projectName -$archiveDir = Safe-Dir (Get-ConfigValue ProjectArchiveDir) -$archiveFormat = Get-ConfigValue ProjectArchiveFormat "zip" - -$timestamp = [DateTime]::Now.ToString("yyyyMMdd_HHmm") -$projectVersion = Get-ProjectVersion $projectName - -$nameParts = @($projectName, $timestamp) -if ($projectVersion) { - $nameParts += "v$projectVersion" -} - -$archiveName = [string]::Join("_", $nameParts) + "." + $archiveFormat -$archivePath = [IO.Path]::Combine($archiveDir, $archiveName) - -cd $projectPath - -& $7z a -mx $archivePath ".\*" diff --git a/auto/lib/Clone-GitProject.ps1 b/auto/lib/Clone-GitProject.ps1 deleted file mode 100644 index ce6aea97..00000000 --- a/auto/lib/Clone-GitProject.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -param ( - $gitURL = $(Read-Host "Git remote repository URL"), - $projectName = $(Read-Host "Directory name for the project ($(([regex]"^.*[/\\]([^/\\]+).git$").Replace($gitURL, "`$1")))"), - [switch]$debug -) - -if ($projectName -eq "") { - $projectName = ([regex]"^.*[/\\]([^/\\]+).git`$").Replace($gitURL, "`$1") -} - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\bench.lib.ps1" - -trap { Write-TrapError $_ } -Set-Debugging $debug - -$projectRoot = Safe-Dir (Get-ConfigValue ProjectRootDir) - -$projectPath = "$projectRoot\$projectName" - -if (Test-Path $projectPath) { - Write-Error "Project with name '$projectName' allready exists." - exit 1 -} -Empty-Dir $projectPath | Out-Null -Write-Output "" - -git clone $gitURL $projectPath - -if (!$?) { - $ec = $LastExitCode - Purge-Dir $projectPath - cmd /C PAUSE - exit $ec -} - -pushd $projectPath - -if ((Test-Path "bower.json" -PathType Leaf) -and -not (Test-Path "bower_components" -PathType Container)) { - bower install -} -if ((Test-Path "package.json" -PathType Leaf) -and -not (Test-Path "node_modules" -PathType Container)) { - npm install -} -if (Test-Path "gulpfile.js" -PathType Leaf) { - gulp -} -if ((Test-Path "Gruntfile.js" -PathType Leaf) -or (Test-Path "Gruntfile.coffee" -PathType Leaf)) { - grunt -} - -popd - -Run-Script Edit-Project -projectName $projectName -Run-Script Shell-Project -projectName $projectName diff --git a/auto/lib/Edit-Project.ps1 b/auto/lib/Edit-Project.ps1 deleted file mode 100644 index e89d29a6..00000000 --- a/auto/lib/Edit-Project.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -param ( - $projectName = $(& ([IO.Path]::Combine([IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition), "Select-Project.ps1"))), - [switch]$debug -) - -if (!$projectName) { return } - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\bench.lib.ps1" - -trap { Write-TrapError $_ } -Set-Debugging $debug - -$projectPath = Get-ProjectPath $projectName -$projectName = Get-ProjectName $projectName -$editor = App-Exe (Get-ConfigValue EditorApp) -if (!$editor) { - throw "Editor not found" -} else { - $editor = [IO.Path]::GetFileName($editor) -} - -cd $projectPath - -function Get-MainFiles($projectPath) { - - function Replace-Dates ($path) { - $p = [regex]"\`$DATE\:([^\`$]+)\`$" - $now = [DateTime]::Now - $me = [System.Text.RegularExpressions.MatchEvaluator] { - param($m) - return $now.ToString($m.Groups[1]) - } - $result = $p.Replace($path, $me) - return $result - } - - $mainFileList = "$projectPath\config\mainfiles" - if (Test-Path $mainFileList) { - return Get-Content $mainFileList -Encoding UTF8 ` - | % { Replace-Dates $_ } - } else { - return @() - } -} - -$defaultMainFiles = @( - "README.md", - "src/main.js", - "src/app.js", - "src/index.js", - "index.js" -) -$searchFiles = $defaultMainFiles + (Get-MainFiles $projectPath) -[Array]::Reverse($searchFiles) - -$foundFiles = @() -foreach ($s in $searchFiles) { - $path = [IO.Path]::Combine($projectPath, $s) - Debug "Test for main file: $path" - if (Test-Path $path -PathType Leaf) { - $foundFiles += $path - break - } -} - -Debug "Found main file(s): $foundFiles" - -Run-Detached $editor $projectPath @foundFiles diff --git a/auto/lib/Initialize-Bench.ps1 b/auto/lib/Initialize-Bench.ps1 deleted file mode 100644 index e64f4193..00000000 --- a/auto/lib/Initialize-Bench.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -$Script:myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -& "$Script:myDir\Load-ClrLibs.ps1" - -[string]$Script:autoDir = Resolve-Path ([IO.Path]::Combine($myDir, "..")) -[string]$Script:rootDir = Resolve-Path ([IO.Path]::Combine($autoDir, "..")) - -$cfg = [Mastersign.Bench.BenchTasks]::InitializeSiteConfiguration($rootDir) -if (!$cfg) -{ - Write-Host "Initialization canceled." - exit 1 -} - -$wizzardStartAutoSetup = $cfg.GetBooleanValue("WizzardStartAutoSetup", $True) -$mgr = New-Object Mastersign.Bench.DefaultBenchManager ($cfg) -$mgr.Verbose = $True -$cfg = $null - -$success = $mgr.SetupRequiredApps() - -if (!$success) -{ - Write-Host "Initial app setup failed." - exit 1 -} - -$cfg = [Mastersign.Bench.BenchTasks]::InitializeCustomConfiguration($mgr) -if (!$cfg) -{ - Write-Host "Initialization canceled." - exit 1 -} - -if ([Mastersign.Bench.BenchTasks]::IsDashboardSupported) -{ - if ($wizzardStartAutoSetup) - { - Start-Process "$autoDir\bin\BenchDashboard.exe" ("-setup") - } - else - { - Start-Process "$autoDir\bin\BenchDashboard.exe" - } -} -else -{ - if ($wizzardStartAutoSetup) - { - Start-Process "$rootDir\actions\bench-ctl.cmd" ("setup") - } - else - { - Start-Process "$rootDir\actions\bench-ctl.cmd" - } -} diff --git a/auto/lib/New-Project.ps1 b/auto/lib/New-Project.ps1 deleted file mode 100644 index 5aee8941..00000000 --- a/auto/lib/New-Project.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -param ( - $projectName = $(Read-Host "Directory name for the new project"), - [switch]$debug -) - -if (!$projectName) { return } - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\bench.lib.ps1" - -trap { Write-TrapError $_ } -Set-Debugging $debug - -$projectRoot = Safe-Dir (Get-ConfigValue ProjectRootDir) - -$projectPath = "$projectRoot\$projectName" - -if (Test-Path $projectPath) { - Write-Error "Project with name '$projectName' allready exists." - exit 1 -} -Empty-Dir $projectPath | Out-Null -Write-Output "" - -pushd $projectPath - -yo -Exit-OnError - -git init -Exit-OnError - -git add -A :/ -Exit-OnError - -git commit -m "Project initialized." -Exit-OnError - -if ((Test-Path "package.json" -PathType Leaf) -and -not (Test-Path "node_modules" -PathType Container)) { - npm install - Exit-OnError -} -if ((Test-Path "bower.json" -PathType Leaf) -and -not (Test-Path "bower_components" -PathType Container)) { - bower install - Exit-OnError -} -if (Test-Path "gulpfile.js" -PathType Leaf) { - gulp - Exit-OnError -} -if ((Test-Path "Gruntfile.js" -PathType Leaf) -or (Test-Path "Gruntfile.coffee" -PathType Leaf)) { - grunt - Exit-OnError -} - -popd - -Run-Script Edit-project -projectName $projectName -Run-Script Shell-Project -projectName $projectName diff --git a/auto/lib/PsExecHost.ps1 b/auto/lib/PsExecHost.ps1 index fce44755..f84263b5 100644 --- a/auto/lib/PsExecHost.ps1 +++ b/auto/lib/PsExecHost.ps1 @@ -1,27 +1,17 @@ -param ($Token = "Bench.Default") +param ($Token = "Bench.Default", $WaitMessage = ">>>>") $scriptsDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) $rootDir = Resolve-Path "$scriptsDir\..\.." . "$scriptsDir\bench.lib.ps1" -. "$scriptsLib\reg.lib.ps1" +. "$scriptsDir\reg.lib.ps1" $Script:BenchEnv = New-Object Mastersign.Bench.BenchEnvironment ($global:BenchConfig) -function _WaitForClient([IO.Pipes.NamedPipeServerStream]$pipe) +function _PrintWaitingMessage() { - Write-Host ">>>>" - while ($true) + if ($WaitMessage) { - try - { - $pipe.WaitForConnection() - break - } - catch - { - Write-Warning $_.Exception.Message - $pipe.Disconnect() - } + Write-Host $WaitMessage } } @@ -73,20 +63,9 @@ function _ParsePsArguments([string]$argStr) return $ExecutionContext.InvokeCommand.InvokeScript($argStr) } -function _HandleExecutionRequest([IO.TextReader]$reader, [IO.TextWriter]$writer) +$Script:executionResult = $null +function _ExecutionHandler([string]$cwd, [string]$cmd, [string]$cmdArgs) { - try - { - $cwd = $reader.ReadLine() - $cmd = $reader.ReadLine() - $cmdArgs = $reader.ReadLine() - } - catch - { - Write-Warning "Bench: Could not read execution arguments from named pipe." - return - } - $Script:BenchEnv.Load() pushd $cwd $exitCode = 0 @@ -112,45 +91,54 @@ function _HandleExecutionRequest([IO.TextReader]$reader, [IO.TextWriter]$writer) if ($_.Exception.Message) { Write-Warning $_.Exception.Message - $writer.WriteLine($_.Exception.Message) } if ($exitCode -eq 0) { $exitCode = -1 } } Stop-Transcript | Out-Null popd - $writer.WriteLine("EXITCODE $Token $exitCode") - $writer.WriteLine("TRANSCRIPT $Token $transcriptPath") + $Script:executionResult = New-Object Mastersign.Bench.RemoteExecHost.ExecutionResult @($exitCode, $transcriptPath) } -function _ReloadBenchConfig([IO.TextReader]$reader, [IO.TextWriter]$writer) +function _ReloadHandler() { - Write-Host "Reloading Bench configuration..." - $rootDir = $global:BenchConfig.BenchRootDir - $global:BenchConfig = New-Object Mastersign.Bench.BenchConfiguration ($rootDir) - $Script:BenchEnv = New-Object Mastersign.Bench.BenchEnvironment ($global:BenchConfig) - $writer.WriteLine("OK") + Write-Host "Reloading Bench configuration..." + $rootDir = $global:BenchConfig.BenchRootDir + $global:BenchConfig = New-Object Mastersign.Bench.BenchConfiguration ($rootDir) + $Script:BenchEnv = New-Object Mastersign.Bench.BenchEnvironment ($global:BenchConfig) } -$server = New-Object System.IO.Pipes.NamedPipeServerStream($Token, [IO.Pipes.PipeDirection]::InOut) +$server = New-Object Mastersign.Bench.RemoteExecHost.RemoteExecHostServer @($token) +Write-Host "PowerShell execution host started." -$closed = $false -while (!$closed) +while($server) { - $reader = New-Object System.IO.StreamReader ($server, [Text.Encoding]::UTF8, $true, 4, $true) - _WaitForClient $server - $writer = New-Object System.IO.StreamWriter ($server, [Text.Encoding]::UTF8, 4, $true) - $cmd = $reader.ReadLine() - switch ($cmd) + _PrintWaitingMessage + $rcmd = [Mastersign.Bench.RemoteExecHost.RemoteExecutionFacade]::WaitForCommand() + switch ($rcmd.Type) { - "exec" { _HandleExecutionRequest $reader $writer | Out-Default } - "reload" { _ReloadBenchConfig $reader $writer | Out-Default } - "close" { $closed = $true } + "Ping" + { + Write-Host "Remoting interface available." + $rcmd.NotifyResult("OK") + } + "Execution" + { + $cwd = $rcmd.Parameter.WorkingDirectory + $exe = $rcmd.Parameter.Executable + $exeArgs = $rcmd.Parameter.Arguments + _ExecutionHandler $cwd $exe $exeArgs + $rcmd.NotifyResult($Script:executionResult) + } + "Reload" + { + _ReloadHandler + } + "Shutdown" + { + $server.Dispose() + $server = $null + Write-Host "PowerShell execution host shut down." + exit + } } - $writer.Flush() - $server.WaitForPipeDrain() - $writer.Dispose() - $reader.Dispose() - $server.Disconnect() } -$server.Dispose() -Write-Host "<<<<" diff --git a/auto/lib/Select-Project.ps1 b/auto/lib/Select-Project.ps1 deleted file mode 100644 index 32f5e023..00000000 --- a/auto/lib/Select-Project.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -param ([switch]$debug) - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\bench.lib.ps1" - -trap { Write-TrapError $_ } -Set-Debugging $debug - -$projectRoot = Safe-Dir (Get-ConfigValue ProjectRootDir) - -[array]$projectNames = [IO.Directory]::GetDirectories($projectRoot) ` - | % { [IO.Path]::GetFileName($_) } ` - | sort - -if ($projectNames.Count -eq 0) { - Write-Host "No projects found." - Pause - return $null -} - -for ($i = 0; $i -lt $projectNames.Count; $i++) { - - Write-Host ([string]::Format("{0,3}) {1}", $i+1, $projectNames[$i])) -} -Write-Host "" - -$selectedName = $null -do { - $number = Read-host "Project Number" - try { - $number = [int]::Parse($number) - if ($number -gt 0 -and $number -le $projectNames.Count) { - $selectedName = $projectNames[$number - 1] - } - } catch { } -} while ($selectedName -eq $null) - -Write-Host "Selected Project: $selectedName" -Write-Host "" - -return $selectedName diff --git a/auto/lib/Setup-Bench.ps1 b/auto/lib/Setup-Bench.ps1 deleted file mode 100644 index 6a494b5b..00000000 --- a/auto/lib/Setup-Bench.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -param ( - $Action = "setup", - [switch]$WithInfo -) -$myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$myDir\bench.lib.ps1" - -$manager = New-Object Mastersign.Bench.DefaultBenchManager ($global:BenchConfig) -$manager.Verbose = $WithInfo - -$success = $False -switch ($Action) -{ - "setup" { $success = $manager.AutoSetup() } - "update-env" { $success = $manager.UpdateEnvironment() } - "download" { $success = $manager.DownloadAppResources() } - "clear-cache" { $success = $manager.DeleteAppResources() } - "install" { $success = $manager.InstallApps() } - "reinstall" { $success = $manager.ReinstallApps() } - "renew" { $success = $manager.UpgradeApps() } - default { Write-Error "Invalid action: '$action'" } -} - -if (!$success) -{ - Write-Warning "Take a look into the last log file in $(Get-ConfigValue LogDir)." - Write-Error "Action failed." -} diff --git a/auto/lib/Shell-Project.ps1 b/auto/lib/Shell-Project.ps1 deleted file mode 100644 index 0483e2d1..00000000 --- a/auto/lib/Shell-Project.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -param ( - $projectName = $(& ([IO.Path]::Combine([IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition), "Select-Project.ps1"))), - [switch]$debug -) - -if (!$projectName) { return } - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\bench.lib.ps1" - -trap { Write-TrapError $_ } -Set-Debugging $debug - -$projectPath = Get-ProjectPath $projectName -$projectName = Get-ProjectName $projectName - -cd $projectPath -Run-Script Shell "PROJECT $projectName" diff --git a/auto/lib/Shell.ps1 b/auto/lib/Shell.ps1 deleted file mode 100644 index c5cff2c7..00000000 --- a/auto/lib/Shell.ps1 +++ /dev/null @@ -1,70 +0,0 @@ -param ($message = $null) - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\profile.ps1" -. "$scriptsLib\common.lib.ps1" -. "$scriptsLib\config.lib.ps1" - -if ($args) { - powershell -NoLogo -NoProfile @args - return -} - -Clear-Host -Get-Content "$scriptsLib\banner.txt" -Encoding UTF8 ` - | % { $_.Replace("`$VERSION`$", [string]::Format("{0,9}", $(Get-ConfigValue Version "0.0.0"))) } ` - | Out-Default - -if ($message) { - $prefix = "====#### " - $postfix = " ####====" - $message = $message.Trim() - $d = 60 - $prefix.Length - $postfix.Length - $message.Length - if ($d -lt 0) { - $text = $prefix + $message + $postfix - } else { - $l = "-" * [Math]::Floor($d / 2.0) - $r = "-" * [Math]::Ceiling($d / 2.0) - $text = $l + $prefix + $message + $postfix + $r - } - Write-Output $text - Write-Output "" -} - -$props = @() -if (test-Path ".svn" -PathType Container) { - $props += "under SVN version control" -} -if (Test-Path ".git" -Pathtype Container) { - $props += "under Git version control" -} -if (Test-Path "bower.json" -PathType Leaf) { - if (Test-Path "bower_components" -PathType Container) { - $props += "consuming Bower components" - } else { - $props += "consuming Bower components, run 'bower install' to load them" - } -} -if (Test-Path "package.json" -PathType Leaf) { - if (Test-Path "node_modules" -PathType Container) { - $props += "a Node Package" - } else { - $props += "a Node Package, run 'npm install' to load dependencies" - } -} -if (Test-Path "gulpfile.js" -PathType Leaf) { - $props += "automated with Gulp" -} -if ((Test-Path "Gruntfile.js" -PathType Leaf) -or (Test-Path "Gruntfile.coffee" -PathType Leaf)) { - $props += "automated with Grunt" -} -if ($props.Count -gt 0) { - Write-Output "The Project is" - foreach ($p in $props) { - if (!$p) { continue } - Write-Output "- $p" - } - Write-Output "" -} - -powershell -NoLogo -NoProfile -NoExit -File "$scriptsLib\profile.ps1" diff --git a/auto/lib/Start-Editor.ps1 b/auto/lib/Start-Editor.ps1 deleted file mode 100644 index c4d726fd..00000000 --- a/auto/lib/Start-Editor.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -param ([switch]$debug) - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\bench.lib.ps1" - -trap { Write-TrapError $_ } -Set-Debugging $debug - -$editor = App-Exe (Get-ConfigValue EditorApp) -if (!$editor) { - throw "Edtor not found" -} else { - $editor = [IO.Path]::GetFileName($editor) -} - -cd $Script:rootDir - -Run-Detached $editor @args diff --git a/auto/lib/Upgrade-Bench.ps1 b/auto/lib/Upgrade-Bench.ps1 deleted file mode 100644 index cfdb0370..00000000 --- a/auto/lib/Upgrade-Bench.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -param ( - [switch]$Silent, - [string]$VersionUrl = "https://github.com/mastersign/bench/raw/master/res/version.txt", - [string]$BootstrapUrl = "https://github.com/mastersign/bench/raw/master/res/bench-install.bat" -) - -$Script:myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -[string]$Script:autoDir = Resolve-Path ([IO.Path]::Combine($myDir, "..")) -[string]$Script:rootDir = Resolve-Path ([IO.Path]::Combine($autoDir, "..")) - -$versionFile = [IO.Path]::Combine($rootDir, "res\version.txt") -$bootstrapFile = [IO.Path]::Combine($rootDir, "bench-install.bat") - -$wc = New-Object System.Net.WebClient - -try -{ - $installedVersion = (Get-Content $versionFile -TotalCount 1).Trim() -} -catch -{ - Write-Warning "Checking the installed version failed:" - Write-Warning $_.Exception.Message - return -} -try -{ - $latestVersion = $wc.DownloadString($VersionUrl).Trim() -} -catch -{ - Write-Warning "Checking the latest version failed:" - Write-Warning $_.Exception.Message - return -} -if ($installedVersion -eq $latestVersion) -{ - Write-Host "You already have the latest version of Bench installed." - Write-Host "" - Write-Host "Latest Bench version: $latestVersion" - return -} - -Write-Host "Downloading latest install script ..." -$wc.DownloadFile($BootstrapUrl, $bootstrapFile) - -Write-Host "Upgrading Bench from $installedVersion to $latestVersion ..." -cmd /C $bootstrapFile diff --git a/auto/lib/Watch-Project.ps1 b/auto/lib/Watch-Project.ps1 deleted file mode 100644 index 562b5f8c..00000000 --- a/auto/lib/Watch-Project.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -param ( - $projectName = $(& ([IO.Path]::Combine([IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition), "Select-Project.ps1"))), - [switch]$debug -) - -if (!$projectName) { return } - -$scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$scriptsLib\bench.lib.ps1" - -trap { Write-TrapError $_ } -Set-Debugging $debug - -$projectPath = Get-ProjectPath $projectName -$projectName = Get-ProjectName $projectName - -cd $projectPath -if (Test-Path "gulpfile.js" -PathType Leaf) { - gulp watch -} elseif ((Test-Path "Gruntfile.js" -PathType Leaf) -or (Test-Path "Gruntfile.coffee" -PathType Leaf)) { - grunt watch -} else { - Write-Warning "The project is not automated with Gulp or Grunt" - Pause -} diff --git a/auto/lib/banner.txt b/auto/lib/banner.txt deleted file mode 100644 index b1226b67..00000000 --- a/auto/lib/banner.txt +++ /dev/null @@ -1,16 +0,0 @@ - - /==================================================\ - || _____________________________________ || - || / _ __ __ /| || - || / /_) /_ /| / / /_/ $VERSION$ // || - || / /_) /_ / |/ |_ / / o //| || - || /____________________________|_______//|| || - || |____________________________O______|/ || || - || | || | || | | || | || || - || | || |_|/ ° | || |_|/ || - || | || | || || - || | || LET'S GET CRACKIN! | || || - || |_|/ |_|/ || - || || - \==================================================/ - diff --git a/auto/lib/bench.lib.ps1 b/auto/lib/bench.lib.ps1 index d1656d92..2b045ba3 100644 --- a/auto/lib/bench.lib.ps1 +++ b/auto/lib/bench.lib.ps1 @@ -1,6 +1,6 @@ -$myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$myDir\profile.ps1" -. "$myDir\config.lib.ps1" +$Script:scriptsDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +. "$Script:scriptsDir\profile.ps1" +. "$Script:scriptsDir\config.lib.ps1" function Purge-Dir ($path) { diff --git a/auto/lib/common.lib.ps1 b/auto/lib/common.lib.ps1 index 39339659..cc13af63 100644 --- a/auto/lib/common.lib.ps1 +++ b/auto/lib/common.lib.ps1 @@ -1,5 +1,5 @@ -[string]$Script:scriptsLib = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -[string]$Script:rootDir = Resolve-Path "$scriptsLib\..\.." +$Script:scriptsDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +[string]$Script:rootDir = Resolve-Path "$scriptsDir\..\.." function Set-Debugging ($enabled) { if ($enabled) { diff --git a/auto/lib/config.lib.ps1 b/auto/lib/config.lib.ps1 index e1df34c3..b73374e0 100644 --- a/auto/lib/config.lib.ps1 +++ b/auto/lib/config.lib.ps1 @@ -1,8 +1,8 @@ -$Script:myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -. "$Script:myDir\common.lib.ps1" -& "$Script:myDir\Load-ClrLibs.ps1" +$Script:scriptsDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +. "$Script:scriptsDir\common.lib.ps1" +& "$Script:scriptsDir\Load-ClrLibs.ps1" -[string]$Script:autoDir = Resolve-Path ([IO.Path]::Combine($myDir, "..")) +[string]$Script:autoDir = Resolve-Path ([IO.Path]::Combine($scriptsDir, "..")) [string]$Script:rootDir = Resolve-Path ([IO.Path]::Combine($autoDir, "..")) $Script:pathBackup = $env:PATH @@ -32,7 +32,7 @@ function App-Force([string]$name) { return $global:BenchConfig.Apps[$name].Force function App-PackageName([string]$name) { return $global:BenchConfig.Apps[$name].PackageName } function App-Dir([string]$name) { return $global:BenchConfig.Apps[$name].Dir } function App-Paths([string]$name) { return $global:BenchConfig.Apps[$name].Path } -function App-Exe([string]$name, [bool]$checkExist = $true) { return $global:BenchConfig.Apps[$name].Exe } + function App-Register([string]$name) { return $global:BenchConfig.Apps[$name].Register } function App-Environment([string]$name) { return $global:BenchConfig.Apps[$name].Environment } function App-AdornedExecutables([string]$name) { return $global:BenchConfig.Apps[$name].AdornedExecutables } @@ -43,8 +43,24 @@ function App-LauncherArguments([string]$name) { return $global:BenchConfig.Apps[ function App-LauncherIcon([string]$name) { return $global:BenchConfig.Apps[$name].LauncherIcon } function App-SetupTestFile([string]$name) { return $global:BenchConfig.Apps[$name].SetupTestFile } function Check-App([string]$name) { return $global:BenchConfig.Apps[$name].IsInstalled } +function App-CustomScript([string]$name, [string]$typ) { return $global:BenchConfig.Apps[$name].GetCustomScript($typ) } +function App-SetupResource([string]$name, [string]$relPath) { return $global:BenchConfig.Apps[$name].GetSetupResource($relPath) } + +function App-Exe([string]$name, [bool]$checkExist = $true) +{ + $p = $global:BenchConfig.Apps[$name].Exe + if (!$checkExist -or [IO.File]::Exists($p)) + { + return $p + } + else + { + return $null + } +} -function App-Path([string]$name) { +function App-Path([string]$name) +{ $path = $global:BenchConfig.Apps[$name].Path if ($path.Length -gt 0) { diff --git a/auto/lib/reg.lib.ps1 b/auto/lib/reg.lib.ps1 index 6baa516c..e3170998 100644 --- a/auto/lib/reg.lib.ps1 +++ b/auto/lib/reg.lib.ps1 @@ -1,6 +1,7 @@ function Get-AppRegistryFileName([string]$name, [string]$typ, [int]$no = 0) { $regBaseDir = Get-ConfigValue AppRegistryBaseDir - $resDir = Safe-Dir ([IO.Path]::Combine($regBaseDir, $name.ToLowerInvariant())) + $path = $global:BenchConfig.Apps[$name].PathSegment + $resDir = Safe-Dir ([IO.Path]::Combine($regBaseDir, $path)) if ($no -gt 0) { $noPart = "_" + $no.ToString("00") } else { diff --git a/build/applibs.txt b/build/applibs.txt new file mode 100644 index 00000000..80afe40a --- /dev/null +++ b/build/applibs.txt @@ -0,0 +1,2 @@ +core=https://github.com/mastersign/bench-apps-core.git +default=https://github.com/mastersign/bench-apps-default.git diff --git a/build/build-docs.ps1 b/build/build-docs.ps1 index 714ec022..9fe54e37 100644 --- a/build/build-docs.ps1 +++ b/build/build-docs.ps1 @@ -11,7 +11,20 @@ if (!(Test-Path $assemblyPath)) & "$myDir\build.ps1" -Mode Debug -MsBuildVerbosity minimal -NoRelease } -# Load Assembly +# Make sure the documentation build tools are available + +if (!(Test-Path "$docsDir\node_modules")) +{ + npm install + check-success +} +if (!(Test-Path "$docsDir\bower_components")) +{ + bower install + check-success +} + +# Load Bench Assemblies & "$scriptsDir\Load-ClrLibs.ps1" @@ -30,31 +43,31 @@ function check-success() pushd $docsDir +# Clean output directory + if (Test-Path .\public) { del .\public -Recurse -Force } +# Write generated content + +& "$myDir\update-bench-cli-docs.ps1" & "$myDir\update-app-list.ps1" & "$myDir\update-dependency-graph.ps1" -if (!(Test-Path "$docsDir\node_modules")) -{ - npm install - check-success -} -if (!(Test-Path "$docsDir\bower_components")) -{ - bower install - check-success -} +# Enhance Markdown gulp check-success +# Build HTML website + hugo -D check-success +# Build CLR documenation + & "$myDir\build-clr-docs.ps1" popd diff --git a/build/build.ps1 b/build/build.ps1 index 35328edb..4f56a3d5 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -4,7 +4,8 @@ param ( [switch]$NoRelease ) -$rootDir = [IO.Path]::GetDirectoryName([IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition)) +$myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +$rootDir = [IO.Path]::GetDirectoryName($myDir) pushd $projectName = "Bench" @@ -13,6 +14,7 @@ $toolsVersion = "14.0" $mode = $Mode $verbosity = $MsBuildVerbosity $msbuild = "$env:SystemRoot\Microsoft.NET\Framework\v$clrVersion\MSBuild.exe" +$compilerPackageVersion = "1.3.2" $nugetUrl = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" $solutionDir = "BenchManager" # relative to root dir $solutionFile = "BenchManager.sln" # relative to solution dir @@ -26,9 +28,15 @@ $buildArtifacts = @( "BenchLib\bin\$mode\BenchLib.dll", "BenchLib\bin\$mode\Interop.IWshRuntimeLibrary.dll", "BenchLib\bin\$mode\DotNetZip.dll", + "BenchLib\bin\$mode\Mastersign.Sequence.dll", + "BenchCLI\bin\$mode\bench.exe", "BenchDashboard\bin\$mode\BenchDashboard.exe", "BenchDashboard\bin\$mode\BenchDashboard.exe.config", - "BenchDashboard\bin\$mode\ConEmu.WinForms.dll" + "BenchDashboard\bin\$mode\ConEmu.WinForms.dll", + "scripts\bench-cmd.cmd", + "scripts\bench-ps.cmd", + "scripts\bench-bash.cmd", + "scripts\runps.cmd" ) # Paths of release artifacts are relative to the root dir $releaseArtifacts = @( @@ -54,6 +62,9 @@ function Copy-Artifact($src, $trgDir) echo "Building Bench ($mode)" +# Add Roslyn compiler to projects +& "$myDir\prepare-project-compiler.ps1" -CompilerPackageVersion $compilerPackageVersion + # Download NuGet $nugetPath = "$rootDir\$solutionDir\nuget.exe" if (!(Test-Path $nugetPath)) @@ -80,7 +91,13 @@ echo "" echo "Building Visual Studio solution $solutionFile ..." cd "$rootDir\$solutionDir" & $msbuild $solutionFile /v:$verbosity /tv:$toolsVersion /m /p:Configuration=$mode /nodereuse:false -if ($LastExitCode -ne 0) +$buildError = $LastExitCode + +# Remove Roslyn compiler from projects +& "$myDir\prepare-project-compiler.ps1" -Remove + +# Canceling after failed build +if ($buildError -ne 0) { Write-Error "Building the solution failed." popd diff --git a/build/clone-applibs.ps1 b/build/clone-applibs.ps1 new file mode 100644 index 00000000..0bb0257c --- /dev/null +++ b/build/clone-applibs.ps1 @@ -0,0 +1,39 @@ +$myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +$rootDir = [IO.Path]::GetDirectoryName($myDir) +$scriptsDir = Resolve-Path "$rootDir\auto\lib" + +. "$scriptsDir\bench.lib.ps1" +$benchEnv = New-Object Mastersign.Bench.BenchEnvironment ($global:BenchConfig) +$benchEnv.Load() + +if (!(Get-Command git -ErrorAction Ignore)) +{ + Write-Error "Git is not available." + return +} + +$appLibsDir = Safe-Dir "$rootDir\applibs" +pushd $appLibsDir + +$appLibs = @() +Get-Content "$myDir\applibs.txt" | % { + $parts = $_.Split("=", 2) + $appLibs += @{ "id"=$parts[0]; "url"=$parts[1] } +} + +foreach ($lib in $appLibs) +{ + $p = "$appLibsDir\$($lib.id)" + if (!(Test-Path $p)) + { + echo "Cloning app library '$($lib.id)' ..." + git clone $lib.url $lib.id + } + else + { + echo "App library '$($lib.id)' is already cloned." + } +} +echo "Finished cloning app libraries." + +popd \ No newline at end of file diff --git a/build/load-applibs.ps1 b/build/load-applibs.ps1 new file mode 100644 index 00000000..073e3649 --- /dev/null +++ b/build/load-applibs.ps1 @@ -0,0 +1,35 @@ +$myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +$rootDir = [IO.Path]::GetDirectoryName($myDir) +$scriptsDir = Resolve-Path "$rootDir\auto\lib" + +. "$scriptsDir\bench.lib.ps1" +$benchEnv = New-Object Mastersign.Bench.BenchEnvironment ($global:BenchConfig) +$benchEnv.Load() + +$appLibsDevDir = Safe-Dir "$rootDir\applibs" +$appLibsDir = Empty-Dir $(Get-ConfigValue "AppLibsDir") + +pushd $appLibsDevDir + +$appLibs = @() +Get-Content "$myDir\applibs.txt" | % { + $parts = $_.Split("=", 2) + $appLibs += @{ "id"=$parts[0]; "url"=$parts[1] } +} + +foreach ($lib in $appLibs) +{ + $id = $lib.id + $p = "$appLibsDevDir\$id" + if (!(Test-Path $p)) + { + echo "App library '$id' is not cloned." + continue + } + echo "Loading app library '$id' ..." + robocopy "$appLibsDevDir\$id" "$appLibsDir\$id" /MIR /XD .git /NJH /NJS + echo "" +} +echo "Finished loading app libraries." + +popd \ No newline at end of file diff --git a/build/prepare-project-compiler.ps1 b/build/prepare-project-compiler.ps1 new file mode 100644 index 00000000..d4ac3425 --- /dev/null +++ b/build/prepare-project-compiler.ps1 @@ -0,0 +1,89 @@ +param ( + [switch]$Remove, + $CompilerPackageVersion = "1.3.2" +) + +$packageId = "Microsoft.Net.Compilers" +$packageVersion = $CompilerPackageVersion + +$myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +$rootDir = Resolve-Path "$myDir\..\BenchManager" + +$projects = @( + @{ + "name" = "BenchLib" + "targetFramework" = "net20" + } + @{ + "name" = "BenchLib.Test" + "targetFramework" = "net45" + } + @{ + "name" = "BenchCLI" + "targetFramework" = "net20" + } + @{ + "name" = "BenchDashboard" + "targetFramework" = "net45" + } +) + +function removeCompiler($name) +{ + $packagesConfigFile = "$rootDir\$name\packages.config" + if (Test-Path $packagesConfigFile -PathType Leaf) + { + [xml]$packagesConfig = Get-Content $packagesConfigFile + $killNodes = @() + foreach ($p in $packagesConfig.SelectNodes("/packages/package")) + { + if ($p.id -eq $packageId) + { + $killNodes += $p + } + } + foreach ($n in $killNodes) + { + echo "Removing package $($n.id) v$($n.version) from project $name" + $_ = $n.ParentNode.RemoveChild($n) + } + $pp = $packagesConfig.SelectNodes("/packages/package") + if ($pp.Count -gt 0) + { + $packagesConfig.Save($packagesConfigFile) + } + else + { + del $packagesConfigFile + } + } +} + +function addCompiler($name, $framework) +{ + $packagesConfigFile = "$rootDir\$name\packages.config" + if (!(Test-Path $packagesConfigFile -PathType Leaf)) + { + [xml]$newDoc = "`n" + $newDoc.Save($packagesConfigFile) + } + echo "Adding package $packageId v$packageVersion to project $name" + [xml]$packagesConfig = Get-Content $packagesConfigFile + [Xml.XmlElement]$p = $packagesConfig.CreateElement("package") + $p.SetAttribute("id", $packageId) + $p.SetAttribute("version", $packageVersion) + $p.SetAttribute("targetFramework", $framework) + $p.SetAttribute("developmentDependency", "true") + $pp = $packagesConfig.SelectSingleNode("/packages") + $_ = $pp.AppendChild($p) + $packagesConfig.Save($packagesConfigFile) +} + +foreach ($project in $projects) +{ + removeCompiler $project.name + if (!$Remove) + { + addCompiler $project.name $project.framework + } +} diff --git a/build/update-app-list.ps1 b/build/update-app-list.ps1 index c903de35..91bdd7f0 100644 --- a/build/update-app-list.ps1 +++ b/build/update-app-list.ps1 @@ -1,97 +1,93 @@ $rootDir = [IO.Path]::GetDirectoryName([IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition)) $scriptsDir = Resolve-Path "$rootDir\auto\lib" $docsDir = Resolve-Path "$rootDir\docs" -& "$scriptsDir\Load-ClrLibs.ps1" - -$cfg = New-Object Mastersign.Bench.BenchConfiguration ($rootDir, $true, $false, $false) +. "$scriptsDir\bench.lib.ps1" +$cfg = New-Object Mastersign.Bench.BenchConfiguration ($rootDir, $true, $true, $false) $apps = $cfg.Apps -$targetFile = "$docsDir\src-content\ref\apps.md" - -function GetFrontMatter($file) -{ - $sourceLines = [IO.File]::ReadAllLines($file, [Text.Encoding]::UTF8) - $hits = 0 - $lastHit = -1 - for ($i = 0; $i -lt $sourceLines.Length; $i++) - { - if ($sourceLines[$i] -eq "+++") - { - $hits++ - $lastHit = $i - } - if ($hits -eq 2) { break; } - } - if ($hits -eq 2) - { - $sb = New-Object System.Text.StringBuilder - for ($i = 0; $i -le $lastHit; $i++) - { - $_ = $sb.AppendLine($sourceLines[$i]) - } - return $sb.ToString() - } - return "" -} +$targetFile = "$docsDir\content\ref\apps.md" +$targetDir = Empty-Dir "$docsDir\content\apps" -function WriteAppBlock($sb, $app) +function WriteAppFile($app, $no) { - $_ = $sb.AppendLine("### $($app.Label) {#$($app.ID)}") - $_ = $sb.AppendLine() - $_ = $sb.AppendLine("* ID: ``$($app.ID)``") - $_ = $sb.AppendLine("* Typ: ``$($app.Typ)``") - if ($app.Website) { $_ = $sb.AppendLine("* Website: <$($app.Website)>") } $version = $app.Version if (!$version) { $version = "latest" } - $_ = $sb.AppendLine("* Version: $version") - if ($app.Dependencies.Length -gt 0) + $ns = $app.Namespace + if (!$ns) { $ns = "(default)" } + $deps = $app.Dependencies + $resp = $app.Responsibilities + + $f = [IO.Path]::Combine($targetDir, $app.ID + ".md") + $w = New-Object System.IO.StreamWriter @($f, $false, [Text.Encoding]::UTF8) + $w.WriteLine("+++") + $w.WriteLine("title = `"$($app.Label)`"") + $w.WriteLine("weight = $no") + # Custom page params + $w.WriteLine("app_library = `"$($app.AppLibrary.ID)`"") + $w.WriteLine("app_category = `"$($app.Category)`"") + $w.WriteLine("app_typ = `"$($app.Typ)`"") + $w.WriteLine("app_ns = `"$ns`"") + $w.WriteLine("app_id = `"$($app.ID)`"") + $w.WriteLine("app_version = `"$version`"") + # Taxonomies + $w.WriteLine("app_categories = [`"$($app.Category)`"]") + $w.WriteLine("app_libraries = [`"$($app.AppLibrary.ID)`"]") + $w.WriteLine("app_types = [`"$($app.Typ)`"]") + $w.WriteLine("+++") + $w.WriteLine() + $w.WriteLine("**ID:** ``$($app.ID)`` ") + $w.WriteLine("**Version:** $version ") + $w.WriteLine("") + $w.WriteLine() + $w.WriteLine("[Back to all apps](/apps/)") + if ($app.MarkdownDocumentation) + { + $w.WriteLine() + $w.WriteLine("## Description") + $w.WriteLine($app.MarkdownDocumentation) + } + $w.WriteLine() + $w.WriteLine("## Source") + $w.WriteLine() + $w.WriteLine("* Library: ``$($app.AppLibrary.ID)``") + $w.WriteLine("* Category: $($app.Category)") + $w.WriteLine("* Order Index: $no") + $w.WriteLine() + $w.WriteLine("## Properties") + $w.WriteLine() + $w.WriteLine("* Namespace: $ns") + $w.WriteLine("* Name: $($app.Name)") + $w.WriteLine("* Typ: ``$($app.Typ)``") + if ($app.Website) { $w.WriteLine("* Website: <$($app.Website)>") } + + if ($deps.Length -gt 0) { - [array]$deps = $app.Dependencies | % { + [array]$deps2 = $deps | % { $depApp = $apps[$_] - return "[$($depApp.Label)](#$_)" + return "[$($depApp.Label)](/app/$_)" } - $depsList = [string]::Join(", ", $deps) - $_ = $sb.AppendLine("* Dependencies: $depsList") + $depsList = [string]::Join(", ", $deps2) + $w.WriteLine("* Dependencies: $depsList") } - $_ = $sb.AppendLine() -} - -function WriteAppTable($sb, $label, $anchor) -{ - $_ = $sb.AppendLine("[**$label**](#$anchor)") - $_ = $sb.AppendLine() - $_ = $sb.AppendLine("") - $_ = $sb.AppendLine() + $w.WriteLine() + $w.Close() } -function WriteAppCategory($sb, $label, $anchor, $name) +$no = 0 +foreach ($app in $apps) { - $_ = $sb.AppendLine("## $label {#$anchor}") - $_ = $sb.AppendLine() - $apps.ByCategory($name) | Sort-Object -Property Label | % { WriteAppBlock $sb $_ } + $no++ + if (!$app.AppLibrary) { continue } + Write-Host "$($no.ToString("0000")) $($app.ID)" + WriteAppFile $app $no } - -$sb = New-Object System.Text.StringBuilder -$_ = $sb.Append((GetFrontMatter $targetFile)) -$_ = $sb.AppendLine() -$_ = $sb.AppendLine("## Overview") -$_ = $sb.AppendLine() -WriteAppTable $sb "Groups" "groups" -WriteAppTable $sb "Required Apps" "apps-required" -WriteAppTable $sb "Optional Apps" "apps-optional" - -WriteAppCategory $sb "Groups" "groups" "Groups" -WriteAppCategory $sb "Required Apps" "apps-required" "Required" -WriteAppCategory $sb "Optional Apps" "apps-optional" "Optional" - -[IO.File]::WriteAllText($targetFile, $sb.ToString(), [Text.Encoding]::UTF8) diff --git a/build/update-bench-cli-docs.ps1 b/build/update-bench-cli-docs.ps1 new file mode 100644 index 00000000..41945aca --- /dev/null +++ b/build/update-bench-cli-docs.ps1 @@ -0,0 +1,17 @@ +$myDir = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +$rootDir = [IO.Path]::GetDirectoryName($myDir) + +$docsDir = "$rootDir\docs" +$targetFile = "$docsDir\content\ref\bench-cli.md" + +@" ++++ +date = "$([DateTime]::Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssK"))" +description = "The command-line interface: bench.exe" +title = "Bench CLI" +weight = 2 ++++ + +"@ | Out-File $targetFile -Encoding utf8 + +& $rootDir\auto\bin\bench.exe --help-format Markdown help --no-title --target-file "$targetFile" --append diff --git a/build/update-dependency-graph.ps1 b/build/update-dependency-graph.ps1 index 4decb963..162caf08 100644 --- a/build/update-dependency-graph.ps1 +++ b/build/update-dependency-graph.ps1 @@ -6,15 +6,14 @@ $docsDir = Resolve-Path "$rootDir\docs" $sourceDirs = @( $rootDir, "$rootDir\auto", - "$rootDir\auto\lib", - "$rootDir\actions" + "$rootDir\auto\lib" ) $constNodes = @( "BenchDashboard.exe", "BenchLib.dll" ) $filter = @("*.bat", "*.cmd", "*.ps1") -$exclude = @("runps.cmd", "env.cmd") +$exclude = @() $targetFile = "$docsDir\src-static\graph\dependencies.gw" $imageFile = "$docsDir\static\img\dependencies.svg" $graphLabel = "Dependencies" diff --git a/docs/config.toml b/docs/config.toml index d8742267..5f677fc8 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -10,9 +10,14 @@ canonifyurls = true relativeurls = false paginate = 10 -[indexes] +preserveTaxonomyNames = true + +[taxonomies] tag = "tags" topic = "topics" + app_library = "app_libraries" + app_category = "app_categories" + app_typ = "app_types" [params] # Shown in the home page @@ -51,28 +56,34 @@ paginate = 10 weight = 3 identifier = "start" url = "/start/" + [[menu.main]] + name = "Apps" + pre = "" + weight = 4 + indentifier = "apps" + url = "/apps/" [[menu.main]] name = "Overview" pre = "" - weight = 4 + weight = 5 identifier = "overview" url = "/overview/" [[menu.main]] name = "Tutorials" pre = "" - weight = 5 + weight = 6 identifier = "tutorials" url = "/tutorial/" [[menu.main]] name = "Tech Guides" pre = "" - weight = 6 + weight = 7 identifier = "guides" url = "/guide/" [[menu.main]] name = "Reference Docs" pre = "" - weight = 7 + weight = 8 identifier = "reference" url = "/ref/" [[menu.main]] diff --git a/docs/content/about.md b/docs/content/about.md index 07d90927..73d27de3 100644 --- a/docs/content/about.md +++ b/docs/content/about.md @@ -9,8 +9,8 @@ Bench is a portable environment for software development on Windows. The recurring pain to install and configure numerous command-line tools and other programs under Windows with all its pit falls and side-effects led to the development of Bench. -It evolved from a collection of some PowerShell scripts, into a .NET library -and a GUI for quick and easy configuration of a development environment. +It evolved from a collection of some PowerShell scripts, into a .NET library, +a CLI, and a GUI for quick and easy configuration of a development environment. It aims to feel comfortable from the command-lines perspective but at the same time to be easy to use via a graphical interface. @@ -29,13 +29,13 @@ Bench by-passes the different setup and installation programs and works with its own setup process. ## Batteries Included -Bench comes with a number of [predefined apps](/ref/apps) you just need to activate: +Bench comes with a number of [predefined apps](/apps) you just need to activate: Git, Node.js, Python, PHP, Ruby, Go, JDK, Maven, Leinigen, MinGW, Clang, MiKTeX, Eclipse, Visual Studio Code, Sublime Text 3, Emacs, GIMP, Inkscape, FFmpeg, GraphicsMagick, OpenSSL, GnuPG, ... -Take a look in the [list of included apps](/ref/apps). +Take a look in the [list of included apps](/apps). If the app you need is not included in the app library of Bench you can easily define you own custom apps. @@ -56,4 +56,4 @@ but only changes files in its root folder, it can be easily moved and also used on a portable drive. The only thing you have to do, when the path to the Bench root folder changes, -is run the [environment update action](/ref/bench-ctl/#update-env). +is run the [environment update action](/ref/bench-cli/#cmd_bench-manage-update-env). diff --git a/docs/content/apps/Bench.7z.md b/docs/content/apps/Bench.7z.md new file mode 100644 index 00000000..b1452e42 --- /dev/null +++ b/docs/content/apps/Bench.7z.md @@ -0,0 +1,37 @@ ++++ +title = "7-Zip" +weight = 2 +app_library = "core" +app_category = "Required" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.7z" +app_version = "16.04" +app_categories = ["Required"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.7z` +**Version:** 16.04 + + +[Back to all apps](/apps/) + +## Description +7-Zip is a file archiver with a high compression ratio. +It comes with a graphical file manager and supports a large range of compression formats for extraction. + +## Source + +* Library: `core` +* Category: Required +* Order Index: 2 + +## Properties + +* Namespace: Bench +* Name: 7z +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.AntRenamer.md b/docs/content/apps/Bench.AntRenamer.md new file mode 100644 index 00000000..58e21d74 --- /dev/null +++ b/docs/content/apps/Bench.AntRenamer.md @@ -0,0 +1,37 @@ ++++ +title = "Ant Renamer" +weight = 67 +app_library = "default" +app_category = "Filesystem" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.AntRenamer" +app_version = "latest" +app_categories = ["Filesystem"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.AntRenamer` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +Ant Renamer is a free program that makes easier the renaming of lots of files and folders +by using specified settings. + +## Source + +* Library: `default` +* Category: Filesystem +* Order Index: 67 + +## Properties + +* Namespace: Bench +* Name: AntRenamer +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Apache.md b/docs/content/apps/Bench.Apache.md new file mode 100644 index 00000000..bdd448c6 --- /dev/null +++ b/docs/content/apps/Bench.Apache.md @@ -0,0 +1,40 @@ ++++ +title = "Apache" +weight = 75 +app_library = "default" +app_category = "Services" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Apache" +app_version = "2.4.25" +app_categories = ["Services"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Apache` +**Version:** 2.4.25 + + +[Back to all apps](/apps/) + +## Description +The Apache HTTP Server is a secure, efficient and extensible server +that provides HTTP services in sync with the current HTTP standards. +The Apache HTTP Server is the most popular web server since over 20 years. + +This application needs the x86 version of the [Visual C++ 14 Redistributable](https://www.microsoft.com/download/details.aspx?id=48145) installed. + +## Source + +* Library: `default` +* Category: Services +* Order Index: 75 + +## Properties + +* Namespace: Bench +* Name: Apache +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Atom.md b/docs/content/apps/Bench.Atom.md new file mode 100644 index 00000000..672ed04d --- /dev/null +++ b/docs/content/apps/Bench.Atom.md @@ -0,0 +1,39 @@ ++++ +title = "Atom" +weight = 36 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Atom" +app_version = "1.13.0" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Atom` +**Version:** 1.13.0 + + +[Back to all apps](/apps/) + +## Description +A hackable text editor for the 21st Century. + +_Hint: Install the `env-from-shell` package to make sure the Bench environment +is picked up from Atom._ + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 36 + +## Properties + +* Namespace: Bench +* Name: Atom +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Blender.md b/docs/content/apps/Bench.Blender.md new file mode 100644 index 00000000..b797eaa2 --- /dev/null +++ b/docs/content/apps/Bench.Blender.md @@ -0,0 +1,36 @@ ++++ +title = "Blender" +weight = 94 +app_library = "default" +app_category = "3D Modeling" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Blender" +app_version = "2.78" +app_categories = ["3D Modeling"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Blender` +**Version:** 2.78 + + +[Back to all apps](/apps/) + +## Description +Blender is the open source, cross platform suite of tools for 3D creation. + +## Source + +* Library: `default` +* Category: 3D Modeling +* Order Index: 94 + +## Properties + +* Namespace: Bench +* Name: Blender +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Bower.md b/docs/content/apps/Bench.Bower.md new file mode 100644 index 00000000..7af55eba --- /dev/null +++ b/docs/content/apps/Bench.Bower.md @@ -0,0 +1,42 @@ ++++ +title = "Bower" +weight = 50 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "node-package" +app_ns = "Bench" +app_id = "Bench.Bower" +app_version = ">=1.7.0 <2.0.0" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["node-package"] ++++ + +**ID:** `Bench.Bower` +**Version:** >=1.7.0 <2.0.0 + + +[Back to all apps](/apps/) + +## Description +Web sites are made of lots of things — frameworks, libraries, assets, and utilities. +Bower manages all these things for you. + +Bower can manage components that contain HTML, CSS, JavaScript, fonts or even image files. +Bower doesn’t concatenate or minify code or do anything else - it just installs +the right versions of the packages you need and their dependencies. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 50 + +## Properties + +* Namespace: Bench +* Name: Bower +* Typ: `node-package` +* Website: +* Dependencies: [Git](/app/Bench.Git), [NPM](/app/Bench.Npm) + diff --git a/docs/content/apps/Bench.CMake.md b/docs/content/apps/Bench.CMake.md new file mode 100644 index 00000000..39d272d0 --- /dev/null +++ b/docs/content/apps/Bench.CMake.md @@ -0,0 +1,46 @@ ++++ +title = "CMake" +weight = 54 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.CMake" +app_version = "3.7.2" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.CMake` +**Version:** 3.7.2 + + +[Back to all apps](/apps/) + +## Description +CMake is an open-source, cross-platform family of tools designed to build, +test and package software. CMake is used to control the software compilation process +using simple platform and compiler independent configuration files, and generate native +makefiles and workspaces that can be used in the compiler environment of your choice. +The suite of CMake tools were created by Kitware in response to the need for a powerful, +cross-platform build environment for open-source projects such as ITK and VTK. + +Usually you want to use this app with _MinGW_. + +To setup a C/C++ project with CMake and MinGW (`mingw32-make`), you have to activate the _MinGW_ app with the `mingw32-make` package. +Setup your project with a `CMakeLists.txt` file and run `cmake -G "MinGW Makefiles" ` to generate the `Makefile`. Run `cmake --build ` to compile the project. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 54 + +## Properties + +* Namespace: Bench +* Name: CMake +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Clang.md b/docs/content/apps/Bench.Clang.md new file mode 100644 index 00000000..6f563186 --- /dev/null +++ b/docs/content/apps/Bench.Clang.md @@ -0,0 +1,44 @@ ++++ +title = "LLVM Clang" +weight = 32 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Clang" +app_version = "3.9.1" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Clang` +**Version:** 3.9.1 + + +[Back to all apps](/apps/) + +## Description +The Clang compiler can act as drop-in replacement for the GCC compilers. + +This app sets the environment variables `CC` and `CXX` to inform _CMake_ +about the C/C++ compiler path. Therefore, if you build your C/C++ projects +with _CMake_, it is sufficient to just activate the _Clang_ app and _CMake_ +will use _Clang_ instead of the GCC compiler from _MinGW_. + +If you want to use the Clang compiler with Eclipse, you must manually +install the LLVM-Plugin for Eclipse CDT. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 32 + +## Properties + +* Namespace: Bench +* Name: Clang +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.CoffeeScript.md b/docs/content/apps/Bench.CoffeeScript.md new file mode 100644 index 00000000..6b4f8244 --- /dev/null +++ b/docs/content/apps/Bench.CoffeeScript.md @@ -0,0 +1,34 @@ ++++ +title = "CoffeeScript" +weight = 35 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "node-package" +app_ns = "Bench" +app_id = "Bench.CoffeeScript" +app_version = ">=1.10.0 <2.0.0" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["node-package"] ++++ + +**ID:** `Bench.CoffeeScript` +**Version:** >=1.10.0 <2.0.0 + + +[Back to all apps](/apps/) + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 35 + +## Properties + +* Namespace: Bench +* Name: CoffeeScript +* Typ: `node-package` +* Website: +* Dependencies: [NPM](/app/Bench.Npm) + diff --git a/docs/content/apps/Bench.ConEmu.md b/docs/content/apps/Bench.ConEmu.md new file mode 100644 index 00000000..88984a2d --- /dev/null +++ b/docs/content/apps/Bench.ConEmu.md @@ -0,0 +1,36 @@ ++++ +title = "ConEmu" +weight = 4 +app_library = "core" +app_category = "Required" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.ConEmu" +app_version = "17.01.18" +app_categories = ["Required"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.ConEmu` +**Version:** 17.01.18 + + +[Back to all apps](/apps/) + +## Description +ConEmu-Maximus5 is a Windows console emulator with tabs, which presents multiple consoles and simple GUI applications as one customizable GUI window with various features. + +## Source + +* Library: `core` +* Category: Required +* Order Index: 4 + +## Properties + +* Namespace: Bench +* Name: ConEmu +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Dia.md b/docs/content/apps/Bench.Dia.md new file mode 100644 index 00000000..d29bdc49 --- /dev/null +++ b/docs/content/apps/Bench.Dia.md @@ -0,0 +1,36 @@ ++++ +title = "Dia" +weight = 90 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Dia" +app_version = "0.97.2" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Dia` +**Version:** 0.97.2 + + +[Back to all apps](/apps/) + +## Description +Dia is a program to draw structured diagrams. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 90 + +## Properties + +* Namespace: Bench +* Name: Dia +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.DotNetCore.md b/docs/content/apps/Bench.DotNetCore.md new file mode 100644 index 00000000..7fddda3c --- /dev/null +++ b/docs/content/apps/Bench.DotNetCore.md @@ -0,0 +1,36 @@ ++++ +title = ".NET Core SDK" +weight = 28 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.DotNetCore" +app_version = "1.1" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.DotNetCore` +**Version:** 1.1 + + +[Back to all apps](/apps/) + +## Description +The build tools and compilers for platform independent .NET Core applications. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 28 + +## Properties + +* Namespace: Bench +* Name: DotNetCore +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.EclipseCpp.md b/docs/content/apps/Bench.EclipseCpp.md new file mode 100644 index 00000000..e4e872b4 --- /dev/null +++ b/docs/content/apps/Bench.EclipseCpp.md @@ -0,0 +1,37 @@ ++++ +title = "Eclipse for C++" +weight = 47 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.EclipseCpp" +app_version = "4.6" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.EclipseCpp` +**Version:** 4.6 + + +[Back to all apps](/apps/) + +## Description +An IDE for C/C++ developers with Mylyn integration. + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 47 + +## Properties + +* Namespace: Bench +* Name: EclipseCpp +* Typ: `default` +* Website: +* Dependencies: [Java Runtime Environment 8](/app/Bench.JRE8) + diff --git a/docs/content/apps/Bench.EclipseJava.md b/docs/content/apps/Bench.EclipseJava.md new file mode 100644 index 00000000..26e74976 --- /dev/null +++ b/docs/content/apps/Bench.EclipseJava.md @@ -0,0 +1,38 @@ ++++ +title = "Eclipse for Java" +weight = 45 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.EclipseJava" +app_version = "4.6" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.EclipseJava` +**Version:** 4.6 + + +[Back to all apps](/apps/) + +## Description +The essential tools for any Java developer, including a Java IDE, a Git client, +XML Editor, Mylyn, Maven and Gradle integration... + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 45 + +## Properties + +* Namespace: Bench +* Name: EclipseJava +* Typ: `default` +* Website: +* Dependencies: [Java Runtime Environment 8](/app/Bench.JRE8) + diff --git a/docs/content/apps/Bench.EclipsePHP.md b/docs/content/apps/Bench.EclipsePHP.md new file mode 100644 index 00000000..0b710fee --- /dev/null +++ b/docs/content/apps/Bench.EclipsePHP.md @@ -0,0 +1,38 @@ ++++ +title = "Eclipse for PHP" +weight = 46 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.EclipsePHP" +app_version = "4.6" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.EclipsePHP` +**Version:** 4.6 + + +[Back to all apps](/apps/) + +## Description +The essential tools for any PHP developer, including PHP language support, +Git client, Mylyn and editors for JavaScript, HTML, CSS and... + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 46 + +## Properties + +* Namespace: Bench +* Name: EclipsePHP +* Typ: `default` +* Website: +* Dependencies: [Java Runtime Environment 8](/app/Bench.JRE8) + diff --git a/docs/content/apps/Bench.Emacs.md b/docs/content/apps/Bench.Emacs.md new file mode 100644 index 00000000..833e4d9b --- /dev/null +++ b/docs/content/apps/Bench.Emacs.md @@ -0,0 +1,41 @@ ++++ +title = "Emacs" +weight = 39 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Emacs" +app_version = "25.1-2" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Emacs` +**Version:** 25.1-2 + + +[Back to all apps](/apps/) + +## Description +An extensible, customizable, free text editor - and more. + +GNU Emacs at its core is an interpreter for Emacs Lisp, a dialect of the Lisp programming language +with extensions to support text editing. + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 39 + +## Properties + +* Namespace: Bench +* Name: Emacs +* Typ: `default` +* Website: +* Dependencies: [GNU TLS](/app/Bench.GnuTLS) +* Responsibilities: [Spacemacs](/app/Bench.Spacemacs) + diff --git a/docs/content/apps/Bench.Erlang.md b/docs/content/apps/Bench.Erlang.md new file mode 100644 index 00000000..f76be53f --- /dev/null +++ b/docs/content/apps/Bench.Erlang.md @@ -0,0 +1,37 @@ ++++ +title = "Erlang" +weight = 34 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Erlang" +app_version = "19.2" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Erlang` +**Version:** 19.2 + + +[Back to all apps](/apps/) + +## Description +Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecoms, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 34 + +## Properties + +* Namespace: Bench +* Name: Erlang +* Typ: `default` +* Website: +* Responsibilities: [RabbitMQ](/app/Bench.RabbitMQ) + diff --git a/docs/content/apps/Bench.FFmpeg.md b/docs/content/apps/Bench.FFmpeg.md new file mode 100644 index 00000000..88acb95d --- /dev/null +++ b/docs/content/apps/Bench.FFmpeg.md @@ -0,0 +1,39 @@ ++++ +title = "FFmpeg" +weight = 85 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.FFmpeg" +app_version = "latest" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.FFmpeg` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +FFmpeg is the leading multimedia framework, able to decode, encode, transcode, +mux, demux, stream, filter and play pretty much anything that humans and machines have created. +It supports the most obscure ancient formats up to the cutting edge. +No matter if they were designed by some standards committee, the community or a corporation. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 85 + +## Properties + +* Namespace: Bench +* Name: FFmpeg +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.FileZilla.md b/docs/content/apps/Bench.FileZilla.md new file mode 100644 index 00000000..eb8bf59a --- /dev/null +++ b/docs/content/apps/Bench.FileZilla.md @@ -0,0 +1,36 @@ ++++ +title = "FileZilla" +weight = 69 +app_library = "default" +app_category = "Network" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.FileZilla" +app_version = "3.24.0" +app_categories = ["Network"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.FileZilla` +**Version:** 3.24.0 + + +[Back to all apps](/apps/) + +## Description +FileZilla Client is a free, open source FTP client. It supports FTP, SFTP, and FTPS (FTP over SSL/TLS). + +## Source + +* Library: `default` +* Category: Network +* Order Index: 69 + +## Properties + +* Namespace: Bench +* Name: FileZilla +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.FreeCAD.md b/docs/content/apps/Bench.FreeCAD.md new file mode 100644 index 00000000..fc2c0d8f --- /dev/null +++ b/docs/content/apps/Bench.FreeCAD.md @@ -0,0 +1,33 @@ ++++ +title = "FreeCAD" +weight = 95 +app_library = "default" +app_category = "3D Modeling" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.FreeCAD" +app_version = "0.16" +app_categories = ["3D Modeling"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.FreeCAD` +**Version:** 0.16 + + +[Back to all apps](/apps/) + +## Source + +* Library: `default` +* Category: 3D Modeling +* Order Index: 95 + +## Properties + +* Namespace: Bench +* Name: FreeCAD +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Gimp.md b/docs/content/apps/Bench.Gimp.md new file mode 100644 index 00000000..9dd28f9d --- /dev/null +++ b/docs/content/apps/Bench.Gimp.md @@ -0,0 +1,40 @@ ++++ +title = "GIMP" +weight = 92 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Gimp" +app_version = "2.8.18" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Gimp` +**Version:** 2.8.18 + + +[Back to all apps](/apps/) + +## Description +The GNU Image Manipulation Program. + +GIMP is a cross-platform image editor. +Whether you are a graphic designer, photographer, illustrator, or scientist, +GIMP provides you with sophisticated tools to get your job done. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 92 + +## Properties + +* Namespace: Bench +* Name: Gimp +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Git.md b/docs/content/apps/Bench.Git.md new file mode 100644 index 00000000..30f0ba27 --- /dev/null +++ b/docs/content/apps/Bench.Git.md @@ -0,0 +1,37 @@ ++++ +title = "Git" +weight = 5 +app_library = "core" +app_category = "Core" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Git" +app_version = "2.10.1" +app_categories = ["Core"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.Git` +**Version:** 2.10.1 + + +[Back to all apps](/apps/) + +## Description +Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. + +## Source + +* Library: `core` +* Category: Core +* Order Index: 5 + +## Properties + +* Namespace: Bench +* Name: Git +* Typ: `default` +* Website: +* Responsibilities: [Spacemacs](/app/Bench.Spacemacs), [Bower](/app/Bench.Bower) + diff --git a/docs/content/apps/Bench.GitKraken.md b/docs/content/apps/Bench.GitKraken.md new file mode 100644 index 00000000..183e1216 --- /dev/null +++ b/docs/content/apps/Bench.GitKraken.md @@ -0,0 +1,38 @@ ++++ +title = "GitKraken" +weight = 20 +app_library = "default" +app_category = "Version Control" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.GitKraken" +app_version = "latest" +app_categories = ["Version Control"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.GitKraken` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +The downright luxurious Git client, for Windows, Mac & Linux. + +No proxy support yet (Version 1.3.0). + +## Source + +* Library: `default` +* Category: Version Control +* Order Index: 20 + +## Properties + +* Namespace: Bench +* Name: GitKraken +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.GnuPG.md b/docs/content/apps/Bench.GnuPG.md new file mode 100644 index 00000000..27e54968 --- /dev/null +++ b/docs/content/apps/Bench.GnuPG.md @@ -0,0 +1,40 @@ ++++ +title = "GnuPG" +weight = 18 +app_library = "default" +app_category = "Security" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.GnuPG" +app_version = "2.0.30" +app_categories = ["Security"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.GnuPG` +**Version:** 2.0.30 + + +[Back to all apps](/apps/) + +## Description +GnuPG is a complete and free implementation of the OpenPGP standard as defined by RFC4880 (also known as PGP). +GnuPG allows to encrypt and sign your data and communication, features a versatile key management system +as well as access modules for all kinds of public key directories. +GnuPG, also known as GPG, is a command line tool with features for easy integration with other applications. + +## Source + +* Library: `default` +* Category: Security +* Order Index: 18 + +## Properties + +* Namespace: Bench +* Name: GnuPG +* Typ: `default` +* Website: +* Responsibilities: [Leiningen](/app/Bench.Leiningen), [Maven](/app/Bench.Maven) + diff --git a/docs/content/apps/Bench.GnuTLS.md b/docs/content/apps/Bench.GnuTLS.md new file mode 100644 index 00000000..88b8e500 --- /dev/null +++ b/docs/content/apps/Bench.GnuTLS.md @@ -0,0 +1,37 @@ ++++ +title = "GNU TLS" +weight = 17 +app_library = "default" +app_category = "Security" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.GnuTLS" +app_version = "3.4.15" +app_categories = ["Security"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.GnuTLS` +**Version:** 3.4.15 + + +[Back to all apps](/apps/) + +## Description +The GnuTLS Transport Layer Security Library. + +## Source + +* Library: `default` +* Category: Security +* Order Index: 17 + +## Properties + +* Namespace: Bench +* Name: GnuTLS +* Typ: `default` +* Website: +* Responsibilities: [Emacs](/app/Bench.Emacs) + diff --git a/docs/content/apps/Bench.Go.md b/docs/content/apps/Bench.Go.md new file mode 100644 index 00000000..4e76323d --- /dev/null +++ b/docs/content/apps/Bench.Go.md @@ -0,0 +1,37 @@ ++++ +title = "Go" +weight = 33 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Go" +app_version = "1.7.4" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Go` +**Version:** 1.7.4 + + +[Back to all apps](/apps/) + +## Description +Go is an open source programming language that makes it easy +to build simple, reliable, and efficient software. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 33 + +## Properties + +* Namespace: Bench +* Name: Go +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.GraphicsMagick.md b/docs/content/apps/Bench.GraphicsMagick.md new file mode 100644 index 00000000..447a307c --- /dev/null +++ b/docs/content/apps/Bench.GraphicsMagick.md @@ -0,0 +1,39 @@ ++++ +title = "Graphics Magick" +weight = 84 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.GraphicsMagick" +app_version = "1.3.25" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.GraphicsMagick` +**Version:** 1.3.25 + + +[Back to all apps](/apps/) + +## Description +GraphicsMagick is the swiss army knife of image processing. It provides a robust +and efficient collection of tools and libraries which support reading, writing, +and manipulating an image in over 88 major formats including important formats +like DPX, GIF, JPEG, JPEG-2000, PNG, PDF, PNM, and TIFF. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 84 + +## Properties + +* Namespace: Bench +* Name: GraphicsMagick +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Graphviz.md b/docs/content/apps/Bench.Graphviz.md new file mode 100644 index 00000000..fced0daa --- /dev/null +++ b/docs/content/apps/Bench.Graphviz.md @@ -0,0 +1,41 @@ ++++ +title = "Graphviz" +weight = 89 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Graphviz" +app_version = "2.38" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Graphviz` +**Version:** 2.38 + + +[Back to all apps](/apps/) + +## Description +Graphviz is open source graph visualization software. +Graph visualization is a way of representing structural information as diagrams +of abstract graphs and networks. It has important applications in networking, +bioinformatics, software engineering, database and web design, machine learning, +and in visual interfaces for other technical domains. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 89 + +## Properties + +* Namespace: Bench +* Name: Graphviz +* Typ: `default` +* Website: +* Responsibilities: [Yeoman Generator for Markdown Projects](/app/User.MdProc) + diff --git a/docs/content/apps/Bench.Grunt.md b/docs/content/apps/Bench.Grunt.md new file mode 100644 index 00000000..9594e4a1 --- /dev/null +++ b/docs/content/apps/Bench.Grunt.md @@ -0,0 +1,37 @@ ++++ +title = "Grunt" +weight = 49 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "node-package" +app_ns = "Bench" +app_id = "Bench.Grunt" +app_version = ">=1.0.0 <2.0.0" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["node-package"] ++++ + +**ID:** `Bench.Grunt` +**Version:** >=1.0.0 <2.0.0 + + +[Back to all apps](/apps/) + +## Description +The JavaScript Task Runner + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 49 + +## Properties + +* Namespace: Bench +* Name: Grunt +* Typ: `node-package` +* Website: +* Dependencies: [NPM](/app/Bench.Npm) + diff --git a/docs/content/apps/Bench.Gulp.md b/docs/content/apps/Bench.Gulp.md new file mode 100644 index 00000000..bf4c946c --- /dev/null +++ b/docs/content/apps/Bench.Gulp.md @@ -0,0 +1,38 @@ ++++ +title = "Gulp" +weight = 48 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "node-package" +app_ns = "Bench" +app_id = "Bench.Gulp" +app_version = ">=3.9.0 <4.0.0" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["node-package"] ++++ + +**ID:** `Bench.Gulp` +**Version:** >=3.9.0 <4.0.0 + + +[Back to all apps](/apps/) + +## Description +The streaming build system. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 48 + +## Properties + +* Namespace: Bench +* Name: Gulp +* Typ: `node-package` +* Website: +* Dependencies: [NPM](/app/Bench.Npm) +* Responsibilities: [Yeoman Generator for Markdown Projects](/app/User.MdProc) + diff --git a/docs/content/apps/Bench.HandBrake.md b/docs/content/apps/Bench.HandBrake.md new file mode 100644 index 00000000..25c36d90 --- /dev/null +++ b/docs/content/apps/Bench.HandBrake.md @@ -0,0 +1,36 @@ ++++ +title = "HandBrake" +weight = 87 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.HandBrake" +app_version = "1.0.2" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.HandBrake` +**Version:** 1.0.2 + + +[Back to all apps](/apps/) + +## Description +The open source video transcoder. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 87 + +## Properties + +* Namespace: Bench +* Name: HandBrake +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.HandBrakeCLI.md b/docs/content/apps/Bench.HandBrakeCLI.md new file mode 100644 index 00000000..cba77bd7 --- /dev/null +++ b/docs/content/apps/Bench.HandBrakeCLI.md @@ -0,0 +1,36 @@ ++++ +title = "HandBrakeCLI" +weight = 86 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.HandBrakeCLI" +app_version = "1.0.2" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.HandBrakeCLI` +**Version:** 1.0.2 + + +[Back to all apps](/apps/) + +## Description +The command line interface for the open source video transcoder. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 86 + +## Properties + +* Namespace: Bench +* Name: HandBrakeCLI +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Hugo.md b/docs/content/apps/Bench.Hugo.md new file mode 100644 index 00000000..6430f211 --- /dev/null +++ b/docs/content/apps/Bench.Hugo.md @@ -0,0 +1,39 @@ ++++ +title = "Hugo" +weight = 83 +app_library = "default" +app_category = "Web" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Hugo" +app_version = "0.18.1" +app_categories = ["Web"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Hugo` +**Version:** 0.18.1 + + +[Back to all apps](/apps/) + +## Description +A fast and modern static website engine. + +Hugo flexibly works with many formats and is ideal for blogs, docs, portfolios +and much more. Hugo’s speed fosters creativity and makes building a website fun again. + +## Source + +* Library: `default` +* Category: Web +* Order Index: 83 + +## Properties + +* Namespace: Bench +* Name: Hugo +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.IPython2.md b/docs/content/apps/Bench.IPython2.md new file mode 100644 index 00000000..70c2b4e5 --- /dev/null +++ b/docs/content/apps/Bench.IPython2.md @@ -0,0 +1,37 @@ ++++ +title = "IPython 2" +weight = 60 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "python2-package" +app_ns = "Bench" +app_id = "Bench.IPython2" +app_version = "latest" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["python2-package"] ++++ + +**ID:** `Bench.IPython2` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +IPython provides a rich architecture for computing with a powerful interactive shell. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 60 + +## Properties + +* Namespace: Bench +* Name: IPython2 +* Typ: `python2-package` +* Website: +* Dependencies: [PyReadline (Python 2)](/app/Bench.PyReadline2), [Python 2](/app/Bench.Python2) + diff --git a/docs/content/apps/Bench.IPython3.md b/docs/content/apps/Bench.IPython3.md new file mode 100644 index 00000000..8049b422 --- /dev/null +++ b/docs/content/apps/Bench.IPython3.md @@ -0,0 +1,37 @@ ++++ +title = "IPython 3" +weight = 61 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "python3-package" +app_ns = "Bench" +app_id = "Bench.IPython3" +app_version = "latest" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["python3-package"] ++++ + +**ID:** `Bench.IPython3` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +IPython provides a rich architecture for computing with a powerful interactive shell. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 61 + +## Properties + +* Namespace: Bench +* Name: IPython3 +* Typ: `python3-package` +* Website: +* Dependencies: [PyReadline (Python 3)](/app/Bench.PyReadline3), [Python 3](/app/Bench.Python3) + diff --git a/docs/content/apps/Bench.Inkscape.md b/docs/content/apps/Bench.Inkscape.md new file mode 100644 index 00000000..e14104fb --- /dev/null +++ b/docs/content/apps/Bench.Inkscape.md @@ -0,0 +1,38 @@ ++++ +title = "Inkscape" +weight = 91 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Inkscape" +app_version = "0.92.0" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Inkscape` +**Version:** 0.92.0 + + +[Back to all apps](/apps/) + +## Description +Inkscape is a professional vector graphics editor for Windows, Mac OS X and Linux. +It's free and open source. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 91 + +## Properties + +* Namespace: Bench +* Name: Inkscape +* Typ: `default` +* Website: +* Responsibilities: [Yeoman Generator for Markdown Projects](/app/User.MdProc) + diff --git a/docs/content/apps/Bench.InnoUnp.md b/docs/content/apps/Bench.InnoUnp.md new file mode 100644 index 00000000..8b15fd44 --- /dev/null +++ b/docs/content/apps/Bench.InnoUnp.md @@ -0,0 +1,36 @@ ++++ +title = "Inno Setup Unpacker" +weight = 3 +app_library = "core" +app_category = "Required" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.InnoUnp" +app_version = "0.45" +app_categories = ["Required"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.InnoUnp` +**Version:** 0.45 + + +[Back to all apps](/apps/) + +## Description +A tool to extract the files from an Inno Setup executable. + +## Source + +* Library: `core` +* Category: Required +* Order Index: 3 + +## Properties + +* Namespace: Bench +* Name: InnoUnp +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Iron.md b/docs/content/apps/Bench.Iron.md new file mode 100644 index 00000000..d394aa36 --- /dev/null +++ b/docs/content/apps/Bench.Iron.md @@ -0,0 +1,36 @@ ++++ +title = "SWare Iron" +weight = 70 +app_library = "default" +app_category = "Network" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Iron" +app_version = "latest" +app_categories = ["Network"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Iron` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +A free portable derivative of Chromium, optimized for privacy. + +## Source + +* Library: `default` +* Category: Network +* Order Index: 70 + +## Properties + +* Namespace: Bench +* Name: Iron +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.JDK7.md b/docs/content/apps/Bench.JDK7.md new file mode 100644 index 00000000..13ddeaf2 --- /dev/null +++ b/docs/content/apps/Bench.JDK7.md @@ -0,0 +1,38 @@ ++++ +title = "Java Development Kit 7" +weight = 25 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.JDK7" +app_version = "7u80" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.JDK7` +**Version:** 7u80 + + +[Back to all apps](/apps/) + +## Description +According to Oracle, Java is the world's #1 programming language. + +The development kit is required for Java source code to get compiled. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 25 + +## Properties + +* Namespace: Bench +* Name: JDK7 +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.JDK8.md b/docs/content/apps/Bench.JDK8.md new file mode 100644 index 00000000..cd6ca1ab --- /dev/null +++ b/docs/content/apps/Bench.JDK8.md @@ -0,0 +1,39 @@ ++++ +title = "Java Development Kit 8" +weight = 26 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.JDK8" +app_version = "121" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.JDK8` +**Version:** 121 + + +[Back to all apps](/apps/) + +## Description +According to Oracle, Java is the world's #1 programming language. + +The development kit is required for Java source code to get compiled. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 26 + +## Properties + +* Namespace: Bench +* Name: JDK8 +* Typ: `default` +* Website: +* Responsibilities: [Leiningen](/app/Bench.Leiningen) + diff --git a/docs/content/apps/Bench.JRE7.md b/docs/content/apps/Bench.JRE7.md new file mode 100644 index 00000000..0baa1d12 --- /dev/null +++ b/docs/content/apps/Bench.JRE7.md @@ -0,0 +1,38 @@ ++++ +title = "Java Runtime Environment 7" +weight = 23 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.JRE7" +app_version = "7u80" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.JRE7` +**Version:** 7u80 + + +[Back to all apps](/apps/) + +## Description +According to Oracle, Java is the world's #1 programming language. + +The runtime environment is required for a compiled Java program to get executed. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 23 + +## Properties + +* Namespace: Bench +* Name: JRE7 +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.JRE8.md b/docs/content/apps/Bench.JRE8.md new file mode 100644 index 00000000..a925f16c --- /dev/null +++ b/docs/content/apps/Bench.JRE8.md @@ -0,0 +1,39 @@ ++++ +title = "Java Runtime Environment 8" +weight = 24 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.JRE8" +app_version = "121" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.JRE8` +**Version:** 121 + + +[Back to all apps](/apps/) + +## Description +According to Oracle, Java is the world's #1 programming language. + +The runtime environment is required for a compiled Java program to get executed. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 24 + +## Properties + +* Namespace: Bench +* Name: JRE8 +* Typ: `default` +* Website: +* Responsibilities: [Eclipse for Java](/app/Bench.EclipseJava), [Eclipse for PHP](/app/Bench.EclipsePHP), [Eclipse for C++](/app/Bench.EclipseCpp), [Maven](/app/Bench.Maven), [JabRef](/app/Bench.JabRef) + diff --git a/docs/content/apps/Bench.JSBeautify.md b/docs/content/apps/Bench.JSBeautify.md new file mode 100644 index 00000000..1189e86c --- /dev/null +++ b/docs/content/apps/Bench.JSBeautify.md @@ -0,0 +1,42 @@ ++++ +title = "JSBeautify" +weight = 56 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "node-package" +app_ns = "Bench" +app_id = "Bench.JSBeautify" +app_version = ">=1.6.8 <2.0.0" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["node-package"] ++++ + +**ID:** `Bench.JSBeautify` +**Version:** >=1.6.8 <2.0.0 + + +[Back to all apps](/apps/) + +## Description +This little beautifier will reformat and reindent bookmarklets, ugly JavaScript, +unpack scripts packed by Dean Edward’s popular packer, +as well as deobfuscate scripts processed by . + + +Supported commands: `js-beautify`, `css-beautify`, `html-beautify` + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 56 + +## Properties + +* Namespace: Bench +* Name: JSBeautify +* Typ: `node-package` +* Website: +* Dependencies: [NPM](/app/Bench.Npm) + diff --git a/docs/content/apps/Bench.JSHint.md b/docs/content/apps/Bench.JSHint.md new file mode 100644 index 00000000..5fc5bb37 --- /dev/null +++ b/docs/content/apps/Bench.JSHint.md @@ -0,0 +1,37 @@ ++++ +title = "JSHint" +weight = 55 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "node-package" +app_ns = "Bench" +app_id = "Bench.JSHint" +app_version = ">=2.9.0 <3.0.0" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["node-package"] ++++ + +**ID:** `Bench.JSHint` +**Version:** >=2.9.0 <3.0.0 + + +[Back to all apps](/apps/) + +## Description +JSHint is a tool that helps to detect errors and potential problems in your JavaScript code. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 55 + +## Properties + +* Namespace: Bench +* Name: JSHint +* Typ: `node-package` +* Website: +* Dependencies: [NPM](/app/Bench.Npm) + diff --git a/docs/content/apps/Bench.JabRef.md b/docs/content/apps/Bench.JabRef.md new file mode 100644 index 00000000..c231c2de --- /dev/null +++ b/docs/content/apps/Bench.JabRef.md @@ -0,0 +1,38 @@ ++++ +title = "JabRef" +weight = 79 +app_library = "default" +app_category = "Writing" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.JabRef" +app_version = "3.8.1" +app_categories = ["Writing"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.JabRef` +**Version:** 3.8.1 + + +[Back to all apps](/apps/) + +## Description +JabRef is an open source bibliography reference manager. +The native file format used by JabRef is BibTeX, the standard LaTeX bibliography format. + +## Source + +* Library: `default` +* Category: Writing +* Order Index: 79 + +## Properties + +* Namespace: Bench +* Name: JabRef +* Typ: `default` +* Website: +* Dependencies: [Java Runtime Environment 8](/app/Bench.JRE8) + diff --git a/docs/content/apps/Bench.Jupyter.md b/docs/content/apps/Bench.Jupyter.md new file mode 100644 index 00000000..fe1f1f33 --- /dev/null +++ b/docs/content/apps/Bench.Jupyter.md @@ -0,0 +1,38 @@ ++++ +title = "Jupyter" +weight = 62 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "python3-package" +app_ns = "Bench" +app_id = "Bench.Jupyter" +app_version = "latest" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["python3-package"] ++++ + +**ID:** `Bench.Jupyter` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +Open source, interactive data science and scientific computing +across over 40 programming languages. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 62 + +## Properties + +* Namespace: Bench +* Name: Jupyter +* Typ: `python3-package` +* Website: +* Dependencies: [Python 3](/app/Bench.Python3) + diff --git a/docs/content/apps/Bench.Leiningen.md b/docs/content/apps/Bench.Leiningen.md new file mode 100644 index 00000000..6abe05e2 --- /dev/null +++ b/docs/content/apps/Bench.Leiningen.md @@ -0,0 +1,39 @@ ++++ +title = "Leiningen" +weight = 27 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Leiningen" +app_version = "latest" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Leiningen` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +Leiningen is the easiest way to use Clojure. +With a focus on project automation and declarative configuration, +it gets out of your way and lets you focus on your code. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 27 + +## Properties + +* Namespace: Bench +* Name: Leiningen +* Typ: `default` +* Website: +* Dependencies: [Java Development Kit 8](/app/Bench.JDK8), [GnuPG](/app/Bench.GnuPG), [Wget](/app/Bench.Wget) + diff --git a/docs/content/apps/Bench.LessMsi.md b/docs/content/apps/Bench.LessMsi.md new file mode 100644 index 00000000..0ca817a8 --- /dev/null +++ b/docs/content/apps/Bench.LessMsi.md @@ -0,0 +1,36 @@ ++++ +title = "Less MSIerables" +weight = 1 +app_library = "core" +app_category = "Required" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.LessMsi" +app_version = "1.3" +app_categories = ["Required"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.LessMsi` +**Version:** 1.3 + + +[Back to all apps](/apps/) + +## Description +A tool to view and extract the contents of a Windows Installer (.msi) file. + +## Source + +* Library: `core` +* Category: Required +* Order Index: 1 + +## Properties + +* Namespace: Bench +* Name: LessMsi +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.LightTable.md b/docs/content/apps/Bench.LightTable.md new file mode 100644 index 00000000..c741cac7 --- /dev/null +++ b/docs/content/apps/Bench.LightTable.md @@ -0,0 +1,36 @@ ++++ +title = "LightTable" +weight = 44 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.LightTable" +app_version = "0.8.1" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.LightTable` +**Version:** 0.8.1 + + +[Back to all apps](/apps/) + +## Description +The next generation code editor. + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 44 + +## Properties + +* Namespace: Bench +* Name: LightTable +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.MarkdownEdit.md b/docs/content/apps/Bench.MarkdownEdit.md new file mode 100644 index 00000000..d45ac1b5 --- /dev/null +++ b/docs/content/apps/Bench.MarkdownEdit.md @@ -0,0 +1,36 @@ ++++ +title = "Markdown Edit" +weight = 14 +app_library = "core" +app_category = "Basics" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.MarkdownEdit" +app_version = "1.32" +app_categories = ["Basics"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.MarkdownEdit` +**Version:** 1.32 + + +[Back to all apps](/apps/) + +## Description +Markdown Edit (MDE) is low distraction editor for Windows. MDE focuses on producing text documents that can be transformed into Web pages and documents. It places an emphasis on content and keyboard shortcuts. Don't let this dissuade you. Markdown Edit is a power-house of an editor. It does its job quietly and without fanfare. + +## Source + +* Library: `core` +* Category: Basics +* Order Index: 14 + +## Properties + +* Namespace: Bench +* Name: MarkdownEdit +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Maven.md b/docs/content/apps/Bench.Maven.md new file mode 100644 index 00000000..5dc1af87 --- /dev/null +++ b/docs/content/apps/Bench.Maven.md @@ -0,0 +1,39 @@ ++++ +title = "Maven" +weight = 52 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Maven" +app_version = "3.3.9" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Maven` +**Version:** 3.3.9 + + +[Back to all apps](/apps/) + +## Description +Apache Maven is a software project management and comprehension tool. +Based on the concept of a project object model (POM), Maven can manage a project's build, +reporting and documentation from a central piece of information. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 52 + +## Properties + +* Namespace: Bench +* Name: Maven +* Typ: `default` +* Website: +* Dependencies: [Java Runtime Environment 8](/app/Bench.JRE8), [GnuPG](/app/Bench.GnuPG) + diff --git a/docs/content/apps/Bench.MeshLab.md b/docs/content/apps/Bench.MeshLab.md new file mode 100644 index 00000000..2e5bcc44 --- /dev/null +++ b/docs/content/apps/Bench.MeshLab.md @@ -0,0 +1,40 @@ ++++ +title = "MeshLab" +weight = 93 +app_library = "default" +app_category = "3D Modeling" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.MeshLab" +app_version = "2016.12" +app_categories = ["3D Modeling"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.MeshLab` +**Version:** 2016.12 + + +[Back to all apps](/apps/) + +## Description +MeshLab is an open source, portable, and extensible system for the processing +and editing of unstructured 3D triangular meshes. +The system is aimed to help the processing of the typical not-so-small +unstructured models arising in 3D scanning, providing a set of tools for editing, +cleaning, healing, inspecting, rendering and converting this kind of meshes. + +## Source + +* Library: `default` +* Category: 3D Modeling +* Order Index: 93 + +## Properties + +* Namespace: Bench +* Name: MeshLab +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.MiKTeX.md b/docs/content/apps/Bench.MiKTeX.md new file mode 100644 index 00000000..532dc0be --- /dev/null +++ b/docs/content/apps/Bench.MiKTeX.md @@ -0,0 +1,42 @@ ++++ +title = "MiKTeX" +weight = 77 +app_library = "default" +app_category = "Writing" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.MiKTeX" +app_version = "2.9.6221" +app_categories = ["Writing"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.MiKTeX` +**Version:** 2.9.6221 + + +[Back to all apps](/apps/) + +## Description +MiKTeX (pronounced mick-tech) is an up-to-date implementation of TeX/LaTeX +and related programs for Windows (all current variants). + + +**Note:** The packages installed by default (property `DefaultPackages`) +are selected to suit the needs of the default LaTeX template of _Pandoc_. + +## Source + +* Library: `default` +* Category: Writing +* Order Index: 77 + +## Properties + +* Namespace: Bench +* Name: MiKTeX +* Typ: `default` +* Website: +* Responsibilities: [TeXnicCenter](/app/Bench.TeXnicCenter), [Yeoman Generator for Markdown Projects](/app/User.MdProc) + diff --git a/docs/content/apps/Bench.MinGW.md b/docs/content/apps/Bench.MinGW.md new file mode 100644 index 00000000..7dc21351 --- /dev/null +++ b/docs/content/apps/Bench.MinGW.md @@ -0,0 +1,55 @@ ++++ +title = "MinGW" +weight = 31 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "meta" +app_ns = "Bench" +app_id = "Bench.MinGW" +app_version = "0.6.2" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["meta"] ++++ + +**ID:** `Bench.MinGW` +**Version:** 0.6.2 + + +[Back to all apps](/apps/) + +## Description +MinGW provides a GNU development environment for Windows, +including compilers for C/C++, Objective-C, Fortran, Ada, ... + + +You can adapt the preselected MinGW packages by putting something like this in your `config\apps.md`: + +```Markdown +* ID: `Bench.MinGW` +* Packages: + + `mingw32-base` + + `mingw32-gcc-g++` + + `mingw32-autotools` + + `msys-bash` + + `msys-grep` +``` + +After the automatic setup by _Bench_, you can use the launcher shortcut +_MinGW Package Manager_ to start the GUI for _MinGW Get_ +and install more MinGW packages. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 31 + +## Properties + +* Namespace: Bench +* Name: MinGW +* Typ: `meta` +* Website: +* Dependencies: [MinGwGet](/app/Bench.MinGwGet), [MinGwGetGui](/app/Bench.MinGwGetGui) + diff --git a/docs/content/apps/Bench.MinGwGet.md b/docs/content/apps/Bench.MinGwGet.md new file mode 100644 index 00000000..5fc6c931 --- /dev/null +++ b/docs/content/apps/Bench.MinGwGet.md @@ -0,0 +1,37 @@ ++++ +title = "MinGwGet" +weight = 29 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.MinGwGet" +app_version = "0.6.2" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.MinGwGet` +**Version:** 0.6.2 + + +[Back to all apps](/apps/) + +## Description +The package manager for [MinGW](http://www.mingw.org/). + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 29 + +## Properties + +* Namespace: Bench +* Name: MinGwGet +* Typ: `default` +* Dependencies: [Wget](/app/Bench.Wget) +* Responsibilities: [MinGwGetGui](/app/Bench.MinGwGetGui), [MinGW](/app/Bench.MinGW) + diff --git a/docs/content/apps/Bench.MinGwGetGui.md b/docs/content/apps/Bench.MinGwGetGui.md new file mode 100644 index 00000000..c2593f08 --- /dev/null +++ b/docs/content/apps/Bench.MinGwGetGui.md @@ -0,0 +1,37 @@ ++++ +title = "MinGwGetGui" +weight = 30 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.MinGwGetGui" +app_version = "0.6.2" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.MinGwGetGui` +**Version:** 0.6.2 + + +[Back to all apps](/apps/) + +## Description +A graphical user interface for the package manager of [MinGW](http://www.mingw.org/). + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 30 + +## Properties + +* Namespace: Bench +* Name: MinGwGetGui +* Typ: `default` +* Dependencies: [MinGwGet](/app/Bench.MinGwGet) +* Responsibilities: [MinGW](/app/Bench.MinGW) + diff --git a/docs/content/apps/Bench.MySQL.md b/docs/content/apps/Bench.MySQL.md new file mode 100644 index 00000000..0967e4a5 --- /dev/null +++ b/docs/content/apps/Bench.MySQL.md @@ -0,0 +1,43 @@ ++++ +title = "MySQL" +weight = 71 +app_library = "default" +app_category = "Services" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.MySQL" +app_version = "5.7.17" +app_categories = ["Services"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.MySQL` +**Version:** 5.7.17 + + +[Back to all apps](/apps/) + +## Description +According to Oracle: +MySQL Community Edition is the freely downloadable version +of the world's most popular open source database. + +The MySQL data is stored in `%USERPROFILE%\mysql_data`. +You can start the MySQL server by running `mysql_start` in the _Bench_ shell. +You can stop the MySQL server by running `mysql_stop` in the _Bench_ shell. +The initial password for _root_ is `bench`. + +## Source + +* Library: `default` +* Category: Services +* Order Index: 71 + +## Properties + +* Namespace: Bench +* Name: MySQL +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.MySQLUtils.md b/docs/content/apps/Bench.MySQLUtils.md new file mode 100644 index 00000000..7f0df683 --- /dev/null +++ b/docs/content/apps/Bench.MySQLUtils.md @@ -0,0 +1,33 @@ ++++ +title = "MySQL Utilities" +weight = 72 +app_library = "default" +app_category = "Services" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.MySQLUtils" +app_version = "1.6.5" +app_categories = ["Services"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.MySQLUtils` +**Version:** 1.6.5 + + +[Back to all apps](/apps/) + +## Source + +* Library: `default` +* Category: Services +* Order Index: 72 + +## Properties + +* Namespace: Bench +* Name: MySQLUtils +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.MySQLWB.md b/docs/content/apps/Bench.MySQLWB.md new file mode 100644 index 00000000..0cd30d0b --- /dev/null +++ b/docs/content/apps/Bench.MySQLWB.md @@ -0,0 +1,41 @@ ++++ +title = "MySQL Workbench" +weight = 73 +app_library = "default" +app_category = "Services" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.MySQLWB" +app_version = "6.3.8" +app_categories = ["Services"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.MySQLWB` +**Version:** 6.3.8 + + +[Back to all apps](/apps/) + +## Description +MySQL Workbench is a unified visual tool for database architects, developers, and DBAs. +MySQL Workbench provides data modeling, SQL development, and comprehensive administration +tools for server configuration, user administration, backup, and much more. + +This application needs the x86 version of the [Visual C++ 12 Redistributable](https://www.microsoft.com/download/details.aspx?id=40784), +and the [Microsoft.NET Framework 4.0 Client Profile](http://www.microsoft.com/download/details.aspx?id=17113) installed. + +## Source + +* Library: `default` +* Category: Services +* Order Index: 73 + +## Properties + +* Namespace: Bench +* Name: MySQLWB +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.NUnitRunners.md b/docs/content/apps/Bench.NUnitRunners.md new file mode 100644 index 00000000..e800330c --- /dev/null +++ b/docs/content/apps/Bench.NUnitRunners.md @@ -0,0 +1,38 @@ ++++ +title = "NUnit 3 Runners" +weight = 53 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "nuget-package" +app_ns = "Bench" +app_id = "Bench.NUnitRunners" +app_version = "latest" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["nuget-package"] ++++ + +**ID:** `Bench.NUnitRunners` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +NUnit is a unit-testing framework for all .Net languages. +The console runner `nunit3-console.exe` executes tests on the console. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 53 + +## Properties + +* Namespace: Bench +* Name: NUnitRunners +* Typ: `nuget-package` +* Website: +* Dependencies: [NuGet](/app/Bench.NuGet) + diff --git a/docs/content/apps/Bench.Node.md b/docs/content/apps/Bench.Node.md new file mode 100644 index 00000000..96e31324 --- /dev/null +++ b/docs/content/apps/Bench.Node.md @@ -0,0 +1,39 @@ ++++ +title = "Node.js" +weight = 6 +app_library = "core" +app_category = "Core" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Node" +app_version = "6.9.4" +app_categories = ["Core"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.Node` +**Version:** 6.9.4 + + +[Back to all apps](/apps/) + +## Description +Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. +Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. +Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world. + +## Source + +* Library: `core` +* Category: Core +* Order Index: 6 + +## Properties + +* Namespace: Bench +* Name: Node +* Typ: `default` +* Website: +* Responsibilities: [NPM](/app/Bench.Npm) + diff --git a/docs/content/apps/Bench.Notepad2.md b/docs/content/apps/Bench.Notepad2.md new file mode 100644 index 00000000..24c39069 --- /dev/null +++ b/docs/content/apps/Bench.Notepad2.md @@ -0,0 +1,37 @@ ++++ +title = "Notepad2" +weight = 13 +app_library = "core" +app_category = "Basics" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Notepad2" +app_version = "4.2.25" +app_categories = ["Basics"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.Notepad2` +**Version:** 4.2.25 + + +[Back to all apps](/apps/) + +## Description +Notepad2 is a fast and light-weight Notepad-like text editor with syntax highlighting. +This program can be run out of the box without installation, and does not touch your system's registry. + +## Source + +* Library: `core` +* Category: Basics +* Order Index: 13 + +## Properties + +* Namespace: Bench +* Name: Notepad2 +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Npm.md b/docs/content/apps/Bench.Npm.md new file mode 100644 index 00000000..65dd37d2 --- /dev/null +++ b/docs/content/apps/Bench.Npm.md @@ -0,0 +1,45 @@ ++++ +title = "NPM" +weight = 7 +app_library = "core" +app_category = "Core" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Npm" +app_version = ">=4.0.0 <5.0.0" +app_categories = ["Core"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.Npm` +**Version:** >=4.0.0 <5.0.0 + + +[Back to all apps](/apps/) + +## Description +npm is the package manager for JavaScript. +Find, share, and reuse packages of code from hundreds of thousands of +developers — and assemble them in powerful new ways. + +Because _Node.js_ is downloaded as bare executable, _NPM_ must be installed seperately. +But NPM, in its latest versions, is only distributed as part of the _Node.js_ setup. +_NPM_ 1.4.12 is the last version of _NPM_ which was released seperately. +Therefore, the latest version of _NPM_ is installed afterwards via the setup script `auto\apps\npm.setup.ps1`. + +## Source + +* Library: `core` +* Category: Core +* Order Index: 7 + +## Properties + +* Namespace: Bench +* Name: Npm +* Typ: `default` +* Website: +* Dependencies: [Node.js](/app/Bench.Node) +* Responsibilities: [CoffeeScript](/app/Bench.CoffeeScript), [Gulp](/app/Bench.Gulp), [Grunt](/app/Bench.Grunt), [Bower](/app/Bench.Bower), [Yeoman](/app/Bench.Yeoman), [JSHint](/app/Bench.JSHint), [JSBeautify](/app/Bench.JSBeautify), [Tern](/app/Bench.Tern), [Yeoman Generator for Markdown Projects](/app/User.MdProc) + diff --git a/docs/content/apps/Bench.NuGet.md b/docs/content/apps/Bench.NuGet.md new file mode 100644 index 00000000..afed08cc --- /dev/null +++ b/docs/content/apps/Bench.NuGet.md @@ -0,0 +1,39 @@ ++++ +title = "NuGet" +weight = 12 +app_library = "core" +app_category = "Core" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.NuGet" +app_version = "latest" +app_categories = ["Core"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.NuGet` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +NuGet is the package manager for the Microsoft development platform including .NET. +The NuGet client tools provide the ability to produce and consume packages. +The NuGet Gallery is the central package repository used by all package authors and consumers. + +## Source + +* Library: `core` +* Category: Core +* Order Index: 12 + +## Properties + +* Namespace: Bench +* Name: NuGet +* Typ: `default` +* Website: +* Responsibilities: [NUnit 3 Runners](/app/Bench.NUnitRunners) + diff --git a/docs/content/apps/Bench.OpenSSL.md b/docs/content/apps/Bench.OpenSSL.md new file mode 100644 index 00000000..a3a32508 --- /dev/null +++ b/docs/content/apps/Bench.OpenSSL.md @@ -0,0 +1,37 @@ ++++ +title = "OpenSSL" +weight = 16 +app_library = "default" +app_category = "Security" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.OpenSSL" +app_version = "1.1.0c" +app_categories = ["Security"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.OpenSSL` +**Version:** 1.1.0c + + +[Back to all apps](/apps/) + +## Description +OpenSSL is an open source project that provides a robust, commercial-grade, and full-featured toolkit for the Transport Layer Security (TLS) and Secure Sockets Layer (SSL) protocols. +It is also a general-purpose cryptography library. + +## Source + +* Library: `default` +* Category: Security +* Order Index: 16 + +## Properties + +* Namespace: Bench +* Name: OpenSSL +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.PHP5.md b/docs/content/apps/Bench.PHP5.md new file mode 100644 index 00000000..d859cac7 --- /dev/null +++ b/docs/content/apps/Bench.PHP5.md @@ -0,0 +1,39 @@ ++++ +title = "PHP 5" +weight = 21 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.PHP5" +app_version = "5.6.29" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.PHP5` +**Version:** 5.6.29 + + +[Back to all apps](/apps/) + +## Description +PHP is a popular general-purpose scripting language that is especially suited to web development. +Fast, flexible and pragmatic, PHP powers everything from your blog to the most popular websites in the world. + +This application needs the x86 version of the [Visual C++ 11 Redistributable](https://www.microsoft.com/download/details.aspx?id=30679) installed. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 21 + +## Properties + +* Namespace: Bench +* Name: PHP5 +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.PHP7.md b/docs/content/apps/Bench.PHP7.md new file mode 100644 index 00000000..9f40707f --- /dev/null +++ b/docs/content/apps/Bench.PHP7.md @@ -0,0 +1,39 @@ ++++ +title = "PHP 7" +weight = 22 +app_library = "default" +app_category = "Languages and Platforms" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.PHP7" +app_version = "7.0.14" +app_categories = ["Languages and Platforms"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.PHP7` +**Version:** 7.0.14 + + +[Back to all apps](/apps/) + +## Description +PHP is a popular general-purpose scripting language that is especially suited to web development. +Fast, flexible and pragmatic, PHP powers everything from your blog to the most popular websites in the world. + +This application needs the x86 version of the [Visual C++ 14 Redistributable](https://www.microsoft.com/download/details.aspx?id=48145) installed. + +## Source + +* Library: `default` +* Category: Languages and Platforms +* Order Index: 22 + +## Properties + +* Namespace: Bench +* Name: PHP7 +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Pandoc.md b/docs/content/apps/Bench.Pandoc.md new file mode 100644 index 00000000..b4a57912 --- /dev/null +++ b/docs/content/apps/Bench.Pandoc.md @@ -0,0 +1,37 @@ ++++ +title = "Pandoc" +weight = 78 +app_library = "default" +app_category = "Writing" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Pandoc" +app_version = "1.19.1" +app_categories = ["Writing"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Pandoc` +**Version:** 1.19.1 + + +[Back to all apps](/apps/) + +## Description +Pandoc is a library and command-line tool for converting from one markup format to another. + +## Source + +* Library: `default` +* Category: Writing +* Order Index: 78 + +## Properties + +* Namespace: Bench +* Name: Pandoc +* Typ: `default` +* Website: +* Responsibilities: [Yeoman Generator for Markdown Projects](/app/User.MdProc) + diff --git a/docs/content/apps/Bench.PostgreSQL.md b/docs/content/apps/Bench.PostgreSQL.md new file mode 100644 index 00000000..4895937a --- /dev/null +++ b/docs/content/apps/Bench.PostgreSQL.md @@ -0,0 +1,46 @@ ++++ +title = "PostgreSQL" +weight = 74 +app_library = "default" +app_category = "Services" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.PostgreSQL" +app_version = "9.6.1-1" +app_categories = ["Services"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.PostgreSQL` +**Version:** 9.6.1-1 + + +[Back to all apps](/apps/) + +## Description +PostgreSQL is a powerful, open source object-relational database system. +It has more than 15 years of active development and a proven architecture +that has earned it a strong reputation for reliability, data integrity, and correctness. +It is fully ACID compliant, has full support for foreign keys, joins, views, +triggers, and stored procedures (in multiple languages). +It also supports storage of binary large objects, including pictures, sounds, or video. +It has native programming interfaces for C/C++, Java, .Net, Perl, Python, +Ruby, Tcl, ODBC, among others + +Contains the _PostgreSQL Server_ and the management tool _pgAdminIII_. +The initial password for _postgres_ is `bench`. + +## Source + +* Library: `default` +* Category: Services +* Order Index: 74 + +## Properties + +* Namespace: Bench +* Name: PostgreSQL +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Putty.md b/docs/content/apps/Bench.Putty.md new file mode 100644 index 00000000..9a94b1ce --- /dev/null +++ b/docs/content/apps/Bench.Putty.md @@ -0,0 +1,36 @@ ++++ +title = "Putty" +weight = 19 +app_library = "default" +app_category = "Security" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Putty" +app_version = "latest" +app_categories = ["Security"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Putty` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +PuTTY is a free (MIT-licensed) Win32 Telnet and SSH client. + +## Source + +* Library: `default` +* Category: Security +* Order Index: 19 + +## Properties + +* Namespace: Bench +* Name: Putty +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.PyReadline2.md b/docs/content/apps/Bench.PyReadline2.md new file mode 100644 index 00000000..c9581ff7 --- /dev/null +++ b/docs/content/apps/Bench.PyReadline2.md @@ -0,0 +1,43 @@ ++++ +title = "PyReadline (Python 2)" +weight = 58 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "python2-package" +app_ns = "Bench" +app_id = "Bench.PyReadline2" +app_version = "latest" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["python2-package"] ++++ + +**ID:** `Bench.PyReadline2` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +Required for colors in IPython. + +for Python 2: + + +for Python 3: + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 58 + +## Properties + +* Namespace: Bench +* Name: PyReadline2 +* Typ: `python2-package` +* Website: +* Dependencies: [Python 2](/app/Bench.Python2) +* Responsibilities: [IPython 2](/app/Bench.IPython2) + diff --git a/docs/content/apps/Bench.PyReadline3.md b/docs/content/apps/Bench.PyReadline3.md new file mode 100644 index 00000000..0513d474 --- /dev/null +++ b/docs/content/apps/Bench.PyReadline3.md @@ -0,0 +1,43 @@ ++++ +title = "PyReadline (Python 3)" +weight = 59 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "python3-package" +app_ns = "Bench" +app_id = "Bench.PyReadline3" +app_version = "latest" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["python3-package"] ++++ + +**ID:** `Bench.PyReadline3` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +Required for colors in IPython. + +for Python 2: + + +for Python 3: + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 59 + +## Properties + +* Namespace: Bench +* Name: PyReadline3 +* Typ: `python3-package` +* Website: +* Dependencies: [Python 3](/app/Bench.Python3) +* Responsibilities: [IPython 3](/app/Bench.IPython3) + diff --git a/docs/content/apps/Bench.Python2.md b/docs/content/apps/Bench.Python2.md new file mode 100644 index 00000000..e0f4694c --- /dev/null +++ b/docs/content/apps/Bench.Python2.md @@ -0,0 +1,37 @@ ++++ +title = "Python 2" +weight = 10 +app_library = "core" +app_category = "Core" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Python2" +app_version = "2.7.13" +app_categories = ["Core"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.Python2` +**Version:** 2.7.13 + + +[Back to all apps](/apps/) + +## Description +Python is a programming language that lets you work quickly and integrate systems more effectively. + +## Source + +* Library: `core` +* Category: Core +* Order Index: 10 + +## Properties + +* Namespace: Bench +* Name: Python2 +* Typ: `default` +* Website: +* Responsibilities: [PyReadline (Python 2)](/app/Bench.PyReadline2), [IPython 2](/app/Bench.IPython2) + diff --git a/docs/content/apps/Bench.Python3.md b/docs/content/apps/Bench.Python3.md new file mode 100644 index 00000000..8ad7c7fe --- /dev/null +++ b/docs/content/apps/Bench.Python3.md @@ -0,0 +1,37 @@ ++++ +title = "Python 3" +weight = 11 +app_library = "core" +app_category = "Core" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Python3" +app_version = "3.4.4" +app_categories = ["Core"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.Python3` +**Version:** 3.4.4 + + +[Back to all apps](/apps/) + +## Description +Python is a programming language that lets you work quickly and integrate systems more effectively. + +## Source + +* Library: `core` +* Category: Core +* Order Index: 11 + +## Properties + +* Namespace: Bench +* Name: Python3 +* Typ: `default` +* Website: +* Responsibilities: [PyReadline (Python 3)](/app/Bench.PyReadline3), [IPython 3](/app/Bench.IPython3), [Jupyter](/app/Bench.Jupyter) + diff --git a/docs/content/apps/Bench.RabbitMQ.md b/docs/content/apps/Bench.RabbitMQ.md new file mode 100644 index 00000000..fb7eb70f --- /dev/null +++ b/docs/content/apps/Bench.RabbitMQ.md @@ -0,0 +1,48 @@ ++++ +title = "RabbitMQ" +weight = 76 +app_library = "default" +app_category = "Services" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.RabbitMQ" +app_version = "3.6.6" +app_categories = ["Services"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.RabbitMQ` +**Version:** 3.6.6 + + +[Back to all apps](/apps/) + +## Description +RabbitMQ is ... +Robust messaging for applications, +Easy to use, +Runs on all major operating systems, +Supports a huge number of developer platforms, +Open source and commercially supported + + +The setup automatically activates the web management plugin. +So after starting the server on the command line with `rabbitmq-server`, +the web UI is available under . +At first start you can login with user `guest` and passwort `guest`. + +## Source + +* Library: `default` +* Category: Services +* Order Index: 76 + +## Properties + +* Namespace: Bench +* Name: RabbitMQ +* Typ: `default` +* Website: +* Dependencies: [Erlang](/app/Bench.Erlang) + diff --git a/docs/content/apps/Bench.Ruby.md b/docs/content/apps/Bench.Ruby.md new file mode 100644 index 00000000..45abbcd2 --- /dev/null +++ b/docs/content/apps/Bench.Ruby.md @@ -0,0 +1,38 @@ ++++ +title = "Ruby" +weight = 8 +app_library = "core" +app_category = "Core" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Ruby" +app_version = "2.3.3" +app_categories = ["Core"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.Ruby` +**Version:** 2.3.3 + + +[Back to all apps](/apps/) + +## Description +A dynamic, open source programming language with a focus on simplicity and productivity. +It has an elegant syntax that is natural to read and easy to write. + +## Source + +* Library: `core` +* Category: Core +* Order Index: 8 + +## Properties + +* Namespace: Bench +* Name: Ruby +* Typ: `default` +* Website: +* Responsibilities: [RubyGems](/app/Bench.RubyGems) + diff --git a/docs/content/apps/Bench.RubyGems.md b/docs/content/apps/Bench.RubyGems.md new file mode 100644 index 00000000..36d1b6e7 --- /dev/null +++ b/docs/content/apps/Bench.RubyGems.md @@ -0,0 +1,38 @@ ++++ +title = "RubyGems" +weight = 9 +app_library = "core" +app_category = "Core" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.RubyGems" +app_version = "2.6.8" +app_categories = ["Core"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.RubyGems` +**Version:** 2.6.8 + + +[Back to all apps](/apps/) + +## Description +RubyGems is a package management framework for Ruby. + +## Source + +* Library: `core` +* Category: Core +* Order Index: 9 + +## Properties + +* Namespace: Bench +* Name: RubyGems +* Typ: `default` +* Website: +* Dependencies: [Ruby](/app/Bench.Ruby) +* Responsibilities: [SASS](/app/Bench.Sass) + diff --git a/docs/content/apps/Bench.Sass.md b/docs/content/apps/Bench.Sass.md new file mode 100644 index 00000000..b375c22b --- /dev/null +++ b/docs/content/apps/Bench.Sass.md @@ -0,0 +1,37 @@ ++++ +title = "SASS" +weight = 82 +app_library = "default" +app_category = "Web" +app_typ = "ruby-package" +app_ns = "Bench" +app_id = "Bench.Sass" +app_version = "latest" +app_categories = ["Web"] +app_libraries = ["default"] +app_types = ["ruby-package"] ++++ + +**ID:** `Bench.Sass` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +Sass is the most mature, stable, and powerful professional grade CSS extension language in the world. + +## Source + +* Library: `default` +* Category: Web +* Order Index: 82 + +## Properties + +* Namespace: Bench +* Name: Sass +* Typ: `ruby-package` +* Website: +* Dependencies: [RubyGems](/app/Bench.RubyGems) + diff --git a/docs/content/apps/Bench.Scribus.md b/docs/content/apps/Bench.Scribus.md new file mode 100644 index 00000000..5aec8536 --- /dev/null +++ b/docs/content/apps/Bench.Scribus.md @@ -0,0 +1,38 @@ ++++ +title = "Scribus" +weight = 81 +app_library = "default" +app_category = "Writing" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Scribus" +app_version = "1.4.6" +app_categories = ["Writing"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Scribus` +**Version:** 1.4.6 + + +[Back to all apps](/apps/) + +## Description +Scribus is a page layout program, available for a lot of operating systems. +Since its humble beginning in the spring of 2001, Scribus has evolved into +one of the premier Open Source desktop applications. + +## Source + +* Library: `default` +* Category: Writing +* Order Index: 81 + +## Properties + +* Namespace: Bench +* Name: Scribus +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Sift.md b/docs/content/apps/Bench.Sift.md new file mode 100644 index 00000000..5428bf95 --- /dev/null +++ b/docs/content/apps/Bench.Sift.md @@ -0,0 +1,36 @@ ++++ +title = "Sift" +weight = 65 +app_library = "default" +app_category = "Filesystem" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Sift" +app_version = "0.9.0" +app_categories = ["Filesystem"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Sift` +**Version:** 0.9.0 + + +[Back to all apps](/apps/) + +## Description +Sift - grep on steroids. A fast and powerful alternative to grep. + +## Source + +* Library: `default` +* Category: Filesystem +* Order Index: 65 + +## Properties + +* Namespace: Bench +* Name: Sift +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Spacemacs.md b/docs/content/apps/Bench.Spacemacs.md new file mode 100644 index 00000000..5b5be487 --- /dev/null +++ b/docs/content/apps/Bench.Spacemacs.md @@ -0,0 +1,37 @@ ++++ +title = "Spacemacs" +weight = 40 +app_library = "default" +app_category = "Editors" +app_typ = "meta" +app_ns = "Bench" +app_id = "Bench.Spacemacs" +app_version = "latest" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["meta"] ++++ + +**ID:** `Bench.Spacemacs` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +The best editor is neither Emacs nor Vim, it's Emacs and Vim! + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 40 + +## Properties + +* Namespace: Bench +* Name: Spacemacs +* Typ: `meta` +* Website: +* Dependencies: [Git](/app/Bench.Git), [Emacs](/app/Bench.Emacs) + diff --git a/docs/content/apps/Bench.SublimeText3.md b/docs/content/apps/Bench.SublimeText3.md new file mode 100644 index 00000000..b05f0fdb --- /dev/null +++ b/docs/content/apps/Bench.SublimeText3.md @@ -0,0 +1,37 @@ ++++ +title = "Sublime Text 3" +weight = 38 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.SublimeText3" +app_version = "Build 3126" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.SublimeText3` +**Version:** Build 3126 + + +[Back to all apps](/apps/) + +## Description +Sublime Text is a sophisticated text editor for code, markup and prose. +You'll love the slick user interface, extraordinary features and amazing performance. + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 38 + +## Properties + +* Namespace: Bench +* Name: SublimeText3 +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.SysInternals.md b/docs/content/apps/Bench.SysInternals.md new file mode 100644 index 00000000..bf2f968d --- /dev/null +++ b/docs/content/apps/Bench.SysInternals.md @@ -0,0 +1,37 @@ ++++ +title = "SysInternals" +weight = 64 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.SysInternals" +app_version = "latest" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.SysInternals` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +A collection of tools by Mark Russinovich, to inspect and investigate +the Microsoft Windows operating systems and its processes. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 64 + +## Properties + +* Namespace: Bench +* Name: SysInternals +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.TeXnicCenter.md b/docs/content/apps/Bench.TeXnicCenter.md new file mode 100644 index 00000000..48c968ac --- /dev/null +++ b/docs/content/apps/Bench.TeXnicCenter.md @@ -0,0 +1,37 @@ ++++ +title = "TeXnicCenter" +weight = 80 +app_library = "default" +app_category = "Writing" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.TeXnicCenter" +app_version = "2.02" +app_categories = ["Writing"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.TeXnicCenter` +**Version:** 2.02 + + +[Back to all apps](/apps/) + +## Description +Premium LaTeX Editing for Windows. + +## Source + +* Library: `default` +* Category: Writing +* Order Index: 80 + +## Properties + +* Namespace: Bench +* Name: TeXnicCenter +* Typ: `default` +* Website: +* Dependencies: [MiKTeX](/app/Bench.MiKTeX) + diff --git a/docs/content/apps/Bench.Tern.md b/docs/content/apps/Bench.Tern.md new file mode 100644 index 00000000..1fe20795 --- /dev/null +++ b/docs/content/apps/Bench.Tern.md @@ -0,0 +1,38 @@ ++++ +title = "Tern" +weight = 57 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "node-package" +app_ns = "Bench" +app_id = "Bench.Tern" +app_version = ">=0.20.0 <=1.0.0" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["node-package"] ++++ + +**ID:** `Bench.Tern` +**Version:** >=0.20.0 <=1.0.0 + + +[Back to all apps](/apps/) + +## Description +Tern is a stand-alone, editor-independent JavaScript analyzer +that can be used to improve the JavaScript integration of existing editors. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 57 + +## Properties + +* Namespace: Bench +* Name: Tern +* Typ: `node-package` +* Website: +* Dependencies: [NPM](/app/Bench.Npm) + diff --git a/docs/content/apps/Bench.VLC.md b/docs/content/apps/Bench.VLC.md new file mode 100644 index 00000000..967e943d --- /dev/null +++ b/docs/content/apps/Bench.VLC.md @@ -0,0 +1,37 @@ ++++ +title = "VLC Player" +weight = 88 +app_library = "default" +app_category = "Multimedia" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.VLC" +app_version = "2.2.4" +app_categories = ["Multimedia"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.VLC` +**Version:** 2.2.4 + + +[Back to all apps](/apps/) + +## Description +VLC is a free and open source cross-platform multimedia player and framework +that plays most multimedia files, and various streaming protocols. + +## Source + +* Library: `default` +* Category: Multimedia +* Order Index: 88 + +## Properties + +* Namespace: Bench +* Name: VLC +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.VSCode.md b/docs/content/apps/Bench.VSCode.md new file mode 100644 index 00000000..d3a749e7 --- /dev/null +++ b/docs/content/apps/Bench.VSCode.md @@ -0,0 +1,36 @@ ++++ +title = "Visual Studio Code" +weight = 37 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.VSCode" +app_version = "latest" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.VSCode` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +A cross platform code editor from Microsoft. + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 37 + +## Properties + +* Namespace: Bench +* Name: VSCode +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Vim.md b/docs/content/apps/Bench.Vim.md new file mode 100644 index 00000000..63cc71b6 --- /dev/null +++ b/docs/content/apps/Bench.Vim.md @@ -0,0 +1,38 @@ ++++ +title = "Vim" +weight = 43 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Vim" +app_version = "8.0" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Vim` +**Version:** 8.0 + + +[Back to all apps](/apps/) + +## Description +Vim is a highly configurable text editor built to enable efficient text editing. +It is an improved version of the vi editor distributed with most UNIX systems. + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 43 + +## Properties + +* Namespace: Bench +* Name: Vim +* Typ: `default` +* Website: +* Dependencies: [VimRT](/app/Bench.VimRT), [VimConsole](/app/Bench.VimConsole) + diff --git a/docs/content/apps/Bench.VimConsole.md b/docs/content/apps/Bench.VimConsole.md new file mode 100644 index 00000000..a0fc01bf --- /dev/null +++ b/docs/content/apps/Bench.VimConsole.md @@ -0,0 +1,38 @@ ++++ +title = "VimConsole" +weight = 42 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.VimConsole" +app_version = "8.0" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.VimConsole` +**Version:** 8.0 + + +[Back to all apps](/apps/) + +## Description +Vim is a highly configurable text editor built to enable efficient text editing. +It is an improved version of the vi editor distributed with most UNIX systems. + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 42 + +## Properties + +* Namespace: Bench +* Name: VimConsole +* Typ: `default` +* Dependencies: [VimRT](/app/Bench.VimRT) +* Responsibilities: [Vim](/app/Bench.Vim) + diff --git a/docs/content/apps/Bench.VimRT.md b/docs/content/apps/Bench.VimRT.md new file mode 100644 index 00000000..25e06ae1 --- /dev/null +++ b/docs/content/apps/Bench.VimRT.md @@ -0,0 +1,37 @@ ++++ +title = "VimRT" +weight = 41 +app_library = "default" +app_category = "Editors" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.VimRT" +app_version = "8.0" +app_categories = ["Editors"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.VimRT` +**Version:** 8.0 + + +[Back to all apps](/apps/) + +## Description +Vim is a highly configurable text editor built to enable efficient text editing. +It is an improved version of the vi editor distributed with most UNIX systems. + +## Source + +* Library: `default` +* Category: Editors +* Order Index: 41 + +## Properties + +* Namespace: Bench +* Name: VimRT +* Typ: `default` +* Responsibilities: [VimConsole](/app/Bench.VimConsole), [Vim](/app/Bench.Vim) + diff --git a/docs/content/apps/Bench.Wget.md b/docs/content/apps/Bench.Wget.md new file mode 100644 index 00000000..50eb8c14 --- /dev/null +++ b/docs/content/apps/Bench.Wget.md @@ -0,0 +1,38 @@ ++++ +title = "Wget" +weight = 15 +app_library = "core" +app_category = "Basics" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Wget" +app_version = "latest" +app_categories = ["Basics"] +app_libraries = ["core"] +app_types = ["default"] ++++ + +**ID:** `Bench.Wget` +**Version:** latest + + +[Back to all apps](/apps/) + +## Description +GNU Wget is a free utility for non-interactive download of files from the Web. +It supports HTTP, HTTPS, and FTP protocols, as well as retrieval through HTTP proxies. + +## Source + +* Library: `core` +* Category: Basics +* Order Index: 15 + +## Properties + +* Namespace: Bench +* Name: Wget +* Typ: `default` +* Website: +* Responsibilities: [Leiningen](/app/Bench.Leiningen), [MinGwGet](/app/Bench.MinGwGet) + diff --git a/docs/content/apps/Bench.WinMerge.md b/docs/content/apps/Bench.WinMerge.md new file mode 100644 index 00000000..267dbc3d --- /dev/null +++ b/docs/content/apps/Bench.WinMerge.md @@ -0,0 +1,38 @@ ++++ +title = "WinMerge" +weight = 66 +app_library = "default" +app_category = "Filesystem" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.WinMerge" +app_version = "2.14.0" +app_categories = ["Filesystem"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.WinMerge` +**Version:** 2.14.0 + + +[Back to all apps](/apps/) + +## Description +WinMerge is an Open Source differencing and merging tool for Windows. +WinMerge can compare both folders and files, presenting differences in a visual text format +that is easy to understand and handle. + +## Source + +* Library: `default` +* Category: Filesystem +* Order Index: 66 + +## Properties + +* Namespace: Bench +* Name: WinMerge +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.Yeoman.md b/docs/content/apps/Bench.Yeoman.md new file mode 100644 index 00000000..fb3bff31 --- /dev/null +++ b/docs/content/apps/Bench.Yeoman.md @@ -0,0 +1,41 @@ ++++ +title = "Yeoman" +weight = 51 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "node-package" +app_ns = "Bench" +app_id = "Bench.Yeoman" +app_version = ">=1.5.0 <2.0.0" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["node-package"] ++++ + +**ID:** `Bench.Yeoman` +**Version:** >=1.5.0 <2.0.0 + + +[Back to all apps](/apps/) + +## Description +The web's scaffolding tool for modern webapps. + +Yeoman helps you to kickstart new projects, prescribing best practices and tools +to help you stay productive. + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 51 + +## Properties + +* Namespace: Bench +* Name: Yeoman +* Typ: `node-package` +* Website: +* Dependencies: [NPM](/app/Bench.Npm) +* Responsibilities: [Yeoman Generator for Markdown Projects](/app/User.MdProc) + diff --git a/docs/content/apps/Bench.Zeal.md b/docs/content/apps/Bench.Zeal.md new file mode 100644 index 00000000..3a343055 --- /dev/null +++ b/docs/content/apps/Bench.Zeal.md @@ -0,0 +1,36 @@ ++++ +title = "Zeal Docs" +weight = 63 +app_library = "default" +app_category = "Software Development Utilities" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.Zeal" +app_version = "0.3.1" +app_categories = ["Software Development Utilities"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.Zeal` +**Version:** 0.3.1 + + +[Back to all apps](/apps/) + +## Description +An offline documentation browser inspired by [Dash](https://kapeli.com/dash/). + +## Source + +* Library: `default` +* Category: Software Development Utilities +* Order Index: 63 + +## Properties + +* Namespace: Bench +* Name: Zeal +* Typ: `default` +* Website: + diff --git a/docs/content/apps/Bench.cURL.md b/docs/content/apps/Bench.cURL.md new file mode 100644 index 00000000..fdfd38ed --- /dev/null +++ b/docs/content/apps/Bench.cURL.md @@ -0,0 +1,41 @@ ++++ +title = "cURL" +weight = 68 +app_library = "default" +app_category = "Network" +app_typ = "default" +app_ns = "Bench" +app_id = "Bench.cURL" +app_version = "7.52.1" +app_categories = ["Network"] +app_libraries = ["default"] +app_types = ["default"] ++++ + +**ID:** `Bench.cURL` +**Version:** 7.52.1 + + +[Back to all apps](/apps/) + +## Description +curl is an open source command line tool and library for transferring data with URL syntax, +supporting DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, +RTMP, RTSP, SCP, SFTP, SMB, SMTP, SMTPS, Telnet and TFTP. curl supports SSL certificates, +HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, HTTP/2, cookies, +user+password authentication (Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and Kerberos), +file transfer resume, proxy tunneling and more. + +## Source + +* Library: `default` +* Category: Network +* Order Index: 68 + +## Properties + +* Namespace: Bench +* Name: cURL +* Typ: `default` +* Website: + diff --git a/docs/content/guide/architecture.md b/docs/content/guide/architecture.md index 8bd474d0..01e54eab 100644 --- a/docs/content/guide/architecture.md +++ b/docs/content/guide/architecture.md @@ -5,35 +5,37 @@ title = "System Architecture" weight = 1 +++ -The Bench system consists of a file system layout, a CLR binary, the Dashboard -and a couple of CMD batch and PowerShell scripts. +The Bench system consists of a [file system layout](#fs), binaries for +[Core Library](#core), [Command Line Interface](#cli), and [Dashboard](#dashboard), +a couple of [PowerShell](#ps-scripts)/[CMD](#cmd-scripts) scripts, +the [app libraries](#app-libs), and the [configuration](#config). Since the architecture is constantly evolving until Bench hits the version 1.0 this article contains only a brief description of the current state. **Overview** -* [Folder Layout](#fs) -* [Interface Scripts](#interface-scripts) -* [Power-Shell Scripts](#ps-scripts) +* [File System Layout](#fs) * [Bench Core Binary](#core) +* [Command Line Interface](#cli) * [Bench Dashboard](#dashboard) +* [Power-Shell Scripts](#ps-scripts) +* [CMD Scripts](#cmd-scripts) +* [App Libraries](#app-libs) +* [Configuration Levels](#config) ![Architecture Overview](/img/architecture.svg) -[Detailed Dependency Diagram](/img/dependencies.svg) - -## Folder Layout {#fs} +## File System Layout {#fs} Bench uses a file system layout with three folder groups. For a detailed description of the file structure see: [File Structure Reference](/ref/file-structure). ### Bench System {#fs-bench} -The first group contains scripts, binaries and resources +The first group contains binaries, scripts and resources of the Bench system itself. This group is replaced in case of an upgrade. -[`actions`](/ref/file-structure/#action-dir), [`auto`](/ref/file-structure/#auto-dir), [`res`](/ref/file-structure/#res-dir) @@ -43,10 +45,10 @@ and user data. This group is not changed by any Bench operation like upgrade, app installation, or app removal. -[`archive`](/ref/file-structure/#archive-dir), [`config`](/ref/file-structure/#config-dir), [`home`](/ref/file-structure/#home-dir), [`projects`](/ref/file-structure/#projects-dir), +[`archive`](/ref/file-structure/#archive-dir), [`log`](/ref/file-structure/#log-dir), [`bench-site.md`](/ref/file-structure/#bench-site) @@ -57,49 +59,75 @@ This group can be deleted without any actual data loss, because it is rebuild by Bench during the app installation automatically. -[`launcher`](/ref/file-structure/#launcher-dir), +[`cache`](/ref/file-structure/#cache-dir), [`lib`](/ref/file-structure/#lib-dir), +[`launcher`](/ref/file-structure/#launcher-dir), [`tmp`](/ref/file-structure/#tmp-dir), [`env.cmd`](/ref/file-structure/#env), `BenchDashboard.lnk` -## Interface Scripts {#interface-scripts} -Bench provides a couple of scripts to perform actions in the Bench environment. -The most important script is `bench-ctl.cmd` for managing -the Bench environment. -Additional examples are `bench-ps.cmd` to start a CMD shell in the Bench -environment and `project-backup.cmd` to create a ZIP file in the project archive. - -_Warning: The interface scripts are under reconsideration and will probably -change in one of the next releases._ - -## Power-Shell Scripts {#ps-scripts} -The bridge between the CMD scripts and the Bench core binary is realized -via PowerShell scripts. -They are loading the `BenchLib.dll` and call its public methods. -They implement the Bench actions and some additional tasks. - -One important task, implemented by PowerShell scripts, is the -[execution adornment](/guide/isolation/#execution-adornment) -and the -[registry isolation](/guide/isolation/#registry-isolation). -In both cases, the an execution proxy in -[`lib\_proxies`](/ref/file-structure/#lib-proxies-dir) -calls `Run-Adorned.ps1` to execute pre- and post-execution scripts -and perform the registry isolation with `reg.lib.ps1`. - -_Warning: The role of the PowerShell scripts in the Bench system -is under reconsideration and will probably change in one of the next releases._ - ## Bench Core Binary {#core} The most logic in the Bench system is implemented in the `BenchLib.dll` -wich is also called the Bench core binary. +wich is also called the _Bench Core_ binary. It is a Microsoft .NET assembly with a [public API](/ref/clr-api). It supports loading the different layers of the [Bench configuration](/ref/config), downloading, installing, updating, and removing apps according to the loaded configuration. +## Command Line Interface {#cli} +Bench provides a command line interface via the [`bench.exe`](/ref/bench-cli). +It allows to manage and interact with the Bench environment. +The _Bench CLI_ depends on the _Bench Core_. + ## Bench Dashboard {#dashboard} The dashboard is a program with a [graphical user interface](/ref/dashboard) which makes the various features of the Bench system available in a mouse-friendly way. +The _Bench Dashboard_ depends on the _Bench Core_. + +## Power-Shell Scripts {#ps-scripts} +Some features are implemented by PowerShell scripts. +They load the `BenchLib.dll` and call its public methods. + +Two important features, implemented by PowerShell scripts, are the +[execution adornment](/guide/isolation/#execution-adornment) +and the +[registry isolation](/guide/isolation/#registry-isolation). +In both cases, an execution proxy in +[`lib\_proxies`](/ref/file-structure/#lib-proxies-dir) +calls `Run-Adorned.ps1` to execute pre- and post-execution scripts. +The registry isolation is implemented as a couple of functions in `reg.lib.ps1`. + +## CMD Scripts {#cmd-scripts} +There are three CMD batch scripts for launching +a shell in the Bench environment: +`bench-cmd.cmd`, `bench-ps.cmd`, and `bench-bash.cmd`. +These scripts depend on the `env.cmd` in the Bench root directory +for loading the environment variables. + +## App Libraries {#app-libs} +The knowledge about app resources and thier installation strategy is +stored in [app libraries](/ref/app-library). +An app library contains an index with the [app properties](/ref/app-properties) +and optionally PowerShell scripts for customization of setup steps +and execution adornment. + +App libraries are hosted independently and are [referenced](/ref/config/#AppLibs) +in the Bench configuration. +The [user configuration](/ref/file-structure/#config-dir) is also an app library. +It is called the _user app library_, and contains app descriptions +written by the user. + +## Configuration Levels {#config} +Bench supports three levels of configuration: + +1. The [default configuration](/ref/file-structure/#res-config), + which comes with the Bench installation and should not be edited. +2. The [user configuration](/ref/file-structure/#config-config), + which can be versioned and shared via Git. +3. And the [site configuration](/ref/file-structure/#bench-site), + which can be stored outside of the Bench environment + and can take influence on multiple Bench environments. + +Configuration files are written in [Markdown list syntax](/ref/markup-syntax/) +and support a number of [configuration properties](/ref/config). diff --git a/docs/content/guide/selection.md b/docs/content/guide/selection.md index f22b8f8f..fbbc4500 100644 --- a/docs/content/guide/selection.md +++ b/docs/content/guide/selection.md @@ -10,7 +10,6 @@ weight = 5 [Activated Apps]: /ref/file-structure/#config-apps-activated [Deactivated Apps]: /ref/file-structure/#config-apps-deactivated [Meta App]: /ref/app-types/#meta -[Groups]: /ref/apps/#groups [Bench Dashboard]: /ref/dashbord/ [Mastersign.Bench.ActivationFile]: http://mastersign.github.io/bench/clr-api/html/T_Mastersign_Bench_ActivationFile.htm @@ -72,7 +71,6 @@ If such an app is activated, all dependencies are activated implicitely. This pattern is useful to group a number of apps for some scenario under one descriptive name. -The [groups section of the Bench app library][Groups] contains some examples. ## Activation and Deactivation The user currently has two options to activate or deactivate an app. diff --git a/docs/content/guide/setup.md b/docs/content/guide/setup.md index 276a113b..1ba9b1a5 100644 --- a/docs/content/guide/setup.md +++ b/docs/content/guide/setup.md @@ -8,16 +8,17 @@ weight = 2 [bootstrap file]: /ref/file-structure/#res-bench-install [site configuration]: /ref/file-structure/#bench-site [custom configuration]: /ref/file-structure/#config-dir -[Bench CLI]: /ref/bench-ctl -[required apps]: /ref/apps/#apps-required +[Bench CLI]: /ref/bench-cli +[app libraries]: /ref/config/#AppLibs +[required apps]: /app_categories/required [Bench environment file]: /ref/file-structure/#env -[initialization mode]: /ref/bench-ctl/#initialize -[setup mode]: /ref/bench-ctl/#setup +[initialization mode]: /ref/bench-cli/#cmd_bench-manage-initialize +[setup mode]: /ref/bench-cli/#cmd_bench-manage-setup Bench is installed and upgraded with a [bootstrap file][], which downloads the archive `Bench.zip` with the Bench system files, extracts its content in the Bench root directory, and starts the [Bench CLI][] with the [initialization mode][] and the [setup mode][]. -The custom configuration is left untouched if it is already exists; +The custom configuration is left untouched if it already exists; otherwise it can be initialized from a template or from a Git repository. @@ -26,20 +27,23 @@ The setup of a Bench installation is performed in the following steps: * Creating a directory as Bench root * Downloading the (latest) [bootstrap file][] in the Bench root * Running the [bootstrap file][] ... - + Downloading the (latest) Bench archive (`Bench.zip`) from GitHub - + Deleting the following folders in the Bench root: `actions`, `auto`, `res`, `tmp`, - and the folders of the required apps in `lib` + + Downloading the (latest) Bench archive (`Bench.zip`) + + Deleting the following folders in the Bench root: + `actions`, `auto`, `res`, `tmp`, `cache\_applibs`, `lib\_applibs`, + and the folders of the [required apps][] in `lib` + Extracting the Bench archive -* Running the Bench CLI in [initialization mode][] ... +* Running the [Bench CLI][] in [initialization mode][] ... + Initializing the [site configuration][] + + Downloading missing [app libraries][] + Downloading missing app resources for [required apps][] + Installing the [required apps][] + Initializing the [custom configuration][] from template or existing Git repository -* Running the Bench CLI in [setup mode][] ... + + Downloading missing [app libraries][] +* Running the [Bench CLI][] in [setup mode][] ... + Downloading missing app resources for activated apps + Installing activated apps + Writing the [Bench environment file][] + Creating Launcher shortcuts -Running the Bench CLI in [upgrade mode](/ref/bench-ctl/#upgrade) +Running the [Bench CLI][] in [upgrade mode](/ref/bench-cli/#cmd_bench-manage-upgrade) performs all steps listed above, except creating the Bench root directory. diff --git a/docs/content/home/apps.md b/docs/content/home/apps.md new file mode 100644 index 00000000..606de8ba --- /dev/null +++ b/docs/content/home/apps.md @@ -0,0 +1,13 @@ ++++ +date = "2017-01-06T19:35:00+02:00" +description = "What apps can I use with Bench?" +title = "Apps" +icon = "database" +follow_up_url = "/apps/" +weight = 5 ++++ + +Bench comes with libraries, which contain descriptions of Windows applications. +Such a description is called an app definition and contains the name, +the download URL of the setup package, the version, and more. +Bench uses the app definitions to download and install the apps in the Bench environment. diff --git a/docs/content/home/docs.md b/docs/content/home/docs.md index fa77894d..419912d6 100644 --- a/docs/content/home/docs.md +++ b/docs/content/home/docs.md @@ -9,9 +9,9 @@ weight = 4 Checkout the documentation of Bench. It is well organized and structured in practice-oriented levels of detail. -There is the [Quickstart], a number of [Tutorials], [Tech Guides] -and for the details the [Reference Docs]. -If you still have a question feel free to [contact] the author. +There is the [Quickstart][], a number of [Tutorials][], [Tech Guides][] +and for the details the [Reference Docs][]. +If you still have a question feel free to [contact][] the author. [Quickstart]: /start/ [Tutorials]: /tutorial/ diff --git a/docs/content/ref/app-library.md b/docs/content/ref/app-library.md new file mode 100644 index 00000000..64e2b96e --- /dev/null +++ b/docs/content/ref/app-library.md @@ -0,0 +1,363 @@ ++++ +date = "2017-01-11T12:30:00+02:00" +description = "The format of app libaries" +title = "App Library" +weight = 4 ++++ + +App libraries provide the app definitions, Bench needs, to download and install programs. +Bench comes with a number of its own app libraries, hosted on [GitHub](https://github.com) +and preconfigured in a newly initialized Bench environment. +But sometimes you want to define your own apps, which are not included +in the official Bench app libraries. +To define your own apps, you can just edit the [`apps.md`](#apps-file) +in the [`config`](/ref/file-structure/#config-dir) directory. +Because, the user configuration directory of a Bench environment is +at the same time the _user app library_. +If you want to share your app definitions with others, +you can create separate app libraries and [host](#hosting) them in different ways. + +**Overview** + +* [App Definition](#app-definition) +* [File and Directory Layout](#file-and-directory-layout) +* [App Index](#apps) +* [Custom Scripts](#scripts) +* [App Setup Resources](#res) +* [Hosting](#hosting) + +## App Definition + +An app library consists of a number of app definitions. +App definitions allows Bench to download extract and install Windows programs. +And an app definition consists of: + +1. a number of [app properties](/ref/app-properties) + specified in the [app index](#apps-file) of the library, +2. [custom scripts](#scripts-dir) to customize the setup steps of an app + via PowerShell, and +3. [setup resources](#res-dir), which can be used by the custom scripts. + +## File and Directory Layout +An app library contains at least an [app index file](#apps-file). +Additionally it can contain the [custom scripts](#scripts-dir) for its apps +and [setup resources](#res-dir) needed by the [custom scripts](#scripts-dir). +It is good practice to add a [readme](#readme-file) and a [license](#license-file) file too. + +The full layout of an app library looks like this: + +* [`apps.md`](#apps-file) App Index File +* `README.md` Readme file with a brief description of the app library +* `LICENSE.md` The license under which the app library is published +* [`scripts`](#scripts-dir) Custom Scripts Directory + ([AppLibCustomScriptDirName](/ref/config/#AppLibCustomScriptDirName)) + + _app namespace_ → + + [`.extract.ps1`](#custom-script-extract) + + [`.setup.ps1`](#custom-script-setup) + + [`.env.ps1`](#custom-script-env) + + [`.remove.ps1`](#custom-script-remove) + + [`.pre-run.ps1`](#custom-script-pre-run) + + [`.post-run.ps1`](#custom-script-post-run) +* [`res`](#res-dir) App Setup Resources Directory + ([AppLibResourceDirName](/ref/config/#AppLibResourceDirName)) + + _app namespace_ → + + ... + +Custom scripts and setup resources are organized by the namespace of their app. +E.g. if an app `MyNamespace.MyApp` has a custom script for the environment setup, +the custom script would have the path `scripts\mynamespace\myapp.env.ps1`. + +## App Index {#apps} + +The IDs and properties of all apps in a library are stored in the app index. +The app index consists of a Markdown file, with a section for every app. +Additionally to the machine readable [app properties](/ref/app-properties), +every app definition can contain Markdown text which is displayed +as a description for an app in the [BenchDashboard](/ref/dashboard). + +### App Index File {#apps-file} + +* Description: The app index file. +* Path: `\apps.md` +* Config Property: [AppLibIndexFileName](/ref/config/#AppLibIndexFileName) +* Type: file +* Required: yes + +The app index file is written in [Markdown list syntax](/ref/markup-syntax). +Every app is defined by a number of [App Properties](/ref/app-properties/). + +## Custom Scripts {#scripts} + +Custom scripts allow the maintainer of an app library to customize certain steps +in the setup and execution of apps via PowerShell scripts. + +The following types of custom scripts are supported: + +* [`extract`](#custom-script-extract) +* [`setup`](#custom-script-setup) +* [`env`](#custom-script-env) +* [`remove`](#custom-script-remove) +* [`pre-run`](#custom-script-pre-run) +* [`post-run`](#custom-script-post-run) +* [`test`](#custom-script-test) + +If the file format of the downloadable setup program for an app is not supported +by Bench, then the file extraction can be implemented in the custom script +for the _extract_ setup. + +If the original setup program of a Windows application performs some necessary +actions to prepare the program files for execution, these actions +usually need to be reimplemented in the custom script for the _setup_ step. +And clean-up tasks to uninstall an app properly need to be implemented in the +custom script for the _remove_ step. + +If the configuration files of a program contain absolute paths the programs +installation directory, updating the configuration files need to be implemented +in the custom script for the _env_ step. + +To perform some tasks every time before or after a program is executed, +they can be implemented in the custom scripts for the _pre-run_ and _post-run_ steps. + +When the app is tested for proper definition, the _test_ step is performed +after the installation. + +### App Custom Script Directory {#scripts-dir} + +* Description: The directory with the custom scripts of the apps. +* Path: `\scripts` +* Config Property: [AppLibCustomScriptDirName](/ref/config/#AppLibCustomScriptDirName) +* Type: directory +* Required: no + +Custom scripts are organized by the namespaces of their app. +E.g. if an app `MyNamespace.MyApp` has a custom script for the environment setup, +the custom script would have the path `scripts\mynamespace\myapp.env.ps1`. + +### App Custom Script `extract` {#custom-script-extract} + +* Description: Custom script for app resource extraction. +* Path: `\scripts\\.extract.ps1` +* Type: file + +Custom scripts for app resource extraction must be named with the app ID +in lower case, and the name extension `.extract.ps1`. + +The _custom script for extraction_ is executed if the +[`ArchiveTyp`](/ref/app-properties/#ArchiveTyp) is set to `auto` or `custom`. +If the [`ArchiveTyp`](/ref/app-properties/#ArchiveTyp) of the app is set +to `auto` and a _custom script for extraction_ for this app exists, +the custom script takes precedence over the other extraction methods. + +Inside of the _custom script_ is the [PowerShell API](/ref/ps-api/) available. +Custom extraction scripts are called with two command line arguments: + +1. The absolute path of the downloaded app resource archive +2. The absolute path of the target directory to extract the resources + +Example for the extraction of a nested archive: + +```PowerShell +param ($archivePath, $targetDir) + +$nestedArchive = "nested.zip" + +# create temporary directory +$tmpDir = Empty-Dir "$(Get-ConfigValue TempDir)\custom_extract" + +# get path of 7-Zip +$7z = App-Exe "Bench.7z" + +# call 7-Zip to extract outer archive +& $7z x "-o$tmpDir" "$archivePath" + +# check if expected inner archive exists +if (!(Test-Path "$tmpDir\$nestedArchive")) +{ + throw "Did not find the expected content in the app resource." +} + +# call 7-Zip to extract inner archive +& $7z x "-o$targetDir" "$tmpDir\$nestedArchive" + +# Delete temporary directory +Purge-Dir $tmpDir +``` + +### App Custom Script `setup` {#custom-script-setup} + +* Description: Custom script for app setup. +* Path: `\scripts\\.setup.md` +* Type: file + +Custom scripts for app resource extraction must be named with the app ID +in lower case, and the name extension `.setup.ps1`. + +If a _custom setup script_ for an app exists, it is executed after +the installation of the (extracted) app resources in the +[apps target dir](#lib-app). +Inside of the _custom script_ is the [PowerShell API](/ref/ps-api/) is available. + +### App Custom Script `env` {#custom-script-env} + +* Description: Custom script for environment setup. +* Path: `\scripts\\.env.ps1` +* Type: file + +Custom scripts for environment setup must be named with the app ID +in lower case, and the name extension `.env.ps1`. + +If a _custom environment setup script_ for an app exists, it is executed +after the setup to update configuration files depending +on the location of Bench or other [configuration properties](/ref/config). +It is also called if the _Upade Environment_ task for Bench is executed. +Inside of the _custom script_ is the [PowerShell API](/ref/ps-api/) available. + +### App Custom Script `remove` {#custom-script-remove} + +* Description: Custom script for app deinstallation. +* Path: `\scripts\\.remove.ps1` +* Type: files + +Custom scripts for deinstallation must be named with the app ID +in lower case, and the name extension `.remove.ps1`. + +If a _custom deinstallation script_ for an app exists, it is executed +instead of the default uninstall method. +Inside of the _custom script_ is the [PowerShell API](/ref/ps-api/) available. + +### App Custom Script `pre-run` {#custom-script-pre-run} + +* Description: Pre-run hook for adorned executables of an app. +* Path: `\scripts\\.pre-run.ps1` +* Type: file + +The _custom pre-run script_ is executed immediately before an app executable is run. +It is only executed if an app executable is run via its execution proxy. +This is usually the case because it is listed in +[AdornedExecutables](/ref/app-properties/#AdornedExecutables). +The [main executable](/ref/app-properties/#Exe) of an app is automatically +included in the list of adorned executables +if the [registry isolation](/ref/app-properties/#RegistryKeys) is used. +Inside of the _custom script_ is the [PowerShell API](/ref/ps-api/) available. + +### App Custom Script `post-run` {#custom-script-post-run} + +* Description: Post-run hook for adorned executables of an app. +* Path: `\scripts\\.post-run.ps1` +* Type: file + +The _custom post-run script_ is executed immediately after an app executable is run. +It is only executed if an app executable is run via its execution proxy. +This is usually the case because it is listed in +[AdornedExecutables](/ref/app-properties/#AdornedExecutables). +The [main executable](/ref/app-properties/#Exe) of an app is automatically +included in the list of adorned executables +if the [registry isolation](/ref/app-properties/#RegistryKeys) is used. +Inside of the _custom script_ is the [PowerShell API](/ref/ps-api/) available. + +### App Custom Script `test` {#custom-script-test} + +* Description: Test hook for testing the installation of an app. +* Path: `\scripts\\.test.ps1` +* Type: file + +The _custom test script_ is executed, when an app definition is tested. +It is executed after a successful installation, after the existence of the +main executable was checked. +The test script fails when it writes an error or throws an exception; +otherwise it will count as a successful test. + +## App Setup Resources {#res} + +App setup resources are files, which are used by custom scripts to +perform necessary tasks during the various setup and execution steps of an app. + +### User App Resources Directory {#res-dir} + +* Description: The directory with setup resources for the apps. +* Path: `\res` +* Config Property: [AppLibResourceDirName](/ref/config/#AppLibResourceDirName) +* Type: directory +* Required: no + +App setup resources are organized by the namespaces of their app. +E.g. if an app `MyNamespace.MyApp` has a setup resource named `config-template.xml`, +it would have the path `\res\mynamespace\myapp\config-template.xml`. + +To get the absolute path of an app setup resource file from a custom script, +you can use the PowerShell API function [`App-SetupResource`](/ref/ps-api/#fun-app-setupresource). + +E.g. to retrieve the path of the `config-template.xml` from above, the custom +script `\scripts\mynamespace\myapp.setup.ps1` could contain: + +```PowerShell +$myAppDir = App-Dir "MyNamespace.MyApp" +$templateFile = App-SetupResource "MyNamespace.MyApp" "config-template.xml" +copy $templateFile "$myAppDir\config.xml" -Force +``` + +## Hosting {#hosting} + +An app library must be reachable via an URL, so it can be refered to in the +[`AppLibs`](/ref/config/#AppLibs) configuration property. +Currently supported are _https(s)_ and _file_ URLs. +Additionally, there is a shorthand form for _GitHub_ hosted app libraries. + +If the library is hosted via _http(s)_, the library must be packed in a ZIP file. +If the library is reachable in the file system, it can be packed in a ZIP file, +but it can also just be an open folder. +If the library is packed in a ZIP file, its content can be put directly in +the ZIP file, or it can be wrapped in exactly one sub-folder. +But in this case, no other files or folders are allowed in the root of the ZIP file. + +### Example 1 – open folder in the filesystem {#hosting-example-1} + +This is a fitting approach if you want to use a locally maintained app library +in multiple Bench environments, or if you have an infrastructure with +SMB shares. + +* Path to the app library folder: `D:\applibs\my-applib` +* Content of the folder: + + `apps.md` + + `scripts` + - ... +* URL in `AppLibs`: `file:///D:/applibs/my-applib` + +### Example 2 – ZIP file on a web server {#hosting-example-2} + +This is a fitting approach if you want to share you app library +without publishing it on GitHub, or if you have an intranet web server +under the control of you development team. +You could even store the app library in a cloud storage like DropBox +and distribute a public link for sharing. + +* Name of the ZIP file: `my-apps.zip` +* Content of the ZIP file: + + `apps.md` + + `README.md` + + `LICENSE.md` + + `scripts` + - ... +* URL in `AppLibs`: `http://www.your-domain.net/bench-app-libs/my-apps.zip` + +### Example 3 – public GitHub repository {#hosting-example-3} + +This is a fitting approach if you app library is free to be used by anybody. + +* Username on GitHub: `the-programmer` +* Repository name: `favorite-bench-apps` +* Content of the repository: + + `apps.md` + + `README.md` + + `LICENSE.md` + + `scripts` + - ... +* URL in the `AppLibs`: `github:the-programmer/favorite-bench-apps` +* Main branch must be: `master` +* Automatically expanded URL: `https://github.com/the-programmer/favorite-bench-apps/archive/master.zip` +* GitHub generated content of the ZIP archive: + + `favorite-bench-apps-master` + - `apps.md` + - `README.md` + - `LICENSE.md` + - `scripts` diff --git a/docs/content/ref/app-properties.md b/docs/content/ref/app-properties.md index 8898752d..203a9bab 100644 --- a/docs/content/ref/app-properties.md +++ b/docs/content/ref/app-properties.md @@ -12,6 +12,8 @@ weight = 8 | [Typ](#Typ) | all | `false` | | [Dependencies](#Dependencies) | all | `false` | | [Website](#Website) | all | `false` | +| [License](#License) | all | `false` | +| [LicenseUrl](#LicenseUrl) | all | `false` | | [Docs](#Docs) | all | `false` | | [Force](#Force) | all | `false` | | [Dir](#Dir) | all | `false` | @@ -19,6 +21,8 @@ weight = 8 | [Register](#Register) | `meta`, `default` | `false` | | [Environment](#Environment) | all | `false` | | [Exe](#Exe) | all | `false` | +| [ExeTestArguments](#ExeTestArguments) | all | | +| [ExeTest](#ExeTest) | all | | | [AdornedExecutables](#AdornedExecutables) | all | `false` | | [RegistryKeys](#RegistryKeys) | all | `false` | | [Launcher](#Launcher) | all | `false` | @@ -89,6 +93,26 @@ The meaning of the different possible values is explained in [App Types](/ref/ap This URL is used to create an entry in the documentation menu in the main window of the Bench Dashboard. +## License {#License} + +* Description: A SPDX license identifier or `unknown` or `commercial`. +* Data Type: string +* Required: `false` +* Default: `unknown` +* App Types: all + +If this value is set to a SPDX identifier listed in the config property +[`KnownLicenses`](/ref/config/#KnownLicenses), +the [`LicenseUrl`](/ref/app-properties/#LicenseUrl) defaults to the associated URL. + +## LicenseUrl {#LicenseUrl} + +* Description: A URL or a relative path to a document with the license text. +* Data Type: URL +* Required: `false` +* Default: empty or SPDX license URL +* App Types: all + ## Docs {#Docs} * Description: A dictionary with documentation URLs for this program @@ -166,6 +190,27 @@ For package apps like `node-package` or `python3-package`, the path can be just the name of CLI wrapper script, given the package provides a CLI. +## ExeTestArguments {#ExeTestArguments} + +* Description: A string to pass as command line arguments when the executable is tested. +* Data Type: string +* Default: empty +* App Types: all + +To test if an app was installed successfully, +the main executable is run with these arguments. +If the process exit code is `0` the test was successful. + +## ExeTest {#ExeTest} + +* Description: A flag to prevent the test of the main executable. +* Data Type: boolean +* Default: `true` +* App Types: all + +If the main executable of an app can not be tested by executing it with the +[`ExeTestArguments`](#ExeTestArguments), this property must be set to `false`. + ## AdornedExecutables {#AdornedExecutables} * Description: A list of executables, which must be adorned with pre- and post-execution scripts @@ -257,7 +302,7 @@ The path can be absolute or relative to the target directory of the app. * Required: `true`* * App Types: `default` -*) Only one of `ResourceName` or `ArchiveName` must be set. +\*) Only one of `ResourceName` or `ArchiveName` must be set. ## ArchiveName {#ArchiveName} @@ -268,7 +313,7 @@ The path can be absolute or relative to the target directory of the app. * Required: `true`* * App Types: `default` -*) Only one of `ResourceName` or `ArchiveName` must be set. +\*) Only one of `ResourceName` or `ArchiveName` must be set. ## ArchiveTyp {#ArchiveTyp} diff --git a/docs/content/ref/app-types.md b/docs/content/ref/app-types.md index 40419380..704d50ce 100644 --- a/docs/content/ref/app-types.md +++ b/docs/content/ref/app-types.md @@ -9,13 +9,13 @@ weight = 7 There are currently the following types of apps: -* Typ `meta`: app groups or apps with a fully customized setup process -* Typ `default`: Windows executables from a downloaded file, archive, or setup -* Typ `node-package`: Node.js packages, installable with NPM -* Typ `python2-package`: Python packages for Python 2 from PyPI, installable with PIP -* Typ `python3-package`: Python packages for Python 3 from PyPI, installable with PIP -* Typ `ruby-package`: Ruby packages, installable with Gem -* Typ `nuget-package`: NuGet packages, installable with NuGet +* Typ [`meta`](#meta): app groups or apps with a fully customized setup process +* Typ [`default`](#default): Windows executables from a downloaded file, archive, or setup +* Typ [`node-package`](#node-package): Node.js packages, installable with NPM +* Typ [`python2-package`](#python-package): Python packages for Python 2 from PyPI, installable with PIP +* Typ [`python3-package`](#python-package): Python packages for Python 3 from PyPI, installable with PIP +* Typ [`ruby-package`](#ruby-package): Ruby packages, installable with Gem +* Typ [`nuget-package`](#nuget-package): NuGet packages, installable with NuGet ## Meta App {#meta} An app is a _Meta App_ if its [`Typ`][] is set to `meta`. diff --git a/docs/content/ref/apps.md b/docs/content/ref/apps.md deleted file mode 100644 index 818cb3ad..00000000 --- a/docs/content/ref/apps.md +++ /dev/null @@ -1,848 +0,0 @@ -+++ -date = "2016-06-22T13:44:20+02:00" -description = "A list with all included apps and app groups" -draft = true -title = "App Library" -weight = 1 -+++ - -## Overview - -[**Groups**](#groups) - -| ID | Name | -|----|------| -| `Dev3D` | [3D Modeling](#Dev3D) | -| `DevCpp` | [C++ Development](#DevCpp) | -| `DevClojure` | [Clojure Development](#DevClojure) | -| `DevJava` | [Java Development](#DevJava) | -| `LaTeX` | [LaTeX Writing](#LaTeX) | -| `Markdown` | [Markdown](#Markdown) | -| `Multimedia` | [Multimedia](#Multimedia) | -| `DevPython2` | [Python 2 Development](#DevPython2) | -| `DevPython3` | [Python 3 Development](#DevPython3) | -| `WebDevPHP5` | [Web Development with PHP 5](#WebDevPHP5) | -| `WebDevPHP7` | [Web Development with PHP 7](#WebDevPHP7) | - -[**Required Apps**](#apps-required) - -| ID | Name | Version | Website | -|----|------|---------|---------| -| `7z` | [7-Zip](#7z) | 16.04 | | -| `ConEmu` | [ConEmu](#ConEmu) | 16.10.09a | | -| `InnoUnp` | [Inno Setup Unpacker](#InnoUnp) | 0.45 | | -| `LessMsi` | [Less MSIerables](#LessMsi) | 1.3 | | - -[**Optional Apps**](#apps-optional) - -| ID | Name | Version | Website | -|----|------|---------|---------| -| `dotnet` | [.NET Core SDK](#dotnet) | latest | | -| `AntRenamer` | [Ant Renamer](#AntRenamer) | latest | | -| `Apache` | [Apache](#Apache) | 2.4.23 | | -| `Atom` | [Atom](#Atom) | 1.11.2 | | -| `Blender` | [Blender](#Blender) | 2.78 | | -| `Bower` | [Bower](#Bower) | >=1.7.0 <2.0.0 | | -| `CMake` | [CMake](#CMake) | 3.6.1 | | -| `CoffeeScript` | [CoffeeScript](#CoffeeScript) | >=1.10.0 <2.0.0 | | -| `cURL` | [cURL](#cURL) | 7.50.1 | | -| `Dia` | [Dia](#Dia) | 0.97.2 | | -| `EclipseCpp` | [Eclipse for C++](#EclipseCpp) | 4.6 | | -| `EclipseJava` | [Eclipse for Java](#EclipseJava) | 4.6 | | -| `EclipsePHP` | [Eclipse for PHP](#EclipsePHP) | 4.6 | | -| `Emacs` | [Emacs](#Emacs) | 24.5 | | -| `Erlang` | [Erlang](#Erlang) | 19.0 | | -| `FFmpeg` | [FFmpeg](#FFmpeg) | latest | | -| `FileZilla` | [FileZilla](#FileZilla) | 3.20.1 | | -| `FreeCAD` | [FreeCAD](#FreeCAD) | 0.16 | | -| `Gimp` | [GIMP](#Gimp) | 2.8.18 | | -| `Git` | [Git](#Git) | 2.10.1 | | -| `GitKraken` | [GitKraken](#GitKraken) | latest | | -| `GnuTLS` | [GNU TLS](#GnuTLS) | 3.3.11 | | -| `GnuPG` | [GnuPG](#GnuPG) | 2.0.30 | | -| `Go` | [Go](#Go) | 1.6 | | -| `GraphicsMagick` | [Graphics Magick](#GraphicsMagick) | 1.3.24 | | -| `Graphviz` | [Graphviz](#Graphviz) | 2.38 | | -| `Grunt` | [Grunt](#Grunt) | >=0.4.5 <0.5.0 | | -| `Gulp` | [Gulp](#Gulp) | >=3.9.0 <4.0.0 | | -| `Hugo` | [Hugo](#Hugo) | 0.16 | | -| `Inkscape` | [Inkscape](#Inkscape) | 0.91-1 | | -| `IPython2` | [IPython 2](#IPython2) | latest | | -| `IPython3` | [IPython 3](#IPython3) | latest | | -| `JabRef` | [JabRef](#JabRef) | 3.5 | | -| `JDK7` | [Java Development Kit 7](#JDK7) | 7u80 | | -| `JDK8` | [Java Development Kit 8](#JDK8) | 112 | | -| `JRE7` | [Java Runtime Environment 7](#JRE7) | 7u80 | | -| `JRE8` | [Java Runtime Environment 8](#JRE8) | 112 | | -| `JSHint` | [JSHint](#JSHint) | >=2.8.0 <3.0.0 | | -| `Leiningen` | [Leiningen](#Leiningen) | latest | | -| `LightTable` | [LightTable](#LightTable) | 0.8.1 | | -| `Clang` | [LLVM Clang](#Clang) | 3.8.1 | | -| `Maven` | [Maven](#Maven) | 3.3.9 | | -| `MeshLab` | [MeshLab](#MeshLab) | 1.3.3 | | -| `MiKTeX` | [MiKTeX](#MiKTeX) | 2.9.5987 | | -| `MinGW` | [MinGW](#MinGW) | latest | | -| `MinGwGet` | [MinGwGet](#MinGwGet) | 0.6.2 | | -| `MinGwGetGui` | [MinGwGetGui](#MinGwGetGui) | latest | | -| `MySQL` | [MySQL](#MySQL) | 5.7.14 | | -| `MySQLWB` | [MySQL Workbench](#MySQLWB) | 6.3.7 | | -| `Node` | [Node.js](#Node) | 6.9.0 | | -| `Npm` | [NPM](#Npm) | >=3.7.0 <4.0.0 | | -| `NuGet` | [NuGet](#NuGet) | latest | | -| `NUnit.Runners` | [NUnit 3 Runners](#NUnit.Runners) | latest | | -| `OpenSSL` | [OpenSSL](#OpenSSL) | 1.0.2h | | -| `Pandoc` | [Pandoc](#Pandoc) | 1.17.2 | | -| `PHP5` | [PHP 5](#PHP5) | 5.6.23 | | -| `PHP7` | [PHP 7](#PHP7) | 7.0.8 | | -| `PostgreSQL` | [PostgreSQL](#PostgreSQL) | 9.5.3-1 | | -| `Putty` | [Putty](#Putty) | latest | | -| `PyReadline2` | [PyReadline (Python 2)](#PyReadline2) | latest | | -| `PyReadline3` | [PyReadline (Python 3)](#PyReadline3) | latest | | -| `Python2` | [Python 2](#Python2) | 2.7.12 | | -| `Python3` | [Python 3](#Python3) | 3.4.4 | | -| `RabbitMQ` | [RabbitMQ](#RabbitMQ) | 3.6.5 | | -| `Ruby` | [Ruby](#Ruby) | 2.3.1 | | -| `Sass` | [SASS](#Sass) | latest | | -| `Scribus` | [Scribus](#Scribus) | 1.4.6 | | -| `Sift` | [Sift](#Sift) | 0.8.0 | | -| `Spacemacs` | [Spacemacs](#Spacemacs) | latest | | -| `SublimeText3` | [Sublime Text 3](#SublimeText3) | Build 3126 | | -| `Iron` | [SWare Iron](#Iron) | latest | | -| `SysInternals` | [SysInternals](#SysInternals) | latest | | -| `TeXnicCenter` | [TeXnicCenter](#TeXnicCenter) | 2.02 | | -| `Vim` | [Vim](#Vim) | 7.4 | | -| `VimConsole` | [VimConsole](#VimConsole) | 7.4 | | -| `VimRT` | [VimRT](#VimRT) | 7.4 | | -| `VSCode` | [Visual Studio Code](#VSCode) | latest | | -| `VLC` | [VLC Player](#VLC) | 2.2.4 | | -| `Wget` | [Wget](#Wget) | 1.11.4-1 | | -| `WgetDeps` | [WgetDeps](#WgetDeps) | 1.11.4-1 | | -| `WinMerge` | [WinMerge](#WinMerge) | 2.14.0 | | -| `Yeoman` | [Yeoman](#Yeoman) | >=1.5.0 <2.0.0 | | -| `MdProc` | [Yeoman Generator for Markdown Projects](#MdProc) | >=0.1.6 <0.2.0 | | -| `Zeal` | [Zeal Docs](#Zeal) | 0.2.1 | | - -## Groups {#groups} - -### 3D Modeling {#Dev3D} - -* ID: `Dev3D` -* Typ: `meta` -* Version: latest -* Dependencies: [Blender](#Blender), [FreeCAD](#FreeCAD), [MeshLab](#MeshLab), [GIMP](#Gimp) - -### C++ Development {#DevCpp} - -* ID: `DevCpp` -* Typ: `meta` -* Version: latest -* Dependencies: [MinGW](#MinGW), [Eclipse for C++](#EclipseCpp) - -### Clojure Development {#DevClojure} - -* ID: `DevClojure` -* Typ: `meta` -* Version: latest -* Dependencies: [Maven](#Maven), [Leiningen](#Leiningen), [LightTable](#LightTable) - -### Java Development {#DevJava} - -* ID: `DevJava` -* Typ: `meta` -* Version: latest -* Dependencies: [Java Development Kit 8](#JDK8), [Maven](#Maven), [Eclipse for Java](#EclipseJava) - -### LaTeX Writing {#LaTeX} - -* ID: `LaTeX` -* Typ: `meta` -* Version: latest -* Dependencies: [MiKTeX](#MiKTeX), [JabRef](#JabRef), [TeXnicCenter](#TeXnicCenter) - -### Markdown {#Markdown} - -* ID: `Markdown` -* Typ: `meta` -* Version: latest -* Dependencies: [Yeoman Generator for Markdown Projects](#MdProc), [Visual Studio Code](#VSCode) - -### Multimedia {#Multimedia} - -* ID: `Multimedia` -* Typ: `meta` -* Version: latest -* Dependencies: [Inkscape](#Inkscape), [Dia](#Dia), [GIMP](#Gimp), [Pandoc](#Pandoc), [MiKTeX](#MiKTeX), [Graphics Magick](#GraphicsMagick), [Graphviz](#Graphviz), [FFmpeg](#FFmpeg), [VLC Player](#VLC), [Blender](#Blender) - -### Python 2 Development {#DevPython2} - -* ID: `DevPython2` -* Typ: `meta` -* Version: latest -* Dependencies: [Python 2](#Python2), [Sublime Text 3](#SublimeText3), [IPython 2](#IPython2) - -### Python 3 Development {#DevPython3} - -* ID: `DevPython3` -* Typ: `meta` -* Version: latest -* Dependencies: [Python 3](#Python3), [Sublime Text 3](#SublimeText3), [IPython 3](#IPython3) - -### Web Development with PHP 5 {#WebDevPHP5} - -* ID: `WebDevPHP5` -* Typ: `meta` -* Version: latest -* Dependencies: [PHP 5](#PHP5), [MySQL](#MySQL), [MySQL Workbench](#MySQLWB), [Apache](#Apache), [Eclipse for PHP](#EclipsePHP) - -### Web Development with PHP 7 {#WebDevPHP7} - -* ID: `WebDevPHP7` -* Typ: `meta` -* Version: latest -* Dependencies: [PHP 7](#PHP7), [MySQL](#MySQL), [MySQL Workbench](#MySQLWB), [Apache](#Apache), [Eclipse for PHP](#EclipsePHP) - -## Required Apps {#apps-required} - -### 7-Zip {#7z} - -* ID: `7z` -* Typ: `default` -* Website: -* Version: 16.04 - -### ConEmu {#ConEmu} - -* ID: `ConEmu` -* Typ: `default` -* Website: -* Version: 16.10.09a - -### Inno Setup Unpacker {#InnoUnp} - -* ID: `InnoUnp` -* Typ: `default` -* Website: -* Version: 0.45 - -### Less MSIerables {#LessMsi} - -* ID: `LessMsi` -* Typ: `default` -* Website: -* Version: 1.3 - -## Optional Apps {#apps-optional} - -### .NET Core SDK {#dotnet} - -* ID: `dotnet` -* Typ: `default` -* Website: -* Version: latest - -### Ant Renamer {#AntRenamer} - -* ID: `AntRenamer` -* Typ: `default` -* Website: -* Version: latest - -### Apache {#Apache} - -* ID: `Apache` -* Typ: `default` -* Website: -* Version: 2.4.23 - -### Atom {#Atom} - -* ID: `Atom` -* Typ: `default` -* Website: -* Version: 1.11.2 - -### Blender {#Blender} - -* ID: `Blender` -* Typ: `default` -* Website: -* Version: 2.78 - -### Bower {#Bower} - -* ID: `Bower` -* Typ: `node-package` -* Website: -* Version: >=1.7.0 <2.0.0 -* Dependencies: [Git](#Git), [NPM](#Npm) - -### CMake {#CMake} - -* ID: `CMake` -* Typ: `default` -* Website: -* Version: 3.6.1 - -### CoffeeScript {#CoffeeScript} - -* ID: `CoffeeScript` -* Typ: `node-package` -* Website: -* Version: >=1.10.0 <2.0.0 -* Dependencies: [NPM](#Npm) - -### cURL {#cURL} - -* ID: `cURL` -* Typ: `default` -* Website: -* Version: 7.50.1 - -### Dia {#Dia} - -* ID: `Dia` -* Typ: `default` -* Website: -* Version: 0.97.2 - -### Eclipse for C++ {#EclipseCpp} - -* ID: `EclipseCpp` -* Typ: `default` -* Website: -* Version: 4.6 -* Dependencies: [Java Runtime Environment 8](#JRE8) - -### Eclipse for Java {#EclipseJava} - -* ID: `EclipseJava` -* Typ: `default` -* Website: -* Version: 4.6 -* Dependencies: [Java Runtime Environment 8](#JRE8) - -### Eclipse for PHP {#EclipsePHP} - -* ID: `EclipsePHP` -* Typ: `default` -* Website: -* Version: 4.6 -* Dependencies: [Java Runtime Environment 8](#JRE8) - -### Emacs {#Emacs} - -* ID: `Emacs` -* Typ: `default` -* Website: -* Version: 24.5 -* Dependencies: [GNU TLS](#GnuTLS) - -### Erlang {#Erlang} - -* ID: `Erlang` -* Typ: `default` -* Website: -* Version: 19.0 - -### FFmpeg {#FFmpeg} - -* ID: `FFmpeg` -* Typ: `default` -* Website: -* Version: latest - -### FileZilla {#FileZilla} - -* ID: `FileZilla` -* Typ: `default` -* Website: -* Version: 3.20.1 - -### FreeCAD {#FreeCAD} - -* ID: `FreeCAD` -* Typ: `default` -* Website: -* Version: 0.16 - -### GIMP {#Gimp} - -* ID: `Gimp` -* Typ: `default` -* Website: -* Version: 2.8.18 - -### Git {#Git} - -* ID: `Git` -* Typ: `default` -* Website: -* Version: 2.10.1 - -### GitKraken {#GitKraken} - -* ID: `GitKraken` -* Typ: `default` -* Website: -* Version: latest - -### GNU TLS {#GnuTLS} - -* ID: `GnuTLS` -* Typ: `default` -* Website: -* Version: 3.3.11 - -### GnuPG {#GnuPG} - -* ID: `GnuPG` -* Typ: `default` -* Website: -* Version: 2.0.30 - -### Go {#Go} - -* ID: `Go` -* Typ: `default` -* Website: -* Version: 1.6 - -### Graphics Magick {#GraphicsMagick} - -* ID: `GraphicsMagick` -* Typ: `default` -* Website: -* Version: 1.3.24 - -### Graphviz {#Graphviz} - -* ID: `Graphviz` -* Typ: `default` -* Website: -* Version: 2.38 - -### Grunt {#Grunt} - -* ID: `Grunt` -* Typ: `node-package` -* Website: -* Version: >=0.4.5 <0.5.0 -* Dependencies: [NPM](#Npm) - -### Gulp {#Gulp} - -* ID: `Gulp` -* Typ: `node-package` -* Website: -* Version: >=3.9.0 <4.0.0 -* Dependencies: [NPM](#Npm) - -### Hugo {#Hugo} - -* ID: `Hugo` -* Typ: `default` -* Website: -* Version: 0.16 - -### Inkscape {#Inkscape} - -* ID: `Inkscape` -* Typ: `default` -* Website: -* Version: 0.91-1 - -### IPython 2 {#IPython2} - -* ID: `IPython2` -* Typ: `python2-package` -* Website: -* Version: latest -* Dependencies: [PyReadline (Python 2)](#PyReadline2), [Python 2](#Python2) - -### IPython 3 {#IPython3} - -* ID: `IPython3` -* Typ: `python3-package` -* Website: -* Version: latest -* Dependencies: [PyReadline (Python 3)](#PyReadline3), [Python 3](#Python3) - -### JabRef {#JabRef} - -* ID: `JabRef` -* Typ: `default` -* Website: -* Version: 3.5 -* Dependencies: [Java Runtime Environment 8](#JRE8) - -### Java Development Kit 7 {#JDK7} - -* ID: `JDK7` -* Typ: `default` -* Website: -* Version: 7u80 - -### Java Development Kit 8 {#JDK8} - -* ID: `JDK8` -* Typ: `default` -* Website: -* Version: 112 - -### Java Runtime Environment 7 {#JRE7} - -* ID: `JRE7` -* Typ: `default` -* Website: -* Version: 7u80 - -### Java Runtime Environment 8 {#JRE8} - -* ID: `JRE8` -* Typ: `default` -* Website: -* Version: 112 - -### JSHint {#JSHint} - -* ID: `JSHint` -* Typ: `node-package` -* Website: -* Version: >=2.8.0 <3.0.0 -* Dependencies: [NPM](#Npm) - -### Leiningen {#Leiningen} - -* ID: `Leiningen` -* Typ: `default` -* Website: -* Version: latest -* Dependencies: [Java Development Kit 8](#JDK8), [GnuPG](#GnuPG), [Wget](#Wget) - -### LightTable {#LightTable} - -* ID: `LightTable` -* Typ: `default` -* Website: -* Version: 0.8.1 - -### LLVM Clang {#Clang} - -* ID: `Clang` -* Typ: `default` -* Website: -* Version: 3.8.1 - -### Maven {#Maven} - -* ID: `Maven` -* Typ: `default` -* Website: -* Version: 3.3.9 -* Dependencies: [Java Runtime Environment 8](#JRE8), [GnuPG](#GnuPG) - -### MeshLab {#MeshLab} - -* ID: `MeshLab` -* Typ: `default` -* Website: -* Version: 1.3.3 - -### MiKTeX {#MiKTeX} - -* ID: `MiKTeX` -* Typ: `default` -* Website: -* Version: 2.9.5987 - -### MinGW {#MinGW} - -* ID: `MinGW` -* Typ: `meta` -* Website: -* Version: latest -* Dependencies: [MinGwGet](#MinGwGet), [MinGwGetGui](#MinGwGetGui) - -### MinGwGet {#MinGwGet} - -* ID: `MinGwGet` -* Typ: `default` -* Version: 0.6.2 -* Dependencies: [Wget](#Wget) - -### MinGwGetGui {#MinGwGetGui} - -* ID: `MinGwGetGui` -* Typ: `default` -* Version: latest -* Dependencies: [MinGwGet](#MinGwGet) - -### MySQL {#MySQL} - -* ID: `MySQL` -* Typ: `default` -* Website: -* Version: 5.7.14 - -### MySQL Workbench {#MySQLWB} - -* ID: `MySQLWB` -* Typ: `default` -* Website: -* Version: 6.3.7 - -### Node.js {#Node} - -* ID: `Node` -* Typ: `default` -* Website: -* Version: 6.9.0 - -### NPM {#Npm} - -* ID: `Npm` -* Typ: `default` -* Website: -* Version: >=3.7.0 <4.0.0 -* Dependencies: [Node.js](#Node) - -### NuGet {#NuGet} - -* ID: `NuGet` -* Typ: `default` -* Website: -* Version: latest - -### NUnit 3 Runners {#NUnit.Runners} - -* ID: `NUnit.Runners` -* Typ: `nuget-package` -* Website: -* Version: latest -* Dependencies: [NuGet](#NuGet) - -### OpenSSL {#OpenSSL} - -* ID: `OpenSSL` -* Typ: `default` -* Website: -* Version: 1.0.2h - -### Pandoc {#Pandoc} - -* ID: `Pandoc` -* Typ: `default` -* Website: -* Version: 1.17.2 - -### PHP 5 {#PHP5} - -* ID: `PHP5` -* Typ: `default` -* Website: -* Version: 5.6.23 - -### PHP 7 {#PHP7} - -* ID: `PHP7` -* Typ: `default` -* Website: -* Version: 7.0.8 - -### PostgreSQL {#PostgreSQL} - -* ID: `PostgreSQL` -* Typ: `default` -* Website: -* Version: 9.5.3-1 - -### Putty {#Putty} - -* ID: `Putty` -* Typ: `default` -* Website: -* Version: latest - -### PyReadline (Python 2) {#PyReadline2} - -* ID: `PyReadline2` -* Typ: `python2-package` -* Website: -* Version: latest -* Dependencies: [Python 2](#Python2) - -### PyReadline (Python 3) {#PyReadline3} - -* ID: `PyReadline3` -* Typ: `python3-package` -* Website: -* Version: latest -* Dependencies: [Python 3](#Python3) - -### Python 2 {#Python2} - -* ID: `Python2` -* Typ: `default` -* Website: -* Version: 2.7.12 - -### Python 3 {#Python3} - -* ID: `Python3` -* Typ: `default` -* Website: -* Version: 3.4.4 - -### RabbitMQ {#RabbitMQ} - -* ID: `RabbitMQ` -* Typ: `default` -* Website: -* Version: 3.6.5 -* Dependencies: [Erlang](#Erlang) - -### Ruby {#Ruby} - -* ID: `Ruby` -* Typ: `default` -* Website: -* Version: 2.3.1 - -### SASS {#Sass} - -* ID: `Sass` -* Typ: `ruby-package` -* Website: -* Version: latest -* Dependencies: [Ruby](#Ruby) - -### Scribus {#Scribus} - -* ID: `Scribus` -* Typ: `default` -* Website: -* Version: 1.4.6 - -### Sift {#Sift} - -* ID: `Sift` -* Typ: `default` -* Website: -* Version: 0.8.0 - -### Spacemacs {#Spacemacs} - -* ID: `Spacemacs` -* Typ: `meta` -* Website: -* Version: latest -* Dependencies: [Git](#Git), [Emacs](#Emacs) - -### Sublime Text 3 {#SublimeText3} - -* ID: `SublimeText3` -* Typ: `default` -* Website: -* Version: Build 3126 - -### SWare Iron {#Iron} - -* ID: `Iron` -* Typ: `default` -* Website: -* Version: latest - -### SysInternals {#SysInternals} - -* ID: `SysInternals` -* Typ: `default` -* Website: -* Version: latest - -### TeXnicCenter {#TeXnicCenter} - -* ID: `TeXnicCenter` -* Typ: `default` -* Website: -* Version: 2.02 -* Dependencies: [MiKTeX](#MiKTeX) - -### Vim {#Vim} - -* ID: `Vim` -* Typ: `default` -* Website: -* Version: 7.4 -* Dependencies: [VimRT](#VimRT), [VimConsole](#VimConsole) - -### VimConsole {#VimConsole} - -* ID: `VimConsole` -* Typ: `default` -* Version: 7.4 -* Dependencies: [VimRT](#VimRT) - -### VimRT {#VimRT} - -* ID: `VimRT` -* Typ: `default` -* Version: 7.4 - -### Visual Studio Code {#VSCode} - -* ID: `VSCode` -* Typ: `default` -* Website: -* Version: latest - -### VLC Player {#VLC} - -* ID: `VLC` -* Typ: `default` -* Website: -* Version: 2.2.4 - -### Wget {#Wget} - -* ID: `Wget` -* Typ: `default` -* Website: -* Version: 1.11.4-1 -* Dependencies: [WgetDeps](#WgetDeps) - -### WgetDeps {#WgetDeps} - -* ID: `WgetDeps` -* Typ: `default` -* Version: 1.11.4-1 - -### WinMerge {#WinMerge} - -* ID: `WinMerge` -* Typ: `default` -* Website: -* Version: 2.14.0 - -### Yeoman {#Yeoman} - -* ID: `Yeoman` -* Typ: `node-package` -* Website: -* Version: >=1.5.0 <2.0.0 -* Dependencies: [NPM](#Npm) - -### Yeoman Generator for Markdown Projects {#MdProc} - -* ID: `MdProc` -* Typ: `node-package` -* Website: -* Version: >=0.1.6 <0.2.0 -* Dependencies: [Yeoman](#Yeoman), [Gulp](#Gulp), [Pandoc](#Pandoc), [Graphviz](#Graphviz), [Inkscape](#Inkscape), [MiKTeX](#MiKTeX), [NPM](#Npm) - -### Zeal Docs {#Zeal} - -* ID: `Zeal` -* Typ: `default` -* Website: -* Version: 0.2.1 - diff --git a/docs/content/ref/bench-cli.md b/docs/content/ref/bench-cli.md new file mode 100644 index 00000000..4a8174f7 --- /dev/null +++ b/docs/content/ref/bench-cli.md @@ -0,0 +1,764 @@ ++++ +date = "2017-01-27T16:09:03+01:00" +description = "The command-line interface: bench.exe" +title = "Bench CLI" +weight = 2 ++++ + +Version: 0.14.0 + +The _Bench CLI_ allows to interact with a Bench environment on the command line. + +It supports a hierarchy of sub-commands with flags and options, which can be specified as command line arguments. +Additionally it supports an _interactive mode_ when called without a sub-command specified. +Help texts can be displayed for each sub-command with the `-?` argument. The help texts can be printed in _different formats_. + +Take a look at [the project website](http://mastersign.github.io/bench) for a description of the Bench system. + +### Commands {#index} + +* [ `bench`](#cmd_bench) +* [ `bench` `app`](#cmd_bench-app) +* [ `bench` `app` `activate`](#cmd_bench-app-activate) +* [ `bench` `app` `deactivate`](#cmd_bench-app-deactivate) +* [ `bench` `app` `download`](#cmd_bench-app-download) +* [ `bench` `app` `execute`](#cmd_bench-app-execute) +* [ `bench` `app` `info`](#cmd_bench-app-info) +* [ `bench` `app` `install`](#cmd_bench-app-install) +* [ `bench` `app` `list-properties`](#cmd_bench-app-list-properties) +* [ `bench` `app` `property`](#cmd_bench-app-property) +* [ `bench` `app` `reinstall`](#cmd_bench-app-reinstall) +* [ `bench` `app` `uninstall`](#cmd_bench-app-uninstall) +* [ `bench` `app` `upgrade`](#cmd_bench-app-upgrade) +* [ `bench` `dashboard`](#cmd_bench-dashboard) +* [ `bench` `help`](#cmd_bench-help) +* [ `bench` `list`](#cmd_bench-list) +* [ `bench` `list` `applibs`](#cmd_bench-list-applibs) +* [ `bench` `list` `apps`](#cmd_bench-list-apps) +* [ `bench` `list` `files`](#cmd_bench-list-files) +* [ `bench` `manage`](#cmd_bench-manage) +* [ `bench` `manage` `config`](#cmd_bench-manage-config) +* [ `bench` `manage` `config` `get`](#cmd_bench-manage-config-get) +* [ `bench` `manage` `downloads`](#cmd_bench-manage-downloads) +* [ `bench` `manage` `initialize`](#cmd_bench-manage-initialize) +* [ `bench` `manage` `load-app-libs`](#cmd_bench-manage-load-app-libs) +* [ `bench` `manage` `reinstall`](#cmd_bench-manage-reinstall) +* [ `bench` `manage` `renew`](#cmd_bench-manage-renew) +* [ `bench` `manage` `setup`](#cmd_bench-manage-setup) +* [ `bench` `manage` `update`](#cmd_bench-manage-update) +* [ `bench` `manage` `update-env`](#cmd_bench-manage-update-env) +* [ `bench` `manage` `upgrade`](#cmd_bench-manage-upgrade) +* [ `bench` `project`](#cmd_bench-project) + +## bench {#cmd_bench} +The `bench` command is the executable of the Bench CLI. +You can call it without a sub-command to enter the _interactive mode_. + +### Usage {#cmd_bench_usage} + +* `bench` ( _<flag>_ | _<option>_)\* +* `bench` ( _<flag>_ | _<option>_)\* _<command>_ ... + +### Help {#cmd_bench_help} +Showing the help can be triggered by one of the following flags: `/?`, `-?`, `-h`, `--help`. + +* `bench` `-?` +* `bench` _<command>_ `-?` + +### Flags {#cmd_bench_flags} + +#### `--verbose` | `-v` +Activate verbose output. + +#### `--yes` | `--force` | `-y` +Suppress all assurance questions. + +### Options {#cmd_bench_options} + +#### `--help-format` | `-f` _<value>_ +Specify the output format of help texts. + +Expected: `Plain` | `Markdown` +Default: `Plain` + +#### `--logfile` | `--log` | `-l` _<value>_ +Specify a custom location for the log file. + +Expected: A path to the log file. +Default: Auto generated filename in _<bench root>_\log\. + +#### `--root` | `--base` | `-r` _<value>_ +Specify the root directory of the Bench environment. + +Expected: A path to a valid Bench root directory. +Default: The root directory of the Bench environment, this Bench CLI belongs to. + +### Commands {#cmd_bench_commands} + +#### [ `app`, `a`](#cmd_bench-app) +Manage individual apps. + +Syntax: `bench` `app` _<sub-command>_ + +#### [ `dashboard`, `gui`, `b`](#cmd_bench-dashboard) +Start the _Bench Dashboard_. + +#### [ `help`, `h`](#cmd_bench-help) +Display the full help for all commands. + +#### [ `list`, `l`](#cmd_bench-list) +List different kinds of objects in the Bench environment. + +#### [ `manage`, `m`](#cmd_bench-manage) +Manage the Bench environment and its configuration. + +#### [ `project`, `prj`, `p`](#cmd_bench-project) +Manage projects in the Bench environment. + +Syntax: `bench` `project` + +## bench app {#cmd_bench-app} +Command: `bench` `app` + +The `app` command allows interacting with Bench apps. + +Use the sub-commands to select the kind of interaction. + +### Usage {#cmd_bench-app_usage} + +* `bench` `app` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` +* `bench` ( _<flag>_ | _<option>_)\* `app` _<command>_ ... + +### Commands {#cmd_bench-app_commands} + +#### [ `activate`, `enable`, `a`](#cmd_bench-app-activate) +Activates an app. + +Syntax: `bench` `app` `activate` _<App ID>_ + +#### [ `deactivate`, `disable`, `d`](#cmd_bench-app-deactivate) +Deactivates an app. + +Syntax: `bench` `app` `deactivate` _<App ID>_ + +#### [ `download`, `cache`, `c`](#cmd_bench-app-download) +Downloads an apps resource. + +Syntax: `bench` `app` `download` _<App ID>_ + +#### [ `execute`, `exec`, `launch`, `run`, `e`](#cmd_bench-app-execute) +Starts an apps main executable. + +Syntax: `bench` `app` `execute` _<flag>_\* _<App ID>_ + +#### [ `info`, `i`](#cmd_bench-app-info) +Shows a detailed, human readable info of an app. + +Syntax: `bench` `app` `info` _<option>_\* _<App ID>_ + +#### [ `install`, `setup`, `s`](#cmd_bench-app-install) +Installs an app, regardless of its activation state. + +Syntax: `bench` `app` `install` _<App ID>_ + +#### [ `list-properties`, `list`, `l`](#cmd_bench-app-list-properties) +Lists the properties of an app. + +Syntax: `bench` `app` `list-properties` ( _<flag>_ | _<option>_)\* _<App ID>_ + +#### [ `property`, `prop`, `p`](#cmd_bench-app-property) +Reads an app property value. + +Syntax: `bench` `app` `property` _<App ID>_ _<Property Name>_ + +#### [ `reinstall`, `r`](#cmd_bench-app-reinstall) +Reinstalls an app. + +Syntax: `bench` `app` `reinstall` _<App ID>_ + +#### [ `uninstall`, `remove`, `x`](#cmd_bench-app-uninstall) +Uninstalls an app, regardless of its activation state. + +Syntax: `bench` `app` `uninstall` _<App ID>_ + +#### [ `upgrade`, `u`](#cmd_bench-app-upgrade) +Upgrades an app. + +Syntax: `bench` `app` `upgrade` _<App ID>_ + +## bench app activate {#cmd_bench-app-activate} +Command: `bench` `app` `activate` + +The `activate` command marks an app as activated. + +To actually install the app, you have to run the `setup` command. + +If the app is currently active as a dependency, it is marked as activated anyways. +If the app is required by Bench, it is not marked as activated. +If the app is marked as deactivated, this mark is removed. + +### Usage {#cmd_bench-app-activate_usage} + +* `bench` `app` `activate` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `activate` _<App ID>_ + +### Positional Arguments {#cmd_bench-app-activate_positionals} + +#### 1. App ID +Specifies the app to activate. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench app deactivate {#cmd_bench-app-deactivate} +Command: `bench` `app` `deactivate` + +The `deactivate` command removes an app from the activation list or marks it as deactivated. + +To actually uninstall the app, you have to run the `setup` command. + +If the app is currently on the activation list, it is removed from it. +If the app is required by Bench, or as a dependency, it is marked as deactivated. + +### Usage {#cmd_bench-app-deactivate_usage} + +* `bench` `app` `deactivate` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `deactivate` _<App ID>_ + +### Positional Arguments {#cmd_bench-app-deactivate_positionals} + +#### 1. App ID +Specifies the app to deactivate. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench app download {#cmd_bench-app-download} +Command: `bench` `app` `download` + +The `download` command downloads the app resources, in case it is not cached already. + +### Usage {#cmd_bench-app-download_usage} + +* `bench` `app` `download` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `download` _<App ID>_ + +### Positional Arguments {#cmd_bench-app-download_positionals} + +#### 1. App ID +Specifies the app to download the resource for. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench app execute {#cmd_bench-app-execute} +Command: `bench` `app` `execute` + +The `execute` command starts the main executable of the specified app. + +### Usage {#cmd_bench-app-execute_usage} + +* `bench` `app` `execute` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `execute` _<flag>_\* _<App ID>_ + +### Flags {#cmd_bench-app-execute_flags} + +#### `--detached` | `--async` | `-d` +Do not wait for the end of the process. + +### Positional Arguments {#cmd_bench-app-execute_positionals} + +#### 1. App ID +Specifies the app to execute. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench app info {#cmd_bench-app-info} +Command: `bench` `app` `info` + +The `info` command displayes a detailed description for an app in human readable form. + +### Usage {#cmd_bench-app-info_usage} + +* `bench` `app` `info` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `info` _<option>_\* _<App ID>_ + +### Options {#cmd_bench-app-info_options} + +#### `--format` | `-f` _<value>_ +Specify the output format. + +Expected: `Plain` | `Markdown` +Default: `Plain` + +### Positional Arguments {#cmd_bench-app-info_positionals} + +#### 1. App ID +Specifies the app to display the description for. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench app install {#cmd_bench-app-install} +Command: `bench` `app` `install` + +The `install` command installes the specified app, regardless of its activation state. + +Missing app resources are downloaded automatically. + +### Usage {#cmd_bench-app-install_usage} + +* `bench` `app` `install` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `install` _<App ID>_ + +### Positional Arguments {#cmd_bench-app-install_positionals} + +#### 1. App ID +Specifies the app to install. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench app list-properties {#cmd_bench-app-list-properties} +Command: `bench` `app` `list-properties` + +The `list-properties` command displayes the properties of an app. + +This command supports different output formats. And you can choose between the expanded or the raw properties. + +### Usage {#cmd_bench-app-list-properties_usage} + +* `bench` `app` `list-properties` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `list-properties` ( _<flag>_ | _<option>_)\* _<App ID>_ + +### Flags {#cmd_bench-app-list-properties_flags} + +#### `--raw` | `-r` +Shows the raw properties without expansion and default values. + +### Options {#cmd_bench-app-list-properties_options} + +#### `--format` | `-f` _<value>_ +Specify the output format. + +Expected: `Plain` | `Markdown` +Default: `Plain` + +### Positional Arguments {#cmd_bench-app-list-properties_positionals} + +#### 1. App ID +Specifies the app of which the properties are to be listed. + +Expected: The apps ID, an alphanumeric string without whitespace. + +## bench app property {#cmd_bench-app-property} +Command: `bench` `app` `property` + +The `property` command reads the value of an app property. + +### Usage {#cmd_bench-app-property_usage} + +* `bench` `app` `property` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `property` _<App ID>_ _<Property Name>_ + +### Positional Arguments {#cmd_bench-app-property_positionals} + +#### 1. App ID +Specifies the app to get the property from. + +Expected: The apps ID, an alphanumeric string without whitespace. + +#### 2. Property Name +Specifies the property to read. + +Expected: The property name, an alphanumeric string without whitespace. + +## bench app reinstall {#cmd_bench-app-reinstall} +Command: `bench` `app` `reinstall` + +The `reinstall` command reinstalles the specified app. + +### Usage {#cmd_bench-app-reinstall_usage} + +* `bench` `app` `reinstall` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `reinstall` _<App ID>_ + +### Positional Arguments {#cmd_bench-app-reinstall_positionals} + +#### 1. App ID +Specifies the app to reinstall. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench app uninstall {#cmd_bench-app-uninstall} +Command: `bench` `app` `uninstall` + +The `uninstall` command uninstalles the specified app, regardless of its activation state. + +### Usage {#cmd_bench-app-uninstall_usage} + +* `bench` `app` `uninstall` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `uninstall` _<App ID>_ + +### Positional Arguments {#cmd_bench-app-uninstall_positionals} + +#### 1. App ID +Specifies the app to install. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench app upgrade {#cmd_bench-app-upgrade} +Command: `bench` `app` `upgrade` + +The `upgrade` command upgrades the specified app to the most current release. + +Updates app resources are downloaded automatically. + +### Usage {#cmd_bench-app-upgrade_usage} + +* `bench` `app` `upgrade` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `app` `upgrade` _<App ID>_ + +### Positional Arguments {#cmd_bench-app-upgrade_positionals} + +#### 1. App ID +Specifies the app to upgrade. + +Expected: An app ID is an alphanumeric string without whitespace. + +## bench dashboard {#cmd_bench-dashboard} +Command: `bench` `dashboard` + +The `dashboard` command starts the graphical user interface _Bench Dashboard_. + +### Usage {#cmd_bench-dashboard_usage} + +* `bench` `dashboard` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `dashboard` + +## bench help {#cmd_bench-help} +Command: `bench` `help` + +The `help` command displays the full help for all commands. + +### Usage {#cmd_bench-help_usage} + +* `bench` `help` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `help` ( _<flag>_ | _<option>_)\* + +### Flags {#cmd_bench-help_flags} + +#### `--append` | `-a` +Append to an existing file, in case a target file is specified. + +#### `--no-index` | `-i` +Suppress the index of the commands. + +#### `--no-title` | `-t` +Suppress the output of the tool name as the document title. + +#### `--no-version` | `-v` +Suppress the output of the tool version number. + +### Options {#cmd_bench-help_options} + +#### `--target-file` | `--out` | `-o` _<value>_ +Specifies a target file to write the help content to. + +Expected: A path to a writable file. The target file will be created or overridden. +Default: None + +## bench list {#cmd_bench-list} +Command: `bench` `list` + +The `list` command lists different kinds of objects from the Bench environment. + +Choose a sub-command to specify the kind of object, you want to list. + +### Usage {#cmd_bench-list_usage} + +* `bench` `list` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `list` ( _<flag>_ | _<option>_)\* +* `bench` ( _<flag>_ | _<option>_)\* `list` ( _<flag>_ | _<option>_)\* _<command>_ ... + +### Flags {#cmd_bench-list_flags} + +#### `--table` | `-t` +Prints properties of the listed objects as a table. Otherwise only a short form is printed. + +### Options {#cmd_bench-list_options} + +#### `--format` | `-f` _<value>_ +Specifies the output format of the listed data. + +Expected: `Plain` | `Markdown` +Default: `Plain` + +### Commands {#cmd_bench-list_commands} + +#### [ `applibs`, `l`](#cmd_bench-list-applibs) +List app libraries with ID and URL. + +#### [ `apps`, `a`](#cmd_bench-list-apps) +List apps from the app library. + +#### [ `files`, `f`](#cmd_bench-list-files) +List configuration and app library index files. + +## bench list applibs {#cmd_bench-list-applibs} +Command: `bench` `list` `applibs` + +The `applibs` command lists all loaded app libraries. + +### Usage {#cmd_bench-list-applibs_usage} + +* `bench` `list` `applibs` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `list` ( _<flag>_ | _<option>_)\* `applibs` + +## bench list apps {#cmd_bench-list-apps} +Command: `bench` `list` `apps` + +The `apps` command lists apps from the app library. + +You can specify the base set of apps and filter the apps to list. + +### Usage {#cmd_bench-list-apps_usage} + +* `bench` `list` `apps` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `list` ( _<flag>_ | _<option>_)\* `apps` _<option>_\* + +### Options {#cmd_bench-list-apps_options} + +#### `--filter` | `-f` _<value>_ +Specifies a filter to reduce the number of listed apps. + +Expected: A comma separated list of criteria. E.g. `ID=JDK*,!IsInstalled,IsCached`. +Default: no filter + +#### `--properties` | `-p` _<value>_ +Specifies the properties to display in the listed output. This option only has an effect, if the flag `list` `--table` is set. + +Expected: A comma separated list of property names. + +#### `--set` | `-s` _<value>_ +Specifies the set of apps to list. + +Expected: `All` | `Active` | `NotActive` | `Activated` | `Deactivated` | `Installed` | `NotInstalled` | `Cached` | `NotCached` | `DefaultApps` | `MetaApps` | `ManagedPackages` +Default: `All` + +#### `--sort-by` | `-o` _<value>_ +Specifies a property to sort the apps by. + +Expected: The name of an app property. +Default: ID + +## bench list files {#cmd_bench-list-files} +Command: `bench` `list` `files` + +The `files` command lists the paths of all loaded configuration files. + +### Usage {#cmd_bench-list-files_usage} + +* `bench` `list` `files` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `list` ( _<flag>_ | _<option>_)\* `files` _<option>_\* + +### Options {#cmd_bench-list-files_options} + +#### `--type` | `-t` _<value>_ +Specify the type of files to show. + +Expected: `BenchConfig` | `UserConfig` | `SiteConfig` | `Config` | `BenchAppLib` | `UserAppLib` | `AppLib` | `Activation` | `Deactivation` | `AppSelection` | `All` +Default: `All` + +## bench manage {#cmd_bench-manage} +Command: `bench` `manage` + +The `manage` command manages the Bench environment via a number of sub-commands. + +### Usage {#cmd_bench-manage_usage} + +* `bench` `manage` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` +* `bench` ( _<flag>_ | _<option>_)\* `manage` _<command>_ ... + +### Commands {#cmd_bench-manage_commands} + +#### [ `config`, `cfg`, `c`](#cmd_bench-manage-config) +Read or write values from the user configuration. + +Syntax: `bench` `manage` `config` _<sub-command>_ + +#### [ `initialize`, `init`, `i`](#cmd_bench-manage-initialize) +Initialize the Bench configuration and start the setup process. + +#### [ `load-app-libs`, `l`](#cmd_bench-manage-load-app-libs) +Load the latest app libraries. + +Syntax: `bench` `manage` `load-app-libs` _<flag>_\* + +#### [ `reinstall`, `r`](#cmd_bench-manage-reinstall) +Remove all installed apps, then install all active apps. + +#### [ `renew`, `n`](#cmd_bench-manage-renew) +Redownload all app resources, remove all installed apps, then install all active apps. + +#### [ `setup`, `s`](#cmd_bench-manage-setup) +Run the auto-setup for the active Bench apps. + +#### [ `update`, `u`](#cmd_bench-manage-update) +Update the app libraries and upgrades all apps. + +#### [ `update-env`, `e`](#cmd_bench-manage-update-env) +Update the paths in the Bench environment. + +#### [ `upgrade`, `g`](#cmd_bench-manage-upgrade) +Download and extract the latest Bench release, then run the auto-setup. + +## bench manage config {#cmd_bench-manage-config} +Command: `bench` `manage` `config` + +The `config` command gives access to the Bench user configuration. + +### Usage {#cmd_bench-manage-config_usage} + +* `bench` `manage` `config` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `config` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `config` _<command>_ ... + +### Commands {#cmd_bench-manage-config_commands} + +#### [ `get`, `read`, `g`](#cmd_bench-manage-config-get) +Reads a configuration value. + +Syntax: `bench` `manage` `config` `get` _<property-name>_ + +## bench manage config get {#cmd_bench-manage-config-get} +Command: `bench` `manage` `config` `get` + +The `get` command reads a configuration value. + +### Usage {#cmd_bench-manage-config-get_usage} + +* `bench` `manage` `config` `get` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `config` `get` _<property-name>_ + +### Positional Arguments {#cmd_bench-manage-config-get_positionals} + +#### 1. property-name +The name of the configuration property to read. + +## bench manage downloads {#cmd_bench-manage-downloads} +Command: `bench` `manage` `downloads` + +The `downloads` command manages the cached app resources. + +### Usage {#cmd_bench-manage-downloads_usage} + +* `bench` `manage` `downloads` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `downloads` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `downloads` _<command>_ ... + +### Commands {#cmd_bench-manage-downloads_commands} + +#### [ `clean`, `cl`, `c`](#cmd_bench-manage-downloads-clean) +Deletes obsolete app resources. + +#### [ `download`, `dl`, `d`](#cmd_bench-manage-downloads-download) +Downloads the app resources for all active apps. + +#### [ `purge`, `x`](#cmd_bench-manage-downloads-purge) +Deletes all cached app resources. + +## bench manage initialize {#cmd_bench-manage-initialize} +Command: `bench` `manage` `initialize` + +The `initialize` command initializes the Bench configuration and starts the setup process. + +### Usage {#cmd_bench-manage-initialize_usage} + +* `bench` `manage` `initialize` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `initialize` + +## bench manage load-app-libs {#cmd_bench-manage-load-app-libs} +Command: `bench` `manage` `load-app-libs` + +The `load-app-libs` command loads missing app libraries. + +### Usage {#cmd_bench-manage-load-app-libs_usage} + +* `bench` `manage` `load-app-libs` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `load-app-libs` _<flag>_\* + +### Flags {#cmd_bench-manage-load-app-libs_flags} + +#### `--update` | `-u` +Clears the cache and loads the latest version of all app libraries. + +## bench manage reinstall {#cmd_bench-manage-reinstall} +Command: `bench` `manage` `reinstall` + +The `reinstall` command removes all installed apps, then installs all active apps. + +### Usage {#cmd_bench-manage-reinstall_usage} + +* `bench` `manage` `reinstall` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `reinstall` + +## bench manage renew {#cmd_bench-manage-renew} +Command: `bench` `manage` `renew` + +The `renew` command redownloads all app resources, removes all installed apps, then installs all active apps. + +### Usage {#cmd_bench-manage-renew_usage} + +* `bench` `manage` `renew` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `renew` + +## bench manage setup {#cmd_bench-manage-setup} +Command: `bench` `manage` `setup` + +The `setup` command runs the auto-setup for the active Bench apps. + +### Usage {#cmd_bench-manage-setup_usage} + +* `bench` `manage` `setup` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `setup` + +## bench manage update {#cmd_bench-manage-update} +Command: `bench` `manage` `update` + +The `update` command updates the app libraries and upgrades all apps. + +### Usage {#cmd_bench-manage-update_usage} + +* `bench` `manage` `update` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `update` + +## bench manage update-env {#cmd_bench-manage-update-env} +Command: `bench` `manage` `update-env` + +The `update-env` command updates the paths in the Bench environment. + +### Usage {#cmd_bench-manage-update-env_usage} + +* `bench` `manage` `update-env` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `update-env` + +## bench manage upgrade {#cmd_bench-manage-upgrade} +Command: `bench` `manage` `upgrade` + +The `upgrade` command checks if a new version of Bench is available and installs it. + +### Usage {#cmd_bench-manage-upgrade_usage} + +* `bench` `manage` `upgrade` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `manage` `upgrade` + +## bench project {#cmd_bench-project} +Command: `bench` `project` + +The `project` command allows you to perform certain tasks on projects in the Bench environment. + + **WARNING: This command is not implemented yet.** + +### Usage {#cmd_bench-project_usage} + +* `bench` `project` `-?` +* `bench` ( _<flag>_ | _<option>_)\* `project` + diff --git a/docs/content/ref/bench-ctl.md b/docs/content/ref/bench-ctl.md deleted file mode 100644 index bcf62840..00000000 --- a/docs/content/ref/bench-ctl.md +++ /dev/null @@ -1,122 +0,0 @@ -+++ -date = "2016-06-22T11:21:28+02:00" -description = "The command-line interface: bench-ctl" -title = "Bench CLI" -weight = 2 -+++ - -The _Bench_ system can be managed and upgraded with the `bench-ctl.cmd` script in the `actions` folder of _Bench_. - -## Execution Modes -Depending on the number of arguments, given to `bench-ctl`, the tool runs in one of the following modes. - -### Interactive `bench-ctl` -You can run `bench-ctl` without any arguments or by just double clicking it in the Windows Explorer, -to run it in interactive mode. -In the interactive mode, you are presented with all available tasks -and you can select a task to run, by typing an indicator key. - -### Automation `bench-ctl ` -You can specifiy a task as the first argument to `bench-ctl`. -The specified task is executed immediatly without further interaction with the user. - -## Tasks - -### `update-env` -If you have moved the _Bench_ directory, or you are using _Bench_ on a USB drive and it has a new drive letter, -you must update the environment file `env.cmd` and the launcher shortcuts. - -The `update-env` task does exactly that. - -### `setup` -If you changed your configuration in the `config` folder by activating more apps or altered -some settings, and also if an error occured during the initial installation of _Bench_, -the `setup` task is the right choice to complete the app installation. - -This tasks performs the following steps: - -* Initializing the custom configuration, if there is none -* Downloading missing app resources -* Deinstalling inactive but installed apps from the _Bench_ environment -* Installing active but not installed apps in the _Bench_ environment -* Updating the _Bench_ environment file `env.cmd` -* Updating the launcher shortcuts - -Usually, you need an internet connection for this task. -This script can be run repeatedly without any riscs. - -### `download` -If you want to download missing application resources without installing -any application in the _Bench_ environment, use this task. -It performs the following steps: - -* Initializing the custom configuration, if there is none -* Download missing app resources - -Running `download` prior to `setup` or `reinstall` prefetches app resources to -reduce the time required later-on by `setup` or `reinstall`. - -However, be aware, that downloading the app resources is mostly not sufficient -to run the tasks `setup` or `reinstall` afterwards, without an internet connection. -This is because NPM and PIP packages are not downloaded as app resources, -but downloaded by the package manager during installation. - -### `reinstall` -If your installed apps are corrupted, or you want to update -NPM or PIP packages, you can run this action. -It performs the following steps: - -* Removing all installed app files -* Downloading missing app resources -* Installing all active apps in the _Bench_ environment -* Updating the _Bench_ environment file `env.cmd` -* Updating the launcher shortcuts - -Usually, you need an internet connection for this task. - -### `renew` -If you want to start fresh with your app selection, you can run this action. -It performs the following steps: - -* Removing all downloaded app resource -* Removing all installed app files -* Downloading missing app resources -* Installing all active apps in the _Bench_ environment -* Updating the _Bench_ environment file `env.cmd` -* Updating the launcher shortcuts - -You need an internet connection for this task. - -### `upgrade` -If you want to upgrade the whole _Bench_ environment, you can run this task. -It performs the following steps: - -* Downloading and executing the latest bootstrap file `bench-install.cmd` - + Downloading the archive `Bench.zip` of the latest release - + Deleting the directories `actions`, `auto`, `res`, `lib` and `tmp` - + Extracting the content of `Bench.zip` - + Starting the app setup... -* Downloading all required app resources -* Installing all active apps in the _Bench_ environment -* Updating the _Bench_ environment file `env.cmd` -* Updating the launcher shortcuts - -If the internet connection is not stable, or some of the app resources are not available, -this task leaves you with a possible unusable environment. -But you can allways run the `setup` and `reinstall` tasks in an attempt -to repair the missing apps. - -This task does not touch any user data in the _Bench_ home directory, -and it does not touch the custom configuration (`config.md`, `apps.md`, ...) -in the `config` folder either. - -### `initialize` -This task performs the initial setup or upgrade of a _Bench_ installation. -This task is run by the [bootstrap file](/ref/file-structure/#bench-install) -and usually does not need to be run manually. -It performs the following steps: - -* Initializing the [site configuration](/ref/file-structure/#bench-site) if none exists -* Installing the [required apps](/ref/apps/#apps-required) -* Initializing the [custom configuration](/ref/file-structure/#config-dir) if none exists -* Running the Bench setup (app installation and environment setup) with the custom configuration diff --git a/docs/content/ref/config.md b/docs/content/ref/config.md index 8831e43b..c26bbcae 100644 --- a/docs/content/ref/config.md +++ b/docs/content/ref/config.md @@ -5,18 +5,20 @@ title = "Configuration Properties" weight = 6 +++ +[syntax]: /ref/markup-syntax + Configuration properties for Bench use the [list syntax in Markdown][syntax] and can be defined in the following files. * Default configuration file `res\config.md` -* Custom configuration file `config\config.md` +* User configuration file `config\config.md` * Site configuration files `bench-site.md` in the Bench root directory and all of its parents. The configuration files are applied in the order they are listed above. The site configuration files are applied with the files near the file system root first and the one in the Bench root directory last. -Configuration files applied later override values from files applied earlier. +Configuration files applied later, override values from files applied earlier. Therefore, the site configuration file in the Bench root directory has the highest priority and the default configuration has the lowest. @@ -27,78 +29,89 @@ Properties of the data type `path` can be absolute, or relative to the Bench roo There are two groups of configuration properties: The first group contains properties, which are predefined by the default -configuration, and can _not_ be set in the custom or site configuration. +configuration, and can _not_ be set in the user or site configuration. These properties can not be overridden, because they are used during the -Bench setup process when no custom or site configuration yet exists. +Bench setup process when no user or site configuration yet exists. The second group contains properties, which are also predefined by the default -configuration, but _can_ be overridden in the custom or site configuration. +configuration, but _can_ be overridden in the user or site configuration. **System Properties** | Name | Data Type | Default | |------|-----------|---------| | [VersionFile](#VersionFile) | path | `res\version.txt` | +| [VersionUrl](#VersionUrl) | url | | +| [UpdateUrlTemplate](#UpdateUrlTemplate) | string | `https://github.com/mastersign/bench/releases/download/v#VERSION#/Bench.zip` | +| [BootstrapUrlTemplate](#BootstrapUrlTemplate) | string | `https://github.com/mastersign/bench/raw/v#VERSION#/res/bench-install.bat` | | [CustomConfigDir](#CustomConfigDir) | path | `config` | | [CustomConfigFile](#CustomConfigFile) | path | `$CustomConfigDir$\config.md` | | [CustomConfigTemplateFile](#CustomConfigTemplateFile) | path | `res\config.template.md` | | [SiteConfigFileName](#SiteConfigFileName) | string | `bench-site.md` | | [SiteConfigTemplateFile](#SiteConfigTemplateFile) | path | `res\bench-site.template.md` | -| [AppIndexFile](#AppIndexFile) | path | `res\apps.md` | +| [AppLibs](#AppLibs) | dictionary | `core: github:mastersign/bench-apps-core` | +| [AppLibsDir](#AppLibsDir) | path | `$LibDir$\_applibs` | +| [AppLibsDownloadDir](#AppLibsDownloadDir) | path | `$DownloadDir$\_applibs` | +| [AppLibIndexFileName](#AppLibIndexFileName) | string | `apps.md` | +| [AppLibCustomScriptDirName](#AppLibCustomScriptDirName) | string | `res` | +| [AppLibResourceDirName](#AppLibResourceDirName) | string | `res` | | [AppActivationFile](#AppActivationFile) | path | `$CustomConfigDir$\apps-activated.txt` | | [AppActivationTemplateFile](#AppActivationTemplateFile) | path | `res\apps-activated.template.txt` | | [AppDeactivationFile](#AppDeactivationFile) | path | `$CustomConfigDir$\apps-deactivated.txt` | | [AppDeactivationTemplateFile](#AppDeactivationTemplateFile) | path | `res\apps-deactivated.template.txt` | -| [CustomAppIndexFile](#CustomAppIndexFile) | path | `$CustomConfigDir$\apps.md` | | [CustomAppIndexTemplateFile](#CustomAppIndexTemplateFile) | path | `res\apps.template.md` | | [ConEmuConfigFile](#ConEmuConfigFile) | path | `$CustomConfigDir$\ConEmu.xml` | | [ConEmuConfigTemplateFile](#ConEmuConfigTemplateFile) | path | `res\ConEmu.template.xml` | -| [AppResourceBaseDir](#AppResourceBaseDir) | path | `res\apps` | -| [ActionDir](#ActionDir) | path | `actions` | | [LibDir](#LibDir) | path | `lib` | | [Website](#Website) | URL | | | [WizzardEditCustomConfigBeforeSetup](#WizzardEditCustomConfigBeforeSetup) | boolean | `false` | +| [WizzardApps](#WizzardApps) | dictionary | groups from the default app library | +| [WizzardSelectedApps](#WizzardSelectedApps) | list | empty | | [WizzardStartAutoSetup](#WizzardStartAutoSetup) | boolean | `true` | **Customizable Properties** | Name | Type | Data Type | Default | |------|------|-----------|---------| +| [AutoUpdateCheck](#AutoUpdateCheck) | User | boolean | `true` | | [UseProxy](#UseProxy) | Site | boolean | `false` | | [ProxyBypass](#ProxyBypass) | Site | list of strings | `localhost` | | [HttpProxy](#HttpProxy) | Site | URL | `http://127.0.0.1:80` | | [HttpsProxy](#HttpsProxy) | Site | URL | `http://127.0.0.1:443` | | [DownloadAttempts](#DownloadAttempts) | Site | integer | 3 | | [ParallelDownloads](#ParallelDownloads) | Site | integer | 4 | -| [UserName](#UserName) | Custom/Site | string | user | -| [UserEmail](#UserEmail) | Custom/Site | string | user@localhost | -| [AppVersionIndexDir](#AppVersionIndexDir) | Custom/Site | path | `$LibDir$\_versions` | -| [DownloadDir](#DownloadDir) | Custom/Site | path | `cache` | -| [AppAdornmentBaseDir](#AppAdornmentBaseDir) | Custom | path | `$LibDir$\_proxies` | -| [AppRegistryBaseDir](#AppRegistryBaseDir) | Custom | path | `$HomeDir$\registry_isolation` | -| [TempDir](#TempDir) | Custom/Site | path | `tmp` | -| [LogDir](#LogDir) | Custom | path | `log` | -| [HomeDir](#HomeDir) | Custom/Site | path | `home` | -| [AppDataDir](#AppDataDir) | Custom/Site | path | `$HomeDir$\AppData\Roaming` | -| [LocalAppDataDir](#LocalAppDataDir) | Custom/Site | path | `$HomeDir$\AppData\Local` | -| [OverrideHome](#OverrideHome) | Custom/Site | boolen | `true` | -| [OverrideTemp](#OverrideTemp) | Custom/Site | boolean | `true` | -| [IgnoreSystemPath](#IgnoreSystemPath) | Custom/Site | boolean | `true` | -| [RegisterInUserProfile](#RegisterInUserProfile) | Custom | boolean | `false` | -| [UseRegistryIsolation](#UseRegistryIsolation) | Custom | boolean | `true` | -| [ProjectRootDir](#ProjectRootDir) | Custom/Site | path | `projects` | -| [ProjectArchiveDir](#ProjectArchiveDir) | Custom/Site | path | `archive` | -| [ProjectArchiveFormat](#ProjectArchiveFormat) | Custom/Site | string | `zip` | -| [LauncherDir](#LauncherDir) | Custom | path | `launcher` | -| [LauncherScriptDir](#LauncherScriptDir) | Custom | path | `$LibDir$\_launcher` | -| [QuickAccessCmd](#QuickAccessCmd) | Custom/Site | boolean | `true` | -| [QuickAccessPowerShell](#QuickAccessPowerShell) | Custom/Site | boolean | `false` | -| [QuickAccessBash](#QuickAccessBash) | Custom/Site | boolean | `false` | -| [EditorApp](#EditorApp) | Custom/Site | string | `VSCode` | +| [UserName](#UserName) | User/Site | string | user | +| [UserEmail](#UserEmail) | User/Site | string | user@localhost | +| [KnownLicenses](#KnownLicenses) | User/Site | dictionary | A selection from | +| [AppVersionIndexDir](#AppVersionIndexDir) | User/Site | path | `$LibDir$\_versions` | +| [DownloadDir](#DownloadDir) | User/Site | path | `cache` | +| [AppAdornmentBaseDir](#AppAdornmentBaseDir) | User | path | `$LibDir$\_proxies` | +| [AppRegistryBaseDir](#AppRegistryBaseDir) | User | path | `$HomeDir$\registry_isolation` | +| [TempDir](#TempDir) | User/Site | path | `tmp` | +| [LogDir](#LogDir) | User | path | `log` | +| [HomeDir](#HomeDir) | User/Site | path | `home` | +| [AppDataDir](#AppDataDir) | User/Site | path | `$HomeDir$\AppData\Roaming` | +| [LocalAppDataDir](#LocalAppDataDir) | User/Site | path | `$HomeDir$\AppData\Local` | +| [OverrideHome](#OverrideHome) | User/Site | boolean | `true` | +| [OverrideTemp](#OverrideTemp) | User/Site | boolean | `true` | +| [IgnoreSystemPath](#IgnoreSystemPath) | User/Site | boolean | `true` | +| [RegisterInUserProfile](#RegisterInUserProfile) | User | boolean | `false` | +| [UseRegistryIsolation](#UseRegistryIsolation) | User | boolean | `true` | +| [ProjectRootDir](#ProjectRootDir) | User/Site | path | `projects` | +| [ProjectArchiveDir](#ProjectArchiveDir) | User/Site | path | `archive` | +| [ProjectArchiveFormat](#ProjectArchiveFormat) | User/Site | string | `zip` | +| [LauncherDir](#LauncherDir) | User | path | `launcher` | +| [LauncherScriptDir](#LauncherScriptDir) | User | path | `$LibDir$\_launcher` | +| [QuickAccessCmd](#QuickAccessCmd) | User/Site | boolean | `true` | +| [QuickAccessPowerShell](#QuickAccessPowerShell) | User/Site | boolean | `false` | +| [QuickAccessBash](#QuickAccessBash) | User/Site | boolean | `false` | +| [DashboardSetupAppListColumns](#DashboardSetupAppListColumns) | User/Site | string list | `Order`, `Label`, `Version`, `Active`, `Deactivated`, `Status`, `Typ`, `Comment` | +| [TextEditorApp](#TextEditorApp) | User/Site | string | `Bench.Notepad2` | +| [MarkdownEditorApp](#MarkdownEditorApp) | User/Site | string | `Bench.MarkdownEdit` | ## System Properties Properties in this group can not be customized, by overriding them in -the custom or site configuration. +the user or site configuration. If it is necessary to change them anyways, the default configuration in `res\config.md` must be edited before the initial Bench setup is executed, @@ -112,18 +125,49 @@ these changes. * Default: `res\version.txt` * Type: System +### VersionUrl {#VersionUrl} + +* Description: The URL to retrieve the version number of the latest Bench release. +* Data Type: url +* Default: +* Type: System + +### UpdateUrlTemplate {#UpdateUrlTemplate} + +* Description: The URL template to generate an URL for retrieving a Bench system update. +* Data Type: string +* Default: `https://github.com/mastersign/bench/releases/download/v#VERSION#/Bench.zip` +* Type: System + +The placeholder `#VERSION#` in the URL template will be replaced +by the version number of the Bench system update, +to generate the actual update URL. + +The update is expected to be a ZIP file, containing the Bench system files. + +### BootstrapUrlTemplate {#BootstrapUrlTemplate} + +* Description: The URL template to generate an URL for retrieveing the bootstrap script file. +* Data Type: string +* Default: `https://github.com/mastersign/bench/raw/v#VERSION#/res/bench-install.bat` +* Type: System + +The placeholder `#VERSION#` in the URL template will be replaced +by the version number of the targeted Bench release, +to generate the actual URL. + ### CustomConfigDir {#CustomConfigDir} -* Description: The path to the directory with the custom configuration (`config.md`, `apps-activated.txt`, ...) is stored. +* Description: The path to the directory with the user configuration (`config.md`, `apps-activated.txt`, ...) is stored. * Data Type: path * Default: `config` * Type: System -The custom configuration directory is designed in a way, that is can be easily put under version control. +The user configuration directory is designed in a way, that is can be easily put under version control. ### CustomConfigFile {#CustomConfigFile} -* Description: The path to the custom configuration file. +* Description: The path to the user configuration file. * Data Type: path * Default: `$CustomConfigDir$\config.md` * Type: System @@ -132,8 +176,8 @@ The specified file must be a Markdown file and follow the [Markdown list syntax] ### CustomConfigTemplateFile {#CustomConfigTemplateFile} -* Description: The path to the custom configuration template file, - which is copied during the Bench setup in case no custom configuration exists. +* Description: The path to the user configuration template file, + which is copied during the Bench setup in case no user configuration exists. * Data Type: path * Default: `res\config.template.md` * Type: System @@ -156,15 +200,78 @@ The specified file must be a Markdown file and follow the [Markdown list syntax] * Default: `res\bench-site.template.md` * Type: System -### AppIndexFile {#AppIndexFile} +### AppLibs {#AppLibs} + +* Description: A table with URLs of app libraries to load in the Bench environment. +* Data Type: dictionary +* Default: `core: github:mastersign/bench-apps-core` +* Type: User + +The table consists of [key/value pairs](/ref/markup-syntax/#lists-and-dictionaries). +Where the key is a unique ID for the app library inside the Bench environment, +and the value is an URL to a ZIP file with the app library. +The order of the table entries dictates the order in which the app libraries are loaded. + +The URL can use one of the following protocols: `http`, `https`, `file`. +If the protocol `file` is used, the URL can refer to a ZIP file +or just a directory containing the app library. +If the app library is hosted as a GitHub repository, a short form for the URL +can be used: `github:/`; +which is automatically expanded to an URL with the `https` protocol. + +The default value of the base configuration contains only apps, which +are required or directly known by Bench. This value should be overridden +in the user or site configuration, to include more app libraries. + +For starters the following list of app libraries is advised: + +```Markdown +* `AppLibs`: + + `core`: `github:mastersign/bench-apps-core` + + `default`: `github:mastersign/bench-apps-default` +``` + +### AppLibsDir {#AppLibsDir} + +* Description: The path of the directory, where to load the app libraries. +* Data Type: path +* Default: `$LibDir$\_applibs` +* Type: System + +### AppLibsDownloadDir {#AppLibsDownloadDir} -* Description: The path to a library file for all program definitions, included in Bench. +* Description: The path of the directory, downloaded app libraries are cached. * Data Type: path -* Default: `res\apps.md` +* Default: `$DownloadDir$\_applibs` +* Type: System + +### AppLibIndexFileName {#AppLibIndexFileName} + +* Description: The name of the index file in an app library. +* Data Type: string +* Default: `apps.md` * Type: System The specified file must be a Markdown file and follow the [Markdown list syntax][syntax]. +### AppLibCustomScriptDirName {#AppLibCustomScriptDirName} + +* Description: The name of the directory with the custom scripts in an app library. +* Data Type: string +* Default: `res` +* Type: System + +It is used from custom scripts to retrieve paths to app resources during the setup. + +### AppLibResourceDirName {#AppLibResourceDirName} + +* Description: The name of the directory with additional setup resources in an app library. +* Data Type: string +* Default: `res` +* Type: System + +It is used from custom scripts to retrieve paths to resources, e.g. during the app setup. + ### AppActivationFile {#AppActivationFile} * Description: The path to a file with a list of activated apps. @@ -179,7 +286,7 @@ Only non-space characters, up to the first space or the end of a line, are consi ### AppActivationTemplateFile {#AppActivationTemplateFile} * Description: The path to the app activation template file, - which is copied during the Bench setup in case no custom configuration exists. + which is copied during the Bench setup in case no user configuration exists. * Data Type: path * Default: `res\apps-activated.template.txt` * Type: System @@ -198,24 +305,15 @@ Only non-space characters, up to the first space or the end of a line, are consi ### AppDeactivationTemplateFile {#AppDeactivationTemplateFile} * Description: The path to the app deactivation template file, - which is copied during the Bench setup in case no custom configuration exists. + which is copied during the Bench setup in case no user configuration exists. * Data Type: path * Default: `res\apps-deactivated.template.txt` * Type: System -### CustomAppIndexFile {#CustomAppIndexFile} - -* Description: The path to a library file with custom program definitions. -* Data Type: path -* Default: `$CustomConfigDir$\apps.md` -* Type: System - -The specified file must be a Markdown file and follow the [Markdown list syntax][syntax]. - ### CustomAppIndexTemplateFile {#CustomAppIndexTemplateFile} -* Description: The path to the custom app library template file, - which is copied during the Bench setup in case no custom configuration exists. +* Description: The path to the user app library template file, + which is copied during the Bench setup in case no user configuration exists. * Data Type: path * Default: `res\apps.template.md` * Type: System @@ -230,30 +328,11 @@ The specified file must be a Markdown file and follow the [Markdown list syntax] ### ConEmuConfigTemplateFile {#ConEmuConfigTemplateFile} * Description: The path to the ConEmu configuration template file, - which is copied during the Bench setup in case no custom configuration exists. + which is copied during the Bench setup in case no user configuration exists. * Data Type: path * Default: `res\ConEmu.template.xml` * Type: System -### AppResourceBaseDir {#AppResourceBaseDir} - -* Description: The path to a directory, containing additional resource files, - which are used during the execution of custom setup scripts. -* Data Type: path -* Default: `res\apps` -* Type: System - -It is used from custom scripts to retrieve absolute paths to the additional resources. - -### ActionDir {#ActionDir} - -* Description: The path to a directory with Bench action scripts. -* Data Type: path -* Default: `actions` -* Type: System - -Bench action scripts are typically `*.cmd` files. - ### LibDir {#LibDir} * Description: The path to the base directory where Bench apps are installed. @@ -275,6 +354,29 @@ Bench action scripts are typically `*.cmd` files. * Default: `false` * Type: Temporary +### WizzardApps {#WizzardApps} + +* Description: A list of apps and groups to offer for activation during the user configuration initialization. +* Data Type: dictionary +* Default: groups from the default app library +* Type: System + +The items of the dictionary must have the format `