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