diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f50af21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ + +/src/dshell/obj +/src/dshell/bin +/src/_ReSharper.dshell.vs2010 +/src/packages \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a90f30 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Deveel Shell Application (dshell) +================================= + +This library provides an easy way to build .NET/Mono console-driven applications, handling complex inputs, defining custom commands, text auto-completion, user sessions, etc. \ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..a411abb --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,3 @@ + +*.user +*.suo \ No newline at end of file diff --git a/src/dshell.vs2010.sln b/src/dshell.vs2010.sln new file mode 100644 index 0000000..62e3836 --- /dev/null +++ b/src/dshell.vs2010.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dshell.vs2010", "dshell\dshell.vs2010.csproj", "{5E849796-A39E-4AF8-A516-D2837C3CCE6F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E849796-A39E-4AF8-A516-D2837C3CCE6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E849796-A39E-4AF8-A516-D2837C3CCE6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E849796-A39E-4AF8-A516-D2837C3CCE6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E849796-A39E-4AF8-A516-D2837C3CCE6F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/dshell/.gitignore b/src/dshell/.gitignore new file mode 100644 index 0000000..4fb7be5 --- /dev/null +++ b/src/dshell/.gitignore @@ -0,0 +1,2 @@ + +*.user \ No newline at end of file diff --git a/src/dshell/Deveel.Collections/SortedMatchEnumerator.cs b/src/dshell/Deveel.Collections/SortedMatchEnumerator.cs new file mode 100644 index 0000000..8e08c04 --- /dev/null +++ b/src/dshell/Deveel.Collections/SortedMatchEnumerator.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Deveel.Collections { + /// + /// An enumerator returning end-truncated + /// matching values from a sorted list. + /// + /// + /// This IEnumerator is initialized with a sorted ISet, sorted + /// IDictionary or another IEnumerator that must be placed at the + /// beginning of the matching area of a sorted set. + /// + /// This IEnumerator is commonly used for TAB-completion. + /// + /// + public class SortedMatchEnumerator : IEnumerator { + #region ctor + public SortedMatchEnumerator(string partialMatch, IEnumerator en) { + this.partialMatch = partialMatch; + this.en = en; + } + + public SortedMatchEnumerator(string partialMatch, ICollection c, IComparer comparer) + : this(partialMatch, SubsetCollection.Tail(c, comparer, partialMatch).GetEnumerator()) { + } + + public SortedMatchEnumerator(string partialMatch, SortedList list) + : this(partialMatch, SubsetCollection.Tail(list, partialMatch).GetEnumerator()) { + } + + public SortedMatchEnumerator(string partialMatch, SortedDictionary dictionary) + : this(partialMatch, SubsetDictionary.Tail(dictionary, partialMatch).Keys.GetEnumerator()) { + } + #endregion + + #region Fields + private readonly IEnumerator en; + private readonly string partialMatch; + + private string prefix; + private string suffix; + + // the current match + private string current; + #endregion + + #region Properties + public string Prefix { + get { return prefix; } + set { prefix = value; } + } + + public string Suffix { + get { return suffix; } + set { suffix = value; } + } + + public string Current { + get { + string result = current; + if (prefix != null) + result = prefix + result; + if (suffix != null) + result = result + suffix; + return result; + } + } + + object IEnumerator.Current { + get { return Current; } + } + + #endregion + + #region Protected Methods + protected virtual bool Exclude(string current) { + return false; + } + #endregion + + #region Public Methods + public bool MoveNext() { + while (en.MoveNext()) { + current = (string)en.Current; + if (current.Length == 0) + continue; + if (!current.StartsWith(partialMatch)) + return false; + if (Exclude(current)) + continue; + return true; + } + return false; + } + + public void Reset() { + current = null; + en.Reset(); + } + + public void Dispose() { + } + + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Collections/SubsetDictionary.cs b/src/dshell/Deveel.Collections/SubsetDictionary.cs new file mode 100644 index 0000000..a24c5a8 --- /dev/null +++ b/src/dshell/Deveel.Collections/SubsetDictionary.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Collections { + public sealed class SubsetDictionary : IDictionary { + private SortedDictionary subset; + + private static TKey NullKey = default(TKey); + + public SubsetDictionary(SortedDictionary dictionary, TKey startKey, TKey endKey) { + subset = new SortedDictionary(dictionary.Comparer); + foreach(KeyValuePair pair in dictionary) { + int c1 = 0; + if (!ReferenceEquals(startKey, NullKey)) + c1 = dictionary.Comparer.Compare(pair.Key, startKey); + int c2 = 0; + if (!ReferenceEquals(endKey, NullKey)) + c2 = dictionary.Comparer.Compare(pair.Key, endKey); + if (c1 >= 0 && c2 <= 0) + subset.Add(pair.Key, pair.Value); + } + } + + public TValue this[TKey key] { + get { return subset[key]; } + } + + TValue IDictionary.this[TKey key] { + get { return this[key]; } + set { throw new NotSupportedException(); } + } + + public ICollection Keys { + get { return subset.Keys; } + } + + public ICollection Values { + get { return subset.Values; } + } + + public int Count { + get { return subset.Count; } + } + + public bool IsReadOnly { + get { return true;} + } + + public bool ContainsKey(TKey key) { + return subset.ContainsKey(key); + } + + void IDictionary.Add(TKey key, TValue value) { + throw new NotSupportedException(); + } + + bool IDictionary.Remove(TKey key) { + throw new NotSupportedException(); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return subset.TryGetValue(key, out value); + } + + void ICollection>.Add(KeyValuePair item) { + throw new NotSupportedException(); + } + + void ICollection>.Clear() { + throw new NotSupportedException(); + } + + public bool Contains(KeyValuePair item) { + return ((ICollection>) subset).Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) { + subset.CopyTo(array, arrayIndex); + } + + bool ICollection>.Remove(KeyValuePair item) { + throw new NotSupportedException(); + } + + public IEnumerator> GetEnumerator() + { + return subset.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + public static SubsetDictionary Tail(SortedDictionary dictionary, TKey startKey) { + return new SubsetDictionary(dictionary, startKey, NullKey); + } + + public static SubsetDictionary Head(SortedDictionary dictionary, TKey endKey) { + return new SubsetDictionary(dictionary, NullKey, endKey); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Collections/SubsetList.cs b/src/dshell/Deveel.Collections/SubsetList.cs new file mode 100644 index 0000000..89ba7d3 --- /dev/null +++ b/src/dshell/Deveel.Collections/SubsetList.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Deveel.Collections { + public sealed class SubsetCollection : ICollection { + private readonly SortedList subset; + + + public int Count { + get { return subset.Count; } + } + + public bool IsReadOnly { + get { return true; } + } + + void ICollection.Add(T item) { + throw new NotSupportedException(); + } + + void ICollection.Clear() { + throw new NotSupportedException(); + } + + public bool Contains(T item) { + return subset.ContainsKey(item); + } + + public void CopyTo(T[] array, int arrayIndex) { + subset.Keys.CopyTo(array, arrayIndex); + } + + bool ICollection.Remove(T item) { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() { + return subset.Keys.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + public static readonly T NullKey = default(T); + + public SubsetCollection(SortedList list, T startKey, T endKey) { + subset = new SortedList(); + foreach(KeyValuePair pair in list) { + int c1 = 0; + if (!ReferenceEquals(startKey, NullKey)) + c1 = list.Comparer.Compare(pair.Key, startKey); + int c2 = 0; + if (!ReferenceEquals(endKey, NullKey)) + c2 = list.Comparer.Compare(pair.Key, endKey); + if (c1 >= 0 && c2 <= 0) + subset.Add(pair.Key, pair.Key); + } + + } + + public SubsetCollection(ICollection list, IComparer comparer, T startKey, T endKey) { + subset = new SortedList(); + foreach(T value in list) { + int c1 = 0; + if (!ReferenceEquals(startKey, NullKey)) + c1 = comparer.Compare(value, startKey); + int c2 = 0; + if (!ReferenceEquals(endKey, NullKey)) + c2 = comparer.Compare(value, endKey); + if (c1 >= 0 && c2 <= 0) + subset.Add(value, value); + } + + } + + public static SubsetCollection Tail(SortedList list, T startKey) { + return new SubsetCollection(list, startKey, NullKey); + } + + public static SubsetCollection Tail(ICollection c, IComparer comparer, T startKey) { + return new SubsetCollection(c, comparer, startKey, NullKey); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/AliasCommand.cs b/src/dshell/Deveel.Console.Commands/AliasCommand.cs new file mode 100644 index 0000000..81aee21 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/AliasCommand.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + sealed class AliasCommand : Command { + public override string Name { + get { return "alias"; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + string alias = args.Current; + // no quoted aliases.. + if (alias.StartsWith("\"") || alias.StartsWith("'")) + return CommandResultCode.SyntaxError; + + // unless we override an alias, moan, if this command already + // exists. + if (!Application.Commands.Aliases.HasAlias(alias) && + Application.Commands.ContainsCommand(alias)) { + Error.WriteLine("cannot alias built-in command!"); + return CommandResultCode.ExecutionFailed; + } + + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + String value = StripQuotes(args.Current); // rest of values. + Application.Commands.Aliases.AddAlias(alias, value); + return CommandResultCode.Success; + } + + + private static String StripQuotes(String value) { + if (value.StartsWith("\"") && value.EndsWith("\"")) { + value = value.Substring(1, value.Length - 2); + } else if (value.StartsWith("\'") && value.EndsWith("\'")) { + value = value.Substring(1, value.Length - 2); + } + return value; + } + + public override IEnumerator Complete(CommandDispatcher disp, string partialCommand, string lastWord) { + return Application.Commands.Aliases.Complete(partialCommand, lastWord); + } + + public override String LongDescription { + get { + return "Add an alias for a command. This means, that you can\n" + + "give a short name for a command you often use. This\n" + + "might be as simple as\n" + + " alias ls tables\n" + + "to execute the tables command with a short 'ls'.\n\n" + + "For longer commands it is even more helpful:\n" + + " alias size select count(*) from\n" + + "This command needs a table name as a parameter to\n" + + "expand to a complete command. So 'size students'\n" + + "expands to 'select count(*) from students' and yields\n" + + "the expected result.\n\n" + + "To make life easier, the application tries to determine\n" + + "the command to be executed so that the tab-completion\n" + + "works even here; in this latter case it would help\n" + + "complete table names."; + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/AliasesCommand.cs b/src/dshell/Deveel.Console.Commands/AliasesCommand.cs new file mode 100644 index 0000000..614dcfe --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/AliasesCommand.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + internal class AliasesCommand : Command { + private static readonly ColumnDesign[] Columns; + + static AliasesCommand() { + Columns = new ColumnDesign[2]; + Columns[0] = new ColumnDesign("Alias"); + Columns[1] = new ColumnDesign("Execute Command"); + } + + public override string Name { + get { return "aliases"; } + } + + public override string GroupName { + get { return "aliases"; } + } + + public override string ShortDescription { + get { return "lists all the aliases"; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + Columns[0].ResetWidth(); + Columns[1].ResetWidth(); + TableRenderer table = new TableRenderer(Columns, Out); + foreach(KeyValuePair alias in Application.Commands.Aliases) { + ColumnValue[] row = new ColumnValue[2]; + row[0] = new ColumnValue(alias.Key); + row[1] = new ColumnValue(alias.Value); + table.AddRow(row); + } + table.CloseTable(); + return CommandResultCode.Success; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/Command.cs b/src/dshell/Deveel.Console.Commands/Command.cs new file mode 100644 index 0000000..8e1f367 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/Command.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; + +using Deveel.Configuration; + +namespace Deveel.Console.Commands { + /// + /// Represents a user level command. + /// + /// + /// This interface needs to be implemented by commands or plugins that + /// should be supported by Minosse. + /// + public abstract class Command : IExecutable, IOptionsHandler { + #region Fields + + private IApplicationContext application; + + #endregion + + #region Properties + + public IApplicationContext Application { + get { return application; } + } + + protected OutputDevice Out { + get { return Application.Out; } + } + + protected OutputDevice Error { + get { return Application.Error; } + } + + public virtual string GroupName { + get { return null; } + } + + public virtual string [] Aliases { + get { return new string[0]; } + } + + public bool HasAliases { + get { + string[] aliases = Aliases; + return aliases != null && aliases.Length > 0; + } + } + + public abstract string Name { get; } + + + internal void Init() { + OnInit(); + } + + /// + /// Gets wheter or not the commands supported by this + /// should not be part of the toplevel command completion. + /// + /// + /// If the user presses TAB on an empty string to get the full + /// list of possible commands, this command should not show up. + /// In Minosse, this returns false for the SQL-commands + /// (select, update, drop ..), since this would + /// clobber the toplevel list of available commands. + /// If unsure, returns true. + /// + public virtual bool CommandCompletion { + get { return true; } + } + + /// + /// Returns a short string describing the purpose of the commands + /// handled by this Command-implementation. + /// + /// + /// This is the string listed in the bare 'help' overview. Should + /// contain no newline, no leading spaces. + /// + public virtual string ShortDescription { + get { return null; } + } + + #endregion + + + #region Public Methods + + /// + /// Executes the given command. + /// + /// The session this command is executed from. + /// The rest parameters following the command. + /// + /// The command is given completely without the final delimiter + /// (which would be newline or semicolon). Before this method is + /// called, the checks with the + /// method, if this command is + /// complete. + /// + /// + /// Returns a code indicating the + /// result of the command execution. + /// + public abstract CommandResultCode Execute(IExecutionContext context, CommandArguments args); + + /// + /// Returns a list of strings that are possible at this stage. + /// + /// The that + /// can be used to access other values through it. + /// The command typed so far. + /// The last word returned by readline. + /// + /// Used for the readline-completion in interactive mode. Based on + /// the partial command and the you have + /// to determine the words that are available at this stage. Returns + /// null, if you don't know a possible completion. + /// + /// + /// + public virtual IEnumerator Complete(CommandDispatcher dispatcher, string partialCommand, string lastWord) { + return null; + } + + /// + /// Checks wether the command is complete or not. + /// + /// The partial command read so far given to + /// decide by the command whether it is complete or not. + /// + /// This method is called, whenever the input encounters a newline or + /// a semicolon to decide if this separator is to separate different + /// commands or if it is part of the command itself. + /// + /// The delimiter (newline or semicolon) is contained (at the end) + /// in the string passed to this method. This method returns false, + /// if the delimiter is part of the command and will not be regarded + /// as delimiter between commands -- the reading part of the command + /// dispatcher will go on reading characters and not execute the + /// command. + /// + /// This method will return true for most simple commands like help. + /// For commands that have a more complicated syntax, this might not + /// be true. + /// + /// + /// + /// select * from foobar is not complete after a return, + /// since we can expect a where clause. If it has a semicolon at + /// the end, we know, that is is complete. So newline is not + /// a delimiter while ; is (return command.EndsWith(";")). + /// + /// + /// definitions of stored procedures are even more complicated: + /// it depends on the syntax whether a semicolon is part of the + /// command or can be regarded as delimiter. Here, neither ; + /// nor newline can be regarded as delimiter per-se. Only the + /// implementation can decide upon this. + /// In Minosse, a single / on one line is used to denote + /// this command-complete. + /// + /// + /// Note, this method should only apply a very lazy syntax check so + /// it does not get confused and uses too much cycles unecessarily. + /// + /// + public virtual bool IsComplete(string command) { + return true; + } + + /// + /// Check wheter or not this command requires a valid context. + /// + public virtual bool RequiresContext { + get { return false; } + } + + /// + /// Called when the command is initialized and registered + /// into the application context. + /// + protected virtual void OnInit() { + } + + /// + /// Alerts the method that the application is closing. + /// + /// + /// This is called on exit of the + /// and allows you to do some cleanup (close connections, flush files..). + /// + public virtual void OnShutdown() { + } + + /// + /// Returns the synopsis string for the command. + /// + /// + /// The synopsis string returned should follow the following conventions: + /// + /// expected parameters are described with angle brackets + /// like in export-xml <table> <filename> + /// optional parameters are described with square brackets + /// like in help [command] + /// + /// + /// The string returned should contain no newline, no leading spaces. + /// This synopsis is printed in the detailed help of a command or if + /// the method returned a . + /// + /// + /// + public virtual string[] Synopsis { + get { return new string[] {Name}; } + } + + /// + /// Gets a full description string of the command. + /// + /// + /// The returned description should start with a TAB-character + /// in each new line (the first line is a new line). The last + /// line should not end with newline. + /// + public virtual string LongDescription { + get { return null; } + } + + public bool HasGroup { + get { + string groupName = GroupName; + return (groupName != null && groupName.Length > 0); + } + } + + /// + /// Registers the command specific command-line options. + /// + /// The command-line options to register. + /// + /// This method is called just before the command-line parser + /// is executed to set the command-specific configurations. + /// + public virtual void RegisterOptions(Options options) { + } + + /// + /// Handles the parsed command-line configurations of the command. + /// + /// The object containing the configurations + /// parsed from the command-line. + /// + /// This method is called after the parse of the command-line + /// configurations is done to handle the values of the options + /// set on the method. + /// + /// + /// Returns true if the command handled the command line + /// arguments and was executed, otherwise it returns false. + /// + public virtual bool HandleCommandLine(CommandLine commandLine) { + return false; + } + + #endregion + + internal virtual void SetApplicationContext(IApplicationContext app) { + application = app; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandAliasAttribute.cs b/src/dshell/Deveel.Console.Commands/CommandAliasAttribute.cs new file mode 100644 index 0000000..ea366df --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandAliasAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace Deveel.Console.Commands { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class CommandAliasAttribute : Attribute, ICommandAttribute { + private readonly string alias; + + public CommandAliasAttribute(string @alias) { + this.alias = alias; + } + + public string Alias { + get { return alias; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandAliases.cs b/src/dshell/Deveel.Console.Commands/CommandAliases.cs new file mode 100644 index 0000000..b24b174 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandAliases.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +using Deveel.Collections; + +namespace Deveel.Console.Commands { + public sealed class CommandAliases : IEnumerable> { + internal CommandAliases(CommandDispatcher dispatcher) { + aliases = new SortedDictionary(); + dispatcher = dispatcher; + currentExecutedAliases = new List(); + } + + private readonly SortedDictionary aliases; + private readonly CommandDispatcher dispatcher; + private bool dirty; + // to determine, if we got a recursion: one alias calls another + // alias which in turn calls the first one .. + private readonly List currentExecutedAliases; + private ConfigurationFile confFile; + + internal CommandDispatcher Dispatcher { + get { return dispatcher; } + } + + internal void AddAlias(string alias, string command) { + aliases.Add(alias, command); + dispatcher.RegisterAdditionalCommand(alias, new AliasedCommand(this, alias)); + dirty = true; + } + + internal void RemoveAlias(string alias) { + aliases.Remove(alias); + dispatcher.UnregisterAdditionalCommand(alias); + dirty = true; + } + + internal IEnumerator Complete(string partialCommand, string lastWord) { + string[] st = partialCommand.Split(' '); + String cmd = (String)st[0]; + int argc = st.Length; + + // 'aliases' command gets no names. + string[] commandNames1 = dispatcher.GetCommandNames(typeof(AliasesCommand)); + if (Array.BinarySearch(commandNames1, cmd) >= 0) + return null; + + // some completion within the alias/unalias commands. + commandNames1 = dispatcher.GetCommandNames(typeof(AliasCommand)); + string[] commandNames2 = dispatcher.GetCommandNames(typeof(UnaliasCommand)); + if (Array.BinarySearch(commandNames1, cmd) >= 0 || + Array.BinarySearch(commandNames2, cmd) >= 0) { + List alreadyGiven = new List(); + + if (Array.BinarySearch(commandNames1, cmd) >= 0) { + // do not complete beyond first word. + if (argc > ("".Equals(lastWord) ? 0 : 1)) { + return null; + } + } else { + /* + * remember all aliases, that have already been given on + * the commandline and exclude from completion.. + * cool, isn't it ? + */ + for (int i = 1; i < st.Length; i++) { + alreadyGiven.Add(st[i]); + } + } + + // ok, now return the list. + return new AliasSortedMatchEnumerator(alreadyGiven, lastWord, aliases); + } + + /* ok, someone tries to complete something that is a command. + * try to find the actual command and ask that command to do + * the completion. + */ + String toExecute = (String)aliases[cmd]; + if (toExecute != null) { + Command c = dispatcher.GetCommand(toExecute); + if (c != null) { + int i = 0; + String param = partialCommand; + while (param.Length < i + && Char.IsWhiteSpace(param[i])) { + ++i; + } + while (param.Length < i + && !Char.IsWhiteSpace(param[i])) { + ++i; + } + return c.Complete(dispatcher, toExecute + param.Substring(i), lastWord); + } + } + + return null; + } + + private class AliasSortedMatchEnumerator : SortedMatchEnumerator { + public AliasSortedMatchEnumerator(List alreadyGiven, string lastWord, SortedDictionary aliases) + : base(lastWord, aliases) { + this.alreadyGiven = alreadyGiven; + } + + private readonly List alreadyGiven; + + protected override bool Exclude(String current) { + return alreadyGiven.Contains(current); + } + } + + #region AliasedCommand + + private class AliasedCommand : Command { + public AliasedCommand(CommandAliases command, string name) { + parent = command; + this.name = name; + } + + private readonly CommandAliases parent; + private readonly string name; + + public override string Name { + get { return name; } + } + + public override string LongDescription { + get { + string dsc = String.Empty; + // not session-proof: + if (parent.currentExecutedAliases.Contains(name)) { + dsc = "\t[ this command cyclicly references itself ]"; + } else { + parent.currentExecutedAliases.Add(name); + dsc = "\tThis is an alias for the command\n" + "\t " + parent.aliases[name]; + + String actualCmdStr = (String)parent.aliases[name]; + if (actualCmdStr != null) { + string[] st = actualCmdStr.Split(' '); + actualCmdStr = st[0].Trim(); + Command c = parent.dispatcher.GetCommand(actualCmdStr); + String longDesc = null; + if (c != null && (longDesc = c.LongDescription) != null) { + dsc += "\n\n\t..the following description could be determined for this"; + dsc += "\n\t------- [" + actualCmdStr + "] ---\n"; + dsc += longDesc; + } + parent.currentExecutedAliases.Clear(); + } + } + + return dsc; + } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + String toExecute = (String)parent.aliases[name]; + + if (toExecute == null) { + return CommandResultCode.ExecutionFailed; + } + // not session-proof: + if (parent.currentExecutedAliases.Contains(name)) { + parent.Dispatcher.Application.Error.WriteLine("Recursive call to aliases [" + name + "]. Stopping this senseless venture."); + parent.currentExecutedAliases.Clear(); + return CommandResultCode.ExecutionFailed; + } + string commandText = args.ToString(); + commandText = toExecute + " " + commandText; + parent.Dispatcher.Application.Error.WriteLine("execute alias: " + commandText); + parent.currentExecutedAliases.Add(name); + parent.Dispatcher.ExecuteCommand(context, commandText); + parent.currentExecutedAliases.Clear(); + return CommandResultCode.Success; + } + } + + #endregion + + public bool HasAlias(string alias) { + return aliases.ContainsKey(alias); + } + + public bool IsAliasOf(string alias, string commandName) { + string cname; + if (!aliases.TryGetValue(alias, out cname)) + return false; + + return cname.Equals(commandName); + } + + public string[] GetCommandAliases(string commandName) { + List commandAliases = new List(); + foreach(KeyValuePair alias in aliases) { + if (alias.Value == commandName) { + commandAliases.Add(alias.Key); + } + } + + return commandAliases.ToArray(); + } + + internal void LoadFromFile(ConfigurationFile file) { + aliases.Clear(); + + foreach(KeyValuePair property in file.Properties) { + aliases.Add(property.Key, property.Value); + } + confFile = file; + } + + internal void Save() { + if (confFile != null && dirty) { + confFile.ClearValues(); + foreach(KeyValuePair alias in aliases) { + confFile.SetValue(alias.Key, alias.Value); + } + confFile.Save("Aliases"); + dirty = false; + } + } + + public IEnumerator> GetEnumerator() { + return aliases.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandArguments.cs b/src/dshell/Deveel.Console.Commands/CommandArguments.cs new file mode 100644 index 0000000..eeefe34 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandArguments.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + public sealed class CommandArguments : IEnumerator { + private int index; + private readonly string[] args; + private int length; + + internal CommandArguments(string[] args) { + this.args = args; + length = args.Length; + index = -1; + } + + public int Count { + get { return length; } + } + + public string Current { + get { return args[index]; } + } + + public int CurrentIndex { + get { return index; } + } + + object IEnumerator.Current { + get { return Current; } + } + + void IDisposable.Dispose() { + } + + public bool MoveNext() { + return ++index < length; + } + + public bool MoveBack() { + return --index > 0; + } + + public bool MoveTo(int offset) { + int moveIndex = index + offset; + if (moveIndex < 0 || moveIndex >= length) + return false; + + index = moveIndex; + return true; + } + + public void Reset() { + index = -1; + length = args.Length; + } + + public string Peek(int offset) { + int peekIndex = index + offset; + if (peekIndex >= length || peekIndex < 0) + return null; + + return args[index + offset]; + } + + public override string ToString() { + return String.Join(" ", args); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandAttribute.cs b/src/dshell/Deveel.Console.Commands/CommandAttribute.cs new file mode 100644 index 0000000..61d1988 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandAttribute.cs @@ -0,0 +1,31 @@ +using System; + +namespace Deveel.Console.Commands { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class CommandAttribute : Attribute, ICommandAttribute { + public CommandAttribute(string name) { + if (name == null) + throw new ArgumentNullException("name"); + + this.name = name; + } + + private readonly string name; + private bool requiresContext; + private string shortDescription; + + public string CommandName { + get { return name; } + } + + public bool RequiresContext { + get { return requiresContext; } + set { requiresContext = value; } + } + + public string ShortDescription { + get { return shortDescription; } + set { shortDescription = value; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandCompletionAttribute.cs b/src/dshell/Deveel.Console.Commands/CommandCompletionAttribute.cs new file mode 100644 index 0000000..8e9f12a --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandCompletionAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace Deveel.Console.Commands { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class CommandCompletionAttribute : Attribute, ICommandAttribute { + public CommandCompletionAttribute(bool completeCommand) { + this.completeCommand = completeCommand; + } + + private readonly bool completeCommand; + + public bool CompleteCommand { + get { return completeCommand; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandDescriptionAttribute.cs b/src/dshell/Deveel.Console.Commands/CommandDescriptionAttribute.cs new file mode 100644 index 0000000..5e1fcc4 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandDescriptionAttribute.cs @@ -0,0 +1,27 @@ +using System; + +namespace Deveel.Console.Commands { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class CommandDesctiprionAttribute : Attribute, ICommandAttribute { + public CommandDesctiprionAttribute(string value, DescriptionSource source) { + this.value = value; + this.source = source; + } + + public CommandDesctiprionAttribute(string value) + : this(value, DescriptionSource.Direct) { + } + + private readonly string value; + private DescriptionSource source; + + public DescriptionSource Source { + get { return source; } + set { source = value; } + } + + public string Value { + get { return value; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandDispatcher.cs b/src/dshell/Deveel.Console.Commands/CommandDispatcher.cs new file mode 100644 index 0000000..96dbe2a --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandDispatcher.cs @@ -0,0 +1,543 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +using Deveel.Collections; +using Deveel.Configuration; + +namespace Deveel.Console.Commands { + public sealed class CommandDispatcher { + #region ctor + + public CommandDispatcher(IApplicationContext application) { + this.application = application; + // aliases = new CommandAliases(this); + commandMap = new SortedDictionary(); + commands = new List(); + batchCount = 0; + + Readline.TabComplete += OnTabComplete; + } + + #endregion + + #region Fields + private readonly IApplicationContext application; + // private readonly CommandAliases aliases; + private readonly List commands; // commands in seq. of addition. + private List regCommands; + private readonly SortedDictionary commandMap; + private int batchCount; + private char commandSeparator; + + #endregion + + #region Events + + /// + /// An event fired during the execution of a command in the + /// method . + /// + public event CommandEventHandler CommandExecuting; + + /// + /// An event fired after the execution of a command in the + /// method . + /// + public event CommandEventHandler CommandExecuted; + + #endregion + + #region Properties + + public bool IsInBatch { + get { return batchCount > 0; } + } + + public ICollection RegisteredCommands { + get { + if (regCommands == null) { + regCommands = new List(); + foreach (CommandInfo commandInfo in commands) + regCommands.Add(commandInfo.Command); + } + return regCommands; + } + } + + public ICollection RegisteredCommandNames { + get { return commandMap.Keys; } + } + + public IApplicationContext Application { + get { return application; } + } + + public char CommandSeparator { + get { return commandSeparator; } + set { commandSeparator = value; } + } + + /* + public CommandAliases Aliases { + get { return aliases; } + } + */ + + #endregion + + #region Private Methods + + private IEnumerator possibleValues; + private String variablePrefix; + + private void OnTabComplete(object sender, TabCompleteEventArgs e) { + try { + string completeCommandString = application.PartialLine.Trim(); + bool variableExpansion = false; + + // ok, do we have a variable expansion ? + int pos = e.Text.Length - 1; + while (pos > 0 && (e.Text[pos] != '$') && + Char.IsLetter(e.Text[pos])) { + --pos; + } + // either $... or ${... + if ((pos >= 0 && e.Text[pos] == '$')) { + variableExpansion = true; + } else if ((pos >= 1) && e.Text[pos - 1] == '$' && + e.Text[pos] == '{') { + variableExpansion = true; + --pos; + } + + if (variableExpansion) { + if (application is ISettingsHandler) { + ApplicationSettings settings = ((ISettingsHandler) application).Settings; + if (e.State == 0) { + variablePrefix = e.Text.Substring(0, pos); + String varname = e.Text.Substring(pos); + possibleValues = settings.CompleteUserVariable(varname); + } + + if (possibleValues.MoveNext()) { + e.Output = variablePrefix + ((String) possibleValues.Current); + } else { + possibleValues.Reset(); + } + } + } + // the first word.. the command. + else if (completeCommandString.Equals(e.Text)) { + string text = e.Text.ToLower(); + if (e.State == 0) { + possibleValues = GetRegisteredCommandNames(text).GetEnumerator(); + } + + while (HasNext(possibleValues)) { + String nextKey = (String) possibleValues.Current; + if (nextKey == null || nextKey.Length == 0) // don't complete the 'empty' thing. + continue; + if (text.Length < 1) { + Command c = (Command) commandMap[nextKey]; + if (!c.CommandCompletion) + continue; + if (c.RequiresContext && + (application.ActiveContext == null || + !application.ActiveContext.IsIsolated)) { + continue; + } + } + if (nextKey.StartsWith(text)) { + e.Output = nextKey; + break; + } + } + } + // .. otherwise get completion from the specific command. + else { + string text = e.Text.ToLower(); + if (e.State == 0) { + Command cmd = GetCommand(completeCommandString); + if (cmd != null) + possibleValues = cmd.Complete(this, completeCommandString, text); + } + + while (HasNext(possibleValues)) { + string key = (string) possibleValues.Current; + if (key.ToLower().StartsWith(text)) { + e.Output = key; + break; + } + } + } + } catch(Exception ex) { + Application.Error.WriteLine("An error occurred while TAB-completing: {0}", ex.Message); + e.Error = true; + possibleValues = null; + throw; + } + } + + private static bool HasNext(IEnumerator en) { + if (en == null) + return false; + bool hasNext = en.MoveNext(); + if (!hasNext) + en.Reset(); + return hasNext; + } + + private void OnExecuting(IExecutionContext session, string commandText) { + if (CommandExecuting != null) + CommandExecuting(session, new CommandEventArgs(commandText)); + } + + private void OnExecuted(IExecutionContext session, string commandText, CommandResultCode resultCode) { + if (CommandExecuted != null) + CommandExecuted(session, new CommandEventArgs(commandText, resultCode)); + } + + private Command GetCommandFromCooked(string completeCmd) { + if (String.IsNullOrEmpty(completeCmd)) + return null; + + Command c; + if (!commandMap.TryGetValue(completeCmd, out c)) + return null; + + return c; + } + #endregion + + internal void RegisterAdditionalCommand(string commandName, Command command) { + commandMap.Add(commandName, command); + } + + internal void UnregisterAdditionalCommand(string commandName) { + commandMap.Remove(commandName); + } + + #region Public Methods + + public void StartBatch() { + ++batchCount; + } + + public void EndBatch() { + --batchCount; + } + + public void Shutdown() { + int i = 0; + while (i < commands.Count) { + CommandInfo cmdInfo = (CommandInfo)commands[i]; + + try { + cmdInfo.Command.OnShutdown(); + } catch (Exception e) { +#if DEBUG + System.Console.Error.Write(e.StackTrace); +#endif + } + i++; + } + } + + public void Register(Command command) { + if (Application.IsRunning) + throw new InvalidOperationException("The application is running."); + + if (command == null) + throw new ArgumentNullException("command"); + + try { + command.Init(); + } catch(Exception e) { + throw new ArgumentException("An error occurred while initializing the command: " + e.Message); + } + + if (command is IInterruptable) + Application.Interruption.Push(command as IInterruptable); + + if (command.Application != null && + command.Application != application) + throw new ArgumentException("The command instance is already registered by another application."); + + command.SetApplicationContext(application); + + CommandInfo commandInfo = new CommandInfo(command.GetType(), command); + commands.Add(commandInfo); + string name = commandInfo.Command.Name; + commandMap.Add(name, commandInfo.Command); + if (commandInfo.Command.HasAliases) { + string[] cmdAliases = commandInfo.Command.Aliases; + for (int i = 0; i < cmdAliases.Length; ++i) { + if (commandMap.ContainsKey(cmdAliases[i])) + throw new ArgumentException("attempt to register command '" + cmdAliases[i] + "', that is already used"); + + commandMap.Add(cmdAliases[i], commandInfo.Command); + } + } + } + + public Command Register(Type commandType, params object[] args) { + if (!typeof(Command).IsAssignableFrom(commandType)) + throw new ArgumentException("The type '" + commandType + "' is not assignable from ICommand."); + + Command command; + try { + command = (Command)Activator.CreateInstance(commandType, args); + } catch (Exception e) { + if (e is TargetInvocationException) + e = e.InnerException; + + throw new ArgumentException(e.Message); + } + + Register(command); + return command; + } + + public Command Register(Type commandType) { + return Register(commandType, null); + } + + public void Unregister(Type commandType) { + bool found = false; + string[] removedCommandNames = null; + for (int i = commands.Count - 1; i >= 0; i--) { + CommandInfo ci = commands[i]; + if (ci.CommandType == commandType) { + found = true; + ArrayList list = new ArrayList(); + list.Add(ci.Command.Name); + if (ci.Command.Aliases != null && ci.Command.Aliases.Length > 0) + list.AddRange(ci.Command.Aliases); + + removedCommandNames = (string[]) list.ToArray(typeof(string)); + commands.RemoveAt(i); + break; + } + } + + if (!found) + throw new ApplicationException("Attempt to unregister the command type '" + commandType + "' not registered."); + + if (removedCommandNames.Length > 0) { + for (int i = 0; i < removedCommandNames.Length; i++) + commandMap.Remove(removedCommandNames[i]); + } + } + + public void Unregister(Command command) { + if (command == null) + throw new ArgumentNullException("command"); + + Unregister(command.GetType()); + } + + public bool Rename(string commandName, string newName) { + if (String.IsNullOrEmpty(commandName)) + throw new ArgumentNullException("commandName"); + if (String.IsNullOrEmpty(newName)) + throw new ArgumentNullException("newName"); + + if (commandMap.ContainsKey(newName)) + return false; + + Command command; + if (!commandMap.TryGetValue(commandName, out command)) + return false; + + if (!commandMap.Remove(commandName)) + return false; + + commandMap[newName] = command; + return true; + } + + public void RegisterOptions(Options options) { + foreach(Command command in commandMap.Values) { + command.RegisterOptions(options); + } + } + + public bool ContainsCommand(string commandText) { + return commandMap.ContainsKey(commandText); + } + + public ICollection GetRegisteredCommandNames(string key) { + // return commandMap.TailDictionary(key).Keys; + return SubsetDictionary.Tail(commandMap, key).Keys; + } + + public string CompleteCommandName(string command) { + if (command == null || command.Length == 0) + return null; + + string cmd = command.ToLower(); + string startChar = cmd.Substring(0, 1); + + ICollection commandNames = GetRegisteredCommandNames(startChar); + string longestMatch = null; + + foreach (string testMatch in commandNames) { + if (cmd.StartsWith(testMatch)) { + longestMatch = testMatch; + } else if (!testMatch.StartsWith(startChar)) { + break; // ok, thats it. + } + } + + // ok, fallback: grab the first whitespace delimited part. + if (longestMatch == null) { + string[] tok = command.Split(new char[] { ' ', ';', '\t', '\n', '\r', '\f' }); + if (tok.Length > 0) + return tok[0]; + } + + return longestMatch; + } + + public void ExecuteCommand(IExecutionContext context, string commandText) { + if (String.IsNullOrEmpty(commandText)) + return; + + // remove trailing command separator and whitespaces. + StringBuilder cmdBuilder = new StringBuilder(commandText.Trim()); + int i; + for (i = cmdBuilder.Length - 1; i > 0; --i) { + char c = cmdBuilder[i]; + if (c != CommandSeparator && !Char.IsWhiteSpace(c)) + break; + } + if (i < 0) + return; + + cmdBuilder.Length = i + 1; + string cmd = cmdBuilder.ToString(); + + string cmdName = CompleteCommandName(cmd); + Command command = GetCommandFromCooked(cmdName); + + if (command != null) { + try { + string[] args = new string[0]; + string parameters = cmd.Substring(cmdName.Length); + if (parameters.Length > 0) { + parameters = parameters.Trim(); + args = parameters.Split(' '); + ArrayList argsList = new ArrayList(); + for (int j = 0; j < args.Length; j++) { + args[j] = args[j].Trim(); + if (args[j].Length > 0) + argsList.Add(args[j]); + } + args = (string[]) argsList.ToArray(typeof (string)); + } + + if (command.RequiresContext && (context == null || !context.IsIsolated)) { + Application.Error.WriteLine(cmdName + " requires a valid isolated context."); + return; + } + + OnExecuting(context, commandText); + CommandResultCode result = command.Execute(context, new CommandArguments(args)); + OnExecuted(context, commandText, result); + + switch (result) { + case CommandResultCode.SyntaxError: { + string[] synopsis = command.Synopsis; + if (synopsis != null && synopsis.Length > 0) { + Application.Error.WriteLine(cmdName + " usage: "); + for (int j = 0; j < synopsis.Length; j++) { + Application.Error.Write(" "); + Application.Error.WriteLine(synopsis[j]); + } + } else { + Application.Error.WriteLine(cmdName + " syntax error."); + } + break; + } + case CommandResultCode.ExecutionFailed: { + // if we are in batch mode, then no message is written + // to the screen by default. Thus we don't know, _what_ + // command actually failed. So in this case, write out + // the offending command. + + if (IsInBatch) { + Application.Error.WriteLine("-- failed command: "); + Application.Error.WriteLine(commandText); + } + break; + } + } + } catch (Exception e) { +#if DEBUG + System.Console.Error.WriteLine(e.Message); + System.Console.Error.WriteLine(e.StackTrace); +#endif + Application.Error.WriteLine(e.ToString()); + OnExecuted(context, commandText, CommandResultCode.ExecutionFailed); + } + } + } + + public Command GetCommand(Type commandType) { + for (int i = 0; i < commands.Count; i++) { + CommandInfo cmdInfo = commands[i]; + + if (cmdInfo.CommandType == commandType) + return cmdInfo.Command; + } + + return null; + } + + public string[] GetCommandNames(Type commandType) { + string baseName = null; + foreach(KeyValuePair pair in commandMap) { + if (commandType.IsInstanceOfType(pair.Value)) { + baseName = pair.Key; + break; + } + } + + if (String.IsNullOrEmpty(baseName)) + return new string[0]; + + List names = new List(); + names.Add(baseName); + + /* + string[] commandAliases = aliases.GetCommandAliases(baseName); + for (int i = 0; i < names.Count; i++) { + names.Add(names[i]); + } + */ + + return names.ToArray(); + } + + public Command GetCommand(string commandText) { + return GetCommandFromCooked(CompleteCommandName(commandText)); + } + + #endregion + + #region CommandInfo + class CommandInfo { + public CommandInfo(Type cmdType, Command command) { + CommandType = cmdType; + Command = command; + } + + public readonly Type CommandType; + public readonly Command Command; + } + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandEventArgs.cs b/src/dshell/Deveel.Console.Commands/CommandEventArgs.cs new file mode 100644 index 0000000..a22bf4f --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandEventArgs.cs @@ -0,0 +1,27 @@ +using System; + +namespace Deveel.Console.Commands { + public delegate void CommandEventHandler(object sender, CommandEventArgs e); + + public sealed class CommandEventArgs : EventArgs { + internal CommandEventArgs(string commandText) { + this.commandText = commandText; + } + + internal CommandEventArgs(string commandText, CommandResultCode resultcode) { + this.commandText = commandText; + this.resultcode = resultcode; + } + + private readonly string commandText; + private readonly CommandResultCode resultcode; + + public string CommandText { + get { return commandText; } + } + + public CommandResultCode ResultCode { + get { return resultcode; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandGroupAttribute.cs b/src/dshell/Deveel.Console.Commands/CommandGroupAttribute.cs new file mode 100644 index 0000000..5d9c79c --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandGroupAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace Deveel.Console.Commands { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class CommandGroupAttribute : Attribute, ICommandAttribute { + private readonly string groupName; + + public CommandGroupAttribute(string groupName) { + this.groupName = groupName; + } + + public string GroupName { + get { return groupName; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandResultCode.cs b/src/dshell/Deveel.Console.Commands/CommandResultCode.cs new file mode 100644 index 0000000..a911df6 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandResultCode.cs @@ -0,0 +1,30 @@ +using System; + +namespace Deveel.Console.Commands { + /// + /// The code returned after the execution of a command + /// from the method . + /// + public enum CommandResultCode { + /// + /// This code is returned is the execution of a command was + /// successfully. + /// + Success = 0, + + /// + /// The code returned if the command could not be executed because + /// of an syntax error. + /// + /// + /// If this code is returned, the synopsis of the command is shown. + /// + SyntaxError = 1, + + /// + /// The code returned if the command could not be executed because + /// of some problem, that is not a syntax error. + /// + ExecutionFailed = 2, + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandSeparator.cs b/src/dshell/Deveel.Console.Commands/CommandSeparator.cs new file mode 100644 index 0000000..1ea9acb --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandSeparator.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections; +using System.Text; + +namespace Deveel.Console.Commands { + public abstract class CommandSeparator : ICommandSeparator { + private ParseState currentState; + private readonly Stack stateStack; + + protected CommandSeparator() { + currentState = new ParseState(); + stateStack = new Stack(); + } + + public string Current { + get { + if (currentState.Type != (int)TokenType.PotentialEndFound) + throw new InvalidOperationException("Current called without MoveNext()"); + return currentState.CommandBuffer.ToString(); + } + } + + object IEnumerator.Current { + get { return Current; } + } + + void IEnumerator.Reset() { + } + + void IDisposable.Dispose() { + } + + private void ParsePartialInput() { + int pos = 0; + char current; + int oldstate = -1; + + // local variables: faster access. + int state = currentState.Type; + bool lastEoline = currentState.NewlineSeen; + + StringBuilder input = currentState.InputBuffer; + StringBuilder parsed = currentState.CommandBuffer; + + if (state == (int) TokenType.NewStatement) { + parsed.Length = 0; + // skip leading whitespaces of next statement... + while (pos < input.Length && + Char.IsWhiteSpace(input[pos])) { + //CHECK: what about \r? + currentState.NewlineSeen = (input[pos] == '\n'); + ++pos; + } + input.Remove(0, pos); + pos = 0; + } + + if (input.Length == 0) + state = (int) TokenType.PotentialEndFound; + + while (state != (int) TokenType.PotentialEndFound && + pos < input.Length) { + bool reIterate; + current = input[pos]; + if (current == '\r') + current = '\n'; // canonicalize. + + if (current == '\n') + currentState.NewlineSeen = true; + + do { + reIterate = false; + ParseToken token = new ParseToken(state, current, lastEoline); + state = Parse(token); + + if (token.NewLineSeenWasSet) + lastEoline = token.NewLineSeen; + + if (token.AppendedCharacter != '\0') + parsed.Append(token.AppendedCharacter); + + if (token.ContinueParsingWasSet) + reIterate = token.ContinueParsing; + } while (reIterate); + + ParseToken postToken = new ParseToken(oldstate, current, lastEoline); + PostParse(postToken); + + if (postToken.AppendedCharacter != '\0') + parsed.Append(postToken.AppendedCharacter); + + oldstate = state; + pos++; + // we maintain the state of 'just seen newline' as long + // as we only skip whitespaces.. + lastEoline &= Char.IsWhiteSpace(current); + } + + // we reached: POTENTIAL_END_FOUND. Store the rest, that + // has not been parsed in the input-buffer. + input.Remove(0, pos); + currentState.Type = state; + } + + protected abstract int Parse(ParseToken token); + + protected virtual void PostParse(ParseToken token) { + } + + public void Push() { + stateStack.Push(currentState); + currentState = new ParseState(); + } + + public void Pop() { + currentState = (ParseState)stateStack.Pop(); + } + + public void Append(string s) { + currentState.InputBuffer.Append(s); + } + + public void Discard() { + currentState.InputBuffer.Length = 0; + currentState.CommandBuffer.Length = 0; + currentState.Type = (int) TokenType.NewStatement; + } + + public void Cont() { + currentState.Type = (int) TokenType.Start; + } + + public void Consumed() { + currentState.Type = (int) TokenType.NewStatement; + } + + public bool MoveNext() { + if (currentState.Type == (int) TokenType.PotentialEndFound) + throw new InvalidOperationException("call Cont() or Consumed() before MoveNext()"); + if (currentState.InputBuffer.Length == 0) + return false; + + ParsePartialInput(); + return (currentState.Type == (int) TokenType.PotentialEndFound); + } + + #region ParseState + + class ParseState { + private int _state; + private StringBuilder _inputBuffer; + private StringBuilder _commandBuffer; + /* + * instead of adding new states, we store the + * fact, that the last 'potential_end_found' was + * a newline here. + */ + private bool _eolineSeen; + + internal ParseState() { + _eolineSeen = true; // we start with a new line. + _state = (int) TokenType.NewStatement; + _inputBuffer = new StringBuilder(); + _commandBuffer = new StringBuilder(); + } + + + public int Type { + get { return _state; } + set { _state = value; } + } + + public bool NewlineSeen { + get { return _eolineSeen; } + set { _eolineSeen = value; } + } + + public StringBuilder InputBuffer { + get { return _inputBuffer; } + } + + public StringBuilder CommandBuffer { + get { return _commandBuffer; } + } + } + #endregion + + #region TokenType + + protected enum TokenType { + NewStatement = 0, + Start = 1, + PotentialEndFound = -1 + } + + #endregion + + #region ParseToken + + protected sealed class ParseToken { + internal ParseToken(int oldState, char current, bool newLineSeen) { + this.oldState = oldState; + this.newLineSeen = newLineSeen; + this.current = current; + } + + private readonly int oldState; + private int newState; + private readonly char current; + private char toAdd; + private bool newLineSeen; + private bool newLineSeenSet; + private bool continueParsing; + private bool _continueParsingWasSet; + + public char CurrentCharacter { + get { return current; } + } + + public int OldState { + get { return oldState; } + } + + public int NewState { + get { return newState; } + set { newState = value; } + } + + public bool NewLineSeen { + get { return newLineSeen; } + set { + newLineSeen = value; + newLineSeenSet = true; + } + } + + internal bool NewLineSeenWasSet { + get { return newLineSeenSet; } + } + + public bool ContinueParsing { + get { return continueParsing; } + set { + continueParsing = value; + _continueParsingWasSet = true; + } + } + + public char AppendedCharacter { + get { return toAdd; } + set { toAdd = value; } + } + + internal bool ContinueParsingWasSet { + get { return _continueParsingWasSet; } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandSynopsisAttributecs.cs b/src/dshell/Deveel.Console.Commands/CommandSynopsisAttributecs.cs new file mode 100644 index 0000000..690892e --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandSynopsisAttributecs.cs @@ -0,0 +1,17 @@ +using System; + +namespace Deveel.Console.Commands { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class CommandSynopsisAttribute : Attribute, ICommandAttribute { + public CommandSynopsisAttribute(string text) { + this.text = text; + } + + private string text; + + public string Text { + get { return text; } + set { text = value; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/CommandWrapper.cs b/src/dshell/Deveel.Console.Commands/CommandWrapper.cs new file mode 100644 index 0000000..2596fd5 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/CommandWrapper.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections; +using System.IO; +using System.Net; +using System.Reflection; +using System.Text; + +using Deveel.Configuration; + +namespace Deveel.Console.Commands { + internal class CommandWrapper : Command { + public CommandWrapper(Type type, object application) { + obj = Instantiate(type, application); + if (obj == null) + throw new InvalidOperationException(); + + executeMethod = GetExecuteMethod(obj); + handleCommandLineMethod = GetHandleCommandLineMethod(obj); + registerOptionsMethod = GetRegisterOptionsMethod(obj); + } + + private readonly object obj; + private readonly MethodInfo executeMethod; + private readonly MethodInfo handleCommandLineMethod; + private readonly MethodInfo registerOptionsMethod; + + private CommandAttributes attributes; + + private CommandAttributes Attributes { + get { + if (attributes == null) + attributes = RetrieveCommandAttributes(); + return attributes; + } + } + + public override string ShortDescription { + get { return Attributes.ShortDescription; } + } + + public override bool CommandCompletion { + get { return Attributes.CommandCompletion; } + } + + public override bool RequiresContext { + get { return Attributes.RequiresContext; } + } + + public override string GroupName { + get { return Attributes.CommandGroup; } + } + + public override string[] Aliases { + get { return (string[])Attributes.Aliases.ToArray(typeof(string)); } + } + + public override string Name { + get { return Attributes.CommandName; } + } + + public override string[] Synopsis { + get { + ArrayList versions = Attributes.Synopsis; + if (versions == null || versions.Count == 0) + return new string[] { Name }; + + return (string[])versions.ToArray(typeof(string)); + } + } + + public override string LongDescription { + get { return Attributes.Description; } + } + + private CommandAttributes RetrieveCommandAttributes() { + CommandAttributes commandAttributes = new CommandAttributes(); + + object[] attrs = obj.GetType().GetCustomAttributes(typeof(ICommandAttribute), false); + if (attrs.Length > 0) { + for (int i = 0; i < attrs.Length; i++) { + object attr = attrs[i]; + if (attr is CommandAttribute) { + CommandAttribute commandAttr = (CommandAttribute)attr; + commandAttributes.CommandName = commandAttr.CommandName; + commandAttributes.RequiresContext = commandAttr.RequiresContext; + commandAttributes.ShortDescription = commandAttr.ShortDescription; + } else if (attr is CommandAliasAttribute) { + CommandAliasAttribute aliasAttribute = (CommandAliasAttribute)attr; + commandAttributes.Aliases.Add(aliasAttribute.Alias); + } else if (attr is CommandSynopsisAttribute) { + CommandSynopsisAttribute synopsisAttr = (CommandSynopsisAttribute)attr; + if (synopsisAttr.Text != null && synopsisAttr.Text.Length > 0) + commandAttributes.Synopsis.Add(synopsisAttr.Text); + } else if (attr is CommandGroupAttribute) { + CommandGroupAttribute groupAttribute = (CommandGroupAttribute)attr; + commandAttributes.CommandGroup = groupAttribute.GroupName; + } else if (attr is CommandCompletionAttribute) { + CommandCompletionAttribute completionAttribute = (CommandCompletionAttribute)attr; + attributes.CommandCompletion = completionAttribute.CompleteCommand; + } else if (attr is CommandDesctiprionAttribute) { + CommandDesctiprionAttribute desctiprionAttribute = (CommandDesctiprionAttribute)attr; + attributes.Description = ReadDescription(desctiprionAttribute.Value, desctiprionAttribute.Source); + } + } + } + + return commandAttributes; + } + + private string ReadDescription(string value, DescriptionSource source) { + if (source == DescriptionSource.Direct) + return value; + + Stream inputStream = null; + + try { + if (source == DescriptionSource.Resource) { + Assembly assembly = Assembly.GetExecutingAssembly(); + inputStream = assembly.GetManifestResourceStream(value); + } else if (source == DescriptionSource.LocalFile) { + inputStream = new FileStream(value, FileMode.Open, FileAccess.Read, FileShare.Read); + } else { + WebRequest request = WebRequest.Create(value); + WebResponse response = request.GetResponse(); + if (response == null) + return null; + + inputStream = response.GetResponseStream(); + } + + if (inputStream == null) + return null; + + StreamReader reader = new StreamReader(inputStream); + StringBuilder sb = new StringBuilder(); + string line; + while ((line = reader.ReadLine()) != null) + sb.AppendLine(line); + + return sb.ToString(); + } catch (Exception) { + Application.Error.WriteLine("Error while retrieving the description for the command."); + return null; + } finally { + if (inputStream != null) + inputStream.Close(); + } + } + + private static MethodInfo GetExecuteMethod(object obj) { + if (obj is IExecutable) + return null; + + Type type = obj.GetType(); + MethodInfo method = type.GetMethod("Execute", new Type[] {typeof (object), typeof (string[])}); + if (method == null) + return null; + + if (method.ReturnType != typeof(CommandResultCode) && + method.ReturnType != typeof(int)) + return null; + + return method; + } + + private static object Instantiate(Type type, object application) { + ConstructorInfo ctor = null; + int parCount = 0; + ConstructorInfo[] ctors = type.GetConstructors(); + for (int i = 0; i < ctors.Length; i++) { + ctor = ctors[i]; + ParameterInfo[] pars = ctor.GetParameters(); + if ((parCount = pars.Length) <= 1) { + break; + } + } + + if (ctor == null) + throw new NotSupportedException(); + + if (parCount == 1) + return ctor.Invoke(new object[] {application}); + return ctor.Invoke(null); + } + + private static MethodInfo GetHandleCommandLineMethod(object obj) { + if (obj is IOptionsHandler) + return null; + + Type type = obj.GetType(); + MethodInfo method = type.GetMethod("HandleCommandLine"); + if (method == null) + return null; + + ParameterInfo[] pars = method.GetParameters(); + if (pars.Length != 1) + return null; + + if (pars[0].ParameterType != typeof(CommandLine)) + return null; + + return method; + } + + private static MethodInfo GetRegisterOptionsMethod(object obj) { + if (obj is IOptionsHandler) + return null; + + Type type = obj.GetType(); + MethodInfo method = type.GetMethod("RegisterOptions"); + if (method == null) + return null; + + ParameterInfo[] pars = method.GetParameters(); + if (pars.Length != 1) + return null; + + if (pars[0].ParameterType != typeof(Options)) + return null; + + return method; + } + + public override void RegisterOptions(Options options) { + if (obj is IOptionsHandler) { + ((IOptionsHandler)obj).RegisterOptions(options); + } else if (registerOptionsMethod != null) { + registerOptionsMethod.Invoke(obj, new object[] {options}); + } else { + throw new NotSupportedException(); + } + } + + public override bool HandleCommandLine(CommandLine commandLine) { + if (obj is IOptionsHandler) + return ((IOptionsHandler)obj).HandleCommandLine(commandLine); + if (handleCommandLineMethod != null) + return (bool)handleCommandLineMethod.Invoke(obj, new object[] {commandLine}); + + return false; + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (obj is IExecutable) + return ((IExecutable) obj).Execute(context, args); + if (executeMethod != null) + return (CommandResultCode) executeMethod.Invoke(obj, new object[] {context, args}); + return CommandResultCode.ExecutionFailed; + } + + private class CommandAttributes { + public readonly ArrayList Aliases = new ArrayList(); + public string CommandName; + public bool RequiresContext; + public string ShortDescription; + public string Description = String.Empty; + public readonly ArrayList Synopsis = new ArrayList(); + public string CommandGroup; + public bool CommandCompletion = true; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/DescriptionSource.cs b/src/dshell/Deveel.Console.Commands/DescriptionSource.cs new file mode 100644 index 0000000..43fd143 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/DescriptionSource.cs @@ -0,0 +1,10 @@ +using System; + +namespace Deveel.Console.Commands { + public enum DescriptionSource { + Direct = 1, + Resource = 2, + LocalFile = 3, + RemoteFile = 4 + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/EchoCommand.cs b/src/dshell/Deveel.Console.Commands/EchoCommand.cs new file mode 100644 index 0000000..0bf65c0 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/EchoCommand.cs @@ -0,0 +1,40 @@ +using System; + +namespace Deveel.Console.Commands { + public sealed class EchoCommand : Command { + public override string ShortDescription { + get { return "prompts the given arguments"; } + } + + public override string Name { + get { return "echo"; } + } + + public override string[] Synopsis { + get { return new string[] { "echo " };} + } + + public override string[] Aliases { + get { return new string[] { "prompt" }; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + String outStr = args.ToString(); + Application.Out.WriteLine(StripQuotes(outStr)); + return CommandResultCode.Success; + } + + private static String StripQuotes(String value) { + if (value.StartsWith("\"") && value.EndsWith("\"")) { + value = value.Substring(1, value.Length - 2); + } else if (value.StartsWith("\'") && value.EndsWith("\'")) { + value = value.Substring(1, value.Length - 2); + } + return value; + } + + public override String LongDescription { + get { return "just echo the string given."; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/ExitCommand.cs b/src/dshell/Deveel.Console.Commands/ExitCommand.cs new file mode 100644 index 0000000..883e883 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/ExitCommand.cs @@ -0,0 +1,30 @@ +using System; + +namespace Deveel.Console.Commands { + class ExitCommand : Command { + public override string Name { + get { return "exit"; } + } + + public override string[] Synopsis { + get { return new string[] { "exit [ ]" }; } + } + + public override string[] Aliases { + get { return new string[] { "quit" }; } + } + + public override string ShortDescription { + get { return "exits the application"; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + int exitCode = 0; + if (args.Count == 1) + exitCode = Int32.Parse(args.Peek(0)); + + Application.Exit(exitCode); + return CommandResultCode.Success; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/HelpCommand.cs b/src/dshell/Deveel.Console.Commands/HelpCommand.cs new file mode 100644 index 0000000..f3d7792 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/HelpCommand.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Deveel.Collections; + +namespace Deveel.Console.Commands { + public class HelpCommand : Command { + public override string Name { + get { return "help"; } + } + + public override string[] Aliases { + get { return new string[] { "?" }; } + } + + public override string[] Synopsis { + get { return new string[] { "help [ ]", "? [ ]" }; } + } + + public override string ShortDescription { + get { return "provides help for commands"; } + } + + #region Private Methods + private void WriteDescription(Command c) { + string desc = c.LongDescription; + if (desc == null) { + if (c.ShortDescription != null) { + desc = "\t[short description]: " + c.ShortDescription; + } + } + + string[] synopsis = c.Synopsis; + + if (synopsis != null && synopsis.Length > 0) { + Application.Error.WriteBold("SYNOPSIS"); + Application.Error.WriteLine(); + + for (int i = 0; i < synopsis.Length; i++) { + Application.Error.WriteLine("\t" + synopsis[i]); + } + + Application.Error.WriteLine(); + } + if (desc != null) { + Application.Error.WriteBold("DESCRIPTION"); + Application.Error.WriteLine(); + + StringReader reader = new StringReader(desc); + string line; + while ((line = reader.ReadLine()) != null) { + Application.Error.Write("\t"); + Application.Error.WriteLine(line); + } + if (c.RequiresContext) { + Application.Error.WriteLine("\t[Requires valid context]"); + } + } + if (desc == null && synopsis == null) { + Application.Error.WriteLine("no detailed help for '" + c.Name + "'"); + } + } + #endregion + + private class CommandHelp { + public string Name; + public string Description; + } + + #region Public Methods + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (args.Count > 1) + return CommandResultCode.SyntaxError; + + string commandName = null; + if (args.MoveNext()) + commandName = args.Current; + + // nothing given: provide generic help. + + Application.Error.WriteLine(); + + int maxPad = 0; + if (commandName == null) { + ICollection commands = Application.Commands.RegisteredCommands; + + // process the command groups first... + Dictionary> groups = new Dictionary>(); + foreach (Command command in commands) { + string groupName = command.GroupName; + if (groupName == null || groupName.Length == 0) + groupName = "commands"; + + List list; + if (!groups.TryGetValue(groupName, out list)) { + list = new List(); + groups[groupName] = list; + } + + CommandHelp commandHelp = new CommandHelp(); + + StringBuilder cmdPrint = new StringBuilder(" "); + string[] aliases = command.Aliases; + + cmdPrint.Append(command.Name); + + if (aliases != null && aliases.Length > 0) { + cmdPrint.Append(" | "); + for (int i = 0; i < aliases.Length; i++) { + if (i != 0) + cmdPrint.Append(" | "); + cmdPrint.Append(aliases[i]); + } + } + + commandHelp.Name = cmdPrint.ToString(); + + string description = command.ShortDescription; + if (description == null) { + // no description ... try to get the groups description... + } + + commandHelp.Description = description; + + maxPad = Math.Max(maxPad, cmdPrint.Length); + + list.Add(commandHelp); + } + + foreach (KeyValuePair> entry in groups) { + string groupName = entry.Key; + Application.Error.Write(groupName); + Application.Error.Write(":"); + Application.Error.WriteLine(); + + List commandList = entry.Value; + foreach (CommandHelp command in commandList) { + Application.Error.Write(" "); + Application.Error.Write(command.Name); + + if (command.Description != null) { + for (int i = 0; i < maxPad - command.Name.Length; ++i) + Application.Error.Write(" "); + + Application.Error.Write(" : "); + Application.Error.Write(command.Description); + } + + Application.Error.WriteLine(); + } + } + } else { + CommandDispatcher disp = Application.Commands; + + string cmdString = disp.CompleteCommandName(commandName); + Command c = disp.GetCommand(cmdString); + if (c == null) { + Application.Error.WriteLine("Help: unknown command '" + cmdString + "'"); + Application.Error.WriteLine(); + return CommandResultCode.ExecutionFailed; + } + + WriteDescription(c); + } + + Application.Error.WriteLine(); + return CommandResultCode.Success; + } + + public override string LongDescription { + get { + return "Provides help for the given command. If invoked without a " + + "command name as parameter, a list of all available commands" + + "is shown."; + } + } + + public override IEnumerator Complete(CommandDispatcher dispatcher, string partialCommand, string lastWord) { + // if we already have one arguemnt and try to expand the next: no. + int argc = partialCommand.Split(' ').Length; + if (argc > 2 || (argc == 2 && lastWord.Length == 0)) { + return null; + } + + IEnumerator it = Application.Commands.GetRegisteredCommandNames(lastWord).GetEnumerator(); + return new SortedMatchEnumerator(lastWord, it); + } + #endregion + + #region HelpSortedMatchEnumerator + + class HelpSortedMatchEnumerator : SortedMatchEnumerator { + private readonly HelpCommand command; + + public HelpSortedMatchEnumerator(HelpCommand command, string lastWord, IEnumerator en) + : base(lastWord, en) { + this.command = command; + } + + protected override bool Exclude(string current) { + Command cmd = command.Application.Commands.GetCommand(current); + return (cmd.LongDescription == null); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/ICommandAttribute.cs b/src/dshell/Deveel.Console.Commands/ICommandAttribute.cs new file mode 100644 index 0000000..0cac655 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/ICommandAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Deveel.Console.Commands { + /// + /// An interface used to sign any + /// that is used on a . + /// + internal interface ICommandAttribute { + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/ICommandSeparator.cs b/src/dshell/Deveel.Console.Commands/ICommandSeparator.cs new file mode 100644 index 0000000..b1a2566 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/ICommandSeparator.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + public interface ICommandSeparator : IEnumerator { + void Append(string line); + + void Cont(); + + void Consumed(); + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/IExecutable.cs b/src/dshell/Deveel.Console.Commands/IExecutable.cs new file mode 100644 index 0000000..6fd230f --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/IExecutable.cs @@ -0,0 +1,7 @@ +using System; + +namespace Deveel.Console.Commands { + public interface IExecutable { + CommandResultCode Execute(IExecutionContext context, CommandArguments args); + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/PlugInCommand.cs b/src/dshell/Deveel.Console.Commands/PlugInCommand.cs new file mode 100644 index 0000000..5fa984d --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/PlugInCommand.cs @@ -0,0 +1,59 @@ +using System; + +namespace Deveel.Console.Commands { + internal class PlugInCommand : Command { + public override string Name { + get { return "plug-in"; } + } + + public override string[] Synopsis { + get { return new string[] { "plug-in " }; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + if (!(Application is IPluginHandler)) { + Error.WriteLine("The application doesn't support plug-ins."); + return CommandResultCode.ExecutionFailed; + } + + ApplicationPlugins plugins = ((IPluginHandler) Application).Plugins; + string pluginType = args.Current; + if (plugins.HasPlugin(pluginType)) { + Application.Error.WriteLine("plugin '" + pluginType + "' already loaded"); + return CommandResultCode.ExecutionFailed; + } + + Command plugin; + try { + plugin = plugins.LoadPlugin(pluginType); + } catch (Exception e) { + Application.Error.WriteLine("couldn't load plugin: " + e.Message); + return CommandResultCode.ExecutionFailed; + } + if (plugin != null) { + plugins.Add(pluginType, plugin); + Out.Write("adding command: "); + Out.Write(plugin.Name); + string[] aliases = plugin.Aliases; + if (aliases.Length > 0) { + Out.Write(" ("); + for (int i = 0; i < aliases.Length; ++i) { + Out.Write(aliases[i]); + + if (i < aliases.Length - 1) + Out.Write(", "); + } + + Out.Write(")"); + } + + Out.WriteLine(); + } + + return CommandResultCode.Success; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/PlugOutCommand.cs b/src/dshell/Deveel.Console.Commands/PlugOutCommand.cs new file mode 100644 index 0000000..0134ff1 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/PlugOutCommand.cs @@ -0,0 +1,38 @@ +using System; + +namespace Deveel.Console.Commands { + internal class PlugOutCommand : Command { + public override string GroupName { + get { return "plugins"; } + } + + public override string Name { + get { return "plug-out"; } + } + + public override string[] Synopsis { + get { return new string[] { "plug-out " }; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + if (!(Application is IPluginHandler)) { + Error.WriteLine("The application doesn't support plug-ins."); + return CommandResultCode.ExecutionFailed; + } + + ApplicationPlugins plugins = ((IPluginHandler) Application).Plugins; + + string pluginType = args.Current; + if (!plugins.HasPlugin(pluginType)) { + Application.Error.WriteLine("unknown plugin '" + pluginType + "'"); + return CommandResultCode.ExecutionFailed; + } + if (!plugins.Unregister(pluginType)) + return CommandResultCode.ExecutionFailed; + return CommandResultCode.Success; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/PropertyCommand.cs b/src/dshell/Deveel.Console.Commands/PropertyCommand.cs new file mode 100644 index 0000000..75608b2 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/PropertyCommand.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + abstract class PropertyCommand : Command { + protected virtual PropertyRegistry Properties { + get { + IPropertyHandler handler = Application as IPropertyHandler; + return handler == null ? null : handler.Properties; + } + } + + public override IEnumerator Complete(CommandDispatcher dispatcher, string partialCommand, string lastWord) { + PropertyRegistry properties = Properties; + return properties == null ? null : properties.Complete(Name, partialCommand, lastWord); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/ResetContextPropertyCommand.cs b/src/dshell/Deveel.Console.Commands/ResetContextPropertyCommand.cs new file mode 100644 index 0000000..cf4419c --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/ResetContextPropertyCommand.cs @@ -0,0 +1,32 @@ +using System; + +namespace Deveel.Console.Commands { + internal class ResetContextPropertyCommand : ResetPropertyCommand { + public override string GroupName { + get { return "variables"; } + } + + public override bool RequiresContext { + get { return true; } + } + + public override string Name { + get { return "reset-context-property"; } + } + + public override string[] Synopsis { + get { return new string[] { "reset-context-property " }; } + } + + public override string ShortDescription { + get { return "resets a registered property in a context"; } + } + + protected override PropertyRegistry Properties { + get { + IPropertyHandler handler = Application.ActiveContext as IPropertyHandler; + return handler == null ? null : handler.Properties; + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/ResetPropertyCommand.cs b/src/dshell/Deveel.Console.Commands/ResetPropertyCommand.cs new file mode 100644 index 0000000..fe7fd9d --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/ResetPropertyCommand.cs @@ -0,0 +1,57 @@ +using System; + +namespace Deveel.Console.Commands { + class ResetPropertyCommand : PropertyCommand { + public override string Name { + get { return "reset-property"; } + } + + public override string GroupName { + get { return "variables"; } + } + + public override string ShortDescription { + get { return "resets a registered application property"; } + } + + public override string LongDescription { + get { + return "Reset the given global application property " + + "to its default value "; + } + } + + public override string[] Synopsis { + get { return new string[] { "reset-property " }; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (args.Count != 1) + return CommandResultCode.SyntaxError; + + PropertyRegistry properties = Properties; + if (properties == null) { + Application.Error.WriteLine("the current context does not support properties."); + return CommandResultCode.ExecutionFailed; + } + + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + String name = args.Current; + PropertyHolder holder = properties.GetProperty(name); + if (holder == null) + return CommandResultCode.ExecutionFailed; + + string defaultValue = holder.DefaultValue; + + try { + properties.SetProperty(name, defaultValue); + } catch (Exception) { + Application.Error.WriteLine("setting to default '" + defaultValue + "' failed."); + return CommandResultCode.ExecutionFailed; + } + return CommandResultCode.Success; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/SetCommand.cs b/src/dshell/Deveel.Console.Commands/SetCommand.cs new file mode 100644 index 0000000..6d3f8d2 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/SetCommand.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + internal sealed class SetCommand : Command { + public override string Name { + get { return "set"; } + } + + public override string ShortDescription { + get { return "sets the value of a variable"; } + } + + public override string GroupName { + get { return "variables"; } + } + + public override string[] Synopsis { + get { return new string[] { "set " }; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (!(Application is ISettingsHandler)) { + Error.WriteLine("The application doesn't support settings."); + return CommandResultCode.ExecutionFailed; + } + + ApplicationSettings settings = ((ISettingsHandler) Application).Settings; + + int argc = args.Count; + if (argc < 2) + return CommandResultCode.SyntaxError; + + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + string varname = args.Current; + + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + string value = args.Current; + + if (value.StartsWith("\"") && value.EndsWith("\"")) { + value = value.Substring(1, value.Length - 2); + } else if (value.StartsWith("\'") && value.EndsWith("\'")) { + value = value.Substring(1, value.Length - 2); + } + + settings.SetVariable(varname, value); + + Out.WriteLine(); + Out.WriteLine("variable {0} set to {1}", varname, value); + Out.WriteLine(); + + return CommandResultCode.Success; + } + + public override IEnumerator Complete(CommandDispatcher dispatcher, string partialCommand, string lastWord) { + ISettingsHandler handler = Application as ISettingsHandler; + return handler == null ? null : handler.Settings.Complete(partialCommand, lastWord); + } + + public override string LongDescription { + get { + return "Sets variable with name to . " + + "Variables are expanded in any command you issue on the " + + "commandline. Variable expansion works like on the shell " + + "with the dollarsign. Both forms, $VARNAME and ${VARNAME}, " + + "are supported. If the variable is _not_ set, then the " + + "text is left untouched. So if there is no variable " + + "$VARNAME, then it is not replaced by an empty string but " + + "stays '$VARNAME'. This is because some scripts use wierd " + + "identifiers containting dollars (esp. Oracle scripts) " + + "If you want to quote the dollarsign explicitly, write " + + "two dollars: $$FOO means $FOO"; + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/SetContextPropertyCommand.cs b/src/dshell/Deveel.Console.Commands/SetContextPropertyCommand.cs new file mode 100644 index 0000000..178dc56 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/SetContextPropertyCommand.cs @@ -0,0 +1,25 @@ +using System; + +namespace Deveel.Console.Commands { + internal class SetContextPropertyCommand : SetPropertyCommand { + public override string Name { + get { return "set-context-property"; } + } + + public override bool RequiresContext { + get { return true; } + } + + public override string GroupName { + get { return "variables"; } + } + + public override string[] Synopsis { + get { return new string[] {"set-context-property "}; } + } + + public override string ShortDescription { + get { return "sets the value of a property"; } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/SetPropertyCommand.cs b/src/dshell/Deveel.Console.Commands/SetPropertyCommand.cs new file mode 100644 index 0000000..ecb24a6 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/SetPropertyCommand.cs @@ -0,0 +1,69 @@ +using System; + +namespace Deveel.Console.Commands { + class SetPropertyCommand : PropertyCommand { + public override string Name { + get { return "set-property"; } + } + + public override string GroupName { + get { return "properties"; } + } + + public override string ShortDescription { + get { return "sets the value of a property"; } + } + + public override string[] Synopsis { + get { return new string[] { "set-property " }; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + string varname = args.Current; + string[] newArgs = new string[args.Count-1]; + while (args.MoveNext()) { + newArgs[args.CurrentIndex - 1] = args.Current; + } + + string param = String.Join(" ", newArgs); + int pos = 0; + int paramLength = param.Length; + // skip whitespace after 'set' + while (pos < paramLength + && Char.IsWhiteSpace(param[pos])) { + ++pos; + } + // skip non-whitespace after 'set ': variable name + while (pos < paramLength + && !Char.IsWhiteSpace(param[pos])) { + ++pos; + } + // skip whitespace before vlue.. + while (pos < paramLength + && Char.IsWhiteSpace(param[pos])) { + ++pos; + } + String value = param.Substring(pos); + if (value.StartsWith("\"") && value.EndsWith("\"")) { + value = value.Substring(1, value.Length - 2); + } else if (value.StartsWith("\'") && value.EndsWith("\'")) { + value = value.Substring(1, value.Length - 2); + } + + try { + PropertyRegistry properties = Properties; + if (properties == null) + throw new Exception("The current context doesn't support properties."); + + properties.SetProperty(varname, value); + } catch (Exception e) { + Application.Error.WriteLine(e.Message); + return CommandResultCode.ExecutionFailed; + } + return CommandResultCode.Success; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/ShowCommand.cs b/src/dshell/Deveel.Console.Commands/ShowCommand.cs new file mode 100644 index 0000000..892b5fe --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/ShowCommand.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + internal class ShowCommand : Command { + public override string Name { + get { return "show"; } + } + + public override string ShortDescription { + get { return "shows information about a given set."; } + } + + public override bool CommandCompletion { + get { return true; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + IInformationProvider provider = Application as IInformationProvider; + if (provider == null) { + Error.WriteLine("The current context does not support information."); + return CommandResultCode.ExecutionFailed; + } + + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + string infoName = args.Current; + if (!provider.IsInfoSupported(infoName)) { + Error.WriteLine("Information " + infoName + " is not supported by the current context."); + return CommandResultCode.ExecutionFailed; + } + + ColumnDesign[] columns = provider.GetColumns(infoName); + for (int i = 0; i < columns.Length; i++) + columns[i].ResetWidth(); + + TableRenderer renderer = new TableRenderer(columns, Out); + // TODO: make it configurable ... + renderer.EnableHeader = true; + renderer.EnableFooter = true; + + IList values = provider.GetValues(infoName); + for (int i = 0; i < values.Count; i++) { + ColumnValue[] rowValues = values[i]; + renderer.AddRow(rowValues); + } + + renderer.Flush(); + renderer.CloseTable(); + return CommandResultCode.Success; + } + + public override IEnumerator Complete(CommandDispatcher dispatcher, string partialCommand, string lastWord) { + return base.Complete(dispatcher, partialCommand, lastWord); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/ShowContextPropertyCommand.cs b/src/dshell/Deveel.Console.Commands/ShowContextPropertyCommand.cs new file mode 100644 index 0000000..1020c71 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/ShowContextPropertyCommand.cs @@ -0,0 +1,32 @@ +using System; + +namespace Deveel.Console.Commands { + internal class ShowContextPropertyCommand : ShowPropertyCommand { + public override string GroupName { + get { return "properties"; } + } + + public override string Name { + get { return "show-context-property"; } + } + + public override string ShortDescription { + get { return "lists all the properties in a context"; } + } + + public override string[] Synopsis { + get { return new string[] { "show-context-property [ ]" }; } + } + + public override bool RequiresContext { + get { return true; } + } + + protected override PropertyRegistry Properties { + get { + IPropertyHandler handler = Application.ActiveContext as IPropertyHandler; + return handler == null ? null : handler.Properties; + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/ShowPropertyCommand.cs b/src/dshell/Deveel.Console.Commands/ShowPropertyCommand.cs new file mode 100644 index 0000000..9e87e7c --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/ShowPropertyCommand.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + class ShowPropertyCommand : PropertyCommand { + private static ColumnDesign[] ProperiesColumns; + + static ShowPropertyCommand() { + ProperiesColumns = new ColumnDesign[3]; + ProperiesColumns[0] = new ColumnDesign("Name"); + ProperiesColumns[1] = new ColumnDesign("Value"); + ProperiesColumns[2] = new ColumnDesign("Description"); + } + + public override string Name { + get { return "show-property"; } + } + + public override string GroupName { + get { return "properties"; } + } + + public override string ShortDescription { + get { return "lists all the properties"; } + } + + public override string[] Synopsis { + get { return new string[] { "show-property [ ]" }; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + PropertyRegistry properties = Properties; + if (properties == null) { + Application.Error.WriteLine("the current context does not support properties."); + return CommandResultCode.ExecutionFailed; + } + + if (args.MoveNext()) { + string name = args.Current; + PropertyHolder holder = properties.GetProperty(name); + if (holder == null) + return CommandResultCode.ExecutionFailed; + + PrintDescription(name, holder, Application.Error); + return CommandResultCode.Success; + } + + ProperiesColumns[0].ResetWidth(); + ProperiesColumns[1].ResetWidth(); + TableRenderer table = new TableRenderer(ProperiesColumns, Application.Out); + foreach(KeyValuePair entry in properties) { + ColumnValue[] row = new ColumnValue[3]; + PropertyHolder holder = entry.Value; + row[0] = new ColumnValue(entry.Key); + row[1] = new ColumnValue(holder.Value); + row[2] = new ColumnValue(holder.ShortDescription); + table.AddRow(row); + } + table.CloseTable(); + return CommandResultCode.Success; + } + + private static void PrintDescription(String propName, PropertyHolder prop, OutputDevice output) { + if (prop.ShortDescription != null) { + output.WriteBold("PROPERTY"); + output.WriteLine(); + output.WriteLine("\t" + propName + " : " + prop.ShortDescription); + output.WriteLine(); + } + + String desc = prop.LongDescription; + if (desc != null) { + output.WriteBold("DESCRIPTION"); + output.WriteLine(); + output.WriteLine(desc); + } else { + output.WriteLine("no detailed help for '" + propName + "'"); + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/SpoolCommand.cs b/src/dshell/Deveel.Console.Commands/SpoolCommand.cs new file mode 100644 index 0000000..c081ad9 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/SpoolCommand.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Deveel.Console.Commands { + public sealed class SpoolCommand : Command { + private Stack outStack; + private Stack msgStack; + + public override string Name { + get { return "spool"; } + } + + public override string ShortDescription { + get { return "logs the output to a file"; } + } + + public override string[] Synopsis { + get { return new string[] { "spool | off" }; } + } + + #region Private Methods + private static OutputDevice OpenStackedDevice(Stack stack, OutputDevice newOut) { + OutputDevice origOut = stack.Peek(); + OutputDevice outDevice = new StackedDevice(origOut, newOut); + stack.Push(outDevice); + return outDevice; + } + + private static OutputDevice CloseStackedDevice(Stack stack) { + OutputDevice output = stack.Pop(); + output.Close(); + return stack.Peek(); + } + + private void OpenSpool(string filename) { + // open file + OutputDevice spool = new TextWriterOutputDevice(new StreamWriter(filename)); + Application.SetOutDevice(OpenStackedDevice(outStack, spool)); + Application.SetErrorDevice(OpenStackedDevice(msgStack, spool)); + Application.Error.WriteLine("-- open spool at " + DateTime.Now); + } + + private bool CloseSpool() { + if (outStack.Count == 1) { + Application.Error.WriteLine("no open spool."); + return false; + } + + Application.Error.WriteLine("-- close spool at " + DateTime.Now); + Application.SetOutDevice(CloseStackedDevice(outStack)); + Application.SetErrorDevice(CloseStackedDevice(msgStack)); + return true; + } + #endregion + + internal override void SetApplicationContext(IApplicationContext app) { + outStack = new Stack(); + msgStack = new Stack(); + outStack.Push(app.Out); + msgStack.Push(app.Error); + base.SetApplicationContext(app); + } + + #region Public Methods + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + try { + string arg = args.Current; + if (arg.ToLower().Equals("off")) { + CloseSpool(); + } else if (arg.Length > 0) { + OpenSpool(arg); + } else { + return CommandResultCode.SyntaxError; + } + } catch (Exception e) { + System.Console.Error.Write(e.StackTrace); + return CommandResultCode.ExecutionFailed; + } + return CommandResultCode.Success; + } + + public override string LongDescription { + get { + return "\tIf command is followed by a filename, opens a file\n" + + "\tand writes all subsequent output not only to the terminal\n" + + "\tbut as well to this file. With\n" + + "\t spool off\n" + + "\tspooling is stopped and the file is closed. The spool\n" + + "\tcommand works recursivly, i.e. you can open more than one \n" + + "\tfile, and you have to close each of them with 'spool off'\n"; + } + } + #endregion + + #region StackedDevice + class StackedDevice : OutputDevice { + public StackedDevice(OutputDevice a, OutputDevice b) { + _a = a; + _b = b; + + writer = new StackedWriter(a, b); + } + + private OutputDevice _a; + private OutputDevice _b; + + private readonly StackedWriter writer; + + protected override TextWriter Output { + get { return writer; } + } + + public override Encoding Encoding { + get { return _a.Encoding; } + } + + public override bool IsTerminal { + get { return _a.IsTerminal && _b.IsTerminal; } + } + + #region StackedWriter + + class StackedWriter : TextWriter { + private readonly OutputDevice a; + private readonly OutputDevice b; + + public StackedWriter(OutputDevice a, OutputDevice b) { + this.a = a; + this.b = b; + } + + public override Encoding Encoding { + get { return a.Encoding; } + } + + public override void Write(char[] buffer, int index, int count) { + a.Write(buffer, index, count); + b.Write(buffer, index, count); + } + + public override void Flush() { + a.Flush(); + b.Flush(); + } + + public override void Close() { + b.Close(); + } + } + + #endregion + } + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/UnaliasCommand.cs b/src/dshell/Deveel.Console.Commands/UnaliasCommand.cs new file mode 100644 index 0000000..a873916 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/UnaliasCommand.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + internal sealed class UnaliasCommand : Command { + public override string Name { + get { return "unalias"; } + } + + public override string GroupName { + get { return "aliases"; } + } + + public override string ShortDescription { + get { return "removes an aliased command"; } + } + + public override string[] Synopsis { + get { return new string[] { "unalias " }; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + if (args.Count < 1) + return CommandResultCode.SyntaxError; + + int failedCount = 0; + while (args.MoveBack()) { + string alias = args.Current; + if (!Application.Commands.Aliases.HasAlias(alias)) { + Error.WriteLine("unknown alias '" + alias + "'"); + failedCount++; + } else { + Application.Commands.Aliases.RemoveAlias(alias); + } + } + + return failedCount < args.Count ? CommandResultCode.Success : CommandResultCode.ExecutionFailed; + } + + public override IEnumerator Complete(CommandDispatcher dispatcher, string partialCommand, string lastWord) { + return Application.Commands.Aliases.Complete(partialCommand, lastWord); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/UnsetCommand.cs b/src/dshell/Deveel.Console.Commands/UnsetCommand.cs new file mode 100644 index 0000000..78eef8e --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/UnsetCommand.cs @@ -0,0 +1,51 @@ +using System; + +namespace Deveel.Console.Commands { + internal class UnsetCommand : Command { + public override string Name { + get { return "unset"; } + } + + public override string GroupName { + get { return "variables"; } + } + + public override string ShortDescription { + get { return "unsets previously set variables"; } + } + + public override string[] Synopsis { + get { return new string[] { "unset [ ... ]" }; } + } + + private bool UnsetVariable(string varName, ApplicationSettings settings) { + if (!settings.HasVariable(varName)) { + Application.Error.WriteLine("unknown variable {0}", varName); + return false; + } + + settings.RemoveVariable(varName); + return true; + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + ISettingsHandler handler = Application as ISettingsHandler; + if (handler == null) { + Error.WriteLine("The application doesn't support settings."); + return CommandResultCode.ExecutionFailed; + } + + if (!args.MoveNext()) + return CommandResultCode.SyntaxError; + + string varName = args.Current; + bool success = UnsetVariable(varName, handler.Settings); + + while (args.MoveNext()) { + success |= UnsetVariable(args.Current, handler.Settings); + } + + return success ? CommandResultCode.Success : CommandResultCode.ExecutionFailed; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console.Commands/VariablesCommand.cs b/src/dshell/Deveel.Console.Commands/VariablesCommand.cs new file mode 100644 index 0000000..db03e71 --- /dev/null +++ b/src/dshell/Deveel.Console.Commands/VariablesCommand.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console.Commands { + internal class VariablesCommand : Command { + private static readonly ColumnDesign[] VarColumns; + + static VariablesCommand() { + VarColumns = new ColumnDesign[2]; + VarColumns[0] = new ColumnDesign("Variable"); + VarColumns[1] = new ColumnDesign("Value"); + } + + public override string Name { + get { return "variables"; } + } + + public override string GroupName { + get { return "variables"; } + } + + public override string ShortDescription { + get { return "shows a lit of all variables registered in the application."; } + } + + public override CommandResultCode Execute(IExecutionContext context, CommandArguments args) { + ISettingsHandler handler = Application as ISettingsHandler; + if (handler == null) { + Error.WriteLine("The application doesn't support settings."); + return CommandResultCode.ExecutionFailed; + } + + if (args.MoveNext()) + return CommandResultCode.SyntaxError; + + VarColumns[0].ResetWidth(); + VarColumns[1].ResetWidth(); + + TableRenderer table = new TableRenderer(VarColumns, Out); + table.EnableHeader = true; + table.EnableFooter = true; + foreach(KeyValuePair setting in handler.Settings) { + if (setting.Key == ApplicationSettings.SpecialLastCommand) + continue; + + ColumnValue[] row = new ColumnValue[4]; + row[0] = new ColumnValue(setting.Key); + row[1] = new ColumnValue(setting.Value); + table.AddRow(row); + } + + table.CloseTable(); + Error.WriteLine(); + + return CommandResultCode.Success; + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/Answer.cs b/src/dshell/Deveel.Console/Answer.cs new file mode 100644 index 0000000..b3f0bb0 --- /dev/null +++ b/src/dshell/Deveel.Console/Answer.cs @@ -0,0 +1,48 @@ +using System; + +namespace Deveel.Console { + public sealed class Answer { + private readonly Question question; + private readonly int[] selected; + private readonly bool valid; + + internal Answer(Question question, int[] selected, bool valid) { + this.question = question; + this.selected = selected; + this.valid = valid; + } + + public Question Question { + get { return question; } + } + + public bool IsValid { + get { return valid; } + } + + public int SelectedOption { + get { return selected.Length == 1 ? selected[0] : -1; } + } + + public int[] SelectedOptions { + get { return (int[])selected.Clone(); } + } + + public object[] SelectedValues { + get { + object[] values = new object[selected.Length]; + for (int i = 0; i < selected.Length; i++) { + values[i] = question.Options[selected[i]]; + } + return values; + } + } + + public object SelectedValue { + get { + int selectedOption = SelectedOption; + return selectedOption == -1 ? null : question.Options[selectedOption]; + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ApplicationInterruptionHandler.cs b/src/dshell/Deveel.Console/ApplicationInterruptionHandler.cs new file mode 100644 index 0000000..4a938af --- /dev/null +++ b/src/dshell/Deveel.Console/ApplicationInterruptionHandler.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console { + public sealed class ApplicationInterruptionHandler { + private readonly IApplicationContext application; + + public ApplicationInterruptionHandler(IApplicationContext application) { + this.application = application; + once = false; + interruptStack = new Stack(); + } + + private bool once; + private readonly Stack interruptStack; + + public IApplicationContext Application { + get { return application; } + } + + internal void Interrupt() { + if (interruptStack.Count > 0) { + IInterruptable toInterrupt = interruptStack.Peek(); + toInterrupt.Interrupt(); + } else { + if (application is ShellApplication) + ((ShellApplication) application).OnInterrupt(); + System.Console.Error.WriteLine("[Ctrl-C ; interrupted]"); + Environment.Exit(1); + } + } + + internal void Push(IInterruptable interruptable) { + interruptStack.Push(interruptable); + } + + internal void Pop() { + once = false; + interruptStack.Pop(); + } + + internal void Reset() { + once = true; + interruptStack.Clear(); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ApplicationPlugins.cs b/src/dshell/Deveel.Console/ApplicationPlugins.cs new file mode 100644 index 0000000..47c8fbc --- /dev/null +++ b/src/dshell/Deveel.Console/ApplicationPlugins.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Deveel.Console.Commands; + +namespace Deveel.Console { + public sealed class ApplicationPlugins : IEnumerable> { + public ApplicationPlugins(IApplicationContext application) { + this.application = application; + plugins = new SortedDictionary(); + } + + private readonly IApplicationContext application; + private readonly SortedDictionary plugins; + + internal Command LoadPlugin(string typeName) { + Type pluginType; + + try { + pluginType = Type.GetType(typeName, true, true); + } catch(Exception) { + throw new ApplicationException("Unable to find the plugin type '" + typeName + "'."); + } + + return application.Commands.Register(pluginType); + } + + internal void Add(string typeName, Command command) { + plugins.Add(typeName, command); + } + + public bool HasPlugin(string typeName) { + return plugins.ContainsKey(typeName); + } + + public bool Unregister(string typeName) { + Command c; + if (!plugins.TryGetValue(typeName, out c)) + return false; + + application.Commands.Unregister(c); + return true; + } + + public IEnumerator> GetEnumerator() { + return plugins.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ApplicationPropertyRegistry.cs b/src/dshell/Deveel.Console/ApplicationPropertyRegistry.cs new file mode 100644 index 0000000..79c1084 --- /dev/null +++ b/src/dshell/Deveel.Console/ApplicationPropertyRegistry.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console { + internal class ApplicationPropertyRegistry : PropertyRegistry { + public ApplicationPropertyRegistry(ShellApplication context, ConfigurationFile config) + : base(context) { + this.config = config; + } + + private bool dirty; + private readonly ConfigurationFile config; + + public override void RegisterProperty(string name, PropertyHolder holder) { + base.RegisterProperty(name, holder); + dirty = true; + } + + public override void SetProperty(string name, string value) { + base.SetProperty(name, value); + dirty = true; + } + + public override void UnregisterProperty(string name) { + base.UnregisterProperty(name); + dirty = true; + } + + internal void Save() { + if (dirty) { + config.ClearValues(); + + foreach (KeyValuePair entry in Properties) + config.SetValue(entry.Key, entry.Value.Value); + + config.Save("Properties"); + + dirty = false; + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ApplicationSettings.cs b/src/dshell/Deveel.Console/ApplicationSettings.cs new file mode 100644 index 0000000..3f59b90 --- /dev/null +++ b/src/dshell/Deveel.Console/ApplicationSettings.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +using Deveel.Collections; +using Deveel.Console.Commands; + +namespace Deveel.Console { + public sealed class ApplicationSettings : IEnumerable> { + public ApplicationSettings(IApplicationContext application) { + this.application = application; + specialVariables = new List(); + specialVariables.Add(SpecialLastCommand); + variables = new SortedDictionary(); + application.Commands.CommandExecuted += CommandExecuted; + } + + void CommandExecuted(object sender, Commands.CommandEventArgs e) { + SetVariable(SpecialLastCommand, e.CommandText.Trim()); + } + + private readonly IApplicationContext application; + private readonly List specialVariables; + private readonly SortedDictionary variables; + + internal const string SpecialLastCommand = "_SHELLAPP_LAST_COMMAND"; + + internal IList SpecialVariables { + get { return specialVariables; } + } + + internal IDictionary Variables { + get { return variables; } + } + + public void SetVariable(string name, string value) { + variables[name] = value; + } + + public void RemoveVariable(string name) { + variables.Remove(name); + } + + public bool HasVariable(string name) { + return variables.ContainsKey(name); + } + + public IEnumerator CompleteUserVariable(string variable) { + if (!variable.StartsWith("$")) + return null; // strange, shouldn't happen. + + bool hasBrace = variable.StartsWith("${"); + string prefix = (hasBrace ? "${" : "$"); + string postfix = (hasBrace ? "}" : ""); + string name = variable.Substring(prefix.Length); + + SortedMatchEnumerator en = new SortedMatchEnumerator(name, variables.Keys.GetEnumerator()); + en.Prefix = prefix; + en.Suffix = postfix; + return en; + } + + public string Substitute(string input) { + int pos = 0; + int endpos = 0; + int startVar = 0; + StringBuilder result = new StringBuilder(); + string varname; + bool hasBrace = false; + bool knownVar = false; + + if (input == null) + return null; + + if (variables == null) + return input; + + while ((pos = input.IndexOf('$', pos)) >= 0) { + startVar = pos; + if (input[pos + 1] == '$') { + // quoting '$' + result.Append(input.Substring(endpos, pos - endpos)); + + endpos = pos + 1; + pos += 2; + continue; + } + + hasBrace = (input[pos + 1] == '{'); + + // text between last variable and here + result.Append(input.Substring(endpos, pos - endpos)); + + if (hasBrace) + pos++; + + endpos = pos + 1; + while (endpos < input.Length && + Char.IsLetterOrDigit(input[endpos])) + endpos++; + + varname = input.Substring(pos + 1, endpos - (pos + 1)); + + if (hasBrace) { + while (endpos < input.Length && input[endpos] != '}') + ++endpos; + + ++endpos; + } + if (endpos > input.Length) { + if (variables.ContainsKey(varname)) + System.Console.Error.WriteLine("warning: missing '}' for variable '" + varname + "'."); + + result.Append(input.Substring(startVar)); + break; + } + + if (variables.ContainsKey(varname)) { + result.Append(variables[varname]); + } else { + System.Console.Error.WriteLine("warning: variable '" + varname + "' not set."); + result.Append(input.Substring(startVar, endpos - startVar)); + } + + pos = endpos; + } + + if (endpos < input.Length) + result.Append(input.Substring(endpos)); + + return result.ToString(); + } + + public IEnumerator> GetEnumerator() { + return variables.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + internal IEnumerator Complete(string partialLine, string lastWord) { + string[] st = partialLine.Split(' '); + string cmd = st[0]; + int argc = st.Length; + + Command setCommand = application.Commands.GetCommand(typeof(SetCommand)); + Command unsetCommand = application.Commands.GetCommand(typeof(UnsetCommand)); + + List alreadyGiven = new List(); + if (setCommand != null && cmd.Equals(setCommand.Name)) { + if (argc > (lastWord.Equals(String.Empty) ? 0 : 1)) + return null; + } else if (unsetCommand != null && cmd.Equals(unsetCommand.Name)) { // 'unset' + // remember all variables, that have already been given on + // the commandline and exclude from completion.. + for (int i = 1; i < st.Length; i++) { + alreadyGiven.Add(st[i]); + } + } + + return new SetSortedMatchEnumerator(alreadyGiven, lastWord, variables); + } + + class SetSortedMatchEnumerator : SortedMatchEnumerator { + public SetSortedMatchEnumerator(List alreadyGiven, string lastWord, SortedDictionary variables) + : base(lastWord, variables) { + this.alreadyGiven = alreadyGiven; + } + + private readonly List alreadyGiven; + + protected override bool Exclude(string current) { + return alreadyGiven.Contains(current); + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/BooleanPropertyHolder.cs b/src/dshell/Deveel.Console/BooleanPropertyHolder.cs new file mode 100644 index 0000000..046ee94 --- /dev/null +++ b/src/dshell/Deveel.Console/BooleanPropertyHolder.cs @@ -0,0 +1,32 @@ +using System; + +namespace Deveel.Console { + public abstract class BooleanPropertyHolder : EnumeratedPropertyHolder { + #region ctor + + protected BooleanPropertyHolder() : + base(BoolValues) { + } + + protected BooleanPropertyHolder(bool initialValue) : + this() { + Value = initialValue ? "true" : "false"; + } + + #endregion + + #region Fields + private readonly static string[] BoolValues = { "0", "off", "false", "1", "on", "true" }; + #endregion + + #region Protected Methods + protected override void OnEnumeratedPropertyChanged(int index, string value) { + OnBooleanPropertyChanged(index >= (BoolValues.Length / 2)); + } + #endregion + + #region Public Methods + public abstract void OnBooleanPropertyChanged(bool value); + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/CancelWriter.cs b/src/dshell/Deveel.Console/CancelWriter.cs new file mode 100644 index 0000000..e3d0916 --- /dev/null +++ b/src/dshell/Deveel.Console/CancelWriter.cs @@ -0,0 +1,104 @@ +using System; + +namespace Deveel.Console { + /// + /// Allows to write a string to the screen and cancel it + /// afterwards (with Backspaces). + /// + public sealed class CancelWriter { + private const string BACKSPACE = "\b"; + + private readonly OutputDevice _out; + private readonly bool _doWrite; + + /** the string that has been written. 'null', if + * nothing has been written or it is cancelled + */ + private String _writtenString; + + public CancelWriter(OutputDevice output) { + _out = output; + _doWrite = _out.IsTerminal; + _writtenString = null; + } + + + /// + /// Gets a value indicating wether this cancel writer will + /// print anything. + /// + public bool IsPrinting { + get { return _doWrite; } + } + + /// + /// Gets a value indicating if this cancel writer has any + /// cancellable output. + /// + public bool HasCancellableOutput { + get { return _writtenString != null; } + } + + /// + /// Print message to screen and cancel out any + /// previous message. + /// + /// The string to write out. + public void Write(String str) { + if (!_doWrite) + return; + + int charCount = Cancel(false); + _out.Write(str); + _writtenString = str; + + /* wipe the difference between the previous + * message and this one */ + int lenDiff = charCount - str.Length; + if (lenDiff > 0) { + writeChars(lenDiff, " "); + writeChars(lenDiff, BACKSPACE); + } + _out.Flush(); + } + + /// + /// Cancels out the written string and wipe it + /// with spaces. + /// + /// + public int Cancel() { + return Cancel(true); + } + + /// + /// Cancels the output of the underlying + /// output. + /// + /// Indicates if the written characters + /// should be wiped out with spaces. Otherwise, the cursor is + /// placed at the beginning of the string without wiping. + /// + /// Returns number of characters cancelled. + /// + public int Cancel(bool wipeOut) { + if (_writtenString == null) + return 0; + int backspaceCount = _writtenString.Length; + writeChars(backspaceCount, BACKSPACE); + if (wipeOut) { + writeChars(backspaceCount, " "); + writeChars(backspaceCount, BACKSPACE); + _out.Flush(); + } + _writtenString = null; + return backspaceCount; + } + + private void writeChars(int count, String str) { + for (int i = 0; i < count; ++i) { + _out.Write(str); + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ColumnAlignment.cs b/src/dshell/Deveel.Console/ColumnAlignment.cs new file mode 100644 index 0000000..1d591e1 --- /dev/null +++ b/src/dshell/Deveel.Console/ColumnAlignment.cs @@ -0,0 +1,9 @@ +using System; + +namespace Deveel.Console { + public enum ColumnAlignment { + Left = 1, + Center = 2, + Right = 3 + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ColumnDesign.cs b/src/dshell/Deveel.Console/ColumnDesign.cs new file mode 100644 index 0000000..5d4a032 --- /dev/null +++ b/src/dshell/Deveel.Console/ColumnDesign.cs @@ -0,0 +1,68 @@ +using System; + +namespace Deveel.Console { + public sealed class ColumnDesign { + #region ctor + public ColumnDesign(string label, ColumnAlignment alignment, int autoWrap) { + this.label = label; + this.alignment = alignment; + initialWidth = label.Length; + width = initialWidth; + display = true; + autoWrapCol = autoWrap; + } + + public ColumnDesign(string label, ColumnAlignment alignment) + : this(label, alignment, -1) { + } + + public ColumnDesign(string label) + : this(label, ColumnAlignment.Left) { + } + #endregion + + #region Fields + private readonly ColumnAlignment alignment; + private readonly string label; + private int initialWidth; + private int width; + private bool display; + // wrap columns automatically at this column; -1 = disabled + private int autoWrapCol; + #endregion + + #region Properties + public bool Display { + get { return display; } + set { display = value; } + } + + public int Width { + get { return width; } + set { + if (value > width) + width = value; + } + } + + public string Label { + get { return label; } + } + + public ColumnAlignment Alignment { + get { return alignment; } + } + + public int AutoWrap { + get { return autoWrapCol; } + set { autoWrapCol = value; } + } + #endregion + + #region Public Methods + public void ResetWidth() { + width = initialWidth; + } + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ColumnValue.cs b/src/dshell/Deveel.Console/ColumnValue.cs new file mode 100644 index 0000000..c252d4c --- /dev/null +++ b/src/dshell/Deveel.Console/ColumnValue.cs @@ -0,0 +1,69 @@ +using System; + +namespace Deveel.Console { + public sealed class ColumnValue { + #region ctor + public ColumnValue(string value) { + if (value == null) { + width = NullLength; + columnValues = null; + } else { + width = 0; + //TODO: Check if is better {newline} + string[] tok = value.Split('\n'); + columnValues = new string[tok.Length]; + for (int i = 0; i < columnValues.Length; ++i) { + string line = (string)tok[i]; + int lWidth = line.Length; + columnValues[i] = line; + if (lWidth > width) { + width = lWidth; + } + } + } + pos = 0; + } + + public ColumnValue(int value) + : this(value.ToString()) { + } + #endregion + + #region Fields + internal readonly static string NullText = "[NULL]"; + internal readonly static int NullLength = NullText.Length; + + private string[] columnValues; // multi-rows + private int width; + private int pos; + #endregion + + #region Properties + public int Width { + get { return width; } + } + + public bool IsNull { + get { return (columnValues == null); } + } + #endregion + + #region Public Methods + public bool HasNextLine() { + return (columnValues != null && pos < columnValues.Length); + } + + public string GetNextLine() { + string result = ""; + if (columnValues == null) { + if (pos == 0) + result = NullText; + } else if (pos < columnValues.Length) { + result = columnValues[pos]; + } + ++pos; + return result; + } + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ConfigurationFile.cs b/src/dshell/Deveel.Console/ConfigurationFile.cs new file mode 100644 index 0000000..0b802ba --- /dev/null +++ b/src/dshell/Deveel.Console/ConfigurationFile.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +using Deveel.Util; + +namespace Deveel.Console { + public delegate void StreamAction(Stream stream); + + public class ConfigurationFile { + public ConfigurationFile(string configFile) { + _configFile = configFile; + LoadFromFile(); + } + + private readonly string _configFile; + private byte[] inputDigest; + private Properties properties; + private Dictionary props; + + public string FileName { + get { return _configFile; } + } + + internal IDictionary Properties { + get { + if (props == null) { + props = new Dictionary(); + foreach(KeyValuePair entry in properties) + props[entry.Key.ToString()] = entry.Value == null ? null : entry.Value.ToString(); + } + return props; + } + } + + private Stream GetInput() { + FileStream input = null; + + try { + MD5 md5 = MD5.Create(); + input = new InputFileStream(this, md5); + if (!input.CanRead) + return null; + + return input; + } catch (Exception) { + if (input != null) + input.Close(); + + return null; + } + } + + private class InputFileStream : FileStream { + public InputFileStream(ConfigurationFile file, HashAlgorithm algorithm) + : base(file._configFile, FileMode.Open, FileAccess.Read, FileShare.Read) { + this.file = file; + this.algorithm = algorithm; + readStream = new MemoryStream(); + } + + private bool closed; + private readonly MemoryStream readStream; + private readonly HashAlgorithm algorithm; + private readonly ConfigurationFile file; + + public override int Read(byte[] array, int offset, int count) { + int len = base.Read(array, offset, count); + readStream.Write(array, offset, len); + return len; + } + + public override void Close() { + if (!closed) { + base.Close(); + file.inputDigest = algorithm.ComputeHash(readStream); + } + closed = true; + } + } + + public void Read(StreamAction action) { + try { + Stream input = GetInput(); + try { + action(input); + } finally { + if (input != null) + input.Close(); + } + } catch (Exception) { + } + } + + public void Write(StreamAction action) { + string tmpFile = null; + try { + tmpFile = CreateTempFile("config-", ".tmp", Path.GetDirectoryName(_configFile)); + MD5 outputDigest = MD5.Create(); + OutputFileStream output = new OutputFileStream(tmpFile, outputDigest); + try { + action(output); + } finally { + output.Close(); + } + if (inputDigest == null || !File.Exists(_configFile) || + !ArraysAreEqual(inputDigest, output.ComputedHash)) { + if (File.Exists(_configFile)) + File.Delete(_configFile); + File.Move(tmpFile, _configFile); + } + } catch (Exception e) { + System.Console.Error.WriteLine("do not write config. Error occured: " + e); + } finally { + if (tmpFile != null) + File.Delete(tmpFile); + } + } + + private static bool ArraysAreEqual(Array a, Array b) { + if (a.Length != b.Length) + return false; + + for (int i = 0; i < a.Length; i++) { + object aValue = a.GetValue(i); + object bValue = b.GetValue(i); + if (!Equals(aValue, bValue)) + return false; + } + return true; + } + + private class OutputFileStream : FileStream { + public OutputFileStream(string file, HashAlgorithm algorithm) + : base(file, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None) { + this.algorithm = algorithm; + writeStream = new MemoryStream(); + } + + private byte[] computedHash; + private readonly HashAlgorithm algorithm; + private bool closed; + private readonly MemoryStream writeStream; + + public byte[] ComputedHash { + get { return computedHash; } + } + + public override void Write(byte[] array, int offset, int count) { + writeStream.Write(array, offset, count); + base.Write(array, offset, count); + } + + public override void Close() { + if (!closed) { + base.Close(); + writeStream.Flush(); + writeStream.Close(); + computedHash = algorithm.ComputeHash(writeStream.ToArray()); + } + closed = true; + } + } + + private static string CreateTempFile(string prefix, string ext, string path) { + Random rnd = new Random(); + string file; + while (File.Exists((file = Path.Combine(path, GenerateTempFileName(rnd, prefix, ext))))) + continue; + return file; + } + + private static string GenerateTempFileName(Random rnd, string prefix, string ext) { + const string hex = "ABCDEFGH0123456789"; + StringBuilder sb = new StringBuilder(prefix.Length + 10 + ext.Length); + sb.Append(prefix); + for (int i = 0; i < 10; i++) + sb.Append(hex[rnd.Next(hex.Length)]); + sb.Append(ext); + return sb.ToString(); + } + + private void LoadFromFile() { + properties = new Properties(); + + Stream input = GetInput(); + if (input != null) { + try { + properties.Load(input); + } catch (Exception e) { + System.Console.Error.WriteLine(e.Message); // can't help. + } finally { + input.Close(); + } + } + } + + public void ClearValues() { + properties.Clear(); + } + + public void SetValue(string key, string value) { + if (value == null) + RemoveValue(key); + else + properties.SetProperty(key, value); + } + + public void RemoveValue(string key) { + properties.Remove(key); + } + + public string GetValue(string key) { + return properties.GetProperty(key); + } + + public void Merge(IDictionary props) { + // all properties, that are not present compared to last read + // should be removed after merge. + ArrayList locallyRemovedProperties = new ArrayList(); + locallyRemovedProperties.AddRange(properties.Keys); + foreach (object key in props.Keys) + locallyRemovedProperties.Remove(key); + + foreach (object key in locallyRemovedProperties) + properties.Remove(key); + + foreach (DictionaryEntry entry in props) + properties[entry.Key] = entry.Value; + } + + public void Save(string comment) { + PropertiesWriter writer = new PropertiesWriter(properties, comment); + Write(new StreamAction(writer.Store)); + } + + public void Save() { + Save(null); + } + + private class PropertiesWriter { + public PropertiesWriter(Properties outputProperties, string comment) { + this.outputProperties = outputProperties; + this.comment = comment; + } + + private readonly string comment; + private readonly Properties outputProperties; + + public void Store(Stream stream) { + outputProperties.Store(stream, comment); + stream.Close(); + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ConsoleInputDevice.cs b/src/dshell/Deveel.Console/ConsoleInputDevice.cs new file mode 100644 index 0000000..0424d5f --- /dev/null +++ b/src/dshell/Deveel.Console/ConsoleInputDevice.cs @@ -0,0 +1,47 @@ +using System; +using System.Text; + +namespace Deveel.Console { + public sealed class ConsoleInputDevice : InputDevice { + private string prompt; + private string emptyPrompt; + private string currentPrompt; + private int lineCount = -1; + + public string Prompt { + get { return prompt; } + set { + prompt = value; + if (!String.IsNullOrEmpty(prompt)) { + StringBuilder emptyBuilder = new StringBuilder(); + for (int i = 0; i < prompt.Length; i++) { + emptyBuilder.Append(' '); + } + emptyPrompt = emptyBuilder.ToString(); + currentPrompt = prompt; + } + } + } + + public override void CompleteLine() { + lineCount = -1; + currentPrompt = prompt; + } + + public override string ReadLine() { + string line = Readline.ReadLine(currentPrompt); + if (++lineCount > 0) + currentPrompt = emptyPrompt; + + return line; + } + + public override int Read() { + throw new NotSupportedException(); + } + + public override int Read(char[] buffer, int index, int count) { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ConsoleOutputDevice.cs b/src/dshell/Deveel.Console/ConsoleOutputDevice.cs new file mode 100644 index 0000000..6d276bf --- /dev/null +++ b/src/dshell/Deveel.Console/ConsoleOutputDevice.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Deveel.Console { + internal class ConsoleOutputDevice : OutputDevice { + public ConsoleOutputDevice(TextWriter std, bool isOut) { + this.std = std; +#if !MONO + if (isOut) { + stdHandle = GetStdHandle(-11); + } +#endif + } + + private string prompt; + private readonly TextWriter std; +#if !MONO + private IntPtr stdHandle; +#endif + + public override Encoding Encoding { + get { return std.Encoding; } + } + + protected override TextWriter Output { + get { return std; } + } + + public override int LineMaxWidth { + get { return System.Console.WindowWidth; } + } + + protected override void AttributeBold() { +#if !MONO + SetConsoleTextAttribute(stdHandle, CharacterAttributes.FOREGROUND_INTENSITY); +#endif + } + + protected override void AttributeGrey() { + System.Console.ForegroundColor = ConsoleColor.Gray; + } + + protected override void AttributeReset() { + System.Console.ResetColor(); + } + + protected override char[] OnNewLine() { + return !String.IsNullOrEmpty(prompt) ? prompt.ToCharArray() : new char[0]; + } + + internal string Prompt { + get { return prompt; } + set { prompt = value; } + } + + //TODO: + public override bool IsTerminal { + get { return true; } + } + +#if !MONO + [DllImport("kernel32.dll")] + private static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleTextAttribute(IntPtr hConsoleOutput, CharacterAttributes wAttributes); + + private enum CharacterAttributes { + FOREGROUND_BLUE = 0x0001, + FOREGROUND_GREEN = 0x0002, + FOREGROUND_RED = 0x0004, + FOREGROUND_INTENSITY = 0x0008, + BACKGROUND_BLUE = 0x0010, + BACKGROUND_GREEN = 0x0020, + BACKGROUND_RED = 0x0040, + BACKGROUND_INTENSITY = 0x0080, + COMMON_LVB_LEADING_BYTE = 0x0100, + COMMON_LVB_TRAILING_BYTE = 0x0200, + COMMON_LVB_GRID_HORIZONTAL = 0x0400, + COMMON_LVB_GRID_LVERTICAL = 0x0800, + COMMON_LVB_GRID_RVERTICAL = 0x1000, + COMMON_LVB_REVERSE_VIDEO = 0x4000, + COMMON_LVB_UNDERSCORE = 0x8000 + } +#endif + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/EnumeratedPropertyHolder.cs b/src/dshell/Deveel.Console/EnumeratedPropertyHolder.cs new file mode 100644 index 0000000..43c4135 --- /dev/null +++ b/src/dshell/Deveel.Console/EnumeratedPropertyHolder.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Deveel.Console { + public abstract class EnumeratedPropertyHolder : PropertyHolder { + #region ctor + public EnumeratedPropertyHolder(string[] enumeratedValues) { + values = enumeratedValues; + completer = new NameCompleter(enumeratedValues); + } + + public EnumeratedPropertyHolder(ICollection enumeratedValues) { + values = (string[])(new List(enumeratedValues)).ToArray(); + completer = new NameCompleter(enumeratedValues.GetEnumerator()); + } + #endregion + + #region Fields + private string[] values; + private NameCompleter completer; + #endregion + + #region Protected Methods + protected override string OnValueChanged(string newValue) { + if (newValue == null) + throw new ArgumentNullException("newValue"); + + newValue = newValue.Trim(); + + IEnumerator possibleValues = completer.GetAlternatives(newValue); + if (possibleValues == null || !possibleValues.MoveNext()) { + StringBuilder expected = new StringBuilder(); + for (int i = 0; i < values.Length; ++i) { + if (i != 0) + expected.Append(", "); + expected.Append(values[i]); + } + throw new Exception("'" + newValue + "' does not match any of [" + expected.ToString() + "]"); + } + + string value = (string)possibleValues.Current; + + //CHECK: check this... + if (possibleValues.MoveNext()) { + StringBuilder matching = new StringBuilder(value); + do { + matching.Append(", "); + matching.Append((string)possibleValues.Current); + } while (possibleValues.MoveNext()); + + throw new Exception("'" + newValue + "' ambiguous. Matches [" + matching.ToString() + "]"); + } + + int index = -1; + // small size of array -- linear search acceptable + for (int i = 0; i < values.Length; ++i) { + if (value.Equals(values[i])) { + index = i; + break; + } + } + + OnEnumeratedPropertyChanged(index, value); + return value; + } + + protected abstract void OnEnumeratedPropertyChanged(int index, string value); + #endregion + + #region Public Methods + public override IEnumerator CompleteValue(string partialValue) { + return completer.GetAlternatives(partialValue); + } + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/FileInputDevice.cs b/src/dshell/Deveel.Console/FileInputDevice.cs new file mode 100644 index 0000000..b1160ae --- /dev/null +++ b/src/dshell/Deveel.Console/FileInputDevice.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; + +namespace Deveel.Console { + public sealed class FileInputDevice : InputDevice { + private readonly string fileName; + private readonly StreamReader reader; + + public FileInputDevice(string fileName) { + this.fileName = fileName; + reader = new StreamReader(fileName); + } + + public string FileName { + get { return fileName; } + } + + public override int Read() { + return reader.Read(); + } + + public override int Read(char[] buffer, int index, int count) { + return reader.Read(buffer, index, count); + } + + public override string ReadLine() { + return reader.ReadLine(); + } + + public override void Close() { + reader.Close(); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/IApplicationContext.cs b/src/dshell/Deveel.Console/IApplicationContext.cs new file mode 100644 index 0000000..f4907a0 --- /dev/null +++ b/src/dshell/Deveel.Console/IApplicationContext.cs @@ -0,0 +1,32 @@ +using System; + +using Deveel.Console.Commands; + +namespace Deveel.Console { + public interface IApplicationContext : IExecutionContext { + OutputDevice Out { get; } + + OutputDevice Error { get; } + + InputDevice Input { get; } + + CommandDispatcher Commands { get; } + + ApplicationInterruptionHandler Interruption { get; } + + string PartialLine { get; } + + IExecutionContext ActiveContext { get; } + + bool IsRunning { get; } + + + void SetOutDevice(OutputDevice device); + + void SetErrorDevice(OutputDevice device); + + void Execute(IExecutionContext context, string commandText); + + void Exit(int code); + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/IExecutionContext.cs b/src/dshell/Deveel.Console/IExecutionContext.cs new file mode 100644 index 0000000..f870497 --- /dev/null +++ b/src/dshell/Deveel.Console/IExecutionContext.cs @@ -0,0 +1,7 @@ +using System; + +namespace Deveel.Console { + public interface IExecutionContext : IDisposable { + bool IsIsolated { get; } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/IInformationProvider.cs b/src/dshell/Deveel.Console/IInformationProvider.cs new file mode 100644 index 0000000..7bc3f6f --- /dev/null +++ b/src/dshell/Deveel.Console/IInformationProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console { + public interface IInformationProvider { + bool IsInfoSupported(string name); + + ColumnDesign[] GetColumns(string name); + + IList GetValues(string name); + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/IInterruptable.cs b/src/dshell/Deveel.Console/IInterruptable.cs new file mode 100644 index 0000000..fedc72f --- /dev/null +++ b/src/dshell/Deveel.Console/IInterruptable.cs @@ -0,0 +1,7 @@ +using System; + +namespace Deveel.Console { + public interface IInterruptable { + void Interrupt(); + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/IOptionsHandler.cs b/src/dshell/Deveel.Console/IOptionsHandler.cs new file mode 100644 index 0000000..5c1ec1e --- /dev/null +++ b/src/dshell/Deveel.Console/IOptionsHandler.cs @@ -0,0 +1,11 @@ +using System; + +using Deveel.Configuration; + +namespace Deveel.Console { + public interface IOptionsHandler { + void RegisterOptions(Options options); + + bool HandleCommandLine(CommandLine commandLine); + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/IPluginHandler.cs b/src/dshell/Deveel.Console/IPluginHandler.cs new file mode 100644 index 0000000..bf1cfeb --- /dev/null +++ b/src/dshell/Deveel.Console/IPluginHandler.cs @@ -0,0 +1,7 @@ +using System; + +namespace Deveel.Console { + public interface IPluginHandler { + ApplicationPlugins Plugins { get; } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/IPropertyHandler.cs b/src/dshell/Deveel.Console/IPropertyHandler.cs new file mode 100644 index 0000000..ea9cf07 --- /dev/null +++ b/src/dshell/Deveel.Console/IPropertyHandler.cs @@ -0,0 +1,7 @@ +using System; + +namespace Deveel.Console { + public interface IPropertyHandler { + PropertyRegistry Properties { get; } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ISettingsHandler.cs b/src/dshell/Deveel.Console/ISettingsHandler.cs new file mode 100644 index 0000000..e51b0f6 --- /dev/null +++ b/src/dshell/Deveel.Console/ISettingsHandler.cs @@ -0,0 +1,7 @@ +using System; + +namespace Deveel.Console { + public interface ISettingsHandler { + ApplicationSettings Settings { get; } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/InputDevice.cs b/src/dshell/Deveel.Console/InputDevice.cs new file mode 100644 index 0000000..33a88ea --- /dev/null +++ b/src/dshell/Deveel.Console/InputDevice.cs @@ -0,0 +1,13 @@ +using System; +using System.IO; + +namespace Deveel.Console { + public abstract class InputDevice : TextReader { + public virtual bool IsTerminal { + get { return false; } + } + + public virtual void CompleteLine() { + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/LineExecutionResultCode.cs b/src/dshell/Deveel.Console/LineExecutionResultCode.cs new file mode 100644 index 0000000..34e0288 --- /dev/null +++ b/src/dshell/Deveel.Console/LineExecutionResultCode.cs @@ -0,0 +1,24 @@ +using System; + +namespace Deveel.Console { + /// + /// Defines the result code for the execution of a single + /// input line. + /// + public enum LineExecutionResultCode { + /// + /// The line parsed has been executed successfully. + /// + Executed = 1, + + /// + /// The line inserted was empty (zero-length). + /// + Empty = 2, + + /// + /// The line parsed is incomplete. + /// + Incomplete = 3 + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/NameCompleter.cs b/src/dshell/Deveel.Console/NameCompleter.cs new file mode 100644 index 0000000..bfeb271 --- /dev/null +++ b/src/dshell/Deveel.Console/NameCompleter.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +using Deveel.Collections; + +namespace Deveel.Console { + public class NameCompleter { + #region ctor + public NameCompleter() { + nameSet = new SortedList(); + canonicalNames = new SortedDictionary(); + } + + public NameCompleter(IEnumerator names) : + this() { + while (names.MoveNext()) { + AddName(names.Current); + } + } + + public NameCompleter(ICollection c) : + this(c.GetEnumerator()) { + } + + public NameCompleter(string[] names) : + this() { + for (int i = 0; i < names.Length; ++i) { + AddName(names[i]); + } + } + #endregion + + #region Fields + private readonly SortedList nameSet; + private readonly SortedDictionary canonicalNames; + #endregion + + #region Public Methods + public void AddName(string name) { + nameSet.Add(name, String.Empty); + canonicalNames.Add(name.ToLower(), name); + } + + public IEnumerator GetNamesEnumerator() { + return nameSet.Keys.GetEnumerator(); + } + + public ICollection GetNames() { + return nameSet.Keys; + } + + public IEnumerator GetAlternatives(string partialName) { + // first test, if we find the name directly + IEnumerator testIt = SubsetCollection.Tail(nameSet, partialName).GetEnumerator(); + string testMatch = (testIt.MoveNext()) ? (string)testIt.Current : null; + if (testMatch == null || !testMatch.StartsWith(partialName)) { + string canonical = partialName.ToLower(); + testIt = SubsetDictionary.Tail(canonicalNames, canonical).Keys.GetEnumerator(); + testMatch = (testIt.MoveNext()) ? (string)testIt.Current : null; + if (testMatch == null || !testMatch.StartsWith(canonical)) + return null; // nope. + string foundName = (string)canonicalNames[testMatch]; + partialName = foundName.Substring(0, partialName.Length); + } + + return new NameEnumerator(this, partialName); + } + #endregion + + #region NameEnumerator + class NameEnumerator : IEnumerator { + #region ctor + public NameEnumerator(NameCompleter completer, string partialName) { + enumerator = SubsetCollection.Tail(completer.nameSet, partialName).GetEnumerator(); + namePattern = partialName; + } + #endregion + + #region Fields + private IEnumerator enumerator; + private string namePattern; + private string current; + #endregion + + #region IEnumerator Members + + public string Current { + get { return current; } + } + + object IEnumerator.Current { + get { return Current; } + } + + public bool MoveNext() { + if (enumerator.MoveNext()) { + current = (string)enumerator.Current; + if (current.StartsWith(namePattern)) + return true; + } + return false; + } + + public void Reset() { + } + + public void Dispose() { + } + + #endregion + } + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/OutputDevice.cs b/src/dshell/Deveel.Console/OutputDevice.cs new file mode 100644 index 0000000..e7e9ee0 --- /dev/null +++ b/src/dshell/Deveel.Console/OutputDevice.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; + +namespace Deveel.Console { + public abstract class OutputDevice : TextWriter { + private int lineWidth; + + public virtual bool IsTerminal { + get { return false; } + } + + public virtual int LineMaxWidth { + get { return -1; } + } + + protected abstract TextWriter Output { get; } + + protected virtual void AttributeBold() { + } + + protected virtual void AttributeGrey() { + } + + protected virtual void AttributeReset() { + } + + protected virtual char [] OnNewLine() { + return new char[0]; + } + + public virtual void WriteBold(string message) { + AttributeBold(); + Write(message); + AttributeReset(); + } + + public virtual void WriteGrey(string value) { + AttributeGrey(); + Write(value); + AttributeReset(); + } + + public override void Write(char[] buffer, int index, int count) { + int lineMaxWidth = LineMaxWidth; + if (lineMaxWidth > 0) { + if (count + lineWidth > lineMaxWidth) { + int lines = (int) Math.Round((double)count/lineMaxWidth, MidpointRounding.ToEven); + + char[] lineTerminator = NewLine.ToCharArray(); + char[] toAdd = OnNewLine(); + char[] newBuffer = new char[lines*(lineTerminator.Length + toAdd.Length + lineMaxWidth)]; + + int srcOffset = 0; + int destOffset = 0; + for (int i = 0; i < lines; i++) { + int endLine = lineMaxWidth - lineTerminator.Length; + Array.Copy(buffer, srcOffset, newBuffer, destOffset, endLine); + Array.Copy(lineTerminator, 0, newBuffer, endLine, lineTerminator.Length); + Array.Copy(toAdd, 0, newBuffer, endLine + lineTerminator.Length, toAdd.Length); + srcOffset += endLine; + destOffset += lineMaxWidth; + lineWidth += lineMaxWidth; + } + + buffer = newBuffer; + count = buffer.Length; + lineWidth = 0; + } else { + char[] lineTerminator = NewLine.ToCharArray(); + char[] test = new char[lineTerminator.Length]; + bool hasEOL = true; + if (count < lineTerminator.Length) { + hasEOL = false; + } else { + Array.Copy(buffer, count - lineTerminator.Length, test, 0, lineTerminator.Length); + for (int i = 0; i < lineTerminator.Length; i++) { + if (test[i] != lineTerminator[i]) { + hasEOL = false; + break; + } + } + } + + if (hasEOL) { + lineWidth = 0; + } else { + lineWidth += count; + } + } + } + + Output.Write(buffer, index, count); + } + + public override void Flush() { + Output.Flush(); + } + + public override void Close() { + Output.Close(); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ProgressWriter.cs b/src/dshell/Deveel.Console/ProgressWriter.cs new file mode 100644 index 0000000..87a7d01 --- /dev/null +++ b/src/dshell/Deveel.Console/ProgressWriter.cs @@ -0,0 +1,78 @@ +using System; + +namespace Deveel.Console { + /// + /// A utility class that can write the progress of an operation + /// to the screen. + /// + public class ProgressWriter { + private const int DefaultScreenWidth = 65; + + /** min time before presenting an eta */ + private const long MinEtaRunningTime = 30 * 1000L; + /** min time between two eta updates */ + private const long MinEtaDiffTime = (1 * 1000L); + + private readonly long _expectedTargetValue; + private readonly OutputDevice _out; + private readonly DateTime _startTime; + private readonly CancelWriter _etaWriter; + + private DateTime _lastEtaUpdate; + + private int _progressDots; + private int _screenWidth; + + public ProgressWriter(long expectedTargetValue, OutputDevice output) { + _expectedTargetValue = expectedTargetValue; + _out = output; + _progressDots = 0; + _startTime = DateTime.Now; + _lastEtaUpdate = DateTime.MinValue; + _etaWriter = new CancelWriter(_out); + ScreenWidth = DefaultScreenWidth; + } + + public int ScreenWidth { + set { _screenWidth = value; } + get { return _screenWidth; } + } + + public void Update(long value) { + if (_expectedTargetValue > 0 && value <= _expectedTargetValue) { + long newDots = (_screenWidth * value) / _expectedTargetValue; + if (newDots > _progressDots) { + _etaWriter.Cancel(false); + while (_progressDots < newDots) { + _out.Write("."); + ++_progressDots; + } + _out.Flush(); + } + WriteEta(value); + } + } + + public void Finish() { + _etaWriter.Cancel(); + } + + private void WriteEta(long value) { + if (!_etaWriter.IsPrinting) + return; + + DateTime now = DateTime.Now; + long runningTime = (long)(now - _startTime).TotalMilliseconds; + if (runningTime < MinEtaRunningTime) + return; + + long lastUpdateDiff = (long)(now - _lastEtaUpdate).TotalMilliseconds; + if (!_etaWriter.HasCancellableOutput || lastUpdateDiff > MinEtaDiffTime) { + long etaTime = _expectedTargetValue * runningTime / value; + long rest = etaTime - runningTime; + _etaWriter.Write("ETA: " + TimeRenderer.RenderTime(rest)); + _lastEtaUpdate = now; + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/PropertyHolder.cs b/src/dshell/Deveel.Console/PropertyHolder.cs new file mode 100644 index 0000000..e081ee2 --- /dev/null +++ b/src/dshell/Deveel.Console/PropertyHolder.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console { + public abstract class PropertyHolder { + #region ctor + protected PropertyHolder() + : this(null) { + } + + protected PropertyHolder(string value) { + this.value = value; + } + #endregion + + #region Fields + private string value; + #endregion + + #region Properties + public string Value { + get { return value; } + set { this.value = value; } + } + + public abstract string DefaultValue { get; } + + public virtual string ShortDescription { + get { return null; } + } + + public virtual string LongDescription { + get { return null; } + } + #endregion + + #region Protected Methods + protected abstract string OnValueChanged(string newValue); + #endregion + + #region Public Methods + public virtual IEnumerator CompleteValue(String partialValue) { + return null; + } + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/PropertyRegistry.cs b/src/dshell/Deveel.Console/PropertyRegistry.cs new file mode 100644 index 0000000..c945a3b --- /dev/null +++ b/src/dshell/Deveel.Console/PropertyRegistry.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +using Deveel.Collections; + +namespace Deveel.Console { + public class PropertyRegistry : IEnumerable> { + #region ctor + public PropertyRegistry(IPropertyHandler context) { + this.context = context; + namedProperties = new SortedDictionary(); + } + #endregion + + #region Fields + private readonly IPropertyHandler context; + private readonly SortedDictionary namedProperties; + #endregion + + #region Properties + + protected SortedDictionary Properties { + get { return namedProperties; } + } + + public IPropertyHandler Context { + get { return context; } + } + + #endregion + + internal IEnumerator Complete(string sourceCommand, string partialCommand, string lastWord) { + partialCommand = partialCommand.Trim(); + if (!String.IsNullOrEmpty(partialCommand)) { + string[] st = partialCommand.Split(' '); + String cmd = st[0]; + int argc = st.Length; + + if (argc > 1) { + // one arg given + if (sourceCommand.Equals(cmd)) { + String name = st[1]; + PropertyHolder holder = GetProperty(name); + if (holder == null) { + return null; + } + return holder.CompleteValue(lastWord); + } + } + } + + return new SortedMatchEnumerator(lastWord, Properties.Keys, Properties.Comparer); + } + + #region Public Methods + + public virtual bool HasProperty(string name) { + return namedProperties.ContainsKey(name); + } + + public virtual void RegisterProperty(string name, PropertyHolder holder) { + if (namedProperties.ContainsKey(name)) + throw new ArgumentException("Property named '" + name + "' already exists"); + + namedProperties.Add(name, holder); + } + + public virtual void UnregisterProperty(string name) { + namedProperties.Remove(name); + } + + public virtual void SetProperty(string name, string value) { + PropertyHolder holder = (PropertyHolder)namedProperties[name]; + if (holder == null) + throw new ArgumentException("unknown property '" + name + "'"); + + holder.Value = value; + } + + public PropertyHolder GetProperty(string name) { + return (PropertyHolder) namedProperties[name]; + } + + public IEnumerator> GetEnumerator() { + return namedProperties.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/Question.cs b/src/dshell/Deveel.Console/Question.cs new file mode 100644 index 0000000..f9ff7b7 --- /dev/null +++ b/src/dshell/Deveel.Console/Question.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; + +namespace Deveel.Console { + public sealed class Question { + private readonly string text; + private object[] options; + private int defaultOption; + private int maxResponse = 1; + + public Question(string text, object[] options, int defaultOption) { + this.text = text; + this.options = options; + this.defaultOption = defaultOption; + } + + public string Text { + get { return text; } + } + + public object[] Options { + get { return options; } + } + + public int DefaultOption { + get { return defaultOption; } + set { + if (value < 0 || value > options.Length) + throw new ArgumentOutOfRangeException("value"); + + defaultOption = value; + } + } + + public int MaxSelected { + get { return maxResponse; } + set { + if (value > options.Length) + throw new ArgumentException(); + maxResponse = value; + } + } + + public bool SingleOption { + get { return maxResponse == 1; } + set { maxResponse = value ? 1 : options.Length; } + } + + public Answer Answer(int[] selected) { + if (selected.Length == 0 || selected.Length > maxResponse) + throw new ArgumentOutOfRangeException(); + + if (selected.Length > options.Length) + throw new ArgumentOutOfRangeException(); + + List check = new List(selected.Length); + for (int i = 0; i < selected.Length; i++) { + if (selected[i] < 0 || selected[i] >= options.Length) + throw new ArgumentOutOfRangeException(); + if (check.Contains(selected[i])) + throw new ArgumentException(); + + check.Add(selected[i]); + } + + return new Answer(this, selected, true); + } + + public Answer Answer(int selected) { + return Answer(new int[] { selected } ); + } + + public Answer Ask(IApplicationContext context, bool verticalAlign) { + context.Out.Write(text); + + if (verticalAlign) { + context.Out.WriteLine(); + } else { + context.Out.Write(" ["); + } + + for(int i = 0; i < options.Length; i++) { + if (verticalAlign) { + context.Out.Write(" "); + + context.Out.Write(i + 1); + context.Out.Write(". "); + + if (defaultOption != -1 && + maxResponse == 1) { + context.Out.Write("["); + if (defaultOption == i) { + context.Out.Write("x"); + } else { + context.Out.Write(" "); + } + context.Out.Write("] "); + } + + context.Out.WriteLine(options[i]); + } else { + if (defaultOption != -1 && + maxResponse == 1 && + defaultOption == i) + context.Out.Write("*"); + + context.Out.Write(options[0]); + if (i < options.Length - 1) + context.Out.Write(", "); + } + } + + if (!verticalAlign) + context.Out.WriteLine("] : "); + + string input = context.Input.ReadLine(); + if (String.IsNullOrEmpty(input)) + return new Answer(this, null, false); + + string[] sp = input.Split(','); + + if (sp.Length > options.Length || + sp.Length > maxResponse) + return new Answer(this, null, false); + + List selected = new List(); + for (int i = 0; i < sp.Length; i++) { + string s = sp[i].Trim(); + bool found = false; + int offset = -1; + + if (verticalAlign && Int32.TryParse(s, out offset)) { + offset = offset -1; + found = true; + } else { + for (int j = 0; j < options.Length; j++) { + if (String.Compare(s, options[j].ToString(), true) == 0) { + offset = j; + found = true; + break; + } + } + } + + if (!found) + return new Answer(this, null, false); + + selected.Add(offset); + } + + return new Answer(this, selected.ToArray(), true); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/ShellApplication.cs b/src/dshell/Deveel.Console/ShellApplication.cs new file mode 100644 index 0000000..366c611 --- /dev/null +++ b/src/dshell/Deveel.Console/ShellApplication.cs @@ -0,0 +1,732 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Deveel.Console.Commands; +using Deveel.Configuration; + +namespace Deveel.Console { + public abstract class ShellApplication : IApplicationContext { + private OutputDevice output; + private OutputDevice message; + private InputDevice input; + private readonly CommandDispatcher dispatcher; + private readonly PropertyRegistry properties; + private ConfigurationFile history; + private readonly ApplicationSettings settings; + private readonly ApplicationPlugins plugins; + private readonly ApplicationInterruptionHandler interruptHandler; + + private ICommandSeparator separator; + + private bool running; + private bool terminated; + private bool shutdownCalled; + private bool initialized; + private volatile bool interrupted; + private StringBuilder historyLine; + + private TextReader fileReader; + //TODO: define a strategy to set this ... + private bool hasTerminal = true; + + private string prompt; +// private string emptyPrompt; + + private Options appOptions; + + private IExecutionContext activeContext; + + public event EventHandler Interrupted; + public event EventHandler BeforeExit; + + protected ShellApplication() { + output = new ConsoleOutputDevice(System.Console.Out, true); + message = new ConsoleOutputDevice(System.Console.Error, false); + input = new ConsoleInputDevice(); + dispatcher = new CommandDispatcher(this); + if (this is ISettingsHandler) + settings = new ApplicationSettings(this); + if (this is IPropertyHandler) + properties = new PropertyRegistry(this as IPropertyHandler); + if (this is IPluginHandler) + plugins = new ApplicationPlugins(this); + interruptHandler = new ApplicationInterruptionHandler(this); + } + + public void Dispose() { + Exit(0); + } + + public ApplicationInterruptionHandler Interruption { + get { return interruptHandler; } + } + + public ApplicationPlugins Plugins { + get { return plugins; } + } + + bool IExecutionContext.IsIsolated { + get { return false; } + } + + public bool IsRunning { + get { return running; } + } + + + public IExecutionContext ActiveContext { + get { return activeContext ?? this; } + } + + public string PartialLine { + get { + StringBuilder line = new StringBuilder(); + if (historyLine != null) + line.Append(historyLine.ToString()); + line.Append(Readline.LineBuffer); + return line.ToString(); + } + } + + public bool HadnlesPlugins { + get { return this is IPluginHandler; } + } + + public PropertyRegistry Properties { + get { return properties; } + } + + public bool IsInBatch { + get { return dispatcher.IsInBatch; } + } + + public bool HandlesProperties { + get { return this is IPropertyHandler; } + } + + public OutputDevice Out { + get { return output; } + } + + public OutputDevice Error { + get { return message; } + } + + public InputDevice Input { + get { return input; } + } + + public CommandDispatcher Commands { + get { return dispatcher; } + } + + protected bool HasPrompt { + get { return !String.IsNullOrEmpty(prompt); } + } + + protected string Prompt { + get { return prompt; } + } + + private bool IsInterruptable { + get { return this is IInterruptable; } + } + + public ApplicationSettings Settings { + get { return settings; } + } + + public bool HandlesSettings { + get { return this is ISettingsHandler; } + } + + private ICommandSeparator GetSeparator() { + if (separator == null) + separator = CreateSeparator(); + return separator; + } + + public void SetPrompt(string text) { + prompt = text; +// StringBuilder tmp = new StringBuilder(); +// int emptyLength = prompt.Length; +// for (int i = emptyLength; i > 0; --i) { +// tmp.Append(' '); +// } +// emptyPrompt = tmp.ToString(); + + if (output == null) + output = new ConsoleOutputDevice(System.Console.Out, true); + if (message == null) + message = new ConsoleOutputDevice(System.Console.Error, false); + if (input == null) + input = new ConsoleInputDevice(); + + if (output is ConsoleOutputDevice) + (output as ConsoleOutputDevice).Prompt = prompt; + if (input is ConsoleInputDevice) + (input as ConsoleInputDevice).Prompt = prompt; + } + + public void SetOutDevice(OutputDevice device) { + if (device == null) + throw new ArgumentNullException("device"); + if (device is ConsoleOutputDevice) + (device as ConsoleOutputDevice).Prompt = prompt; + + output = device; + } + + public void SetErrorDevice(OutputDevice device) { + if (device == null) + throw new ArgumentNullException("device"); + + message = device; + } + + protected void LoadHistory(Stream inputStream) { + StreamReader reader = new StreamReader(inputStream); + StringBuilder line = new StringBuilder(); + int c; + do { + while ((c = reader.Read()) >= 0 && c != '\n') { + char ch = (char)c; + if (ch == '\r') + continue; + + if (ch == '\\') { + line.Append((char)reader.Read()); + } else { + line.Append(ch); + } + } + if (line.Length > 0) { + History.AddHistory(line.ToString()); + line.Length = 0; + } + } while (c >= 0); + reader.Close(); + } + + protected void WriteHistory(Stream outputStream) { + StreamWriter writer = new StreamWriter(outputStream, Encoding.UTF8); + int len = History.Count; + for (int i = 0; i < len; ++i) { + String line = History.GetHistory(i); + if (line == null) + continue; + + line = EscapeHistoryLine(line); + writer.WriteLine(line); + } + writer.Close(); + } + + protected void CreateHistoryFile(string fileName) { + history = new ConfigurationFile(fileName); + } + + protected void WriteHistoryToFile() { + if (history != null) + history.Write(WriteHistory); + } + + private static String EscapeHistoryLine(String s) { + if (s.IndexOf('\\') >= 0 || s.IndexOf('\n') >= 0) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.Length; ++i) { + char c = s[i]; + switch (c) { + case '\\': + sb.Append("\\\\"); + break; + case '\n': + sb.Append("\\\n"); + break; + default: + sb.Append(c); + break; + } + } + return sb.ToString(); + } + + return s; + } + + private string previousHistoryLine; + + private void StoreHistoryLine() { + string line = historyLine.ToString().Trim(); + if (!line.Equals(String.Empty) && + !line.Equals(previousHistoryLine)) { + History.AddHistory(line); + previousHistoryLine = line; + } + } + + private string ReadLineFromFile() { + if (fileReader == null) + fileReader = new StreamReader(System.Console.OpenStandardInput()); + + string line = fileReader.ReadLine(); + if (line == null) + throw new EndOfStreamException(); + + return (line.Length == 0) ? null : line; + } + + private void RegisterDefaults() { + if (this is IPropertyHandler) { + if (!Properties.HasProperty("echo-commands")) + Properties.RegisterProperty("echo-commands", new EchoCommandProperty(this, dispatcher)); + } + + Commands.Register(typeof(ExitCommand)); + Commands.Register(typeof(HelpCommand)); + Commands.Register(typeof(SpoolCommand)); + Commands.Register(typeof(EchoCommand)); + + // Commands.Register(typeof(AliasCommand)); + + Commands.Register(typeof(SetCommand)); + Commands.Register(typeof(UnsetCommand)); + Commands.Register(typeof(VariablesCommand)); + + if (properties != null) { + Commands.Register(typeof(SetPropertyCommand)); + Commands.Register(typeof(ResetPropertyCommand)); + Commands.Register(typeof(SetContextPropertyCommand)); + Commands.Register(typeof(ResetContextPropertyCommand)); + Commands.Register(typeof(ShowPropertyCommand)); + Commands.Register(typeof(ShowContextPropertyCommand)); + } + } + + protected virtual void OnRunning() { + } + + private void Readline_Interrupt(object sender, EventArgs args) { + Interruption.Interrupt(); + } + + protected ApplicationPlugins LoadPlugins(string fileName) { + if (this is IPluginHandler && File.Exists(fileName)) { + StreamReader reader = new StreamReader(fileName); + string line; + while ((line = reader.ReadLine()) != null) { + line = line.Trim(); + if (String.IsNullOrEmpty(line)) + continue; + + Command command; + + try { + command = plugins.LoadPlugin(line); + } catch(Exception) { + Error.WriteLine("Cannot load the plugin '" + line + "'."); + continue; + } + + plugins.Add(line, command); + } + } + + return plugins; + } + + protected void SavePlugins(string fileName) { + if (this is IPluginHandler) { + if (!File.Exists(fileName)) + File.Create(fileName); + + ConfigurationFile configFile = new ConfigurationFile(fileName); + foreach(KeyValuePair plugin in plugins) { + configFile.SetValue(plugin.Key, plugin.Value.GetType().FullName); + } + + configFile.Save("Plugins"); + } + } + + + protected PropertyRegistry LoadProperties(string fileName) { + if (this is IPropertyHandler && File.Exists(fileName)) { + ConfigurationFile confFile = new ConfigurationFile(fileName); + foreach(KeyValuePair property in confFile.Properties) { + properties.SetProperty(property.Key, property.Value); + } + } + + return properties; + } + + protected ApplicationSettings LoadSettings(string fileName) { + if (HandlesSettings && File.Exists(fileName)) { + ConfigurationFile confFile = new ConfigurationFile(fileName); + foreach(KeyValuePair pair in confFile.Properties) { + settings.SetVariable(pair.Key, pair.Value); + } + } + return settings; + } + + protected void SaveSettings(string fileName) { + if (!HandlesSettings) + return; + + if (!File.Exists(fileName)) + File.Create(fileName); + + IDictionary toWrite = new Dictionary(); + foreach (KeyValuePair entry in settings.Variables) + toWrite.Add(entry.Key, entry.Value); + foreach (string variable in settings.SpecialVariables) + toWrite.Remove(variable); + + ConfigurationFile configFile = new ConfigurationFile(fileName); + foreach (KeyValuePair entry in toWrite) + configFile.SetValue(entry.Key, entry.Value); + configFile.Save("Settings"); + } + + protected virtual bool OnTerminated() { + return true; + } + + protected virtual void OnInterrupted() { + + } + + protected virtual ICommandSeparator CreateSeparator() { + return null; + } + + protected virtual LineExecutionResultCode ExecuteLine(string line) { + ICommandSeparator commandSeparator = GetSeparator(); + if (commandSeparator != null) { + StringBuilder lineBuilder = new StringBuilder(line); + lineBuilder.Append(Environment.NewLine); + commandSeparator.Append(lineBuilder.ToString()); + + LineExecutionResultCode resultCode = LineExecutionResultCode.Incomplete; + + while (commandSeparator.MoveNext()) { + string completeCommand = commandSeparator.Current; + if (HandlesSettings) + completeCommand = Settings.Substitute(completeCommand); + + Command c = Commands.GetCommand(completeCommand); + + if (c == null) { + commandSeparator.Consumed(); + // do not shadow successful executions with the 'line-empty' + // message. Background is: when we consumed a command, that + // is complete with a trailing ';', then the following newline + // would be considered as empty command. So return only the + // 'Empty', if we haven't got a succesfully executed line. + if (resultCode != LineExecutionResultCode.Executed) + resultCode = LineExecutionResultCode.Empty; + } else if (!c.IsComplete(completeCommand)) { + commandSeparator.Cont(); + resultCode = LineExecutionResultCode.Incomplete; + } else { + Execute(ActiveContext, completeCommand.Trim()); + + commandSeparator.Consumed(); + + resultCode = LineExecutionResultCode.Executed; + } + } + + return resultCode; + } + + // if we don't have any separator, we assume it's a complete command + Execute(ActiveContext, line); + return LineExecutionResultCode.Executed; + } + + public void Interrupt() { + if (!IsInterruptable) + throw new InvalidOperationException("The application is not interruptable."); + + interrupted = true; + } + + public void Execute(IExecutionContext context, string commandText) { + dispatcher.ExecuteCommand(context, commandText); + } + + protected IExecutionContext SetActiveContext(IExecutionContext context) { + activeContext = context; + return ActiveContext; + } + + protected virtual Options CreateOptions() { + return null; + } + + protected virtual void RegisterCommands() { + } + + public void RegisterOptions(Options options) { + if (running) + throw new InvalidOperationException("The application is running."); + + Initialize(); + + appOptions = options; + + foreach(Command command in dispatcher.RegisteredCommands) { + command.RegisterOptions(options); + } + } + + public void HandleCommandLine(CommandLine commandLine) { + if (running) + throw new InvalidOperationException("The application is running."); + + Initialize(); + + foreach(Command command in dispatcher.RegisteredCommands) { + if (command.HandleCommandLine(commandLine)) + return; + } + } + + public void HandleCommandLine(string [] args, ICommandLineParser parser) { + if (parser == null) + throw new ArgumentNullException("parser"); + + Initialize(); + + parser.Options = appOptions; + CommandLine commandLine = parser.Parse(args); + if (commandLine.HasParsed) + HandleCommandLine(commandLine); + } + + public void HandleCommandLine(string[] args, ParseStyle parseStyle) { + ICommandLineParser parser = Parser.Create(parseStyle); + HandleCommandLine(args, parser); + } + + public void HandleCommandLine(string[] args) { + HandleCommandLine(args, ParseStyle.Gnu); + } + + private void Initialize() { + if (!initialized) { + Readline.ControlCInterrupts = true; + Readline.Interrupt += Readline_Interrupt; + RegisterDefaults(); + RegisterCommands(); + + appOptions = CreateOptions(); + + initialized = true; + } + } + + public void Run(string[] args) { + HandleCommandLine(args); + Run(); + } + + public void Run() { + Initialize(); + + OnRunning(); + + running = true; + + string cmdLine = null; +// string displayPrompt = prompt; + + historyLine = new StringBuilder(); + + try { + while (!terminated) { + interrupted = false; + + // a CTRL-C will not interrupt the current reading + // thus it does not make much sense here to interrupt. + // WORKAROUND: Write message in the Interrupt() method. + // TODO: find out, if we can do something that behaves + // like a shell. This requires, that CTRL-C makes + // Readline.ReadLine() return. + if (IsInterruptable) + Interruption.Push(this as IInterruptable); + + try { +// cmdLine = (hasTerminal) ? Readline.ReadLine(displayPrompt) : ReadLineFromFile(); + cmdLine = Input.ReadLine(); + } catch(EndOfStreamException) { + // EOF on CTRL-D + if (OnTerminated()) { +// displayPrompt = prompt; + Input.CompleteLine(); + continue; + } + + break; + } catch(Exception e) { +#if DEBUG + System.Console.Error.WriteLine(e.Message); + System.Console.Error.WriteLine(e.StackTrace); +#endif + } + + if (IsInterruptable) + Interruption.Reset(); + + // anyone pressed CTRL-C + if (interrupted) { + if ((cmdLine == null || cmdLine.Trim().Length == 0) && + historyLine.Length == 0) { + terminated = true; // terminate if we press CTRL on empty line. + } + + historyLine.Length = 0; + +// displayPrompt = prompt; + Input.CompleteLine(); + continue; + } + + if (cmdLine == null) { + continue; + } + + // if there is already some line in the history, then add + // newline. But if the only thing we added was a delimiter (';'), + // then this would be annoying. + + if (historyLine.Length > 0 && + !cmdLine.Trim().Equals(dispatcher.CommandSeparator.ToString())) { + historyLine.Append(Environment.NewLine); + } + + historyLine.Append(cmdLine); + + LineExecutionResultCode lineExecState = ExecuteLine(cmdLine); + + // displayPrompt = lineExecState == LineExecutionResultCode.Incomplete ? emptyPrompt : prompt; + + if (lineExecState != LineExecutionResultCode.Incomplete) { + Input.CompleteLine(); + StoreHistoryLine(); + historyLine.Length = 0; + } + } + + if (IsInterruptable) + Interruption.Reset(); + } catch(Exception e) { +#if DEBUG + System.Console.Error.WriteLine(e.Message); + System.Console.Error.WriteLine(e.StackTrace); +#endif + Exit(1); + } finally { + running = false; + } + } + + protected virtual void OnShutdown() { + } + + public void Shutdown() { + Shutdown(true); + } + + public void Shutdown(bool exit) { + if (shutdownCalled) + return; + + Interruption.Reset(); + + try { + if (dispatcher != null) + dispatcher.Shutdown(); + + WriteHistoryToFile(); + + OnShutdown(); + } finally { + shutdownCalled = true; + } + + GC.Collect(); + GC.Collect(); + + if (exit) + Exit(0); + } + + protected virtual void OnExit() { + } + + private void OnBeforeExit() { + if (BeforeExit != null) + BeforeExit(this, EventArgs.Empty); + } + + public void Exit(int code) { + terminated = true; + + OnBeforeExit(); + OnExit(); + + Environment.Exit(code); + } + + #region EchoCommandProperty + + class EchoCommandProperty + : BooleanPropertyHolder { + private readonly ShellApplication app; + private readonly CommandDispatcher dispatcher; + private readonly CommandEventHandler handler; + + public EchoCommandProperty(ShellApplication app, CommandDispatcher dispatcher) + : base(false) { + this.app = app; + this.dispatcher = dispatcher; + handler = new CommandEventHandler(OnDispatcherExecuting); + } + + public override string DefaultValue { + get { return "off"; } + } + + public override void OnBooleanPropertyChanged(bool echoCommands) { + if (echoCommands) { + dispatcher.CommandExecuting += handler; + } else { + dispatcher.CommandExecuting -= handler; + } + } + + void OnDispatcherExecuting(object sender, CommandEventArgs e) { + app.Error.WriteLine(e.CommandText.Trim()); + } + + public override String ShortDescription { + get { return "echo commands prior to execution."; } + } + } + + #endregion + + internal void OnInterrupt() { + if (Interrupted != null) + Interrupted(this, EventArgs.Empty); + + OnInterrupted(); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/SignalInterruptHandler.cs b/src/dshell/Deveel.Console/SignalInterruptHandler.cs new file mode 100644 index 0000000..b12b73e --- /dev/null +++ b/src/dshell/Deveel.Console/SignalInterruptHandler.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections; + +namespace Deveel.Console { + public sealed class SignalInterruptHandler : SystemSignalHandler { + #region ctor + public SignalInterruptHandler() + : base(Signals.CtrlC) { + once = false; + interruptStack = new Stack(); + } + #endregion + + #region Fields + private bool once; + private readonly Stack interruptStack; + private bool ctrlCBackup; + #endregion + + #region Properties + + public static SignalInterruptHandler Current { + get { return GetInstalledHandler(Signals.CtrlC) as SignalInterruptHandler; } + } + + #endregion + + #region Protected Methods + + protected override void OnInstall() { +// ctrlCBackup = System.Console.TreatControlCAsInput; +// System.Console.TreatControlCAsInput = false; + //Readline.ControlCIsEOF = true; + } + + protected override void OnSignal() { + if (once) + // got the interrupt more than once. May happen if you press + // Ctrl-C multiple times .. or with broken thread lib on Linux. + return; + + once = true; + if (interruptStack.Count > 0) { + IInterruptable toInterrupt = (IInterruptable)interruptStack.Peek(); + toInterrupt.Interrupt(); + } else { + System.Console.Error.WriteLine("[Ctrl-C ; interrupted]"); + Environment.Exit(1); + } + } + + protected override void OnUnintall() { +// System.Console.TreatControlCAsInput = ctrlCBackup; + } + + #endregion + + #region Public Methods + + public void Push(IInterruptable interruptable) { + interruptStack.Push(interruptable); + } + + public void Pop() { + once = false; + interruptStack.Pop(); + } + + public void Reset() { + once = true; + interruptStack.Clear(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/Signals.cs b/src/dshell/Deveel.Console/Signals.cs new file mode 100644 index 0000000..28bf7ad --- /dev/null +++ b/src/dshell/Deveel.Console/Signals.cs @@ -0,0 +1,9 @@ +using System; + +namespace Deveel.Console { + public enum Signals { + CtrlC = 1, + CtrlBreak = 2, + CtrlShutdown = 3, + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/SystemSignalHandler.cs b/src/dshell/Deveel.Console/SystemSignalHandler.cs new file mode 100644 index 0000000..10bce40 --- /dev/null +++ b/src/dshell/Deveel.Console/SystemSignalHandler.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections; + +#if MONO +using Mono.Unix.Native; +#else +using System.Runtime.InteropServices; +#endif + +namespace Deveel.Console { + public abstract class SystemSignalHandler { + #region ctor + protected SystemSignalHandler(Signals signal) { + this.signal = signal; + signum = GetSignum(signal); + +#if MONO + signalHandler = new SignalHandler(OnSignal); +#else + signalHandler = new EventHandler(OnSignal); +#endif + } + + static SystemSignalHandler() { + installedHandlers = new Hashtable(); + } + #endregion + + #region Fields + + private readonly Signals signal; +#if MONO + private readonly SignalHandler signalHandler; + private readonly Signum signum; +#else + private readonly EventHandler signalHandler; + private readonly CtrlType signum; +#endif + + private static readonly Hashtable installedHandlers; + + #endregion + + #region Properties + public Signals Signal { + get { return signal; } + } + #endregion + + #region Protected Methods + + protected virtual void OnInstall() { + } + + protected abstract void OnSignal(); + + protected virtual void OnUnintall() { + } + + #endregion + + #region Private Methods + +#if !MONO + [DllImport("Kernel32")] + private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add); +#endif + + private void Install() { +#if MONO + Stdlib.Signal(signum, signalHandler); +#else + SetConsoleCtrlHandler(signalHandler, true); +#endif + OnInstall(); + } + + private void Uninstall() { +#if MONO + //TODO: +#else + if (SetConsoleCtrlHandler(signalHandler, false)) + OnUnintall(); +#endif + } + +#if MONO + private void OnSignal(int signal) { + if (Stdlib.ToSignum(signal) == (Signum)signum) + OnSignal(); + } +#else + private bool OnSignal(CtrlType sig) { + if (sig == signum) { + try { + OnSignal(); + } catch(Exception) { + return false; + } + } + + return true; + } +#endif + #endregion + + public static void Install(Type type) { + Install(type, new object[0]); + } + + public static void Install(Type type, params object[] args) { + if (type == null) + throw new ArgumentNullException("type"); + + if (!typeof(SystemSignalHandler).IsAssignableFrom(type)) + throw new ArgumentException("The specified type '" + type + "' is not assignable from SystemSignalHandler."); + + SystemSignalHandler handler = (SystemSignalHandler)Activator.CreateInstance(type, args); + + Install(handler); + } + + public static void Install(SystemSignalHandler handler) { + if (handler == null) + throw new ArgumentNullException("handler"); + + Signals signum = handler.Signal; + if (installedHandlers.ContainsKey(signum)) + throw new InvalidOperationException("An handler for the system signal " + signum + " is already installed."); + + handler.Install(); + + installedHandlers.Add(signum, handler); + } + + public static SystemSignalHandler Install(System.EventHandler handler, Signals signum) { + DelegateSignalHandler signalHandler = new DelegateSignalHandler(signum, handler); + Install(signalHandler); + return signalHandler; + } + + public static SystemSignalHandler GetInstalledHandler(Signals signum) { + return installedHandlers[signum] as SystemSignalHandler; + } + + public static bool Uninstall(SystemSignalHandler handler) { + if (handler == null) + throw new ArgumentNullException("handler"); + + return Uninstall(handler.Signal); + } + + public static bool Uninstall(Signals signum) { + SystemSignalHandler handler = installedHandlers[signum] as SystemSignalHandler; + if (handler == null) + return false; + + handler.Uninstall(); + return true; + } + +#if MONO + private static Signum GetSignum(Signals signal) { + //TODO: + } +#else + private static CtrlType GetSignum(Signals signal) { + switch (signal) { + case Signals.CtrlC: + return CtrlType.CTRL_C_EVENT; + case Signals.CtrlBreak: + return CtrlType.CTRL_BREAK_EVENT; + case Signals.CtrlShutdown: + return CtrlType.CTRL_CLOSE_EVENT; + default: + throw new NotSupportedException(); + } + } +#endif + +#if !MONO + private delegate bool EventHandler(CtrlType sig); + + enum CtrlType { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6 + } +#endif + + #region DelegateSignalHandler + + private class DelegateSignalHandler : SystemSignalHandler { + public DelegateSignalHandler(Signals signal, System.EventHandler handler) + : base(signal) { + this.handler = handler; + } + + private readonly System.EventHandler handler; + + protected override void OnSignal() { + if (handler != null) + handler(this, EventArgs.Empty); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/TableRenderer.cs b/src/dshell/Deveel.Console/TableRenderer.cs new file mode 100644 index 0000000..d3bb801 --- /dev/null +++ b/src/dshell/Deveel.Console/TableRenderer.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections; +using System.Text; + +namespace Deveel.Console { + public class TableRenderer { + #region ctor + public TableRenderer(ColumnDesign[] columns, OutputDevice output, string separator, bool enableHeader, bool enableFooter) { + this.columns = columns; + this.output = output; + this.enableHeader = enableHeader; + this.enableFooter = enableFooter; + + // we cache the rows in order to dynamically determine the + // output width of each column. + cacheRows = new ArrayList(MaxCacheElements); + alreadyFlushed = false; + writtenRows = 0; + colSeparator = " " + separator; + separatorWidth = separator.Length; + } + + public TableRenderer(ColumnDesign[] columns, OutputDevice output) + : this(columns, output, "|", true, true) { + } + + #endregion + + #region Fields + + private const int MaxCacheElements = 500; + + private ArrayList cacheRows; + private bool alreadyFlushed; + private int writtenRows; + private int separatorWidth; + + private bool enableHeader; + private bool enableFooter; + + protected ColumnDesign[] columns; + protected OutputDevice output; + protected string colSeparator; + + #endregion + + #region Properties + + public ColumnDesign[] Columns { + get { return columns; } + } + + public bool EnableFooter { + get { return enableFooter; } + set { enableFooter = value; } + } + + public bool EnableHeader { + get { return enableHeader; } + set { enableHeader = value; } + } + + public string ColumnSeparator { + get { return colSeparator; } + set { + colSeparator = " " + value; + separatorWidth = value.Length; + } + } + + #endregion + + #region Private Methods + private void WriteHorizontalLine() { + for (int i = 0; i < columns.Length; ++i) { + if (!columns[i].Display) + continue; + + string txt = FormatString(String.Empty, '-', columns[i].Width + separatorWidth + 1, ColumnAlignment.Left); + output.Write(txt); + output.Write("+"); + } + + output.WriteLine(); + } + + private void WriteTableHeader() { + WriteHorizontalLine(); + + for (int i = 0; i < columns.Length; ++i) { + if (!columns[i].Display) + continue; + + string txt = FormatString(columns[i].Label, ' ', columns[i].Width + 1, ColumnAlignment.Center); + output.WriteBold(txt); + output.Write(colSeparator); + } + + output.WriteLine(); + WriteHorizontalLine(); + } + #endregion + + #region Protected Methods + protected string FormatString(string text, char fillchar, int len, ColumnAlignment alignment) { + // Console.Out.WriteLine("[formatString] len: " + len + ", text.Length: " + text.Length); + // text = "hi"; + StringBuilder fillstr = new StringBuilder(); + + if (len > 4000) + len = 4000; + + if (text == null) + text = ColumnValue.NullText; + + int slen = text.Length; + + if (alignment == ColumnAlignment.Left) + fillstr.Append(text); + + int fillNumber = len - slen; + int boundary = 0; + if (alignment == ColumnAlignment.Center) + boundary = fillNumber / 2; + + while (fillNumber > boundary) { + fillstr.Append(fillchar); + --fillNumber; + } + + if (alignment != ColumnAlignment.Left) + fillstr.Append(text); + + while (fillNumber > 0) { + fillstr.Append(fillchar); + --fillNumber; + } + + return fillstr.ToString(); + } + + protected void AddRowToCache(ColumnValue[] row) { + cacheRows.Add(row); + if (cacheRows.Count >= MaxCacheElements) { + Flush(); + cacheRows.Clear(); + } + } + + protected virtual void UpdateColumnWidths(ColumnValue[] row) { + for (int i = 0; i < columns.Length; ++i) { + columns[i].Width = row[i].Width; + } + } + + protected bool WriteColumns(ColumnValue[] currentRow, bool hasMoreLines) { + for (int i = 0; i < columns.Length; ++i) { + if (!columns[i].Display) + continue; + + hasMoreLines = WriteColumn(currentRow[i], hasMoreLines, i); + } + + return hasMoreLines; + } + + protected bool WriteColumn(ColumnValue column, bool hasMoreLines, int i) { + output.Write(" "); + + string txt = FormatString(column.GetNextLine(), ' ', columns[i].Width, columns[i].Alignment); + + hasMoreLines |= column.HasNextLine(); + if (column.IsNull) { + output.WriteGrey(txt); + } else { + output.Write(txt); + } + + output.Write(colSeparator); + return hasMoreLines; + } + #endregion + + #region Public Methods + public void AddRow(ColumnValue[] row) { + UpdateColumnWidths(row); + AddRowToCache(row); + } + + public void CloseTable() { + Flush(); + if (writtenRows > 0 && enableFooter) { + WriteHorizontalLine(); + } + } + + public void Flush() { + if (!alreadyFlushed) { + if (enableHeader) + WriteTableHeader(); + alreadyFlushed = true; + } + + foreach (ColumnValue[] currentRow in cacheRows) { + bool hasMoreLines; + + do { + hasMoreLines = false; + hasMoreLines = WriteColumns(currentRow, hasMoreLines); + output.WriteLine(); + } while (hasMoreLines); + + ++writtenRows; + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/TextWriterOutputDevice.cs b/src/dshell/Deveel.Console/TextWriterOutputDevice.cs new file mode 100644 index 0000000..7f1d2e5 --- /dev/null +++ b/src/dshell/Deveel.Console/TextWriterOutputDevice.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Text; + +namespace Deveel.Console { + public sealed class TextWriterOutputDevice : OutputDevice { + private readonly TextWriter writer; + + public TextWriterOutputDevice(TextWriter writer) { + this.writer = writer; + } + + public override int LineMaxWidth { + get { return -1; } + } + + public override Encoding Encoding { + get { return writer.Encoding; } + } + + protected override TextWriter Output { + get { return writer; } + } + + public override void Flush() { + writer.Flush(); + } + + public override void Close() { + writer.Close(); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Console/TimeRenderer.cs b/src/dshell/Deveel.Console/TimeRenderer.cs new file mode 100644 index 0000000..f60d2c7 --- /dev/null +++ b/src/dshell/Deveel.Console/TimeRenderer.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.Text; + +namespace Deveel.Console { + public class TimeRenderer { + private const long SecondMillis = 1000; + private const long MinuteMillis = 60 * SecondMillis; + private const long HourMillis = 60 * MinuteMillis; + + public static void PrintFraction(long execTime, long number, OutputDevice output) { + if (number == 0) { + output.Write(" -- "); + return; + } + long milli = execTime / number; + long micro = (execTime - number * milli) * 1000 / number; + PrintTime(milli, micro, output); + } + + /** render time as string */ + public static String RenderTime(long execTimeInMs) { + return RenderTime(execTimeInMs, 0); + } + + /** render time as string */ + public static String RenderTime(long execTimeInMs, long usec) { + StringBuilder result = new StringBuilder(); + PrintTime(execTimeInMs, usec, new StringBuilderOutputDevice(result)); + return result.ToString(); + } + + private class StringBuilderOutputDevice : OutputDevice { + public StringBuilderOutputDevice(StringBuilder builder) { + writer = new StringWriter(builder); + } + + private readonly StringWriter writer; + + protected override TextWriter Output { + get { return writer; } + } + + public override Encoding Encoding { + get { return Encoding.UTF8; } + } + } + + /** print time to output device */ + public static void PrintTime(long execTimeInMs, OutputDevice output) { + PrintTime(execTimeInMs, 0, output); + } + + /** print time to output device */ + public static void PrintTime(long execTimeInMs, long usec, OutputDevice output) { + long totalTime = execTimeInMs; + + bool hourPrinted = false; + bool minutePrinted = false; + + if (execTimeInMs > HourMillis) { + output.Write(Convert.ToString(execTimeInMs / HourMillis)); + output.Write("h "); + execTimeInMs %= HourMillis; + hourPrinted = true; + } + + if (hourPrinted || execTimeInMs > MinuteMillis) { + long minute = execTimeInMs / 60000; + if (hourPrinted && minute < 10) { + output.Write("0"); // need padding. + } + output.Write(minute.ToString()); + output.Write("m "); + execTimeInMs %= MinuteMillis; + minutePrinted = true; + } + + if (minutePrinted || execTimeInMs >= SecondMillis) { + long seconds = execTimeInMs / SecondMillis; + if (minutePrinted && seconds < 10) { + output.Write("0"); // need padding. + } + output.Write(seconds.ToString()); + output.Write("."); + execTimeInMs %= SecondMillis; + // milliseconds + if (execTimeInMs < 100) + output.Write("0"); + if (execTimeInMs < 10) + output.Write("0"); + output.Write(execTimeInMs.ToString()); + } else if (execTimeInMs > 0) { + output.Write(execTimeInMs.ToString()); + } + + if (usec > 0) { + if (totalTime > 0) { // need delimiter and padding. + output.Write("."); + if (usec < 100) + output.Write("0"); + if (usec < 10) + output.Write("0"); + } + output.Write(usec.ToString()); + } else if (execTimeInMs == 0) { + output.Write("0 "); + } + + if (totalTime > MinuteMillis) { + output.Write("s"); + } else if (totalTime >= SecondMillis) + output.Write(" "); + else if (totalTime > 0 && totalTime < SecondMillis) + output.Write(" m"); + else if (totalTime == 0 && usec > 0) + output.Write(" µ"); + else + output.Write("sec"); + } + } +} \ No newline at end of file diff --git a/src/dshell/Deveel.Util/Properties.cs b/src/dshell/Deveel.Util/Properties.cs new file mode 100644 index 0000000..971ea8a --- /dev/null +++ b/src/dshell/Deveel.Util/Properties.cs @@ -0,0 +1,385 @@ +// Derived from Apache Harmony java.util.Properties class + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Deveel.Util { + class Properties : Dictionary { + public Properties() + : base() { + } + + public Properties(Properties defaults) { + this.defaults = defaults; + } + + private const int NONE = 0, + SLASH = 1, + UNICODE = 2, + CONTINUE = 3, + KEY_DONE = 4, + IGNORE = 5; + + protected Properties defaults; + + private static string lineSeparator; + + public ICollection PropertyNames { + get { + Dictionary selected = new Dictionary(); + SelectProperties(selected); + return selected.Keys; + } + } + + private void SelectProperties(IDictionary selectProperties) { + if (defaults != null) { + defaults.SelectProperties(selectProperties); + } + + ICollection keys = Keys; + foreach (object key in keys) { + // Only select property with string key and value + if (key is String) { + object value = base[key]; + if (value is string) { + selectProperties.Add((string)key, value); + } + } + } + } + + private void dumpString(StringBuilder buffer, String s, bool key) { + int i = 0; + if (!key && i < s.Length && s[i] == ' ') { + buffer.Append("\\ "); //$NON-NLS-1$ + i++; + } + + for (; i < s.Length; i++) { + char ch = s[i]; + switch (ch) { + case '\t': + buffer.Append("\\t"); //$NON-NLS-1$ + break; + case '\n': + buffer.Append("\\n"); //$NON-NLS-1$ + break; + case '\f': + buffer.Append("\\f"); //$NON-NLS-1$ + break; + case '\r': + buffer.Append("\\r"); //$NON-NLS-1$ + break; + default: + if ("\\#!=:".IndexOf(ch) >= 0 || (key && ch == ' ')) { + buffer.Append('\\'); + } + if (ch >= ' ' && ch <= '~') { + buffer.Append(ch); + } else { + string hex = ((int)ch).ToString("0x{0:x}"); + buffer.Append("\\u"); //$NON-NLS-1$ + for (int j = 0; j < 4 - hex.Length; j++) { + buffer.Append("0"); //$NON-NLS-1$ + } + buffer.Append(hex); + } + break; + } + } + } + + public string GetProperty(string name) { + object result; + if (!TryGetValue(name, out result)) + return null; + + string property = result is string ? (string)result : null; + if (property == null && defaults != null) + property = defaults.GetProperty(name); + return property; + } + + public string GetProperty(string name, string defaultValue) { + object result; + if (!TryGetValue(name, out result)) + return defaultValue; + + string property = result is string ? (string)result : null; + if (property == null && defaults != null) + property = defaults.GetProperty(name); + if (property == null) + return defaultValue; + return property; + } + + public object SetProperty(string name, string value) { + return base[name] = value; + } + + public void Load(Stream input) { + lock (this) { + if (input == null) + throw new ArgumentNullException("input"); + if (!input.CanRead) + throw new ArgumentException(); + + BufferedStream bis = new BufferedStream(input); + bool isEbcdic; + + try { + long pos = bis.Position; + isEbcdic = IsEbcdic(bis); + bis.Seek(pos, SeekOrigin.Begin); + } catch { + isEbcdic = false; + } + + if (!isEbcdic) { + Load(new StreamReader(bis, Encoding.GetEncoding("ISO8859-1"))); //$NON-NLS-1$ + } else { + Load(new StreamReader(bis)); //$NON-NLS-1$ + } + } + } + + private static bool IsEbcdic(Stream input) { + byte b; + while ((b = (byte)input.ReadByte()) != -1) { + if (b == 0x23 || b == 0x0a || b == 0x3d) {//ascii: newline/#/= + return false; + } + if (b == 0x15) {//EBCDIC newline + return true; + } + } + //we found no ascii newline, '#', neither '=', relative safe to consider it + //as non-ascii, the only exception will be a single line with only key(no value and '=') + //in this case, it should be no harm to read it in default charset + return false; + } + + public void Load(TextReader reader) { + lock (this) { + int mode = NONE, unicode = 0, count = 0; + char nextChar; + char[] buf = new char[40]; + int offset = 0, keyLength = -1, intVal; + bool firstChar = true; + + while (true) { + intVal = reader.Read(); + if (intVal == -1) break; + nextChar = (char)intVal; + + if (offset == buf.Length) { + char[] newBuf = new char[buf.Length * 2]; + Array.Copy(buf, 0, newBuf, 0, offset); + buf = newBuf; + } + if (mode == UNICODE) { + int digit = Convert.ToInt32(nextChar.ToString(), 16); + if (digit >= 0) { + unicode = (unicode << 4) + digit; + if (++count < 4) { + continue; + } + } else if (count <= 4) { + // luni.09=Invalid Unicode sequence: illegal character + throw new ArgumentException(); + } + mode = NONE; + buf[offset++] = (char)unicode; + if (nextChar != '\n' && nextChar != '\u0085') { + continue; + } + } + if (mode == SLASH) { + mode = NONE; + switch (nextChar) { + case '\r': + mode = CONTINUE; // Look for a following \n + continue; + case '\u0085': + case '\n': + mode = IGNORE; // Ignore whitespace on the next line + continue; + case 'b': + nextChar = '\b'; + break; + case 'f': + nextChar = '\f'; + break; + case 'n': + nextChar = '\n'; + break; + case 'r': + nextChar = '\r'; + break; + case 't': + nextChar = '\t'; + break; + case 'u': + mode = UNICODE; + unicode = count = 0; + continue; + } + } else { + switch (nextChar) { + case '#': + case '!': + if (firstChar) { + while (true) { + intVal = reader.Read(); + if (intVal == -1) break; + nextChar = (char)intVal; // & 0xff + // not + // required + if (nextChar == '\r' || nextChar == '\n' || nextChar == '\u0085') { + break; + } + } + continue; + } + break; + case '\n': + if (mode == CONTINUE) { // Part of a \r\n sequence + mode = IGNORE; // Ignore whitespace on the next line + continue; + } + // fall into the next case + goto case '\r'; + case '\u0085': + case '\r': + mode = NONE; + firstChar = true; + if (offset > 0 || (offset == 0 && keyLength == 0)) { + if (keyLength == -1) { + keyLength = offset; + } + String temp = new String(buf, 0, offset); + Add(temp.Substring(0, keyLength), temp.Substring(keyLength)); + } + keyLength = -1; + offset = 0; + continue; + case '\\': + if (mode == KEY_DONE) { + keyLength = offset; + } + mode = SLASH; + continue; + case ':': + case '=': + if (keyLength == -1) { // if parsing the key + mode = NONE; + keyLength = offset; + continue; + } + break; + } + if (Char.IsWhiteSpace(nextChar)) { + if (mode == CONTINUE) { + mode = IGNORE; + } + // if key length == 0 or value length == 0 + if (offset == 0 || offset == keyLength || mode == IGNORE) { + continue; + } + if (keyLength == -1) { // if parsing the key + mode = KEY_DONE; + continue; + } + } + if (mode == IGNORE || mode == CONTINUE) { + mode = NONE; + } + } + firstChar = false; + if (mode == KEY_DONE) { + keyLength = offset; + mode = NONE; + } + buf[offset++] = nextChar; + } + if (mode == UNICODE && count <= 4) { + // luni.08=Invalid Unicode sequence: expected format \\uxxxx + throw new ArgumentException(); + } + if (keyLength == -1 && offset > 0) { + keyLength = offset; + } + if (keyLength >= 0) { + String temp = new String(buf, 0, offset); + String key = temp.Substring(0, keyLength); + String value = temp.Substring(keyLength); + if (mode == SLASH) { + value += "\u0000"; + } + Add(key, value); + } + } + } + + public void Store(Stream output, String comment) { + lock (this) { + if (lineSeparator == null) { + lineSeparator = Environment.NewLine; //$NON-NLS-1$ + } + + StringBuilder buffer = new StringBuilder(200); + StreamWriter writer = new StreamWriter(output, Encoding.GetEncoding("ISO-8859-1")); //$NON-NLS-1$ + if (comment != null) { + writer.Write("#"); //$NON-NLS-1$ + writer.Write(comment); + writer.Write(lineSeparator); + } + writer.Write("#"); //$NON-NLS-1$ + writer.Write(DateTime.Now.ToString()); + writer.Write(lineSeparator); + + foreach (KeyValuePair pair in this) { + string key = (string)pair.Key; + dumpString(buffer, key, true); + buffer.Append('='); + dumpString(buffer, (String)pair.Value, false); + buffer.Append(lineSeparator); + writer.Write(buffer.ToString()); + buffer.Length = 0; + } + writer.Flush(); + } + } + + public void Store(TextWriter writer, String comment) { + lock (this) { + if (lineSeparator == null) { + lineSeparator = Environment.NewLine; //$NON-NLS-1$ + } + StringBuilder buffer = new StringBuilder(200); + if (comment != null) { + writer.Write("#"); //$NON-NLS-1$ + writer.Write(comment); + writer.Write(lineSeparator); + } + writer.Write("#"); //$NON-NLS-1$ + writer.Write(DateTime.Now.ToString()); + writer.Write(lineSeparator); + + foreach (KeyValuePair pair in this) { + String key = (String)pair.Key; + dumpString(buffer, key, true); + buffer.Append('='); + dumpString(buffer, (String)pair.Value, false); + buffer.Append(lineSeparator); + writer.Write(buffer.ToString()); + buffer.Length = 0; + } + writer.Flush(); + } + } + } +} \ No newline at end of file diff --git a/src/dshell/Properties/AssemblyInfo.cs b/src/dshell/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2a40cf7 --- /dev/null +++ b/src/dshell/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("dshell")] +[assembly: AssemblyDescription("Deveel Shell Library")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Deveel")] +[assembly: AssemblyProduct("dshell")] +[assembly: AssemblyCopyright("Copyright © Deveel 2010-2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("4f78519b-56e5-4495-80fd-0c9785559226")] + +[assembly: AssemblyVersion("0.6.7.*")] +[assembly: AssemblyFileVersion("0.6.7.*")] diff --git a/src/dshell/dshell.vs2010.csproj b/src/dshell/dshell.vs2010.csproj new file mode 100644 index 0000000..881ab1d --- /dev/null +++ b/src/dshell/dshell.vs2010.csproj @@ -0,0 +1,132 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {5E849796-A39E-4AF8-A516-D2837C3CCE6F} + Library + Properties + dshell + dshell + v2.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\deveel-cli.1.0.1\lib\net20\deveel-cli.dll + + + ..\packages\deveelrl.1.0.0\lib\net20\deveelrl.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dshell/packages.config b/src/dshell/packages.config new file mode 100644 index 0000000..211bf05 --- /dev/null +++ b/src/dshell/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file