Added color, better nailgun support, new argument processing model, base commands.

ConsoleColor
    Added to support for terminal colors by using ANSI escape codes.
    Basically cross-platform (as close as we're going to get with Java's API)

TwitterCLI
    Added colored output and configurable settings controlling it.
    Created a specific Nailgun entry-point. When using Nailgun the Twitter
      object is cached in the JVM so that it doesn't need to be recreated
      each time a call is made. This also means that settings changed from the
      command line persist until the Nailgun server is reset.
    Added functionality to wrap the status nicely to a set width (used for
      printing timelines).
    Arguments are not processed internally as a Queue, so each command/option
      is popped off the queue before it is processed.
    Added and updated base commands:
        help - not implemented, will be used to display online help about a
               command.
        post - Basic implementation added. It checks the length of the status,
               and asks for confirmation from the user before posting.
        reconfigure - If a Nailgun server is being used, it is useful to be
               able to reconfigure the client without restarting the nailgun
               server. This scraps the current instance cached by the ng
               server and creates a fresh instance.
        set  - Allows the user to set a configurable value on the command line.
               So far, only the following configurable values are available:
                 terminalWidth - integer, used for calculating text wrapping.
                 colored       - boolean, enables or disables colored output.
        show - not implemented, will be used to show various information
This commit is contained in:
Jonathan Bernard 2010-11-06 16:24:29 -05:00
parent ec6aed4e7f
commit 1565a04b3a
5 changed files with 250 additions and 58 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,7 @@
#Sat, 06 Nov 2010 10:35:28 -0500
#Sat, 06 Nov 2010 16:20:53 -0500
lib.local=true
name=groovy-twitter-cli
version=0.1
build.number=19
build.number=60
nailgun.classpath.dir=/home/jdbernard/programs/nailgun/classpath
win32.nailgun.classpath.dir=C\:/Documents and Settings/jbernard/My Documents/ng-classpath

View File

@ -0,0 +1,58 @@
package com.jdbernard.twitter;
public class ConsoleColor {
public final Colors fg;
public final Colors bg;
public final boolean bright;
public static enum Colors {
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }
public ConsoleColor(String propString) {
String[] vals = propString.split("[,;: ]");
fg = Colors.valueOf(vals[0]);
if (vals.length == 2) {
bg = null;
bright = Boolean.parseBoolean(vals[1]);
} else if (vals.length == 3) {
bg = Colors.valueOf(vals[1]);
bright = Boolean.parseBoolean(vals[2]);
} else { bg = null; bright = false; }
}
public ConsoleColor(Colors fgColor) { this(fgColor, Colors.BLACK, false); }
public ConsoleColor(Colors fgColor, boolean bright) {
this(fgColor, Colors.BLACK, bright);
}
public ConsoleColor(Colors fgColor, Colors bgColor, boolean bright) {
this.fg = fgColor; this.bg = bgColor; this.bright = bright;
}
public String toString() {
String result = "\u001b[";
boolean needSemi = false;
if (bright) {
result += "1";
needSemi = true;
}
if (fg != null) {
if (needSemi) result += ";";
result += "3" + Integer.toString(fg.ordinal());
needSemi = true;
}
if (bg != null) {
if (needSemi) result += ";";
result += "4" + Integer.toString(bg.ordinal());
}
return result + "m";
}
}

View File

@ -1,5 +1,6 @@
package com.jdbernard.twitter
import com.martiansoftware.nailgun.NGContext
import twitter4j.Twitter
import twitter4j.TwitterFactory
import twitter4j.conf.Configuration
@ -7,102 +8,235 @@ import twitter4j.conf.PropertyConfiguration
public class TwitterCLI {
private static String EOL = System.getProperty("line.separator")
private static TwitterCLI nailgunInst
private Twitter twitter
private Scanner stdin
private Map colors = [:]
private int terminalWidth
private boolean colored
public static void main(String[] args) {
TwitterCLI inst = new TwitterCLI(new File(System.getProperty("user.home"),
".groovy-twitter-cli-rc"))
inst.run(args as List)
inst.run((args as List) as Queue)
}
public static void nailMain(String[] args) {
public static void nailMain(NGContext context) {
if (nailgunInst == null)
nailgunInst = new TwitterCLI(new File(
System.getProperty("user.home"), ".groovy-twitter-cli-rc"))
else
nailgunInst.stdin = new Scanner(context.in)
if (nailgunInst == null) nailgunInst = new TwitterCLI(new File(
nailgunInst.
nailgunInst.run((context.args as List) as Queue)
}
public static void reconfigure(Queue args) {
if (nailgunInst == null) main(args as String[])
else {
nailgunInst = null
nailgunInst = new TwitterCLI(new File(
System.getProperty("user.home"), ".groovy-twitter-cli-rc"))
nailgunInst.run(args as List)
nailgunInst.run(args)
}
}
public static void setColor(boolean bright, int code) {
print "\u001b[${bright?'1':'0'};${code}m"
static String wrapToWidth(String text, int width, String prefix, String suffix) {
int lastSpaceIdx = 0;
int curLineLength = 0;
int lineStartIdx = 0;
int i = 0;
int actualWidth = width - prefix.length() - suffix.length()
String wrapped = ""
text = text.replaceAll("[\n\r]", " ")
for (i = 0; i < text.length(); i++) {
curLineLength++
if (curLineLength > actualWidth) {
wrapped += prefix + text[lineStartIdx..<lastSpaceIdx] + suffix + EOL
curLineLength = 0
lineStartIdx = lastSpaceIdx + 1
i = lastSpaceIdx
}
public static void resetColor() { print "\u001b[m" }
if (text.charAt(i).isWhitespace())
lastSpaceIdx = i
public static void colorPrint(def message, boolean bright, int color) {
setColor(bright, color)
print message
resetColor()
}
public static void colorPrintln(def message, boolean bright, int color) {
setColor(bright, color)
println message
resetColor()
if (i - lineStartIdx > 1)
wrapped += prefix + text[lineStartIdx..<text.length()]
return wrapped
}
public TwitterCLI(File propFile) {
// load the configuration
Configuration conf
propFile.withInputStream { is -> conf = new PropertyConfiguration(is) }
Properties cfg = new Properties()
propFile.withInputStream { is -> cfg.load(is) }
// create a twitter instance
twitter = (new TwitterFactory(conf)).getInstance()
twitter = (new TwitterFactory(new PropertyConfiguration(cfg))).getInstance()
// configure the colors
colors.author = new ConsoleColor(cfg.getProperty("colors.author", "BLUE:false"))
colors.mentioned = new ConsoleColor(cfg.getProperty("colors.mentioned", "GREEN:false"))
colors.error = new ConsoleColor(cfg.getProperty("colors.error", "RED:true"))
colors.option = new ConsoleColor(cfg.getProperty("colors.option", "YELLOW:true"))
colors.even = new ConsoleColor(cfg.getProperty("colors.even", "WHITE"))
colors.odd = new ConsoleColor(cfg.getProperty("colors.odd", "YELLOW"))
// configure the terminal width
terminalWidth = (System.getenv().COLUMNS ?: cfg.terminalWidth ?: 79) as int
colored = (cfg.colored ?: 'true') as boolean
stdin = new Scanner(System.in)
}
public void run(List args) {
public void run(Queue args) {
if (args.size() < 1) printUsage()
switch (args[0].toLowerCase()) {
case ~/t.*/: timeline(args.tail()); break
case ~/p.*/: post(args.tail()); break
while (args.peek()) {
def command = args.poll()
switch (command.toLowerCase()) {
case ~/h.*/: help(args); break
case ~/p.*/: post(args); break
case ~/r.*/: reconfigure(args); break
case ~/se.*/: set(args); break
case ~/sh.*/: show(args); break
case ~/t.*/: timeline(args); break
default:
printUsage()
}
}
}
public void help(Queue args) {
}
public void timeline(List args) {
public void post(Queue args) {
def status = args.poll()
// default to showing my friends timeline
if (args.size() == 0) args = ["friends"]
if (!status) {
println color("post ", colors.option) +
color("command requires one option: ", colors.error) +
"twitter post <status>"
return
}
if (status.length() > 140) {
println color("Status exceeds Twitter's 140 character limit.", colors.error)
return
}
print "Update status: '$status'? "
if (stdin.nextLine() ==~ /yes|y|true|t/)
twitter.updateStatus(status)
}
public void set(Queue args) {
def option = args.poll()
def value = args.poll()
if (!value) { // note: if option is null, value is null
println color("set", colors.option) +
color(" command requires two options: ", colors.error) +
"twitter set <param> <value>"
return
}
while (args.size() > 0) {
def option = args.pop()
switch (option) {
case "terminalWidth": terminalWidth = value as int; break
case "colored": colored = value.toLowerCase() ==~ /true|t|on|yes|y/
break
default:
println color("No property named ", colors.error) +
color(option, colors.option) +
color(" exists.", colors.error)
}
}
public void show(Queue args) {
}
public void timeline(Queue args) {
String timeline = args.poll() ?: "friends"
switch (timeline) {
// friends
case ~/f.*/: printTimeline(twitter.friendsTimeline); break
// mine
case ~/m.*/: printTimeline(twitter.userTimeline); break
// user
case ~/u.*/:
if (args.size() == 0)
colorPrintln("No user specificied.", true, 31)
else printTimeline(twitter.getUserTimeline(args.pop()))
String user = args.poll()
if (user) {
if (user.isNumber())
printTimeline(twitter.getUserTimeline(user as int))
else printTimeline(twitter.getUserTimeline(user))
} else println color("No user specified.", colors.error)
break;
default:
colorPrint("Unknown timeline: ", true, 31)
colorPrintln(option, true, 33)
println color("Unknown timeline: ", colors.error) +
color(timeline, colors.option)
break;
}
}
}
void printTimeline(def timeline) {
int authorLen = 0, textLen
String statusIndent
timeline.each { status ->
colorPrint(status.user.screenName, true, 34)
if (status.user.screenName.length() > authorLen)
authorLen = status.user.screenName.length()
}
timeline.eachWithIndex { status, rowNum ->
String text = status.text
print color(status.user.screenName.padLeft(authorLen), colors.author)
print ": "
println status.text
println ""
statusIndent = "".padLeft(authorLen + 2)
textLen = terminalWidth - statusIndent.length()
if (text.length() > textLen) {
text = wrapToWidth(text, terminalWidth, statusIndent, "").
substring(statusIndent.length())
}
text = text.replaceAll(/(@\w+)/, color("\$1", colors.mentioned))
println color(text, (rowNum % 2 == 0 ? colors.even : colors.odd))
}
}
public static void printUsage() {
// TODO
}
public String resetColor() { colored ? "\u001b[m" : "" }
public String color(def message, ConsoleColor color) {
if (!colored) return message
return color.toString() + message + resetColor()
}
}