blob: 60159eab52f44fdff739d7ca7a3624f12f35b2cb [file] [log] [blame]
/*
* 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.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.felix.gogo.options.Option;
import org.apache.felix.gogo.options.Options;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.CommandSession;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
public class Shell
{
static final String[] functions = { "gosh", "sh", "source", "history" };
private final static URI CWD = new File(".").toURI();
private final URI baseURI;
private final BundleContext context;
private final CommandProcessor processor;
private final History history;
private volatile Bundle systemBundle;
public Shell(BundleContext context, CommandProcessor processor)
{
this.context = context;
this.processor = processor;
String baseDir = context.getProperty("gosh.home");
baseDir = (baseDir == null) ? context.getProperty("user.dir") : baseDir;
baseURI = new File(baseDir).toURI();
this.history = new History();
}
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",
" --nointeractive don't start interactive session",
" --login login shell (same session, reads etc/gosh_profile)",
" -s --noshutdown don't shutdown framework when script completes",
" -q --quiet don't display message-of-the-day",
" -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");
boolean interactive = !opt.isSet("nointeractive");
// We grab this bundle as early as possible to avoid having to deal with invalid bundleContexts of this bundle during shutdowns...
systemBundle = context.getBundle(0);
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));
// Make some of the given arguments available to the shell itself...
newSession.put(".gosh_login", login);
newSession.put(".gosh_interactive", interactive);
newSession.put(".gosh_quiet", opt.isSet("quiet"));
if (opt.isSet("xtrace"))
{
newSession.put("echo", true);
}
if (login && interactive)
{
URI uri = baseURI.resolve("etc/gosh_profile");
if (!new File(uri).exists())
{
URL url = getClass().getResource("/ext/gosh_profile");
if (url == null)
{
url = getClass().getResource("/gosh_profile");
}
uri = (url == null) ? null : url.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 = null;
if (args.isEmpty())
{
if (interactive)
{
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 && interactive)
{
if (opt.isSet("noshutdown"))
{
System.out.println("gosh: stopping shell");
}
else
{
System.out.println("gosh: stopping shell and framework");
shutdown();
}
}
return result;
}
public Object sh(final CommandSession session, String[] argv) throws Exception
{
return gosh(session, argv);
}
private void shutdown() throws BundleException
{
if (systemBundle != null)
{
systemBundle.stop();
systemBundle = null;
}
}
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
}
}
private Object console(CommandSession session)
{
Console console = new Console(session, history);
console.run();
return null;
}
private CharSequence readScript(URI script) throws Exception
{
CharBuffer buf = CharBuffer.allocate(4096);
StringBuilder sb = new StringBuilder();
URLConnection conn = script.toURL().openConnection();
try (InputStreamReader in = new InputStreamReader(conn.getInputStream()))
{
while (in.read(buf) > 0)
{
buf.flip();
sb.append(buf);
buf.clear();
}
}
finally
{
if (conn instanceof HttpURLConnection)
{
((HttpURLConnection) conn).disconnect();
}
}
return sb;
}
@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;
}
}
public String[] history()
{
Iterator<String> history = this.history.getHistory();
List<String> lines = new ArrayList<>();
for (int i = 1; history.hasNext(); i++)
{
lines.add(String.format("%5d %s", i, history.next()));
}
return lines.toArray(new String[lines.size()]);
}
}