private void ParseArgs(string[] args) { var options = new[] { new Option { Key = "Plugin", Setter = value => PluginPath = value, Getter = () => PluginPath, Description = "Path to the plugin assembly to debug" }, new Option { Key = "Data", Setter = value => DataPath = value, Getter = () => DataPath, Description = "Path to the data file to be parsed" }, new Option { Key = "Location", Setter = value => LocationIdentifier = value, Getter = () => LocationIdentifier, Description = "Optional location identifier context" }, new Option { Key = "Json", Setter = value => JsonPath = value, Getter = () => JsonPath, Description = "Optional path to write the appended results as JSON" }, }; var usageMessage = $"Parse a file using a field data plugin, logging the results." + $"\n\nusage: {GetProgramName()} [-option=value] ..." + $"\n\nSupported -option=value settings (/option=value works too):\n\n -{string.Join("\n -", options.Select(o => o.UsageText()))}" ; foreach (var arg in args) { var match = ArgRegex.Match(arg); if (!match.Success) { throw new ExpectedException($"Unknown argument: {arg}\n\n{usageMessage}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } if (string.IsNullOrEmpty(PluginPath)) { throw new ExpectedException("No plugin assembly specified."); } if (string.IsNullOrEmpty(DataPath)) { throw new ExpectedException("No data file specified."); } }
private static Context ParseArgs(string[] args) { var context = new Context(); var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Description = "Export configuration settings. Changes will trigger a full resync:" }, new Option { Key = nameof(context.Config.AquariusServer), Setter = value => context.Config.AquariusServer = value, Getter = () => context.Config.AquariusServer, Description = "AQTS server name" }, new Option { Key = nameof(context.Config.AquariusUsername), Setter = value => context.Config.AquariusUsername = value, Getter = () => context.Config.AquariusUsername, Description = "AQTS username" }, new Option { Key = nameof(context.Config.AquariusPassword), Setter = value => context.Config.AquariusPassword = value, Getter = () => context.Config.AquariusPassword, Description = "AQTS password" }, new Option { Key = nameof(context.Config.SosServer), Setter = value => context.Config.SosServer = value, Getter = () => context.Config.SosServer, Description = "SOS server name" }, new Option { Key = nameof(context.Config.SosUsername), Setter = value => context.Config.SosUsername = value, Getter = () => context.Config.SosUsername, Description = "SOS username" }, new Option { Key = nameof(context.Config.SosPassword), Setter = value => context.Config.SosPassword = value, Getter = () => context.Config.SosPassword, Description = "SOS password" }, new Option(), new Option { Description = "/Publish/v2/GetTimeSeriesUniqueIdList settings. Changes will trigger a full resync:" }, new Option { Key = nameof(context.Config.LocationIdentifier), Setter = value => context.Config.LocationIdentifier = value, Getter = () => context.Config.LocationIdentifier, Description = "Optional location filter." }, new Option { Key = nameof(context.Config.Publish), Setter = value => context.Config.Publish = string.IsNullOrEmpty(value) ? (bool?)null : bool.Parse(value), Getter = () => context.Config.Publish?.ToString(), Description = "Optional publish filter." }, new Option { Key = nameof(context.Config.ChangeEventType), Setter = value => context.Config.ChangeEventType = (ChangeEventType)Enum.Parse(typeof(ChangeEventType), value, true), Getter = () => context.Config.ChangeEventType?.ToString(), Description = $"Optional change event type filter. One of {string.Join(", ", Enum.GetNames(typeof(ChangeEventType)))}" }, new Option { Key = nameof(context.Config.Parameter), Setter = value => context.Config.Parameter = value, Getter = () => context.Config.Parameter, Description = "Optional parameter filter." }, new Option { Key = nameof(context.Config.ComputationIdentifier), Setter = value => context.Config.ComputationIdentifier = value, Getter = () => context.Config.ComputationIdentifier, Description = "Optional computation filter." }, new Option { Key = nameof(context.Config.ComputationPeriodIdentifier), Setter = value => context.Config.ComputationPeriodIdentifier = value, Getter = () => context.Config.ComputationPeriodIdentifier, Description = "Optional computation period filter." }, new Option { Key = nameof(context.Config.ExtendedFilters), Setter = value => { var split = value.Split('='); if (split.Length != 2) { throw new ExpectedException($"Can't parse '{value}' as Name=Value extended attribute filter"); } context.Config.ExtendedFilters.Add(new ExtendedAttributeFilter { FilterName = split[0], FilterValue = split[1] }); }, Getter = () => string.Empty, Description = "Extended attribute filter in Name=Value format. Can be set multiple times." }, new Option(), new Option { Description = "Aggressive time-series filtering. Changes will trigger a full resync:" }, new Option { Key = nameof(context.Config.TimeSeries), Setter = value => context.Config.TimeSeries.Add(ParseTimeSeriesFilter(value)), Getter = () => string.Empty, Description = "Time-series identifier regular expression filter. Can be specified multiple times." }, new Option { Key = "TimeSeriesDescription", Setter = value => context.Config.TimeSeriesDescriptions.Add(ParseTimeSeriesFilter(value)), Getter = () => string.Empty, Description = "Time-series description regular expression filter. Can be specified multiple times." }, new Option { Key = nameof(context.Config.Approvals), Setter = value => context.Config.Approvals.Add(ParseApprovalFilter(value)), Getter = () => string.Empty, Description = "Filter points by approval level or name. Can be specified multiple times." }, new Option { Key = nameof(context.Config.Grades), Setter = value => context.Config.Grades.Add(ParseGradeFilter(value)), Getter = () => string.Empty, Description = "Filter points by grade code or name. Can be specified multiple times." }, new Option { Key = nameof(context.Config.Qualifiers), Setter = value => context.Config.Qualifiers.Add(ParseQualifierFilter(value)), Getter = () => string.Empty, Description = "Filter points by qualifier. Can be specified multiple times." }, new Option(), new Option { Description = "Maximum time range of points to upload: Changes will trigger a full resync:" }, new Option { Key = nameof(context.Config.MaximumPointDays), Setter = value => { var components = value.Split('='); if (components.Length == 2) { var periodText = components[0]; var daysText = components[1]; var period = (ComputationPeriod)Enum.Parse(typeof(ComputationPeriod), periodText, true); var days = daysText.Equals("All", StringComparison.InvariantCultureIgnoreCase) ? -1 : int.Parse(daysText); context.Config.MaximumPointDays[period] = days; } }, Getter = () => "\n " + string.Join("\n ", context.Config.MaximumPointDays.Select(kvp => { var daysText = kvp.Value > 0 ? kvp.Value.ToString() : "All"; return($"{kvp.Key,-8} = {daysText}"); })) + "\n ", Description = "Days since the last point to upload, in Frequency=Value format." }, new Option(), new Option { Description = "Other options: (Changing these values won't trigger a full resync)" }, new Option { Key = nameof(context.ConfigurationName), Setter = value => context.ConfigurationName = value, Getter = () => context.ConfigurationName, Description = "The name of the export configuration, to be saved in the AQTS global settings." }, new Option { Key = nameof(context.DryRun), Setter = value => context.DryRun = bool.Parse(value), Getter = () => context.DryRun.ToString(), Description = "When true, don't export to SOS. Only log the changes that would have been performed." }, new Option { Key = nameof(context.ForceResync), Setter = value => context.ForceResync = bool.Parse(value), Getter = () => context.ForceResync.ToString(), Description = "When true, force a full resync of all time-series." }, new Option { Key = nameof(context.NeverResync), Setter = value => context.NeverResync = bool.Parse(value), Getter = () => context.NeverResync.ToString(), Description = "When true, avoid full time-series resync, even when the algorithm recommends it." }, new Option { Key = nameof(context.ChangesSince), Setter = value => context.ChangesSince = DateTimeOffset.Parse(value), Getter = () => string.Empty, Description = "The starting changes-since time in ISO 8601 format. Defaults to the saved AQTS global setting value." }, new Option { Key = nameof(context.ApplyRounding), Setter = value => context.ApplyRounding = bool.Parse(value), Getter = () => context.ApplyRounding.ToString(), Description = "When true, export the rounded point values." }, new Option { Key = nameof(context.MaximumPointsPerObservation), Setter = value => context.MaximumPointsPerObservation = int.Parse(value), Getter = () => context.MaximumPointsPerObservation.ToString(), Description = "The maximum number of points per SOS observation" }, new Option { Key = nameof(context.MaximumExportDuration), Setter = value => context.MaximumExportDuration = TimeSpan.Parse(value, CultureInfo.InvariantCulture), Getter = () => context.MaximumExportDuration?.Humanize(2), Description = "The maximum duration before polling AQTS for more changes, in hh:mm:ss format. Defaults to the AQTS global setting." }, new Option { Key = nameof(context.Timeout), Setter = value => context.Timeout = TimeSpan.Parse(value, CultureInfo.InvariantCulture), Getter = () => context.Timeout.Humanize(2), Description = "The timeout used for all web requests, in hh:mm:ss format." } }; var usageMessage = $"Export time-series changes in AQTS time-series to an OGC SOS server." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n {string.Join("\n ", options.Select(o => o.UsageText()))}" + $"\n" + $"\nISO 8601 timestamps use a yyyy'-'mm'-'dd'T'HH':'mm':'ss'.'fffffffzzz format." + $"\n" + $"\n The 7 fractional seconds digits are optional." + $"\n The zzz timezone can be 'Z' for UTC, or +HH:MM, or -HH:MM" + $"\n" + $"\n Eg: 2017-04-01T00:00:00Z represents April 1st, 2017 in UTC." + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace are ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeywords.Contains(arg)) { throw new ExpectedException($"Showing help page\n\n{usageMessage}"); } throw new ExpectedException($"Unknown command line argument: {arg}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key != null && o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } if (string.IsNullOrWhiteSpace(context.Config.AquariusServer) || string.IsNullOrEmpty(context.Config.AquariusUsername) || string.IsNullOrEmpty(context.Config.AquariusPassword)) { throw new ExpectedException($"Ensure your AQTS server credentials are set."); } if (string.IsNullOrWhiteSpace(context.Config.SosServer) || string.IsNullOrEmpty(context.Config.SosUsername) || string.IsNullOrEmpty(context.Config.SosPassword)) { throw new ExpectedException($"Ensure your SOS server credentials are set."); } return(context); }
private void ParseArgs(string[] args) { var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Key = nameof(Context.Server), Setter = value => Context.Server = value, Getter = () => Context.Server, Description = "The AQTS app server from which time-series data will be retrieved." }, new Option { Key = nameof(Context.Username), Setter = value => Context.Username = value, Getter = () => Context.Username, Description = "AQTS username." }, new Option { Key = nameof(Context.Password), Setter = value => Context.Password = value, Getter = () => Context.Password, Description = "AQTS credentials." }, new Option { Key = nameof(Context.TemplatePath), Setter = value => Context.TemplatePath = value, Getter = () => Context.TemplatePath, Description = "Path of the SharpShooter Report template file (*.RST)" }, new Option { Key = nameof(Context.OutputPath), Setter = value => Context.OutputPath = value, Getter = () => Context.OutputPath, Description = "Path to the generated report output. Only PDF output is supported." }, new Option { Key = nameof(Context.LaunchReportDesigner), Setter = value => Context.LaunchReportDesigner = bool.Parse(value), Getter = () => Context.LaunchReportDesigner.ToString(), Description = "When true, launch the SharpShooter Report Designer." }, new Option { Key = "TimeSeries", Setter = value => Context.TimeSeries.Add(ParseTimeSeries(value)), Getter = () => string.Empty, Description = "Load the specified time-series as a dataset." }, new Option { Key = "RatingModel", Setter = value => Context.RatingModels.Add(ParseRatingModel(value)), Getter = () => string.Empty, Description = "Load the specified rating-model as a dataset." }, new Option { Key = "ExternalDataSet", Setter = value => Context.ExternalDataSets.Add(ParseExternalDataSet(value)), Getter = () => string.Empty, Description = "Load the external DataSet XML file." }, new Option { Key = nameof(Context.UploadedReportLocation), Setter = value => Context.UploadedReportLocation = value, Getter = () => Context.UploadedReportLocation, Description = "Upload the generated report to this AQTS location identifier." }, new Option { Key = nameof(Context.UploadedReportTitle), Setter = value => Context.UploadedReportTitle = value, Getter = () => Context.UploadedReportTitle, Description = "Upload the generated report with this title. Defaults to the -OutputPath value." }, }; var usageMessage = $"Run a SharpShooter Reports template with AQTS data." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n -{string.Join("\n -", options.Select(o => o.UsageText()))}" + $"\n" + $"\nRetrieving time-series data from AQTS: (more than one -TimeSeries=value option can be specified)" + $"\n" + $"\n -TimeSeries=identifierOrUniqueId[,From=date][,To=date][,Unit=outputUnit][,GroupBy=option]" + $"\n" + $"\n =identifierOrUniqueId - Use either the uniqueId or the <parameter>.<label>@<location> syntax." + $"\n ,From=date - Retrieve data from this date. [default: Beginning of record]" + $"\n ,To=date - Retrieve data until this date. [default; End of record]" + $"\n ,Unit=outputUnit - Convert the values to the unit. [default: The default unit of the time-series]" + $"\n ,GroupBy=option - Groups data by {string.Join("|", Enum.GetNames(typeof(GroupBy)))} [default: Year]" + $"\n" + $"\n Dates specified as yyyy-MM-ddThh:mm:ss.fff. Only the year component is required." + $"\n" + $"\nRetrieving rating model info from AQTS: (more than one -RatingModel=value option can be specified)" + $"\n" + $"\n -RatingModel=identifierOrUniqueId[,From=date][,To=date][,Unit=outputUnit][,GroupBy=option]" + $"\n" + $"\n =identifierOrUniqueId - Use either the uniqueId or the <InputParameter>-<OutputParameter>.<label>@<location> syntax." + $"\n ,From=date - Retrieve data from this date. [default: Beginning of record]" + $"\n ,To=date - Retrieve data until this date. [default: End of record]" + $"\n ,StepSize=increment - Set the expanded table step size. [default: 0.1]" + $"\n" + $"\n Dates specified as yyyy-MM-ddThh:mm:ss.fff. Only the year component is required." + $"\n" + $"\nUsing external data sets: (more than one -ExternalDataSet=value option can be specified)" + $"\n" + $"\n -ExternalDataSet=pathToXml[,Name=datasetName]" + $"\n" + $"\n =pathToXml - A standard .NET DataSet, serialized to XML." + $"\n ,Name=datasetName - Override the name of the dataset. [default: The name stored within the XML]" + $"\n" + $"\nUnknown -name=value options will be merged with the appropriate data set and table." + $"\n" + $"\n Simple -name=value options like -MySetting=MyValue will be added to the Common.CommandLineParameters table." + $"\n" + $"\n Dotted -name=value options like -ReportParameters.Parameters.Description=MyValue will be merged into the named dataset.table.column." + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeywords.Contains(arg)) { throw new ExpectedException($"Showing help.\n\n{usageMessage}"); } throw new ExpectedException($"Unknown argument '{arg}'."); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { AddReportParameter(match.Groups["key"].Value, value); continue; } option.Setter(value); } }
private static Context ParseArgs(string[] args) { var context = new Context(); var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Key = nameof(context.Server), Setter = value => context.Server = value, Getter = () => context.Server, Description = "The AQTS app server." }, new Option { Key = nameof(context.Username), Setter = value => context.Username = value, Getter = () => context.Username, Description = "AQTS username." }, new Option { Key = nameof(context.Password), Setter = value => context.Password = value, Getter = () => context.Password, Description = "AQTS credentials." }, new Option { Key = nameof(context.SessionToken), Setter = value => context.SessionToken = value, Getter = () => context.SessionToken }, new Option { Key = nameof(context.SkipConfirmation), Setter = value => context.SkipConfirmation = bool.Parse(value), Getter = () => context.SkipConfirmation.ToString(), Description = "When true, skip the confirmation prompt. '/Y' is a shortcut for this option." }, new Option { Key = nameof(context.DryRun), Setter = value => context.DryRun = bool.Parse(value), Getter = () => context.DryRun.ToString(), Description = "When true, don't make any changes. '/N' is a shortcut for this option." }, new Option { Key = nameof(context.RecreateLocations), Setter = value => context.RecreateLocations = bool.Parse(value), Getter = () => context.RecreateLocations.ToString(), Description = "When true, recreate the location with the same properties." }, new Option { Key = "Location", Setter = value => context.LocationsToDelete.Add(value), Getter = () => string.Join(", ", context.LocationsToDelete), Description = "Locations to delete." }, new Option { Key = "TimeSeries", Setter = value => context.TimeSeriesToDelete.Add(value), Getter = () => string.Join(", ", context.TimeSeriesToDelete), Description = "Time-series to delete." }, new Option { Key = "RatingModel", Setter = value => context.RatingModelsToDelete.Add(value), Getter = () => string.Join(", ", context.RatingModelsToDelete), Description = "Rating models to delete." }, new Option { Key = "Visit", Setter = value => context.VisitsToDelete.Add(value), Getter = () => string.Join(", ", context.VisitsToDelete), Description = "Visits to delete." }, new Option { Key = nameof(context.VisitsBefore), Setter = value => context.VisitsBefore = ParseDateTime(value), Getter = () => context.VisitsBefore?.ToString("O"), Description = "Delete all visits in matching locations before and including this date." }, new Option { Key = nameof(context.VisitsAfter), Setter = value => context.VisitsAfter = ParseDateTime(value), Getter = () => context.VisitsAfter?.ToString("O"), Description = "Delete all visits in matching locations after and including this date." }, }; var usageMessage = $"Deletes locations, time-series, rating models, and/or field visits from an AQTS server." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] [location] [time-series] [rating model] [specific-visit] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n -{string.Join("\n -", options.Select(o => o.UsageText()))}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." + $"\n" + $"\nLocation deletion:" + $"\n=================" + $"\nLocations can be specified by either location identifier or location unique ID." + $"\nLocation identifiers are matched case-insensitively." + $"\nPublish API wildcard expansion of '*' is supported. '02*' will match locations beginning with '02'." + $"\n" + $"\nTime-series deletion:" + $"\n=====================" + $"\nTime-series can specified by identifier or by time-series unique ID." + $"\n" + $"\nRating model deletion:" + $"\n=====================" + $"\nRating models can specified by identifier." + $"\n" + $"\nField-visit deletion:" + $"\n=====================" + $"\nVisits can be specified by locationIdentifier@date, or locationIdentifier@dateAndTime." + $"\n" + $"\nWhen locationIdentifier@date is used, all visits starting on the date will be deleted." + $"\nWhen locationIdentifier@dateAndTime is used, only visits starting at the exact date and time will be deleted." + $"\n" + $"\nWhen the /{nameof(context.VisitsBefore)}= or /{nameof(context.VisitsAfter)}= options are given, all the visits falling within the time-range will be deleted." + $"\nIf no locations are specified when deleting field visits, visits from all locations will be considered." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (arg.StartsWith("/") || arg.StartsWith("-")) { var keyword = arg.Substring(1); if (keyword.Equals("y", StringComparison.InvariantCultureIgnoreCase)) { context.SkipConfirmation = true; continue; } if (keyword.Equals("n", StringComparison.InvariantCultureIgnoreCase)) { context.DryRun = true; continue; } throw new ExpectedException($"Unknown argument: {arg}\n\n{usageMessage}"); } if (RatingModelIdentifier.TryParse(arg, out _)) { context.RatingModelsToDelete.Add(arg); continue; } if (TimeSeriesIdentifier.TryParse(arg, out _)) { context.TimeSeriesToDelete.Add(arg); continue; } if (VisitIdentifier.TryParse(arg, out _)) { context.VisitsToDelete.Add(arg); continue; } // Otherwise assume it is a location to delete context.LocationsToDelete.Add(arg); continue; } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } if (string.IsNullOrWhiteSpace(context.Server)) { throw new ExpectedException("No AQTS server specified. See /help or -help for details"); } if (string.IsNullOrWhiteSpace(context.SessionToken) && (string.IsNullOrWhiteSpace(context.Username) || string.IsNullOrWhiteSpace(context.Password))) { throw new ExpectedException("Valid AQTS credentials must be supplied."); } if (!context.LocationsToDelete.Any() && !context.TimeSeriesToDelete.Any() && !context.RatingModelsToDelete.Any() && !context.VisitsToDelete.Any() && !context.VisitsAfter.HasValue && !context.VisitsBefore.HasValue) { throw new ExpectedException($"You must specify something to delete. See /help or -help for details."); } return(context); }
private static Context ParseArgs(string[] args) { var context = new Context(); var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Key = nameof(context.Server), Setter = value => context.Server = value, Getter = () => context.Server, Description = "The AQTS app server." }, new Option { Key = nameof(context.Username), Setter = value => context.Username = value, Getter = () => context.Username, Description = "AQTS username." }, new Option { Key = nameof(context.Password), Setter = value => context.Password = value, Getter = () => context.Password, Description = "AQTS credentials." }, new Option { Key = nameof(context.SessionToken), Setter = value => context.SessionToken = value, Getter = () => context.SessionToken }, new Option { Key = nameof(context.ApprovalLevel), Setter = value => context.ApprovalLevel = int.Parse(value), Getter = () => context.ApprovalLevel.ToString(), Description = "Sets the target approval level by numeric value." }, new Option { Key = nameof(context.ApprovalName), Setter = value => context.ApprovalName = value, Getter = () => context.ApprovalName, Description = "Sets the target approval level by name." }, new Option { Key = nameof(context.SkipConfirmation), Setter = value => context.SkipConfirmation = bool.Parse(value), Getter = () => context.SkipConfirmation.ToString(), Description = "When true, skip the confirmation prompt. '/Y' is a shortcut for this option." }, new Option { Key = nameof(context.DryRun), Setter = value => context.DryRun = bool.Parse(value), Getter = () => context.DryRun.ToString(), Description = "When true, don't make any changes. '/N' is a shortcut for this option." }, new Option { Key = "Location", Setter = value => context.Locations.Add(value), Getter = () => string.Join(", ", context.Locations), Description = "Locations to examine." }, new Option { Key = nameof(context.VisitsBefore), Setter = value => context.VisitsBefore = ParseDateTime(value), Getter = () => context.VisitsBefore?.ToString("O"), Description = "Change all visits in matching locations before and including this date." }, new Option { Key = nameof(context.VisitsAfter), Setter = value => context.VisitsAfter = ParseDateTime(value), Getter = () => context.VisitsAfter?.ToString("O"), Description = "Change all visits in matching locations after and including this date." }, }; var usageMessage = $"Changes field visit approval levels on an AQTS server." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] [location] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n -{string.Join("\n -", options.Select(o => o.UsageText()))}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." + $"\n" + $"\nLocation filtering:" + $"\n=================" + $"\nLocations can be specified by either location identifier or location unique ID." + $"\nLocation identifiers are matched case-insensitively." + $"\nPublish API wildcard expansion of '*' is supported. '02*' will match locations beginning with '02'." + $"\n" + $"\nTime-range filtering:" + $"\n=====================" + $"\nWhen the /{nameof(context.VisitsBefore)}= or /{nameof(context.VisitsAfter)}= options are given, only the visits falling within the time-range will be changed." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (arg.StartsWith("/") || arg.StartsWith("-")) { var keyword = arg.Substring(1); if (keyword.Equals("y", StringComparison.InvariantCultureIgnoreCase)) { context.SkipConfirmation = true; continue; } if (keyword.Equals("n", StringComparison.InvariantCultureIgnoreCase)) { context.DryRun = true; continue; } throw new ExpectedException($"Unknown argument: {arg}\n\n{usageMessage}"); } // Otherwise assume it is a location constraint context.Locations.Add(arg); continue; } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } if (string.IsNullOrWhiteSpace(context.Server)) { throw new ExpectedException("No AQTS server specified. See /help or -help for details"); } if (string.IsNullOrWhiteSpace(context.SessionToken) && (string.IsNullOrWhiteSpace(context.Username) || string.IsNullOrWhiteSpace(context.Password))) { throw new ExpectedException("Valid AQTS credentials must be supplied."); } if (!context.ApprovalLevel.HasValue && string.IsNullOrWhiteSpace(context.ApprovalName)) { throw new ExpectedException($"You must specify a target approval level with /{nameof(context.ApprovalName)} or /{nameof(context.ApprovalLevel)} options."); } if (context.ApprovalLevel.HasValue && !string.IsNullOrWhiteSpace(context.ApprovalName)) { throw new ExpectedException($"Only one of /{nameof(context.ApprovalName)} or /{nameof(context.ApprovalLevel)} can be specified."); } return(context); }
private static Context ParseArgs(string[] args) { var context = new Context(); var resolvedArgs = InjectOptionsFileByDefault(args) .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Key = nameof(context.Server), Setter = value => context.Server = value, Getter = () => context.Server, Description = "AQTS server name" }, new Option { Key = nameof(context.Username), Setter = value => context.Username = value, Getter = () => context.Username, Description = "AQTS username" }, new Option { Key = nameof(context.Password), Setter = value => context.Password = value, Getter = () => context.Password, Description = "AQTS password" }, new Option { Key = nameof(context.ConfigPath), Setter = value => context.ConfigPath = value, Getter = () => context.ConfigPath, Description = $"Path to the JSON configuration file. [default: '{nameof(Config)}.json' in the same folder as the EXE]" }, new Option { Key = nameof(context.CreateMissingTimeSeries), Setter = value => context.CreateMissingTimeSeries = bool.Parse(value), Getter = () => $"{context.CreateMissingTimeSeries}", Description = "When true, any missing time-series will be created." }, }; var usageMessage = $"An external processor for calculating total discharge for arbitrary-length events." + $"\n" + $"\nusage: {ExeHelper.ExeName} [-option=value] [@optionsFile] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n {string.Join("\n ", options.Select(o => o.UsageText()))}" + $"\n" + $"\nWhen no other command line options are given, the Options.txt file in" + $"\nsame folder as the EXE will be used if it exists." + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; var helpGuidance = "See /help screen for details."; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeyWords.Contains(arg)) { throw new ExpectedException(usageMessage); } throw new ExpectedException($"Unknown argument: {arg}\n\n{helpGuidance}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key != null && o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{helpGuidance}"); } option.Setter(value); } return(context); }
private static void ParseArgsIntoContext(Context context, string[] resolvedArgs) { var options = new[] { new Option { Key = nameof(context.Files), Description = "Parse the NEM12 file. Can be set multiple times.", Setter = value => context.Files.Add(value), Getter = () => string.Empty, }, }; var usageMessage = $"Converts the NEM12 CSV file into a single CSV row per point." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] NEM12File1 NEM12File2 ..." + $"\n" + $"\nIf no NEM12 CSV file is specified, the standard input will be used." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n {string.Join("\n ", options.Select(o => o.UsageText()))}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace are ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeywords.Contains(arg)) { throw new ExpectedException($"Showing help page\n\n{usageMessage}"); } if (File.Exists(arg)) { context.Files.Add(arg); continue; } throw new ExpectedException($"Unknown command line argument: {arg}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key != null && o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } }
private static Context ParseArgs(string[] args) { var context = new Context(); var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Description = "AQSamples connection options:" }, new Option { Key = nameof(context.ServerUrl), Setter = value => context.ServerUrl = value, Getter = () => context.ServerUrl, Description = "AQS server URL" }, new Option { Key = nameof(context.ApiToken), Setter = value => context.ApiToken = value, Getter = () => context.ApiToken, Description = "AQS API Token" }, new Option(), new Option { Description = "Output options:" }, new Option { Key = nameof(context.CsvOutputPath), Setter = value => context.CsvOutputPath = value, Getter = () => context.CsvOutputPath, Description = $"Path to output file [default: ExportedObservations-yyyyMMddHHmmss.csv in the same folder as the EXE]" }, new Option { Key = nameof(context.Overwrite), Setter = value => context.Overwrite = ParseBoolean(value), Getter = () => $"{context.Overwrite}", Description = "Overwrite existing files?" }, new Option { Key = nameof(context.UtcOffset), Setter = value => context.UtcOffset = ParseOffset(value), Getter = () => string.Empty, Description = $"UTC offset for output times [default: Use system timezone, currently {context.UtcOffset:m}]" }, new Option(), new Option { Description = "Cumulative filter options: (ie. AND-ed together). Can be set multiple times." }, new Option { Key = nameof(context.StartTime), Setter = value => context.StartTime = ParseDateTimeOffset(value), Getter = () => string.Empty, Description = "Include observations after this time." }, new Option { Key = nameof(context.EndTime), Setter = value => context.EndTime = ParseDateTimeOffset(value), Getter = () => string.Empty, Description = "Include observations before this time." }, new Option { Key = nameof(context.LocationIds).Singularize(), Setter = value => context.LocationIds.Add(value), Getter = () => string.Empty, Description = "Observations matching these locations." }, new Option { Key = nameof(context.AnalyticalGroupIds).Singularize(), Setter = value => context.AnalyticalGroupIds.Add(value), Getter = () => string.Empty, Description = "Observations matching these analytical groups." }, new Option { Key = nameof(context.ObservedPropertyIds).Singularize(), Setter = value => context.ObservedPropertyIds.Add(value), Getter = () => string.Empty, Description = "Observations matching these observed properties." }, new Option { Key = nameof(context.ProjectIds).Singularize(), Setter = value => context.ProjectIds.Add(value), Getter = () => string.Empty, Description = "Observations matching these projects." }, }; var usageMessage = $"Export observations from an AQUARIUS Samples server." + $"\n" + $"\nusage: {ExeHelper.ExeName} [-option=value] [@optionsFile] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n {string.Join("\n ", options.Select(o => o.UsageText()))}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; var helpGuidance = "See /help screen for details."; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeyWords.Contains(arg)) { throw new ExpectedException(usageMessage); } throw new ExpectedException($"Unknown argument: {arg}\n\n{helpGuidance}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key != null && o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{helpGuidance}"); } option.Setter(value); } return(context); }
private static Context ParseArgs(string[] args) { var context = new Context(); var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Key = nameof(context.AssemblyPath), Setter = value => context.AssemblyPath = value, Getter = () => context.AssemblyPath, Description = "Path to the plugin assembly." }, new Option { Key = nameof(context.OutputPath), Setter = value => context.OutputPath = value, Getter = () => context.OutputPath, Description = "Path to packaged output file, usually with a '.report' extension" }, new Option { Key = nameof(context.DeployedFolderName), Setter = value => context.DeployedFolderName = value, Getter = () => context.DeployedFolderName, Description = "Name of the deployed folder" }, new Option { Key = nameof(context.Subfolders), Setter = value => context.Subfolders = bool.Parse(value), Getter = () => context.Subfolders.ToString(), Description = "Include all subfolders" }, new Option { Key = nameof(context.Include), Setter = value => AddToList(value, context.Include), Getter = () => string.Join(", ", context.Include), Description = "Include file or DOS wildcard pattern" }, new Option { Key = nameof(context.Exclude), Setter = value => AddToList(value, context.Exclude), Getter = () => string.Join(", ", context.Exclude), Description = "Exclude file or DOS wildcard pattern" }, new Option { Key = nameof(context.ForceInclude), Setter = value => AddToList(value, context.ForceInclude), Getter = () => string.Join(", ", context.ForceInclude), Description = "Force include file or DOS wildcard pattern" }, }; var usageMessage = $"Package a report plugin into a deployable .report file." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] pluginFolderOrAssembly" + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n -{string.Join("\n -", options.Select(o => o.UsageText()))}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (File.Exists(arg)) { context.AssemblyPath = arg; continue; } throw new ExpectedException($"Unknown argument: {arg}\n\n{usageMessage}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } if (string.IsNullOrEmpty(context.AssemblyPath)) { throw new ExpectedException($"You must specify the /{nameof(context.AssemblyPath)} option."); } if (string.IsNullOrEmpty(context.OutputPath)) { throw new ExpectedException($"You must specify the /{nameof(context.OutputPath)} option."); } return(context); }
private void ParseArgs(string[] args) { var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Key = "Plugin", Setter = value => Context.PluginPath = value, Getter = () => Context.PluginPath, Description = "Path to the plugin assembly to debug" }, new Option { Key = "Data", Setter = value => AddDataPath(Context, value), Getter = () => string.Empty, Description = "Path to the data file to be parsed. Can be set more than once." }, new Option { Key = "Location", Setter = value => Context.LocationIdentifier = value, Getter = () => Context.LocationIdentifier, Description = "Optional location identifier context" }, new Option { Key = "UtcOffset", Setter = value => Context.LocationUtcOffset = TimeSpan.Parse(value), Getter = () => Context.LocationUtcOffset.ToString(), Description = "UTC offset in .NET TimeSpan format." }, new Option { Key = "Json", Setter = value => Context.JsonPath = value, Getter = () => Context.JsonPath, Description = "Optional path to write the appended results as JSON" }, new Option { Key = "ExpectedError", Setter = value => Context.ExpectedError = value, Getter = () => Context.ExpectedError, Description = "Expected error message" }, new Option { Key = "ExpectedStatus", Setter = value => Context.ExpectedStatus = (ParseFileStatus)Enum.Parse(typeof(ParseFileStatus), value, true), Getter = () => Context.ExpectedStatus.ToString(), Description = $"Expected parse status. One of {string.Join(", ", Enum.GetNames(typeof(ParseFileStatus)))}" }, }; var usageMessage = $"Parse a file using a field data plugin, logging the results." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n -{string.Join("\n -", options.Select(o => o.UsageText()))}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { throw new ExpectedException($"Unknown argument: {arg}\n\n{usageMessage}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } if (string.IsNullOrEmpty(Context.PluginPath)) { throw new ExpectedException("No plugin assembly specified."); } if (!Context.DataPaths.Any()) { throw new ExpectedException("No data file specified."); } }
private static Context ParseArgs(string[] args) { var context = new Context(); var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Key = nameof(context.Server), Setter = value => context.Server = value, Getter = () => context.Server, Description = "AQTS server name" }, new Option { Key = nameof(context.Username), Setter = value => context.Username = value, Getter = () => context.Username, Description = "AQTS username" }, new Option { Key = nameof(context.Password), Setter = value => context.Password = value, Getter = () => context.Password, Description = "AQTS password" }, new Option { Key = nameof(context.SessionToken), Setter = value => context.SessionToken = value, Getter = () => context.SessionToken, }, new Option { Key = nameof(context.LocationIdentifier), Setter = value => context.LocationIdentifier = value, Getter = () => context.LocationIdentifier, Description = "Optional location filter." }, new Option { Key = nameof(context.Publish), Setter = value => context.Publish = bool.Parse(value), Getter = () => context.Publish?.ToString(), Description = "Optional publish filter." }, new Option { Key = nameof(context.ChangeEventType), Setter = value => context.ChangeEventType = (ChangeEventType)Enum.Parse(typeof(ChangeEventType), value, true), Getter = () => context.ChangeEventType?.ToString(), Description = $"Optional change event type filter. One of {string.Join(", ", Enum.GetNames(typeof(ChangeEventType)))}" }, new Option { Key = nameof(context.Parameter), Setter = value => context.Parameter = value, Getter = () => context.Parameter, Description = "Optional parameter filter." }, new Option { Key = nameof(context.ComputationIdentifier), Setter = value => context.ComputationIdentifier = value, Getter = () => context.ComputationIdentifier, Description = "Optional computation filter." }, new Option { Key = nameof(context.ComputationPeriodIdentifier), Setter = value => context.ComputationPeriodIdentifier = value, Getter = () => context.ComputationPeriodIdentifier, Description = "Optional computation period filter." }, new Option { Key = nameof(context.ExtendedFilters), Setter = value => { var split = value.Split('='); if (split.Length != 2) { throw new ExpectedException($"Can't parse '{value}' as Name=Value extended attribute filter"); } context.ExtendedFilters.Add(new ExtendedAttributeFilter { FilterName = split[0], FilterValue = split[1] }); }, Getter = () => string.Empty, Description = "Optional extended attribute filter in Name=Value format. Can be set multiple times." }, new Option { Key = nameof(context.TimeSeries), Setter = value => context.TimeSeries.Add(value), Getter = () => string.Empty, Description = "Optional time-series to monitor. Can be set multiple times." }, new Option { Key = nameof(context.ChangesSinceTime), Setter = value => context.ChangesSinceTime = InstantPattern.ExtendedIsoPattern.Parse(value).GetValueOrThrow(), Getter = () => string.Empty, Description = "The starting changes-since time in ISO 8601 format. Defaults to 'right now'" }, new Option { Key = nameof(context.PollInterval), Setter = value => context.PollInterval = value.ToUpperInvariant().ParseDuration(), Getter = () => context.PollInterval.SerializeToString(), Description = "The polling interval in ISO 8601 Duration format." }, new Option { Key = nameof(context.MaximumChangeCount), Setter = value => context.MaximumChangeCount = int.Parse(value), Getter = () => context.MaximumChangeCount.ToString(), Description = "When greater than 0, exit after detecting this many changed time-series." }, new Option { Key = nameof(context.AllowQuickPolling), Setter = value => context.AllowQuickPolling = bool.Parse(value), Getter = () => context.AllowQuickPolling.ToString(), Description = "Allows very quick polling. Good for testing, bad for production." }, new Option { Key = nameof(context.SavedChangesSinceJson), Setter = value => context.SavedChangesSinceJson = value, Getter = () => context.SavedChangesSinceJson, Description = $"Loads the /{nameof(context.ChangesSinceTime)} value from this JSON file." }, new Option { Key = nameof(context.DetectedChangesCsv), Setter = value => context.DetectedChangesCsv = value, Getter = () => context.DetectedChangesCsv, Description = $"When set, save all detected changes to this CSV file and exit." }, }; var usageMessage = $"Monitor time-series changes in an AQTS time-series." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] [location] [timeSeriesIdentifierOrGuid] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n -{string.Join("\n -", options.Select(o => o.UsageText()))}" + $"\n" + $"\nISO 8601 timestamps use a yyyy'-'mm'-'dd'T'HH':'mm':'ss'.'fffffffzzz format." + $"\n" + $"\n The 7 fractional seconds digits are optional." + $"\n The zzz timezone can be 'Z' for UTC, or +HH:MM, or -HH:MM" + $"\n" + $"\n Eg: 2017-04-01T00:00:00Z represents April 1st, 2017 in UTC." + $"\n" + $"\nISO 8601 durations use a 'PT'[nnH][nnM][nnS] format." + $"\n" + $"\n Only the required components are needed." + $"\n" + $"\n Eg: PT5M represents 5 minutes." + $"\n PT90S represents 90 seconds (1.5 minutes)" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeywords.Contains(arg)) { throw new ExpectedException($"Showing help page\n\n{usageMessage}"); } // Try positional arguments: [locationIdentifier] [timeSeriesIdentifier] if (Guid.TryParse(arg, out var _)) { context.TimeSeries.Add(arg); continue; } if (TimeSeriesIdentifier.TryParse(arg, out var _)) { context.TimeSeries.Add(arg); continue; } if (arg.EndsWith(".json", StringComparison.InvariantCultureIgnoreCase)) { context.SavedChangesSinceJson = arg; continue; } if (arg.EndsWith(".csv", StringComparison.InvariantCultureIgnoreCase)) { context.DetectedChangesCsv = arg; continue; } context.LocationIdentifier = arg; continue; } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } if (string.IsNullOrWhiteSpace(context.Server)) { throw new ExpectedException($"A /{nameof(context.Server)} option is required.\n\n{usageMessage}"); } return(context); }
private static Context GetContext(string[] args) { var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var context = new Context(); var options = new[] { new Option { Key = nameof(context.ExcelPath), Setter = value => context.ExcelPath = value, Getter = () => context.ExcelPath, Description = "Specifies the Excel workbook to split" }, new Option { Key = nameof(context.Sheets), Setter = value => context.Sheets.Add(value), Description = "Split out the named sheets. [default: All sheets]" }, new Option { Key = nameof(context.OutputPath), Setter = value => context.OutputPath = value, Getter = () => context.OutputPath, Description = $"Output path of CSVs (default: {{{Splitter.ExcelPathPattern}}}.{{{Splitter.SheetNamePattern}}}.csv" }, new Option { Key = nameof(context.Overwrite), Setter = value => context.Overwrite = bool.Parse(value), Getter = () => $"{context.Overwrite}", Description = "Set to true to overwrite existing files." }, new Option { Key = nameof(context.TrimEmptyColumns), Setter = value => context.TrimEmptyColumns = bool.Parse(value), Getter = () => $"{context.TrimEmptyColumns}", Description = "Set to false to retain empty columns at the end of each row." }, new Option { Key = nameof(context.DateTimeFormat), Setter = value => context.DateTimeFormat = value, Getter = () => context.DateTimeFormat, Description = "Sets the format of any timestamps, using .NET datetime format [default: ISO8601]" }, }; var usageMessage = $"Extracts all the sheets in an Excel workbook into multiple CSV files" + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] [location] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n -{string.Join("\n -", options.Select(o => o.UsageText()))}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (ResolvePositionalArgument(context, arg)) { continue; } throw new ExpectedException($"Unknown argument: {arg}\n\n{usageMessage}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } ValidateContext(context); return(context); }
private void ParseArgs(string[] args) { var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Description = "AQUARIUS Time-Series connection options:" }, new Option { Key = nameof(Context.Server), Setter = value => Context.Server = value, Getter = () => Context.Server , Description = "The AQTS app server from which time-series data will be retrieved." }, new Option { Key = nameof(Context.Username), Setter = value => Context.Username = value, Getter = () => Context.Username, Description = "AQTS username." }, new Option { Key = nameof(Context.Password), Setter = value => Context.Password = value, Getter = () => Context.Password, Description = "AQTS credentials." }, new Option(), new Option { Description = "SharpShooter Reports options:" }, new Option { Key = nameof(Context.TemplatePath), Setter = value => Context.TemplatePath = value, Getter = () => Context.TemplatePath, Description = "Path of the SharpShooter Reports template (*.RST) or Aquarius Report template (*.ART) file." }, new Option { Key = nameof(Context.OutputPath), Setter = value => Context.OutputPath = value, Getter = () => Context.OutputPath, Description = "Path to the generated report output. Only PDF output is supported." }, new Option { Key = nameof(Context.LaunchReportDesigner), Setter = value => Context.LaunchReportDesigner = bool.Parse(value), Getter = () => Context.LaunchReportDesigner.ToString(), Description = "When true, launch the SharpShooter Report Designer." }, new Option(), new Option { Description = "Dataset options:" }, new Option { Key = nameof(Context.QueryFrom), Setter = value => Context.QueryFrom = value, Getter = () => Context.QueryFrom, Description = "The starting point for all time-series. Can be overriden by individual series. [default: Beginning of record]" }, new Option { Key = nameof(Context.QueryTo), Setter = value => Context.QueryTo = value, Getter = () => Context.QueryTo, Description = "The ending point for all time-series. Can be overriden by individual series. [default: End of record]" }, new Option { Key = nameof(Context.GroupBy), Setter = value => Context.GroupBy = ParseEnum <GroupBy>(value), Getter = () => $"{Context.GroupBy}", Description = $"The grouping for all time-series. One of {string.Join(", ", Enum.GetNames(typeof(GroupBy)))}. Can be overriden by individual series." }, new Option { Key = "TimeSeries", Setter = value => Context.TimeSeries.Add(ParseTimeSeries(value)), Getter = () => string.Empty, Description = "Load the specified time-series as a dataset." }, new Option { Key = "RatingModel", Setter = value => Context.RatingModels.Add(ParseRatingModel(value)), Getter = () => string.Empty, Description = "Load the specified rating-model as a dataset." }, new Option { Key = "ExternalDataSet", Setter = value => Context.ExternalDataSets.Add(ParseExternalDataSet(value)), Getter = () => string.Empty, Description = "Load the external DataSet XML file." }, new Option(), new Option { Description = "Report uploading options:" }, new Option { Key = nameof(Context.UploadedReportLocation), Setter = value => Context.UploadedReportLocation = value, Getter = () => Context.UploadedReportLocation, Description = "Upload the generated report to this AQTS location identifier. If empty, no report will be uploaded." }, new Option { Key = nameof(Context.UploadedReportTitle), Setter = value => Context.UploadedReportTitle = value, Getter = () => Context.UploadedReportTitle, Description = $"Upload the generated report with this title. Defaults to the -{nameof(Context.OutputPath)} base filename." }, }; var usageMessage = $"Run a SharpShooter Reports template with AQTS data." + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n {string.Join("\n ", options.Select(o => o.UsageText()))}" + $"\n" + $"\nRetrieving time-series data from AQTS: (more than one -TimeSeries=value option can be specified)" + $"\n" + $"\n -TimeSeries=identifierOrUniqueId[,From=date][,To=date][,Unit=outputUnit][,GroupBy=option][,DataSetName=dataSetName]" + $"\n" + $"\n =identifierOrUniqueId - Use either the uniqueId or the <parameter>.<label>@<location> syntax." + $"\n ,From=date - Retrieve data from this date. [default: Beginning of record]" + $"\n ,To=date - Retrieve data until this date. [default: End of record]" + $"\n ,Unit=outputUnit - Convert the values to the unit. [default: The default unit of the time-series]" + $"\n ,GroupBy=option - Groups data by {string.Join("|", Enum.GetNames(typeof(GroupBy)))} [default: {Context.GroupBy}]" + $"\n ,DataSetName=datasetName - Override the name of the dataset. [default: 'TimeSeries#' where # is the 1-based index of the time-series]" + $"\n" + $"\n Use the -TimeSeries4=... or -TimeSeries[4]=... syntax to force the dataset name to a specific index." + $"\n" + $"\n Dates specified as yyyy-MM-ddThh:mm:ss.fff. Only the year component is required." + $"\n" + $"\nRetrieving rating model info from AQTS: (more than one -RatingModel=value option can be specified)" + $"\n" + $"\n -RatingModel=identifierOrUniqueId[,From=date][,To=date][,Unit=outputUnit][,GroupBy=option][,DataSetName=dataSetName]" + $"\n" + $"\n =identifierOrUniqueId - Use either the uniqueId or the <InputParameter>-<OutputParameter>.<label>@<location> syntax." + $"\n ,From=date - Retrieve data from this date. [default: Beginning of record]" + $"\n ,To=date - Retrieve data until this date. [default: End of record]" + $"\n ,StepSize=increment - Set the expanded table step size. [default: 0.1]" + $"\n ,DataSetName=datasetName - Override the name of the dataset. [default: 'RatingCurve#' where # is the 1-based index of the rating model]" + $"\n" + $"\n Use the -RatingModel4=... or -RatingModel[4]=... syntax to force the dataset name to a specific index." + $"\n" + $"\n Dates specified as yyyy-MM-ddThh:mm:ss.fff. Only the year component is required." + $"\n" + $"\nUsing external data sets: (more than one -ExternalDataSet=value option can be specified)" + $"\n" + $"\n -ExternalDataSet=pathToXml[,DataSetName=dataSetName]" + $"\n" + $"\n =pathToXml - A standard .NET DataSet, serialized to XML." + $"\n ,DataSetName=datasetName - Override the name of the dataset. [default: The name stored within the XML]" + $"\n" + $"\nUnknown -name=value options will be merged with the appropriate data set and table." + $"\n" + $"\n Simple -name=value options like -MySetting=MyValue will be added to the Common.CommandLineParameters table." + $"\n" + $"\n Dotted -name=value options like -ReportParameters.Parameters.Description=MyValue will be merged into the named dataset.table.column." + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeywords.Contains(arg)) { throw new ExpectedException($"Showing help.\n\n{usageMessage}"); } throw new ExpectedException($"Unknown argument '{arg}'."); } var key = match.Groups["key"].Value; var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key != null && o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option != null) { option.Setter(value); continue; } match = IndexedDataSetRegex.Match(key); if (!match.Success) { AddReportParameter(key, value); continue; } var category = match.Groups["category"].Value; var index = int.Parse(match.Groups["index"].Value); if (index <= 0) { throw new ExpectedException($"'{arg}' has an index of {index}. A value greater than zero is required."); } if (category.StartsWith(nameof(Context.TimeSeries), StringComparison.InvariantCultureIgnoreCase)) { var timeSeries = ParseTimeSeries(value); ForceDataSetName(timeSeries, $"TimeSeries{index}"); Context.TimeSeries.Add(timeSeries); } else { var ratingModel = ParseRatingModel(value); ForceDataSetName(ratingModel, $"RatingCurve{index}"); Context.RatingModels.Add(ratingModel); } } }
private static Context ParseArgs(string[] args) { var context = new Context(); var resolvedArgs = args .SelectMany(ResolveOptionsFromFile) .ToArray(); var options = new[] { new Option { Description = "AQUARIUS Samples connection options:" }, new Option { Key = nameof(context.SamplesServer), Setter = value => context.SamplesServer = value, Getter = () => context.SamplesServer, Description = "AQS server URL" }, new Option { Key = nameof(context.SamplesApiToken), Setter = value => context.SamplesApiToken = value, Getter = () => context.SamplesApiToken, Description = "AQS API Token" }, new Option(), new Option { Description = "AQUARIUS Time-Series connection options:" }, new Option { Key = nameof(context.TimeSeriesServer), Setter = value => context.TimeSeriesServer = value, Getter = () => context.TimeSeriesServer, Description = "AQTS server" }, new Option { Key = nameof(context.TimeSeriesUsername), Setter = value => context.TimeSeriesUsername = value, Getter = () => context.TimeSeriesUsername, Description = "AQTS username" }, new Option { Key = nameof(context.TimeSeriesPassword), Setter = value => context.TimeSeriesPassword = value, Getter = () => context.TimeSeriesPassword, Description = "AQTS password" }, new Option(), new Option { Description = "Export options:" }, new Option { Key = nameof(context.DryRun), Setter = value => context.DryRun = ParseBoolean(value), Getter = () => $"{context.DryRun}", Description = "When true, don't export and upload reports, just validate what would be done." }, new Option { Key = nameof(context.ExportTemplateName), Setter = value => context.ExportTemplateName = value, Getter = () => context.ExportTemplateName, Description = "The Observation Export Spreadsheet Template to use for all exports." }, new Option { Key = nameof(context.AttachmentFilename), Setter = value => context.AttachmentFilename = value, Getter = () => context.AttachmentFilename, Description = $"Filename of the exported attachment." }, new Option { Key = nameof(context.AttachmentTags), Setter = value => ParseAttachmentTagValue(context, value), Getter = () => string.Empty, Description = "Uploaded attachments will have these tag values applies, in key:value format." }, new Option { Key = nameof(context.DeleteExistingAttachments), Setter = value => context.DeleteExistingAttachments = ParseBoolean(value), Getter = () => $"{context.DeleteExistingAttachments}", Description = "Delete any existing location attachments with the same name." }, new Option { Key = nameof(context.ExportTime), Setter = value => context.ExportTime = ParseDateTimeOffset(value), Getter = () => string.Empty, Description = $"The timestamp used for all {{{FilenameGenerator.TimePattern}}} pattern substitutions. [default: The current time]" }, new Option(), new Option { Description = "Cumulative filter options: (ie. AND-ed together). Can be set multiple times." }, new Option { Key = nameof(context.StartTime), Setter = value => context.StartTime = ParseDateTimeOffset(value), Getter = () => string.Empty, Description = "Include observations after this time. [default: Start of record]" }, new Option { Key = nameof(context.EndTime), Setter = value => context.EndTime = ParseDateTimeOffset(value), Getter = () => string.Empty, Description = "Include observations before this time. [default: End of record]" }, new Option { Key = nameof(context.LocationIds).Singularize(), Setter = value => ParseListItem(context.LocationIds, value), Getter = () => string.Empty, Description = "Observations matching these sampling locations." }, new Option { Key = nameof(context.LocationGroupIds).Singularize(), Setter = value => ParseListItem(context.LocationGroupIds, value), Getter = () => string.Empty, Description = "Observations matching these sampling location groups." }, new Option { Key = nameof(context.AnalyticalGroupIds).Singularize(), Setter = value => ParseListItem(context.AnalyticalGroupIds, value), Getter = () => string.Empty, Description = "Observations matching these analytical groups." }, new Option { Key = nameof(context.ObservedPropertyIds).Singularize(), Setter = value => ParseListItem(context.ObservedPropertyIds, value), Getter = () => string.Empty, Description = "Observations matching these observed properties." }, }; var usageMessage = $"Export observations from AQUARIUS Samples using a spreadsheet template into AQUARIUS Time-Series locations as attachments." + $"\n" + $"\nusage: {ExeHelper.ExeName} [-option=value] [@optionsFile] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n {string.Join("\n ", options.Select(o => o.UsageText()))}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace is ignored." + $"\n Comment lines begin with a # or // marker." ; var helpGuidance = "See /help screen for details."; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeyWords.Contains(arg)) { throw new ExpectedException(usageMessage); } throw new ExpectedException($"Unknown argument: {arg}\n\n{helpGuidance}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key != null && o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{helpGuidance}"); } option.Setter(value); } return(context); }
private static void ParseArgsIntoContext(Context context, string[] resolvedArgs) { var options = new[] { new Option { Description = "https://waterwatch.io credentials" }, new Option { Key = nameof(context.WaterWatchOrgId), Description = "WaterWatch.io organisation Id", Setter = value => context.WaterWatchOrgId = value, Getter = () => context.WaterWatchOrgId, }, new Option { Key = nameof(context.WaterWatchApiKey), Description = "WaterWatch.io API key", Setter = value => context.WaterWatchApiKey = value, Getter = () => context.WaterWatchApiKey, }, new Option { Key = nameof(context.WaterWatchApiToken), Description = "WaterWatch.io API token", Setter = value => context.WaterWatchApiToken = value, Getter = () => context.WaterWatchApiToken, }, new Option(), new Option { Description = "Configuration options" }, new Option { Key = nameof(context.OutputMode), Description = $"Measurement value output mode. One of {string.Join(", ", Enum.GetNames(typeof(OutputMode)))}.", Setter = value => context.OutputMode = (OutputMode)Enum.Parse(typeof(OutputMode), value, true), Getter = () => context.OutputMode.ToString(), }, new Option { Key = nameof(context.OutputDivisor), Description = "Divisor applied to all output values.", Setter = value => context.OutputDivisor = double.Parse(value), Getter = () => context.OutputDivisor.ToString("G"), }, new Option { Key = nameof(context.SaveStatePath), Description = "Path to persisted state file", Setter = value => context.SaveStatePath = value, Getter = () => context.SaveStatePath, }, new Option { Key = nameof(context.SyncFromUtc), Description = "Optional UTC sync time. [default: last known sensor time]", Setter = value => context.SyncFromUtc = ParseDateTime(value), }, new Option { Key = nameof(context.NewSensorSyncDays), Description = "Number of days to sync data when a new sensor is detected.", Setter = value => context.NewSensorSyncDays = int.Parse(value), Getter = () => context.NewSensorSyncDays.ToString(), }, new Option(), new Option { Description = "Sensor filtering options" }, new Option { Key = "SensorName", Description = "Sensor name regular expression filter. Can be specified multiple times.", Setter = value => context.SensorNameFilters.Add(ParseRegexFilter(value)) }, new Option { Key = "SensorSerial", Description = "Sensor serial number regular expression filter. Can be specified multiple times.", Setter = value => context.SensorSerialFilters.Add(ParseRegexFilter(value)) }, }; var usageMessage = $"Extract the latest sensor readings from a https://waterwatch.io account" + $"\n" + $"\nusage: {GetProgramName()} [-option=value] [@optionsFile] ..." + $"\n" + $"\nSupported -option=value settings (/option=value works too):\n\n {string.Join("\n ", options.Select(o => o.UsageText()))}" + $"\n" + $"\nSupported /{nameof(context.SyncFromUtc)} date formats:" + $"\n" + $"\n {string.Join("\n ", SupportedDateFormats)}" + $"\n" + $"\nUse the @optionsFile syntax to read more options from a file." + $"\n" + $"\n Each line in the file is treated as a command line option." + $"\n Blank lines and leading/trailing whitespace are ignored." + $"\n Comment lines begin with a # or // marker." ; foreach (var arg in resolvedArgs) { var match = ArgRegex.Match(arg); if (!match.Success) { if (HelpKeywords.Contains(arg)) { throw new ExpectedException($"Showing help page\n\n{usageMessage}"); } if (File.Exists(arg)) { // This is the magic which allows the preprocessor to be used in AQUARIUS DAS and EnviroSCADA 2018.1-or-earlier. // Those products require that a preprocessor has one and only one argument, which is a "script file". // This recursive call interprets any existing file as an @options.txt argument list. // This is not necessary for EnviroSCADA 2019.1+ or Connect, since both of those allow arbitrary preprocessor command line arguments. ParseArgsIntoContext(context, LoadArgsFromFile(arg).ToArray()); continue; } throw new ExpectedException($"Unknown command line argument: {arg}"); } var key = match.Groups["key"].Value.ToLower(); var value = match.Groups["value"].Value; var option = options.FirstOrDefault(o => o.Key != null && o.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (option == null) { throw new ExpectedException($"Unknown -option=value: {arg}\n\n{usageMessage}"); } option.Setter(value); } }