You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
308 lines
7.6 KiB
308 lines
7.6 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Text.RegularExpressions; |
|
using System.Threading.Tasks; |
|
|
|
using Newtonsoft.Json; |
|
|
|
using SBRL.Utilities; |
|
using LibSearchBox; |
|
|
|
namespace SearchBoxCLI |
|
{ |
|
enum OperatingModes |
|
{ |
|
Query, |
|
Index, |
|
Add, |
|
Remove, |
|
Update, |
|
GenerateContext |
|
} |
|
|
|
enum OutputModes |
|
{ |
|
Json, |
|
Text, |
|
Html |
|
} |
|
|
|
class MainClass { |
|
private static List<string> Extras = new List<string>(); |
|
|
|
private static OperatingModes Mode = OperatingModes.Query; |
|
private static OutputModes OutputMode = OutputModes.Text; |
|
private static bool Batch = false; |
|
|
|
private static string Name = string.Empty; |
|
private static IEnumerable<string> Tags; |
|
|
|
private static string SearchIndexFilepath = string.Empty; |
|
private static TextReader Source = Console.In; |
|
private static TextReader SourceOld = null, SourceNew = null; |
|
|
|
private static string Query = string.Empty; |
|
private static int ResultsLimit = -1; |
|
private static int ResultsOffset = 0; |
|
|
|
public static int Main(string[] args) |
|
{ |
|
for (int i = 0; i < args.Length; i++) |
|
{ |
|
if (!args[i].StartsWith("-")) { |
|
Extras.Add(args[i]); |
|
continue; |
|
} |
|
|
|
switch (args[i].TrimStart("-".ToCharArray())) { |
|
case "s": |
|
case "source": |
|
string sourceFilename = args[++i]; |
|
Source = new StreamReader(sourceFilename); |
|
Name = Name.Length > 0 ? Name : sourceFilename; |
|
break; |
|
|
|
case "batch": |
|
Batch = true; |
|
break; |
|
|
|
case "old-source": |
|
SourceOld = new StreamReader(args[++i]); |
|
break; |
|
case "new-source": |
|
string newSourceFilename = args[++i]; |
|
SourceNew = new StreamReader(newSourceFilename); |
|
Name = Name.Length > 0 ? Name : newSourceFilename; |
|
break; |
|
|
|
case "tags": |
|
Tags = Regex.Split(args[++i], @",\s*"); |
|
break; |
|
|
|
case "n": |
|
case "name": |
|
Name = args[++i]; |
|
break; |
|
|
|
case "index": |
|
SearchIndexFilepath = args[++i]; |
|
break; |
|
|
|
case "limit": |
|
ResultsLimit = int.Parse(args[++i]); |
|
break; |
|
|
|
case "offset": |
|
ResultsOffset = int.Parse(args[++i]); |
|
break; |
|
|
|
case "query": |
|
Query = args[++i]; |
|
break; |
|
|
|
case "format": |
|
OutputMode = (OutputModes)Enum.Parse(typeof(OutputModes), args[++i], true); |
|
break; |
|
|
|
case "help": |
|
return HandleHelp(); |
|
|
|
default: |
|
Console.Error.WriteLine($"Error: Unknown property {args[i]}."); |
|
return 1; |
|
} |
|
} |
|
if (Extras.Count < 1) return HandleHelp(); |
|
string modeText = Extras.First().Replace("context", "generatecontext"); Extras.RemoveAt(0); |
|
Mode = (OperatingModes)Enum.Parse(typeof(OperatingModes), modeText, true); |
|
|
|
switch (Mode) { |
|
case OperatingModes.Index: return HandleIndex(); |
|
case OperatingModes.Add: return HandleAdd(); |
|
case OperatingModes.Remove: return HandleRemove(); |
|
case OperatingModes.Query: return HandleQuery(); |
|
case OperatingModes.GenerateContext: return HandleContextGeneration(); |
|
default: |
|
Console.Error.WriteLine($"Error: Don't know how to handle mode {Mode}."); |
|
return 128; |
|
} |
|
} |
|
|
|
private static int HandleHelp() |
|
{ |
|
Console.WriteLine(EmbeddedFiles.ReadAllText("SearchBoxCLI.EmbeddedFiles.Help.txt")); |
|
return 1; |
|
} |
|
|
|
private static int HandleAdd() |
|
{ |
|
if (Name == string.Empty && !Batch) { |
|
Console.Error.WriteLine("Error: The document name must be specified when reading from stdin!"); |
|
return 1; |
|
} |
|
if (SearchIndexFilepath == string.Empty) { |
|
Console.Error.WriteLine("Error: No search index file path specified."); |
|
return 1; |
|
} |
|
|
|
// -------------------------------------- |
|
|
|
SearchBox searchBox; |
|
if (!File.Exists(SearchIndexFilepath)) |
|
searchBox = new SearchBox(); |
|
else |
|
searchBox = JsonConvert.DeserializeObject<SearchBox>(File.ReadAllText(SearchIndexFilepath)); |
|
|
|
|
|
if (!Batch) |
|
searchBox.AddDocument(Name, Tags, Source.ReadToEnd()); |
|
else { |
|
try |
|
{ |
|
Parallel.ForEach(LineIterator.GetLines(Source), (string nextLine) => { |
|
string[] parts = nextLine.Split('|'); |
|
if (parts[0].Trim().Length == 0) |
|
return; |
|
|
|
searchBox.AddDocument( |
|
parts[1].Trim(), |
|
Regex.Split(parts[2], @",\s*"), |
|
File.ReadAllText(parts[0].Trim()) |
|
); |
|
Console.Error.WriteLine($"[Searchbox] [add] {parts[0].Trim()}"); |
|
}); |
|
} catch (FileNotFoundException error) { |
|
Console.Error.WriteLine(error.Message); |
|
return 1; |
|
} |
|
} |
|
|
|
File.WriteAllText(SearchIndexFilepath, JsonConvert.SerializeObject(searchBox)); |
|
|
|
Console.Error.WriteLine($"[Searchbox] [save] {Name} -> {SearchIndexFilepath}"); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
|
|
private static int HandleRemove() |
|
{ |
|
if (string.IsNullOrEmpty(Name)) { |
|
Console.Error.WriteLine("Error: The document name must be specified when removing a document!"); |
|
return 1; |
|
} |
|
|
|
// -------------------------------------- |
|
|
|
SearchBox searchBox = JsonConvert.DeserializeObject<SearchBox>( |
|
File.ReadAllText(SearchIndexFilepath) |
|
); |
|
|
|
searchBox.RemoveDocument(Name); |
|
|
|
|
|
File.WriteAllText(SearchIndexFilepath, JsonConvert.SerializeObject(searchBox)); |
|
|
|
Console.Error.WriteLine($"[Searchbox] [remove] {Name} <- {SearchIndexFilepath}"); |
|
|
|
return 0; |
|
} |
|
|
|
private static int HandleQuery() |
|
{ |
|
if (string.IsNullOrEmpty(Query)) { |
|
Console.Error.WriteLine("Error: No query specified!"); |
|
return 1; |
|
} |
|
if (SearchIndexFilepath == string.Empty) { |
|
Console.Error.WriteLine("Error: No search index file path specified."); |
|
return 1; |
|
} |
|
|
|
// Use the first line of stdin instead of the actual query string if "-" is specified |
|
if (Query == "-") { |
|
Query = Console.ReadLine().Trim(); |
|
} |
|
|
|
SearchBox searchBox = JsonConvert.DeserializeObject<SearchBox>( |
|
File.ReadAllText(SearchIndexFilepath) |
|
); |
|
|
|
IEnumerable<SearchResult> resultsRaw = searchBox.Query(Query, new QuerySettings()).Skip(ResultsOffset); |
|
List<SearchResult> results = new List<SearchResult>( |
|
ResultsLimit > 0 ? resultsRaw.Take(ResultsLimit) : resultsRaw |
|
); |
|
|
|
switch (OutputMode) |
|
{ |
|
case OutputModes.Json: |
|
Console.WriteLine(JsonConvert.SerializeObject(results)); |
|
break; |
|
case OutputModes.Text: |
|
int i = 0; |
|
foreach (SearchResult nextResult in results) { |
|
Console.WriteLine($"#{i}: {nextResult}"); |
|
i++; |
|
} |
|
break; |
|
} |
|
return 0; |
|
} |
|
|
|
private static int HandleContextGeneration() |
|
{ |
|
if (string.IsNullOrEmpty(Name)) { |
|
Console.Error.WriteLine("Error: No document name specified."); |
|
return 1; |
|
} |
|
if (string.IsNullOrEmpty(Query)) { |
|
Console.Error.WriteLine("Error: No query specified."); |
|
return 1; |
|
} |
|
if (SearchIndexFilepath == string.Empty) { |
|
Console.Error.WriteLine("Error: No search index file path specified."); |
|
return 1; |
|
} |
|
|
|
SearchBox searchBox = JsonConvert.DeserializeObject<SearchBox>( |
|
File.ReadAllText(SearchIndexFilepath) |
|
); |
|
|
|
ContextSettings generationSettings = new ContextSettings(); |
|
switch (OutputMode) { |
|
case OutputModes.Json: |
|
Console.Error.WriteLine("Error: JSON output for context generation is not supported."); |
|
return 1; |
|
case OutputModes.Html: |
|
generationSettings.Html = true; |
|
break; |
|
case OutputModes.Text: |
|
generationSettings.Html = false; |
|
break; |
|
} |
|
|
|
Console.WriteLine(searchBox.GenerateContext(Name, Source.ReadToEnd(), Query, generationSettings)); |
|
|
|
return 0; |
|
} |
|
|
|
private static int HandleIndex() |
|
{ |
|
Index index = new Index(Source.ReadToEnd()); |
|
switch (OutputMode) |
|
{ |
|
case OutputModes.Json: |
|
Console.WriteLine(JsonConvert.SerializeObject(index)); |
|
break; |
|
case OutputModes.Text: |
|
Console.WriteLine(index); |
|
break; |
|
} |
|
|
|
return 0; |
|
} |
|
} |
|
}
|
|
|