added scripting support (FELIX-2339).
moved registration of all commands and converters from runtime to shell (FELIX-2328).
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@943947 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/console/pom.xml b/gogo/console/pom.xml
index 8c430a7..5c2e4be 100644
--- a/gogo/console/pom.xml
+++ b/gogo/console/pom.xml
@@ -24,8 +24,8 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>bundle</packaging>
- <name>Apache Felix Gogo Shell Console</name>
- <artifactId>org.apache.felix.gogo.console</artifactId>
+ <name>Apache Felix Gogo Shell</name>
+ <artifactId>org.apache.felix.gogo.shell</artifactId>
<version>0.5.0-SNAPSHOT</version>
<dependencies>
<dependency>
@@ -54,16 +54,15 @@
<Export-Package>
</Export-Package>
<Import-Package>
- org.osgi.service.component*; resolution:=optional,
- org.osgi.service.log*; resolution:=optional,
- org.osgi.service.packageadmin*; resolution:=optional,
- org.osgi.service.startlevel*; resolution:=optional,
*
</Import-Package>
- <Private-Package>org.apache.felix.gogo.*</Private-Package>
+ <Private-Package>
+ org.apache.felix.gogo.shell,
+ org.apache.felix.gogo.options
+ </Private-Package>
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
<Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
- <Bundle-Activator>org.apache.felix.gogo.console.Activator</Bundle-Activator>
+ <Bundle-Activator>org.apache.felix.gogo.shell.Activator</Bundle-Activator>
<Include-Resource>{maven-resources},META-INF/NOTICE=NOTICE</Include-Resource>
<_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
<_removeheaders>Private-Package,Ignore-Package,Include-Resource</_removeheaders>
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/console/Activator.java b/gogo/console/src/main/java/org/apache/felix/gogo/console/Activator.java
deleted file mode 100644
index dc865cc..0000000
--- a/gogo/console/src/main/java/org/apache/felix/gogo/console/Activator.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.gogo.console;
-
-import org.apache.felix.gogo.console.stdio.StdioConsole;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.command.CommandProcessor;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class Activator implements BundleActivator
-{
-
- private ServiceTracker commandProcessorTracker;
- private StdioConsole console;
-
- public void start(final BundleContext context) throws Exception
- {
- commandProcessorTracker = new ServiceTracker(context, CommandProcessor.class.getName(), null) {
- @Override
- public Object addingService(ServiceReference reference)
- {
- CommandProcessor processor = (CommandProcessor) super.addingService(reference);
- startConsole(processor);
- return processor;
- }
-
- @Override
- public void removedService(ServiceReference reference, Object service)
- {
- if (console != null) {
- console.close();
- console = null;
- }
- super.removedService(reference, service);
- }
- };
- commandProcessorTracker.open();
- }
-
- protected void startConsole(final CommandProcessor processor) {
- console = new StdioConsole();
- console.setProcessor(processor);
- console.start();
- }
-
- public void stop(BundleContext context) throws Exception
- {
- if (console != null) {
- console.close();
- }
- commandProcessorTracker.close();
- }
-
-}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/console/stdio/Console.java b/gogo/console/src/main/java/org/apache/felix/gogo/console/stdio/Console.java
deleted file mode 100644
index 7756a74..0000000
--- a/gogo/console/src/main/java/org/apache/felix/gogo/console/stdio/Console.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.gogo.console.stdio;
-
-import org.osgi.service.command.CommandSession;
-import org.osgi.service.command.Converter;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class Console implements Runnable
-{
- StringBuilder sb;
- CommandSession session;
- List<CharSequence> history = new ArrayList<CharSequence>();
- int current = 0;
- boolean quit;
-
- public void setSession(CommandSession session)
- {
- this.session = session;
- }
-
- public void run()
- {
- try
- {
- while (!quit)
- {
- try
- {
- CharSequence line = getLine(session.getKeyboard());
- if (line != null)
- {
- history.add(line);
- if (history.size() > 40)
- {
- history.remove(0);
- }
- Object result = session.execute(line);
- if (result != null)
- {
- session.getConsole().println(session.format(result, Converter.INSPECT));
- }
- }
- else
- {
- quit = true;
- }
-
- }
- catch (InvocationTargetException ite)
- {
- session.getConsole().println("E: " + ite.getTargetException());
- session.put("exception", ite.getTargetException());
- }
- catch (Throwable e)
- {
- if (!quit)
- {
- session.getConsole().println("E: " + e.getMessage());
- session.put("exception", e);
- }
- }
- }
- }
- catch (Exception e)
- {
- if (!quit)
- {
- e.printStackTrace();
- }
- }
- }
-
- CharSequence getLine(InputStream in) throws IOException
- {
- sb = new StringBuilder();
- session.getConsole().print("$ ");
- int outer = 0;
- while (!quit)
- {
- session.getConsole().flush();
- int c = in.read();
- if (c < 0)
- {
- quit = true;
- }
- else
- {
- switch (c)
- {
- case '\r':
- break;
- case '\n':
- if (outer == 0 && sb.length() > 0)
- {
- return sb;
- }
- else
- {
- session.getConsole().print("$ ");
- }
- break;
-
- case '\u001b':
- c = in.read();
- if (c == '[')
- {
- c = in.read();
- session.getConsole().print("\b\b\b");
- switch (c)
- {
- case 'A':
- history(current - 1);
- break;
- case 'B':
- history(current + 1);
- break;
- case 'C': // right(); break;
- case 'D': // left(); break;
- }
- }
- break;
-
- case '\b':
- if (sb.length() > 0)
- {
- session.getConsole().print("\b \b");
- sb.deleteCharAt(sb.length() - 1);
- }
- break;
-
- default:
- sb.append((char) c);
- break;
- }
- }
- }
- return null;
- }
-
- void history(int n)
- {
- if (n < 0 || n > history.size())
- {
- return;
- }
- current = n;
- for (int i = 0; i < sb.length(); i++)
- {
- session.getConsole().print("\b \b");
- }
-
- sb = new StringBuilder(history.get(current));
- session.getConsole().print(sb);
- }
-
- public void close()
- {
- quit = true;
- }
-
- public void open()
- {
- }
-}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/console/stdio/StdioConsole.java b/gogo/console/src/main/java/org/apache/felix/gogo/console/stdio/StdioConsole.java
deleted file mode 100644
index e83c1b4..0000000
--- a/gogo/console/src/main/java/org/apache/felix/gogo/console/stdio/StdioConsole.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.gogo.console.stdio;
-
-import org.osgi.service.command.CommandProcessor;
-
-public class StdioConsole extends Thread
-{
- final Console console = new Console();
-
- public StdioConsole()
- {
- super("StdioConsole");
- }
-
- public void close()
- {
- console.close();
- interrupt();
- }
-
- public void setProcessor(CommandProcessor processor)
- {
- console.setSession(processor.createSession(System.in, System.out, System.err));
- }
-
- public void run()
- {
- console.run();
- }
-}
\ No newline at end of file
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/console/telnet/Handler.java b/gogo/console/src/main/java/org/apache/felix/gogo/console/telnet/Handler.java
deleted file mode 100644
index 4b12381..0000000
--- a/gogo/console/src/main/java/org/apache/felix/gogo/console/telnet/Handler.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.gogo.console.telnet;
-
-import org.apache.felix.gogo.console.stdio.Console;
-import org.osgi.service.command.CommandSession;
-
-import java.io.IOException;
-import java.net.Socket;
-
-public class Handler extends Thread
-{
- TelnetShell master;
- Socket socket;
- CommandSession session;
- Console console;
-
- public Handler(TelnetShell master, CommandSession session, Socket socket) throws IOException
- {
- this.master = master;
- this.socket = socket;
- this.session = session;
- }
-
- public void run()
- {
- try
- {
- console = new Console();
- console.setSession(session);
- console.run();
- }
- finally
- {
- close();
- master.handlers.remove(this);
- }
- }
-
- public void close()
- {
- session.close();
- try
- {
- socket.close();
- }
- catch (IOException e)
- {
- // Ignore, this is close
- }
- }
-
-}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/console/telnet/TelnetShell.java b/gogo/console/src/main/java/org/apache/felix/gogo/console/telnet/TelnetShell.java
deleted file mode 100644
index 4b02d74..0000000
--- a/gogo/console/src/main/java/org/apache/felix/gogo/console/telnet/TelnetShell.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.gogo.console.telnet;
-
-import org.osgi.service.command.CommandProcessor;
-import org.osgi.service.command.CommandSession;
-import org.osgi.service.component.ComponentContext;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.BindException;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.util.ArrayList;
-import java.util.List;
-
-public class TelnetShell extends Thread
-{
- boolean quit;
- CommandProcessor processor;
- ServerSocket server;
- int port = 2019;
- List<Handler> handlers = new ArrayList<Handler>();
-
- protected void activate(ComponentContext context)
- {
- String s = (String) context.getProperties().get("port");
- if (s != null)
- {
- port = Integer.parseInt(s);
- }
- System.out.println("Telnet Listener at port " + port);
- start();
- }
-
- protected void deactivate(ComponentContext ctx) throws Exception
- {
- try
- {
- quit = true;
- server.close();
- interrupt();
- }
- catch (Exception e)
- {
- // Ignore
- }
- }
-
- public void run()
- {
- int delay = 0;
- try
- {
- while (!quit)
- {
- try
- {
- server = new ServerSocket(port);
- delay = 5;
- while (!quit)
- {
- Socket socket = server.accept();
- CommandSession session = processor.createSession(socket.getInputStream(), new PrintStream(socket.getOutputStream()), System.err);
- Handler handler = new Handler(this, session, socket);
- handlers.add(handler);
- handler.start();
- }
- }
- catch (BindException be)
- {
- delay += 5;
- System.err.println("Can not bind to port " + port);
- try
- {
- Thread.sleep(delay * 1000);
- }
- catch (InterruptedException e)
- {
- // who cares?
- }
- }
- catch (Exception e)
- {
- if (!quit)
- {
- e.printStackTrace();
- }
- }
- finally
- {
- try
- {
- server.close();
- Thread.sleep(2000);
- }
- catch (Exception ie)
- {
- //
- }
- }
- }
-
- }
- finally
- {
- try
- {
- if (server != null)
- {
- server.close();
- }
- }
- catch (IOException e)
- {
- //
- }
- for (Handler handler : handlers)
- {
- handler.close();
- }
- }
- }
-
- public void setProcessor(CommandProcessor processor)
- {
- this.processor = processor;
- }
-}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/options/Option.java b/gogo/console/src/main/java/org/apache/felix/gogo/options/Option.java
new file mode 100644
index 0000000..6b4a496
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/options/Option.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.options;
+
+import java.util.List;
+
+public interface Option {
+ /**
+ * stop parsing on the first unknown option. This allows one parser to get its own options and
+ * then pass the remaining options to another parser.
+ *
+ * @param stopOnBadOption
+ */
+ Option setStopOnBadOption(boolean stopOnBadOption);
+
+ /**
+ * require options to precede args. Default is false, so options can appear between or after
+ * args.
+ *
+ * @param optionsFirst
+ */
+ Option setOptionsFirst(boolean optionsFirst);
+
+ /**
+ * parse arguments. If skipArgv0 is true, then parsing begins at arg1. This allows for commands
+ * where argv0 is the command name rather than a real argument.
+ *
+ * @param argv
+ * @param skipArg0
+ * @return
+ */
+ Option parse(List<? extends Object> argv, boolean skipArg0);
+
+ /**
+ * parse arguments.
+ *
+ * @see {@link #parse(List, boolean)
+
+ */
+ Option parse(List<? extends Object> argv);
+
+ /**
+ * parse arguments.
+ *
+ * @see {@link #parse(List, boolean)
+
+ */
+ Option parse(Object[] argv, boolean skipArg0);
+
+ /**
+ * parse arguments.
+ *
+ * @see {@link #parse(List, boolean)
+
+ */
+ Option parse(Object[] argv);
+
+ /**
+ * test whether specified option has been explicitly set.
+ *
+ * @param name
+ * @return
+ */
+ boolean isSet(String name);
+
+ /**
+ * get value of named option. If multiple options given, this method returns the last one. Use
+ * {@link #getList(String)} to get all values.
+ *
+ * @param name
+ * @return
+ * @throws IllegalArgumentException
+ * if value is not a String.
+ */
+ String get(String name);
+
+ /**
+ * get list of all values for named option.
+ *
+ * @param name
+ * @return empty list if option not given and no default specified.
+ * @throws IllegalArgumentException
+ * if all values are not Strings.
+ */
+ List<String> getList(String name);
+
+ /**
+ * get value of named option as an Object. If multiple options given, this method returns the
+ * last one. Use {@link #getObjectList(String)} to get all values.
+ *
+ * @param name
+ * @return
+ */
+ Object getObject(String name);
+
+ /**
+ * get list of all Object values for named option.
+ *
+ * @param name
+ * @return
+ */
+ List<Object> getObjectList(String name);
+
+ /**
+ * get value of named option as a Number.
+ *
+ * @param name
+ * @return
+ * @throws IllegalArgumentException
+ * if argument is not a Number.
+ */
+ int getNumber(String name);
+
+ /**
+ * get remaining non-options args as Strings.
+ *
+ * @return
+ * @throws IllegalArgumentException
+ * if args are not Strings.
+ */
+ List<String> args();
+
+ /**
+ * get remaining non-options args as Objects.
+ *
+ * @return
+ */
+ List<Object> argObjects();
+
+ /**
+ * print usage message to System.err.
+ */
+ void usage();
+
+ /**
+ * print specified usage error to System.err. You should explicitly throw the returned
+ * exception.
+ *
+ * @param error
+ * @return IllegalArgumentException
+ */
+ IllegalArgumentException usageError(String error);
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/options/Options.java b/gogo/console/src/main/java/org/apache/felix/gogo/options/Options.java
new file mode 100644
index 0000000..e2fdba0
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/options/Options.java
@@ -0,0 +1,528 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.options;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Yet another GNU long options parser. This one is configured by parsing its Usage string.
+ */
+public class Options implements Option {
+ public static void main(String[] args) {
+ final String[] usage = {
+ "test - test Options usage",
+ " text before Usage: is displayed when usage() is called and no error has occurred.",
+ " so can be used as a simple help message.",
+ "",
+ "Usage: testOptions [OPTION]... PATTERN [FILES]...",
+ " Output control: arbitary non-option text can be included.",
+ " -? --help show help",
+ " -c --count=COUNT show COUNT lines",
+ " -h --no-filename suppress the prefixing filename on output",
+ " -q --quiet, --silent suppress all normal output",
+ " --binary-files=TYPE assume that binary files are TYPE",
+ " TYPE is 'binary', 'text', or 'without-match'",
+ " -I equivalent to --binary-files=without-match",
+ " -d --directories=ACTION how to handle directories (default=skip)",
+ " ACTION is 'read', 'recurse', or 'skip'",
+ " -D --devices=ACTION how to handle devices, FIFOs and sockets",
+ " ACTION is 'read' or 'skip'",
+ " -R, -r --recursive equivalent to --directories=recurse" };
+
+ Option opt = Options.compile(usage).parse(args);
+
+ if (opt.isSet("help")) {
+ opt.usage(); // includes text before Usage:
+ return;
+ }
+
+ if (opt.args().size() == 0)
+ throw opt.usageError("PATTERN not specified");
+
+ System.out.println(opt);
+ if (opt.isSet("count"))
+ System.out.println("count = " + opt.getNumber("count"));
+ System.out.println("--directories specified: " + opt.isSet("directories"));
+ System.out.println("directories=" + opt.get("directories"));
+ }
+
+ public static final String NL = System.getProperty("line.separator", "\n");
+
+ // Note: need to double \ within ""
+ private static final String regex = "(?x)\\s*" + "(?:-([^-]))?" + // 1: short-opt-1
+ "(?:,?\\s*-(\\w))?" + // 2: short-opt-2
+ "(?:,?\\s*--(\\w[\\w-]*)(=\\w+)?)?" + // 3: long-opt-1 and 4:arg-1
+ "(?:,?\\s*--(\\w[\\w-]*))?" + // 5: long-opt-2
+ ".*?(?:\\(default=(.*)\\))?\\s*"; // 6: default
+
+ private static final int GROUP_SHORT_OPT_1 = 1;
+ private static final int GROUP_SHORT_OPT_2 = 2;
+ private static final int GROUP_LONG_OPT_1 = 3;
+ private static final int GROUP_ARG_1 = 4;
+ private static final int GROUP_LONG_OPT_2 = 5;
+ private static final int GROUP_DEFAULT = 6;
+
+ private final Pattern parser = Pattern.compile(regex);
+ private final Pattern uname = Pattern.compile("^Usage:\\s+(\\w+)");
+
+ private final Map<String, Boolean> unmodifiableOptSet;
+ private final Map<String, Object> unmodifiableOptArg;
+ private final Map<String, Boolean> optSet = new HashMap<String, Boolean>();
+ private final Map<String, Object> optArg = new HashMap<String, Object>();
+
+ private final Map<String, String> optName = new HashMap<String, String>();
+ private final Map<String, String> optAlias = new HashMap<String, String>();
+ private final List<Object> xargs = new ArrayList<Object>();
+ private List<String> args = null;
+
+ private static final String UNKNOWN = "unknown";
+ private String usageName = UNKNOWN;
+ private int usageIndex = 0;
+
+ private final String[] spec;
+ private final String[] gspec;
+ private final String defOpts;
+ private final String[] defArgs;
+ private PrintStream errStream = System.err;
+ private String error = null;
+
+ private boolean optionsFirst = false;
+ private boolean stopOnBadOption = false;
+
+ public static Option compile(String[] optSpec) {
+ return new Options(optSpec, null, null);
+ }
+
+ public static Option compile(String optSpec) {
+ return compile(optSpec.split("\\n"));
+ }
+
+ public static Option compile(String[] optSpec, Option gopt) {
+ return new Options(optSpec, null, gopt);
+ }
+
+ public static Option compile(String[] optSpec, String[] gspec) {
+ return new Options(optSpec, gspec, null);
+ }
+
+ public Option setStopOnBadOption(boolean stopOnBadOption) {
+ this.stopOnBadOption = stopOnBadOption;
+ return this;
+ }
+
+ public Option setOptionsFirst(boolean optionsFirst) {
+ this.optionsFirst = optionsFirst;
+ return this;
+ }
+
+ public boolean isSet(String name) {
+ if (!optSet.containsKey(name))
+ throw new IllegalArgumentException("option not defined in spec: " + name);
+
+ return optSet.get(name);
+ }
+
+ public Object getObject(String name) {
+ if (!optArg.containsKey(name))
+ throw new IllegalArgumentException("option not defined with argument: " + name);
+
+ List<Object> list = getObjectList(name);
+
+ return list.isEmpty() ? "" : list.get(list.size() - 1);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<Object> getObjectList(String name) {
+ List<Object> list;
+ Object arg = optArg.get(name);
+
+ if ( arg == null ) {
+ throw new IllegalArgumentException("option not defined with argument: " + name);
+ }
+
+ if (arg instanceof String) { // default value
+ list = new ArrayList<Object>();
+ if (!"".equals(arg))
+ list.add(arg);
+ }
+ else {
+ list = (List<Object>) arg;
+ }
+
+ return list;
+ }
+
+ public List<String> getList(String name) {
+ ArrayList<String> list = new ArrayList<String>();
+ for (Object o : getObjectList(name)) {
+ try {
+ list.add((String) o);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException("option not String: " + name);
+ }
+ }
+ return list;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addArg(String name, Object value) {
+ List<Object> list;
+ Object arg = optArg.get(name);
+
+ if (arg instanceof String) { // default value
+ list = new ArrayList<Object>();
+ optArg.put(name, list);
+ }
+ else {
+ list = (List<Object>) arg;
+ }
+
+ list.add(value);
+ }
+
+ public String get(String name) {
+ try {
+ return (String) getObject(name);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException("option not String: " + name);
+ }
+ }
+
+ public int getNumber(String name) {
+ String number = get(name);
+ try {
+ if (number != null)
+ return Integer.parseInt(number);
+ return 0;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("option '" + name + "' not Number: " + number);
+ }
+ }
+
+ public List<Object> argObjects() {
+ return xargs;
+ }
+
+ public List<String> args() {
+ if (args == null) {
+ args = new ArrayList<String>();
+ for (Object arg : xargs) {
+ args.add(arg == null ? "null" : arg.toString());
+ }
+ }
+ return args;
+ }
+
+ public void usage() {
+ StringBuilder buf = new StringBuilder();
+ int index = 0;
+
+ if (error != null) {
+ buf.append(error);
+ buf.append(NL);
+ index = usageIndex;
+ }
+
+ for (int i = index; i < spec.length; ++i) {
+ buf.append(spec[i]);
+ buf.append(NL);
+ }
+
+ String msg = buf.toString();
+
+ if (errStream != null) {
+ errStream.print(msg);
+ }
+ }
+
+ /**
+ * prints usage message and returns IllegalArgumentException, for you to throw.
+ */
+ public IllegalArgumentException usageError(String s) {
+ error = usageName + ": " + s;
+ usage();
+ return new IllegalArgumentException(error);
+ }
+
+ // internal constructor
+ private Options(String[] spec, String[] gspec, Option opt) {
+ this.gspec = gspec;
+ Options gopt = (Options) opt;
+
+ if (gspec == null && gopt == null) {
+ this.spec = spec;
+ }
+ else {
+ ArrayList<String> list = new ArrayList<String>();
+ list.addAll(Arrays.asList(spec));
+ list.addAll(Arrays.asList(gspec != null ? gspec : gopt.gspec));
+ this.spec = list.toArray(new String[0]);
+ }
+
+ Map<String, Boolean> myOptSet = new HashMap<String, Boolean>();
+ Map<String, Object> myOptArg = new HashMap<String, Object>();
+
+ parseSpec(myOptSet, myOptArg);
+
+ if (gopt != null) {
+ for (Entry<String, Boolean> e : gopt.optSet.entrySet()) {
+ if (e.getValue())
+ myOptSet.put(e.getKey(), true);
+ }
+
+ for (Entry<String, Object> e : gopt.optArg.entrySet()) {
+ if (!e.getValue().equals(""))
+ myOptArg.put(e.getKey(), e.getValue());
+ }
+
+ gopt.reset();
+ }
+
+ unmodifiableOptSet = Collections.unmodifiableMap(myOptSet);
+ unmodifiableOptArg = Collections.unmodifiableMap(myOptArg);
+
+ defOpts = System.getenv(usageName.toUpperCase() + "_OPTS");
+ defArgs = (defOpts != null) ? defOpts.split("\\s+") : new String[0];
+ }
+
+ /**
+ * parse option spec.
+ */
+ private void parseSpec(Map<String, Boolean> myOptSet, Map<String, Object> myOptArg) {
+ int index = 0;
+ for (String line : spec) {
+ Matcher m = parser.matcher(line);
+
+ if (m.matches()) {
+ final String opt = m.group(GROUP_LONG_OPT_1);
+ final String name = (opt != null) ? opt : m.group(GROUP_SHORT_OPT_1);
+
+ if (name != null) {
+ if (myOptSet.containsKey(name))
+ throw new IllegalArgumentException("duplicate option in spec: --" + name);
+ myOptSet.put(name, false);
+ }
+
+ String dflt = (m.group(GROUP_DEFAULT) != null) ? m.group(GROUP_DEFAULT) : "";
+ if (m.group(GROUP_ARG_1) != null)
+ myOptArg.put(opt, dflt);
+
+ String opt2 = m.group(GROUP_LONG_OPT_2);
+ if (opt2 != null) {
+ optAlias.put(opt2, opt);
+ myOptSet.put(opt2, false);
+ if (m.group(GROUP_ARG_1) != null)
+ myOptArg.put(opt2, "");
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ String sopt = m.group(i == 0 ? GROUP_SHORT_OPT_1 : GROUP_SHORT_OPT_2);
+ if (sopt != null) {
+ if (optName.containsKey(sopt))
+ throw new IllegalArgumentException("duplicate option in spec: -" + sopt);
+ optName.put(sopt, name);
+ }
+ }
+ }
+
+ if (usageName == UNKNOWN) {
+ Matcher u = uname.matcher(line);
+ if (u.find()) {
+ usageName = u.group(1);
+ usageIndex = index;
+ }
+ }
+
+ index++;
+ }
+ }
+
+ private void reset() {
+ optSet.clear();
+ optSet.putAll(unmodifiableOptSet);
+ optArg.clear();
+ optArg.putAll(unmodifiableOptArg);
+ xargs.clear();
+ args = null;
+ error = null;
+ }
+
+ public Option parse(Object[] argv) {
+ return parse(argv, false);
+ }
+
+ public Option parse(List<? extends Object> argv) {
+ return parse(argv, false);
+ }
+
+ public Option parse(Object[] argv, boolean skipArg0) {
+ if (null == argv)
+ throw new IllegalArgumentException("argv is null");
+
+ return parse(Arrays.asList(argv), skipArg0);
+ }
+
+ public Option parse(List<? extends Object> argv, boolean skipArg0) {
+ reset();
+ List<Object> args = new ArrayList<Object>();
+ args.addAll(Arrays.asList(defArgs));
+
+ for (Object arg : argv) {
+ if (skipArg0) {
+ skipArg0 = false;
+ usageName = arg.toString();
+ }
+ else {
+ args.add(arg);
+ }
+ }
+
+ String needArg = null;
+ String needOpt = null;
+ boolean endOpt = false;
+
+ for (Object oarg : args) {
+ String arg = oarg == null ? "null" : oarg.toString();
+
+ if (endOpt) {
+ xargs.add(oarg);
+ }
+ else if (needArg != null) {
+ addArg(needArg, oarg);
+ needArg = null;
+ needOpt = null;
+ }
+ else if (!arg.startsWith("-") || "-".equals(oarg)) {
+ if (optionsFirst)
+ endOpt = true;
+ xargs.add(oarg);
+ }
+ else {
+ if (arg.equals("--"))
+ endOpt = true;
+ else if (arg.startsWith("--")) {
+ int eq = arg.indexOf("=");
+ String value = (eq == -1) ? null : arg.substring(eq + 1);
+ String name = arg.substring(2, ((eq == -1) ? arg.length() : eq));
+ List<String> names = new ArrayList<String>();
+
+ if (optSet.containsKey(name)) {
+ names.add(name);
+ }
+ else {
+ for (String k : optSet.keySet()) {
+ if (k.startsWith(name))
+ names.add(k);
+ }
+ }
+
+ switch (names.size()) {
+ case 1:
+ name = names.get(0);
+ optSet.put(name, true);
+ if (optArg.containsKey(name)) {
+ if (value != null)
+ addArg(name, value);
+ else
+ needArg = name;
+ }
+ else if (value != null) {
+ throw usageError("option '--" + name + "' doesn't allow an argument");
+ }
+ break;
+
+ case 0:
+ if (stopOnBadOption) {
+ endOpt = true;
+ xargs.add(oarg);
+ break;
+ }
+ else
+ throw usageError("invalid option '--" + name + "'");
+
+ default:
+ throw usageError("option '--" + name + "' is ambiguous: " + names);
+ }
+ }
+ else {
+ int i = 0;
+ for (String c : arg.substring(1).split("")) {
+ if (i++ == 0)
+ continue;
+ if (optName.containsKey(c)) {
+ String name = optName.get(c);
+ optSet.put(name, true);
+ if (optArg.containsKey(name)) {
+ if (i < arg.length()) {
+ addArg(name, arg.substring(i));
+ }
+ else {
+ needOpt = c;
+ needArg = name;
+ }
+ break;
+ }
+ }
+ else {
+ if (stopOnBadOption) {
+ xargs.add("-" + c);
+ endOpt = true;
+ }
+ else
+ throw usageError("invalid option '" + c + "'");
+ }
+ }
+ }
+ }
+ }
+
+ if (needArg != null) {
+ String name = (needOpt != null) ? needOpt : "--" + needArg;
+ throw usageError("option '" + name + "' requires an argument");
+ }
+
+ // remove long option aliases
+ for (Entry<String, String> alias : optAlias.entrySet()) {
+ if (optSet.get(alias.getKey())) {
+ optSet.put(alias.getValue(), true);
+ if (optArg.containsKey(alias.getKey()))
+ optArg.put(alias.getValue(), optArg.get(alias.getKey()));
+ }
+ optSet.remove(alias.getKey());
+ optArg.remove(alias.getKey());
+ }
+
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "isSet" + optSet + "\nArg" + optArg + "\nargs" + xargs;
+ }
+
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/shell/Activator.java b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Activator.java
new file mode 100644
index 0000000..4a723f3
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Activator.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.shell;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.command.CommandProcessor;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Converter;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class Activator implements BundleActivator, Runnable
+{
+ private ServiceTracker commandProcessorTracker;
+ private Set<ServiceRegistration> regs = new HashSet<ServiceRegistration>();
+ private CommandSession session;
+ private Shell shell;
+ private Thread thread;
+
+ public void start(final BundleContext context) throws Exception
+ {
+ shell = new Shell(context);
+ registerCommands(context);
+ commandProcessorTracker = processorTracker(context);
+ }
+
+ public void stop(BundleContext context) throws Exception
+ {
+ if (thread != null)
+ {
+ thread.interrupt();
+ }
+
+ commandProcessorTracker.close();
+
+ Iterator<ServiceRegistration> iterator = regs.iterator();
+ while (iterator.hasNext())
+ {
+ ServiceRegistration reg = iterator.next();
+ reg.unregister();
+ iterator.remove();
+ }
+ }
+
+ public void run()
+ {
+ try
+ {
+ Thread.sleep(100); // wait for gosh command to be registered
+ String args = System.getProperty("gosh.args", "");
+ session.execute("gosh --login " + args);
+ }
+ catch (Exception e)
+ {
+ Object loc = session.get(".location");
+ if (null == loc || !loc.toString().contains(":"))
+ {
+ loc = "gogo";
+ }
+
+ System.err.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ e.printStackTrace();
+ }
+ finally
+ {
+ session.close();
+ }
+ }
+
+ private void startShell(CommandProcessor processor)
+ {
+ session = processor.createSession(System.in, System.out, System.err);
+ shell.setProcessor(processor);
+ thread = new Thread(this, "Gogo shell");
+ thread.start();
+ }
+
+ private void registerCommands(BundleContext context)
+ {
+ // default converters
+ regs.add(context.registerService(Converter.class.getName(), new Converters(context), null));
+
+ Dictionary<String, Object> dict = new Hashtable<String, Object>();
+ dict.put(CommandProcessor.COMMAND_SCOPE, "gogo");
+
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Shell.functions);
+ regs.add(context.registerService(Shell.class.getName(), shell, dict));
+
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Builtin.functions);
+ regs.add(context.registerService(Builtin.class.getName(), new Builtin(), dict));
+
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Procedural.functions);
+ regs.add(context.registerService(Procedural.class.getName(), new Procedural(), dict));
+
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Posix.functions);
+ regs.add(context.registerService(Posix.class.getName(), new Posix(), dict));
+ }
+
+ private ServiceTracker processorTracker(BundleContext context)
+ {
+ ServiceTracker t = new ServiceTracker(context, CommandProcessor.class.getName(),
+ null)
+ {
+ @Override
+ public Object addingService(ServiceReference reference)
+ {
+ CommandProcessor processor = (CommandProcessor) super.addingService(reference);
+ startShell(processor);
+ return processor;
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service)
+ {
+ if (thread != null)
+ {
+ thread.interrupt();
+ }
+ super.removedService(reference, service);
+ }
+ };
+
+ t.open();
+ return t;
+ }
+
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/shell/Builtin.java b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Builtin.java
new file mode 100644
index 0000000..f66a0ae
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Builtin.java
@@ -0,0 +1,596 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.shell;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Map.Entry;
+
+import org.apache.felix.gogo.options.Option;
+import org.apache.felix.gogo.options.Options;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Converter;
+
+/**
+ * gosh built-in commands.
+ */
+public class Builtin
+{
+
+ static final String[] functions = { "format", "getopt", "new", "set", "tac", "type" };
+
+ private static final String[] packages = { "java.lang", "java.io", "java.net",
+ "java.util" };
+
+ public CharSequence format(CommandSession session)
+ {
+ return format(session, session.get("_")); // last result
+ }
+
+ public CharSequence format(CommandSession session, Object arg)
+ {
+ CharSequence result = session.format(arg, Converter.INSPECT);
+ System.out.println(result);
+ return result;
+ }
+
+ /**
+ * script access to Options.
+ */
+ public Option getopt(List<Object> spec, Object[] args)
+ {
+ String[] optSpec = new String[spec.size()];
+ for (int i = 0; i < optSpec.length; ++i)
+ {
+ optSpec[i] = spec.get(i).toString();
+ }
+ return Options.compile(optSpec).parse(args);
+ }
+
+ // FIXME: the "new" command should be provided by runtime,
+ // so it can leverage same argument coercion mechanism, used to invoke methods.
+ public Object _new(Object name, Object[] argv) throws Exception
+ {
+ Class<?> clazz = null;
+
+ if (name instanceof Class<?>)
+ {
+ clazz = (Class<?>) name;
+ }
+ else
+ {
+ clazz = loadClass(name.toString());
+ }
+
+ for (Constructor<?> c : clazz.getConstructors())
+ {
+ Class<?>[] types = c.getParameterTypes();
+ if (types.length != argv.length)
+ {
+ continue;
+ }
+
+ boolean match = true;
+
+ for (int i = 0; i < argv.length; ++i)
+ {
+ if (!types[i].isAssignableFrom(argv[i].getClass()))
+ {
+ if (!types[i].isAssignableFrom(String.class))
+ {
+ match = false;
+ break;
+ }
+ argv[i] = argv[i].toString();
+ }
+ }
+
+ if (!match)
+ {
+ continue;
+ }
+
+ try
+ {
+ return c.newInstance(argv);
+ }
+ catch (InvocationTargetException ite)
+ {
+ Throwable cause = ite.getCause();
+ if (cause instanceof Exception)
+ {
+ throw (Exception) cause;
+ }
+ throw ite;
+ }
+ }
+
+ throw new IllegalArgumentException("can't coerce " + Arrays.asList(argv)
+ + " to any of " + Arrays.asList(clazz.getConstructors()));
+ }
+
+ private Class<?> loadClass(String name) throws ClassNotFoundException
+ {
+ if (!name.contains("."))
+ {
+ for (String p : packages)
+ {
+ String pkg = p + "." + name;
+ try
+ {
+ return Class.forName(pkg);
+ }
+ catch (ClassNotFoundException e)
+ {
+ }
+ }
+ }
+ return Class.forName(name);
+ }
+
+ public void set(CommandSession session, String[] argv) throws Exception
+ {
+ final String[] usage = {
+ "set - show session variables",
+ "Usage: set [OPTIONS] [PREFIX]",
+ " -? --help show help",
+ " -a --all show all variables, including those starting with .",
+ " -x set xtrace option",
+ " +x unset xtrace option",
+ "If PREFIX given, then only show variable(s) starting with PREFIX" };
+
+ Option opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help"))
+ {
+ opt.usage();
+ return;
+ }
+
+ List<String> args = opt.args();
+ String prefix = (args.isEmpty() ? "" : args.get(0));
+
+ if (opt.isSet("x"))
+ {
+ session.put("echo", true);
+ }
+ else if ("+x".equals(prefix))
+ {
+ session.put("echo", null);
+ }
+ else
+ {
+ boolean all = opt.isSet("all");
+ for (String key : new TreeSet<String>(Shell.getVariables(session)))
+ {
+ if (!key.startsWith(prefix))
+ continue;
+
+ if (key.startsWith(".") && !(all || prefix.length() > 0))
+ continue;
+
+ Object target = session.get(key);
+ String type = null;
+ String value = null;
+
+ if (target != null)
+ {
+ Class<? extends Object> clazz = target.getClass();
+ type = clazz.getSimpleName();
+ value = target.toString();
+ }
+
+ String trunc = value == null || value.length() < 55 ? "" : "...";
+ System.out.println(String.format("%-15.15s %-15s %.45s%s", type, key,
+ value, trunc));
+ }
+ }
+ }
+
+ public Object tac(CommandSession session, String[] argv) throws IOException
+ {
+ final String[] usage = {
+ "tac - capture stdin as String or List and optionally write to file.",
+ "Usage: tac [-al] [FILE]", " -a --append append to FILE",
+ " -l --list return List<String>",
+ " -? --help show help" };
+
+ Option opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help"))
+ {
+ opt.usage();
+ return null;
+ }
+
+ List<String> args = opt.args();
+ BufferedWriter fw = null;
+
+ if (args.size() == 1)
+ {
+ String path = args.get(0);
+ File file = new File(Shell.cwd(session).resolve(path));
+ fw = new BufferedWriter(new FileWriter(file, opt.isSet("append")));
+ }
+
+ StringWriter sw = new StringWriter();
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in));
+
+ ArrayList<String> list = null;
+
+ if (opt.isSet("list"))
+ {
+ list = new ArrayList<String>();
+ }
+
+ boolean first = true;
+ String s;
+
+ while ((s = rdr.readLine()) != null)
+ {
+ if (list != null)
+ {
+ list.add(s);
+ }
+ else
+ {
+ if (!first)
+ {
+ sw.write(' ');
+ }
+ first = false;
+ sw.write(s);
+ }
+
+ if (fw != null)
+ {
+ fw.write(s);
+ fw.newLine();
+ }
+ }
+
+ if (fw != null)
+ {
+ fw.close();
+ }
+
+ return list != null ? list : sw.toString();
+ }
+
+ // FIXME: expose API in runtime so type command doesn't have to duplicate the runtime
+ // command search strategy.
+ public boolean type(CommandSession session, String[] argv) throws Exception
+ {
+ final String[] usage = { "type - show command type",
+ "Usage: type [OPTIONS] [name[:]]",
+ " -a --all show all matches",
+ " -? --help show help",
+ " -q --quiet don't print anything, just return status",
+ " -s --scope=NAME list all commands in named scope",
+ " -t --types show full java type names" };
+
+ Option opt = Options.compile(usage).parse(argv);
+ List<String> args = opt.args();
+
+ if (opt.isSet("help"))
+ {
+ opt.usage();
+ return true;
+ }
+
+ boolean all = opt.isSet("all");
+
+ String optScope = null;
+ if (opt.isSet("scope"))
+ {
+ optScope = opt.get("scope");
+ }
+
+ if (args.size() == 1)
+ {
+ String arg = args.get(0);
+ if (arg.endsWith(":"))
+ {
+ optScope = args.remove(0);
+ }
+ }
+
+ if (optScope != null || (args.isEmpty() && all))
+ {
+ Set<String> snames = new TreeSet<String>();
+
+ for (String sname : (getCommands(session)))
+ {
+ if ((optScope == null) || sname.startsWith(optScope))
+ {
+ snames.add(sname);
+ }
+ }
+
+ for (String sname : snames)
+ {
+ System.out.println(sname);
+ }
+
+ return true;
+ }
+
+ if (args.size() == 0)
+ {
+ Map<String, Integer> scopes = new TreeMap<String, Integer>();
+
+ for (String sname : getCommands(session))
+ {
+ int colon = sname.indexOf(':');
+ String scope = sname.substring(0, colon);
+ Integer count = scopes.get(scope);
+ if (count == null)
+ {
+ count = 0;
+ }
+ scopes.put(scope, ++count);
+ }
+
+ for (Entry<String, Integer> entry : scopes.entrySet())
+ {
+ System.out.println(entry.getKey() + ":" + entry.getValue());
+ }
+
+ return true;
+ }
+
+ final String name = args.get(0).toLowerCase();
+
+ final int colon = name.indexOf(':');
+ final String MAIN = "_main"; // FIXME: must match Reflective.java
+
+ StringBuilder buf = new StringBuilder();
+ Set<String> cmds = new LinkedHashSet<String>();
+
+ // get all commands
+ if ((colon != -1) || (session.get(name) != null))
+ {
+ cmds.add(name);
+ }
+ else if (session.get(MAIN) != null)
+ {
+ cmds.add(MAIN);
+ }
+ else
+ {
+ String path = session.get("SCOPE") != null ? session.get("SCOPE").toString()
+ : "*";
+
+ for (String s : path.split(":"))
+ {
+ if (s.equals("*"))
+ {
+ for (String sname : getCommands(session))
+ {
+ if (sname.endsWith(":" + name))
+ {
+ cmds.add(sname);
+ if (!all)
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ String sname = s + ":" + name;
+ if (session.get(sname) != null)
+ {
+ cmds.add(sname);
+ if (!all)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for (String key : cmds)
+ {
+ Object target = session.get(key);
+ if (target == null)
+ {
+ continue;
+ }
+
+ CharSequence source = getClosureSource(session, key);
+
+ if (source != null)
+ {
+ buf.append(name);
+ buf.append(" is function {");
+ buf.append(source);
+ buf.append("}");
+ continue;
+ }
+
+ for (Method m : getMethods(session, key))
+ {
+ StringBuilder params = new StringBuilder();
+
+ for (Class<?> type : m.getParameterTypes())
+ {
+ if (params.length() > 0)
+ {
+ params.append(", ");
+ }
+ params.append(type.getSimpleName());
+ }
+
+ String rtype = m.getReturnType().getSimpleName();
+
+ if (buf.length() > 0)
+ {
+ buf.append("\n");
+ }
+
+ if (opt.isSet("types"))
+ {
+ String cname = m.getDeclaringClass().getName();
+ buf.append(String.format("%s %s.%s(%s)", rtype, cname, m.getName(),
+ params));
+ }
+ else
+ {
+ buf.append(String.format("%s is %s %s(%s)", name, rtype, key, params));
+ }
+ }
+ }
+
+ if (buf.length() > 0)
+ {
+ if (!opt.isSet("quiet"))
+ {
+ System.out.println(buf);
+ }
+ return true;
+ }
+
+ if (!opt.isSet("quiet"))
+ {
+ System.err.println("type: " + name + " not found.");
+ }
+
+ return false;
+ }
+
+ /*
+ * the following methods depend on the internals of the runtime implementation.
+ * ideally, they should be available via some API.
+ */
+
+ @SuppressWarnings("unchecked")
+ static Set<String> getCommands(CommandSession session)
+ {
+ return (Set<String>) session.get(".commands");
+ }
+
+ private boolean isClosure(Object target)
+ {
+ return target.getClass().getSimpleName().equals("Closure");
+ }
+
+ private boolean isCommand(Object target)
+ {
+ return target.getClass().getSimpleName().equals("CommandProxy");
+ }
+
+ private CharSequence getClosureSource(CommandSession session, String name)
+ throws Exception
+ {
+ Object target = session.get(name);
+
+ if (target == null)
+ {
+ return null;
+ }
+
+ if (!isClosure(target))
+ {
+ return null;
+ }
+
+ Field sourceField = target.getClass().getDeclaredField("source");
+ sourceField.setAccessible(true);
+ return (CharSequence) sourceField.get(target);
+ }
+
+ private List<Method> getMethods(CommandSession session, String scmd) throws Exception
+ {
+ final int colon = scmd.indexOf(':');
+ final String function = colon == -1 ? scmd : scmd.substring(colon + 1);
+ final String name = KEYWORDS.contains(function) ? ("_" + function) : function;
+ final String get = "get" + function;
+ final String is = "is" + function;
+ final String set = "set" + function;
+ final String MAIN = "_main"; // FIXME: must match Reflective.java
+
+ Object target = session.get(scmd);
+ if (target == null)
+ {
+ return null;
+ }
+
+ if (isClosure(target))
+ {
+ return null;
+ }
+
+ if (isCommand(target))
+ {
+ Method method = target.getClass().getMethod("getTarget", (Class[])null);
+ method.setAccessible(true);
+ target = method.invoke(target, (Object[])null);
+ }
+
+ ArrayList<Method> list = new ArrayList<Method>();
+ Class<?> tc = (target instanceof Class<?>) ? (Class<?>) target
+ : target.getClass();
+ Method[] methods = tc.getMethods();
+
+ for (Method m : methods)
+ {
+ String mname = m.getName().toLowerCase();
+
+ if (mname.equals(name) || mname.equals(get) || mname.equals(set)
+ || mname.equals(is) || mname.equals(MAIN))
+ {
+ list.add(m);
+ }
+ }
+
+ return list;
+ }
+
+ private final static Set<String> KEYWORDS = new HashSet<String>(
+ Arrays.asList(new String[] { "abstract", "continue", "for", "new", "switch",
+ "assert", "default", "goto", "package", "synchronized", "boolean", "do",
+ "if", "private", "this", "break", "double", "implements", "protected",
+ "throw", "byte", "else", "import", "public", "throws", "case", "enum",
+ "instanceof", "return", "transient", "catch", "extends", "int", "short",
+ "try", "char", "final", "interface", "static", "void", "class",
+ "finally", "long", "strictfp", "volatile", "const", "float", "native",
+ "super", "while" }));
+
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/shell/Console.java b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Console.java
new file mode 100644
index 0000000..6b0bca4
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Console.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.shell;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Converter;
+
+public class Console implements Runnable
+{
+ private final CommandSession session;
+ private final InputStream in;
+ private final PrintStream out;
+ private boolean quit;
+
+ public Console(CommandSession session)
+ {
+ this.session = session;
+ in = session.getKeyboard();
+ out = session.getConsole();
+ }
+
+ public void run()
+ {
+ try
+ {
+ while (!quit)
+ {
+ try
+ {
+ Object prompt = session.get("prompt");
+ if (prompt == null)
+ {
+ prompt = "g! ";
+ }
+
+ CharSequence line = getLine(prompt.toString());
+
+ if (line == null)
+ {
+ break;
+ }
+
+ Object result = session.execute(line);
+ session.put("_", result); // set $_ to last result
+
+ if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format")))
+ {
+ out.println(session.format(result, Converter.INSPECT));
+ }
+ }
+ catch (Throwable e)
+ {
+ if (!quit)
+ {
+ session.put("exception", e);
+ Object loc = session.get(".location");
+
+ if (null == loc || !loc.toString().contains(":"))
+ {
+ loc = "gogo";
+ }
+
+ out.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ if (!quit)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private CharSequence getLine(String prompt) throws IOException
+ {
+ StringBuilder sb = new StringBuilder();
+ out.print(prompt);
+
+ while (!quit)
+ {
+ out.flush();
+ int c = in.read();
+
+ switch (c)
+ {
+ case -1:
+ case 4: // EOT, ^D from telnet
+ quit = true;
+ break;
+
+ case '\r':
+ break;
+
+ case '\n':
+ if (sb.length() > 0)
+ {
+ return sb;
+ }
+ out.print(prompt);
+ break;
+
+ case '\b':
+ if (sb.length() > 0)
+ {
+ out.print("\b \b");
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ break;
+
+ default:
+ sb.append((char) c);
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ public void close()
+ {
+ quit = true;
+ }
+
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/shell/Converters.java b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Converters.java
new file mode 100644
index 0000000..55aa871
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Converters.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.shell;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Formatter;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.command.Converter;
+import org.osgi.service.command.Function;
+import org.osgi.service.startlevel.StartLevel;
+
+public class Converters implements Converter
+{
+ private final BundleContext context;
+ public Converters(BundleContext context)
+ {
+ this.context = context;
+ }
+
+ private CharSequence print(Bundle bundle)
+ {
+ // [ ID ] [STATE ] [ SL ] symname
+ StartLevel sl = null;
+ ServiceReference ref = context.getServiceReference(StartLevel.class.getName());
+ if (ref != null)
+ {
+ sl = (StartLevel) context.getService(ref);
+ }
+
+ if (sl == null)
+ {
+ return String.format("%5d|%-11s|%s (%s)", bundle.getBundleId(),
+ getState(bundle), bundle.getSymbolicName(), bundle.getVersion());
+ }
+
+ int level = sl.getBundleStartLevel(bundle);
+ context.ungetService(ref);
+
+ return String.format("%5d|%-11s|%5d|%s (%s)", bundle.getBundleId(),
+ getState(bundle), level, bundle.getSymbolicName(), bundle.getVersion());
+ }
+
+ private CharSequence print(ServiceReference ref)
+ {
+ StringBuilder sb = new StringBuilder();
+ Formatter f = new Formatter(sb);
+
+ String spid = "";
+ Object pid = ref.getProperty("service.pid");
+ if (pid != null)
+ {
+ spid = pid.toString();
+ }
+
+ f.format("%06d %3s %-40s %s", ref.getProperty("service.id"),
+ ref.getBundle().getBundleId(),
+ getShortNames((String[]) ref.getProperty("objectclass")), spid);
+ return sb;
+ }
+
+ private CharSequence getShortNames(String[] list)
+ {
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (String s : list)
+ {
+ sb.append(del + getShortName(s));
+ del = " | ";
+ }
+ return sb;
+ }
+
+ private CharSequence getShortName(String name)
+ {
+ int n = name.lastIndexOf('.');
+ if (n < 0)
+ {
+ n = 0;
+ }
+ else
+ {
+ n++;
+ }
+ return name.subSequence(n, name.length());
+ }
+
+ private String getState(Bundle bundle)
+ {
+ switch (bundle.getState())
+ {
+ case Bundle.ACTIVE:
+ return "Active";
+
+ case Bundle.INSTALLED:
+ return "Installed";
+
+ case Bundle.RESOLVED:
+ return "Resolved";
+
+ case Bundle.STARTING:
+ return "Starting";
+
+ case Bundle.STOPPING:
+ return "Stopping";
+
+ case Bundle.UNINSTALLED:
+ return "Uninstalled ";
+ }
+ return null;
+ }
+
+ public Bundle bundle(Bundle i)
+ {
+ return i;
+ }
+
+ public Object convert(Class<?> desiredType, final Object in) throws Exception
+ {
+ if (desiredType == Bundle.class)
+ {
+ return convertBundle(in);
+ }
+
+ if (desiredType == ServiceReference.class)
+ {
+ return convertServiceReference(in);
+ }
+
+ if (desiredType == Class.class)
+ {
+ try
+ {
+ return Class.forName(in.toString());
+ }
+ catch (ClassNotFoundException e)
+ {
+ return null;
+ }
+ }
+
+ if (desiredType.isAssignableFrom(String.class) && in instanceof InputStream)
+ {
+ return read(((InputStream) in));
+ }
+
+ if (in instanceof Function && desiredType.isInterface()
+ && desiredType.getDeclaredMethods().length == 1)
+ {
+ return Proxy.newProxyInstance(desiredType.getClassLoader(),
+ new Class[] { desiredType }, new InvocationHandler()
+ {
+ Function command = ((Function) in);
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable
+ {
+ return command.execute(null, Arrays.asList(args));
+ }
+ });
+ }
+
+ return null;
+ }
+
+ private Object convertServiceReference(Object in) throws InvalidSyntaxException
+ {
+ String s = in.toString();
+ if (s.startsWith("(") && s.endsWith(")"))
+ {
+ ServiceReference refs[] = context.getServiceReferences(null, String.format(
+ "(|(service.id=%s)(service.pid=%s))", in, in));
+ if (refs != null && refs.length > 0)
+ {
+ return refs[0];
+ }
+ }
+
+ ServiceReference refs[] = context.getServiceReferences(null, String.format(
+ "(|(service.id=%s)(service.pid=%s))", in, in));
+ if (refs != null && refs.length > 0)
+ {
+ return refs[0];
+ }
+ return null;
+ }
+
+ private Object convertBundle(Object in)
+ {
+ String s = in.toString();
+ try
+ {
+ long id = Long.parseLong(s);
+ return context.getBundle(id);
+ }
+ catch (NumberFormatException nfe)
+ {
+ // Ignore
+ }
+
+ Bundle bundles[] = context.getBundles();
+ for (Bundle b : bundles)
+ {
+ if (b.getLocation().equals(s))
+ {
+ return b;
+ }
+
+ if (b.getSymbolicName().equals(s))
+ {
+ return b;
+ }
+ }
+
+ return null;
+ }
+
+ public CharSequence format(Object target, int level, Converter converter)
+ throws IOException
+ {
+ if (level == INSPECT && target instanceof InputStream)
+ {
+ return read(((InputStream) target));
+ }
+ if (level == LINE && target instanceof Bundle)
+ {
+ return print((Bundle) target);
+ }
+ if (level == LINE && target instanceof ServiceReference)
+ {
+ return print((ServiceReference) target);
+ }
+ if (level == PART && target instanceof Bundle)
+ {
+ return ((Bundle) target).getSymbolicName();
+ }
+ if (level == PART && target instanceof ServiceReference)
+ {
+ return getShortNames((String[]) ((ServiceReference) target).getProperty("objectclass"));
+ }
+ return null;
+ }
+
+ private CharSequence read(InputStream in) throws IOException
+ {
+ int c;
+ StringBuffer sb = new StringBuffer();
+ while ((c = in.read()) > 0)
+ {
+ if (c >= 32 && c <= 0x7F || c == '\n' || c == '\r')
+ {
+ sb.append((char) c);
+ }
+ else
+ {
+ String s = Integer.toHexString(c).toUpperCase();
+ sb.append("\\");
+ if (s.length() < 1)
+ {
+ sb.append(0);
+ }
+ sb.append(s);
+ }
+ }
+ return sb;
+ }
+
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/shell/Posix.java b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Posix.java
new file mode 100644
index 0000000..389dca7
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Posix.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.shell;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.gogo.options.Option;
+import org.apache.felix.gogo.options.Options;
+import org.osgi.service.command.CommandSession;
+
+/**
+ * Posix-like utilities.
+ *
+ * @see http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html
+ */
+public class Posix
+{
+ static final String[] functions = { "cat", "echo", "grep" };
+
+ public void cat(CommandSession session, String[] args) throws Exception
+ {
+ if (args.length == 0)
+ {
+ copy(System.in, System.out);
+ return;
+ }
+
+ URI cwd = Shell.cwd(session);
+
+ for (String arg : args)
+ {
+ copy(cwd.resolve(arg), System.out);
+ }
+ }
+
+ public void echo(Object[] args)
+ {
+ StringBuilder buf = new StringBuilder();
+
+ if (args == null)
+ {
+ System.out.println("Null");
+ return;
+ }
+
+ for (Object arg : args)
+ {
+ if (buf.length() > 0)
+ buf.append(' ');
+ buf.append(String.valueOf(arg));
+ }
+
+ System.out.println(buf);
+ }
+
+ public boolean grep(CommandSession session, String[] argv) throws IOException
+ {
+ final String[] usage = {
+ "grep - search for PATTERN in each FILE or standard input.",
+ "Usage: grep [OPTIONS] PATTERN [FILES]",
+ " -? --help show help",
+ " -i --ignore-case ignore case distinctions",
+ " -n --line-number prefix each line with line number within its input file",
+ " -q --quiet, --silent suppress all normal output",
+ " -v --invert-match select non-matching lines" };
+
+ Option opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help"))
+ {
+ opt.usage();
+ return true;
+ }
+
+ List<String> args = opt.args();
+
+ if (args.size() == 0)
+ {
+ throw opt.usageError("no pattern supplied.");
+ }
+
+ String regex = args.remove(0);
+ if (opt.isSet("ignore-case"))
+ {
+ regex = "(?i)" + regex;
+ }
+
+ if (args.isEmpty())
+ {
+ args.add(null);
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ if (args.size() > 1)
+ {
+ buf.append("%1$s:");
+ }
+
+ if (opt.isSet("line-number"))
+ {
+ buf.append("%2$s:");
+ }
+
+ buf.append("%3$s");
+ String format = buf.toString();
+
+ Pattern pattern = Pattern.compile(regex);
+ boolean status = true;
+ boolean match = false;
+
+ for (String arg : args)
+ {
+ InputStream in = null;
+
+ try
+ {
+ URI cwd = Shell.cwd(session);
+ in = (arg == null) ? System.in : cwd.resolve(arg).toURL().openStream();
+
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(in));
+ int line = 0;
+ String s;
+ while ((s = rdr.readLine()) != null)
+ {
+ line++;
+ Matcher matcher = pattern.matcher(s);
+ if (!(matcher.find() ^ !opt.isSet("invert-match")))
+ {
+ match = true;
+ if (opt.isSet("quiet"))
+ break;
+
+ System.out.println(String.format(format, arg, line, s));
+ }
+ }
+
+ if (match && opt.isSet("quiet"))
+ {
+ break;
+ }
+ }
+ catch (IOException e)
+ {
+ System.err.println("grep: " + e.getMessage());
+ status = false;
+ }
+ finally
+ {
+ if (arg != null && in != null)
+ {
+ in.close();
+ }
+ }
+ }
+
+ return match && status;
+ }
+
+ public static void copy(URI source, OutputStream out) throws IOException {
+ InputStream in = source.toURL().openStream();
+ try {
+ copy(in, out);
+ } finally {
+ in.close();
+ }
+ }
+
+
+ public static void copy(InputStream in, OutputStream out) throws IOException {
+ byte buf[] = new byte[10240];
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ out.flush();
+ }
+
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/shell/Procedural.java b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Procedural.java
new file mode 100644
index 0000000..4821ef26
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Procedural.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.shell;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Function;
+
+public class Procedural
+{
+ static final String[] functions = { "each", "if", "not", "throw", "try", "until",
+ "while" };
+
+ public List<Object> each(CommandSession session, Collection<Object> list,
+ Function closure) throws Exception
+ {
+ List<Object> args = new ArrayList<Object>();
+ List<Object> results = new ArrayList<Object>();
+ args.add(null);
+
+ for (Object x : list)
+ {
+ checkInterrupt();
+ args.set(0, x);
+ results.add(closure.execute(session, args));
+ }
+
+ return results;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object _if(CommandSession session, Function[] fns) throws Exception
+ {
+ int length = fns.length;
+ if (length < 2)
+ {
+ throw new IllegalArgumentException(
+ "Usage: if {condition} {if-action} ... {else-action}");
+ }
+
+ List<Object> args = (List<Object>) session.get("args");
+
+ for (int i = 0; i < length; ++i)
+ {
+ if (i == length - 1 || isTrue(fns[i++].execute(session, args)))
+ {
+ return fns[i].execute(session, args);
+ }
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean not(CommandSession session, Function condition) throws Exception
+ {
+ if (null == condition)
+ {
+ return true;
+ }
+
+ List<Object> args = (List<Object>) session.get("args");
+ return !isTrue(condition.execute(session, args));
+ }
+
+ // Reflective.coerce() prefers to construct a new Throwable(String)
+ // than to call this method directly.
+ public void _throw(String message)
+ {
+ throw new IllegalArgumentException(message);
+ }
+
+ public void _throw(Exception e) throws Exception
+ {
+ throw e;
+ }
+
+ public void _throw(CommandSession session) throws Throwable
+ {
+ Object exception = session.get("exception");
+ if (exception instanceof Throwable)
+ throw (Throwable) exception;
+ else
+ throw new IllegalArgumentException("exception not set or not Throwable.");
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object _try(CommandSession session, Function func) throws Exception
+ {
+ List<Object> args = (List<Object>) session.get("args");
+ try
+ {
+ return func.execute(session, args);
+ }
+ catch (Exception e)
+ {
+ session.put("exception", e);
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object _try(CommandSession session, Function func, Function error)
+ throws Exception
+ {
+ List<Object> args = (List<Object>) session.get("args");
+ try
+ {
+ return func.execute(session, args);
+ }
+ catch (Exception e)
+ {
+ session.put("exception", e);
+ return error.execute(session, args);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void _while(CommandSession session, Function condition, Function ifTrue)
+ throws Exception
+ {
+ List<Object> args = (List<Object>) session.get("args");
+ while (isTrue(condition.execute(session, args)))
+ {
+ ifTrue.execute(session, args);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void until(CommandSession session, Function condition, Function ifTrue)
+ throws Exception
+ {
+ List<Object> args = (List<Object>) session.get("args");
+ while (!isTrue(condition.execute(session, args)))
+ {
+ ifTrue.execute(session, args);
+ }
+ }
+
+ private boolean isTrue(Object result) throws InterruptedException
+ {
+ checkInterrupt();
+
+ if (result == null)
+ return false;
+
+ if (result instanceof Boolean)
+ return ((Boolean) result).booleanValue();
+
+ if (result instanceof Number)
+ {
+ if (0 == ((Number) result).intValue())
+ return false;
+ }
+
+ if ("".equals(result))
+ return false;
+
+ if ("0".equals(result))
+ return false;
+
+ return true;
+ }
+
+ private void checkInterrupt() throws InterruptedException
+ {
+ if (Thread.currentThread().isInterrupted())
+ throw new InterruptedException("loop interrupted");
+ }
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/shell/Shell.java b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Shell.java
new file mode 100644
index 0000000..e7e3d5d
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Shell.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.shell;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.net.URLConnection;
+import java.nio.CharBuffer;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.gogo.options.Option;
+import org.apache.felix.gogo.options.Options;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.service.command.CommandProcessor;
+import org.osgi.service.command.CommandSession;
+
+public class Shell
+{
+ static final String[] functions = { "gosh", "sh", "shutdown", "source", "telnetd" };
+
+ private final static URI CWD = new File(".").toURI();
+
+ private final URI baseURI;
+ private final BundleContext context;
+
+ private CommandProcessor processor;
+ private Telnet telnet;
+
+ public Shell(BundleContext context)
+ {
+ this.context = context;
+ String baseDir = System.getProperty("gosh.home", System.getProperty("user.dir"));
+ baseURI = new File(baseDir).toURI();
+ }
+
+ public void setProcessor(CommandProcessor processor)
+ {
+ this.processor = processor;
+ }
+
+ public Object gosh(final CommandSession session, String[] argv) throws Exception
+ {
+ final String[] usage = {
+ "gosh - execute script with arguments in a new session",
+ " args are available as session variables $1..$9 and $args.",
+ "Usage: gosh [OPTIONS] [script-file [args..]]",
+ " -c --command pass all remaining args to sub-shell",
+ " --login login shell (same session, reads etc/gosh_profile)",
+ " -s --noshutdown don't shutdown framework when script completes",
+ " -x --xtrace echo commands before execution",
+ " -? --help show help",
+ "If no script-file, an interactive shell is started, type $D to exit." };
+
+ Option opt = Options.compile(usage).setOptionsFirst(true).parse(argv);
+ List<String> args = opt.args();
+
+ boolean login = opt.isSet("login");
+
+ if (opt.isSet("help"))
+ {
+ opt.usage();
+ if (login && !opt.isSet("noshutdown"))
+ {
+ shutdown();
+ }
+ return null;
+ }
+
+ if (opt.isSet("command") && args.isEmpty())
+ {
+ throw opt.usageError("option --command requires argument(s)");
+ }
+
+ CommandSession newSession = (login ? session : processor.createSession(
+ session.getKeyboard(), session.getConsole(), System.err));
+
+ if (opt.isSet("xtrace"))
+ {
+ newSession.put("echo", true);
+ }
+
+ if (login)
+ {
+ URI uri = baseURI.resolve("etc/gosh_profile");
+ if (!new File(uri).exists())
+ {
+ uri = getClass().getResource("/gosh_profile").toURI();
+ }
+ if (uri != null)
+ {
+ source(session, uri.toString());
+ }
+ }
+
+ // export variables starting with upper-case to newSession
+ for (String key : getVariables(session))
+ {
+ if (key.matches("[.]?[A-Z].*"))
+ {
+ newSession.put(key, session.get(key));
+ }
+ }
+
+ Object result;
+
+ if (args.isEmpty())
+ {
+ result = console(newSession);
+ }
+ else
+ {
+ CharSequence program;
+
+ if (opt.isSet("command"))
+ {
+ StringBuilder buf = new StringBuilder();
+ for (String arg : args)
+ {
+ if (buf.length() > 0)
+ {
+ buf.append(' ');
+ }
+ buf.append(arg);
+ }
+ program = buf;
+ }
+ else
+ {
+ URI script = cwd(session).resolve(args.remove(0));
+
+ // set script arguments
+ newSession.put("0", script);
+ newSession.put("args", args);
+
+ for (int i = 0; i < args.size(); ++i)
+ {
+ newSession.put(String.valueOf(i + 1), args.get(i));
+ }
+
+ program = readScript(script);
+ }
+
+ result = newSession.execute(program);
+ }
+
+ if (login && !opt.isSet("noshutdown"))
+ {
+ shutdown();
+ }
+
+ return result;
+ }
+
+ public Object sh(final CommandSession session, String[] argv) throws Exception
+ {
+ return gosh(session, argv);
+ }
+
+ public void shutdown() throws BundleException
+ {
+ context.getBundle(0).stop();
+ }
+
+ public Object source(CommandSession session, String script) throws Exception
+ {
+ URI uri = cwd(session).resolve(script);
+ session.put("0", uri);
+ try
+ {
+ return session.execute(readScript(uri));
+ }
+ finally
+ {
+ session.put("0", null); // API doesn't support remove
+ }
+ }
+
+ public void telnetd(String[] argv) throws IOException
+ {
+ if (telnet == null)
+ {
+ telnet = new Telnet(processor);
+ }
+ telnet.telnetd(argv);
+ }
+
+ private Object console(CommandSession session)
+ {
+ Console console = new Console(session);
+ console.run();
+ return null;
+ }
+
+ private CharSequence readScript(URI script) throws Exception
+ {
+ URLConnection conn = script.toURL().openConnection();
+ int length = conn.getContentLength();
+
+ if (length == -1)
+ {
+ System.err.println("eek! unknown Contentlength for: " + script);
+ length = 10240;
+ }
+
+ InputStream in = conn.getInputStream();
+ CharBuffer cbuf = CharBuffer.allocate(length);
+ Reader reader = new InputStreamReader(in);
+ reader.read(cbuf);
+ in.close();
+ cbuf.rewind();
+
+ return cbuf;
+ }
+
+ @SuppressWarnings("unchecked")
+ static Set<String> getVariables(CommandSession session)
+ {
+ return (Set<String>) session.get(".variables");
+ }
+
+ static URI cwd(CommandSession session)
+ {
+ Object cwd = session.get("_cwd"); // _cwd is set by felixcommands:cd
+
+ if (cwd instanceof URI)
+ {
+ return (URI) cwd;
+ }
+ else if (cwd instanceof File)
+ {
+ return ((File) cwd).toURI();
+ }
+ else
+ {
+ return CWD;
+ }
+ }
+}
diff --git a/gogo/console/src/main/java/org/apache/felix/gogo/shell/Telnet.java b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Telnet.java
new file mode 100644
index 0000000..dd129df
--- /dev/null
+++ b/gogo/console/src/main/java/org/apache/felix/gogo/shell/Telnet.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.shell;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.List;
+
+import org.apache.felix.gogo.options.Option;
+import org.apache.felix.gogo.options.Options;
+import org.osgi.service.command.CommandProcessor;
+import org.osgi.service.command.CommandSession;
+
+/*
+ * a very simple Telnet server.
+ * real remote access should be via ssh.
+ */
+public class Telnet implements Runnable
+{
+ private static final int defaultPort = 2019;
+ private final CommandProcessor processor;
+ private ServerSocket server;
+ private Thread thread;
+ private boolean quit;
+ private int port;
+
+ public Telnet(CommandProcessor procesor)
+ {
+ this.processor = procesor;
+ }
+
+ public void telnetd(String[] argv) throws IOException
+ {
+ final String[] usage = { "telnetd - start simple telnet server",
+ "Usage: telnetd [-p port] start | stop | status",
+ " -p --port=PORT listen port (default=" + defaultPort + ")",
+ " -? --help show help" };
+
+ Option opt = Options.compile(usage).parse(argv);
+ List<String> args = opt.args();
+
+ if (opt.isSet("help") || args.isEmpty())
+ {
+ opt.usage();
+ return;
+ }
+
+ String command = args.get(0);
+
+ if ("start".equals(command))
+ {
+ if (server != null)
+ {
+ throw new IllegalStateException("telnetd is already running on port "
+ + port);
+ }
+ port = opt.getNumber("port");
+ start();
+ status();
+ }
+ else if ("stop".equals(command))
+ {
+ if (server == null)
+ {
+ throw new IllegalStateException("telnetd is not running.");
+ }
+ stop();
+ }
+ else if ("status".equals(command))
+ {
+ status();
+ }
+ else
+ {
+ throw opt.usageError("bad command: " + command);
+ }
+ }
+
+ private void status()
+ {
+ if (server != null)
+ {
+ System.out.println("telnetd is running on port " + port);
+ }
+ else
+ {
+ System.out.println("telnetd is not running.");
+ }
+ }
+
+ private void start() throws IOException
+ {
+ quit = false;
+ server = new ServerSocket(port);
+ thread = new Thread(this, "gogo telnet");
+ thread.start();
+ }
+
+ private void stop() throws IOException
+ {
+ quit = true;
+ server.close();
+ server = null;
+ thread.interrupt();
+ }
+
+ public void run()
+ {
+ try
+ {
+ while (!quit)
+ {
+ final Socket socket = server.accept();
+ PrintStream out = new PrintStream(socket.getOutputStream());
+ final CommandSession session = processor.createSession(
+ socket.getInputStream(), out, out);
+
+ Thread handler = new Thread()
+ {
+ public void run()
+ {
+ try
+ {
+ session.execute("gosh --login --noshutdown");
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ session.close();
+ try
+ {
+ socket.close();
+ }
+ catch (IOException e)
+ {
+ }
+ }
+ }
+ };
+ handler.start();
+ }
+ }
+ catch (IOException e)
+ {
+ if (!quit)
+ {
+ e.printStackTrace();
+ }
+ }
+ finally
+ {
+ try
+ {
+ if (server != null)
+ {
+ server.close();
+ }
+ }
+ catch (IOException e)
+ {
+ }
+ }
+ }
+}
diff --git a/gogo/console/src/main/resources/gosh_profile b/gogo/console/src/main/resources/gosh_profile
new file mode 100644
index 0000000..2219e25
--- /dev/null
+++ b/gogo/console/src/main/resources/gosh_profile
@@ -0,0 +1,36 @@
+# default gosh_profile
+# only read if etc/gosh_profile doesn't exist relative to the System property
+# gosh.home or failing that the current directory.
+
+# ensure gogo commands are found first
+SCOPE = gogo:*
+
+# add methods on BundleContext object as commands
+#addcommand context ${.context} (${.context} class)
+# bug: above invokes (String, Object, String) instead of (String, Object, Class)
+addcommand context ${.context}
+
+# add methods on System object as commands
+# FELIX-2335 prevents the use of (bundle 0) loadclass
+addcommand system ((bundle 1) loadclass java.lang.System)
+
+# alias to print full stack trace
+e = { $exception printStackTrace }
+
+## disable console auto-formatting of each result
+# you will then need to explicitly use the 'format' command
+# to print the result of commands that don't write to stdout.
+#.Gogo.format = false
+
+## disable printing the formatted result of a command into pipelines
+#.Format.Pipe = false
+
+# set prompt
+prompt = 'g! '
+
+# print welcome message
+try {
+ cat ($0 resolve motd)
+}
+
+# end
diff --git a/gogo/console/src/main/resources/motd b/gogo/console/src/main/resources/motd
new file mode 100644
index 0000000..7e26be1
--- /dev/null
+++ b/gogo/console/src/main/resources/motd
@@ -0,0 +1,4 @@
+_______________
+Welcome to Gogo
+Use 'type' to explore registered commands, 'type -?' for help.
+