Wednesday, May 20, 2009

HWSched

I've been working on a new program: a homework scheduler suitable for high school and college students. Right now, it has the following objects that can be added, deleted, etc.:

  • Quarters/Terms
  • Instructors
  • Classes (each has a Quarter and Instructor)
  • Additional Class Sections (for each class)
  • Exams (for each class)
  • Assignments and their respective parts (for each class)

To add an assignment, all one needs to do is specify a class, due date, and all of its parts. I had previously written something similar, but it wasn't as efficient as this system.

I'll put it up here when I've tested it enough.

Thursday, May 7, 2009

cwork.py

I finally realized that the time I spent figuring out the path do a directory I wanted to go to via tab-completion was being wasted! I've created an alternative method for oft-accessed directories:

cwork.py

#!/usr/bin/python
from sys import argv, stderr, exit
from subprocess import Popen
from os import chdir, environ, getcwd
from os.path import join
from sqlite3 import Connection, IntegrityError
from datetime import datetime


class Sdatetime(datetime):
def strftime(self, fmt = None):
return datetime.strftime(self, '%Y-%m-%d %H:%M:%S.%f' if fmt is None else fmt)
@staticmethod
def strptime(string, fmt = None):
return datetime.strptime(string, '%Y-%m-%d %H:%M:%S.%f' if fmt is None else fmt)

def cdzsh(dir):
chdir(path)
Popen(('screen', 'zsh'))

def getrows_ordered(db):
c = db.cursor()
ret = {}
for row in c.execute('SELECT id, added, path FROM Paths'):
id, now, path = row
ret[Sdatetime.strptime(now)] = id, path
keys = ret.keys()
keys.sort(reverse = True)
for i in xrange(len(keys)):
key = keys[i]
id, value = ret[key]
yield i, id, key, value

db = Connection(join(environ['HOME'], '.path.db'))
db.execute('CREATE TABLE IF NOT EXISTS Paths(id INTEGER PRIMARY KEY AUTOINCREMENT, added CHAR(48), path VARCHAR(255) UNIQUE)')
db.commit()

if len(argv) >= 2:
if len(argv) >= 2 and argv[1] == 'add':
c = db.cursor()
path = getcwd()
if len(argv) >= 3:
path = argv[2]
now = Sdatetime.now()

try:
c.execute('INSERT INTO Paths(added, path) VALUES(?, ?)', (now.strftime(), path))
db.commit()
except IntegrityError:
print >>stderr, 'Path already in database:', path
elif len(argv) >= 2 and argv[1] == 'del':
c = db.cursor()
ids = set([int(arg) for arg in argv[2:]])
rows = list(getrows_ordered(db))
for id in ids:
try:
c.execute('DELETE FROM Paths WHERE id = ?', (rows[id][1],))
db.commit()
except IndexError:
print >>stderr, 'Invalid index:', id
elif len(argv) >= 2 and argv[1] == 'delall':
c = db.cursor()
c.execute('DELETE FROM Paths')
db.commit()
elif len(argv) >= 2:
c = db.cursor()
ids = [int(arg) for arg in argv[1:]]
rows = list(getrows_ordered(db))
for id in ids:
row = rows[id]
now = Sdatetime.now()
c.execute('UPDATE PATHS SET added = ? WHERE id = ?', (now.strftime(), row[1]))
path = rows[id][3]
cdzsh(path)
db.commit()
else:
print >>stderr, 'Usage: %s [ [ path# | add [ path ] | del path# | delall ] ]' % argv[0]
exit(1)
else:
for i, id, key, value in getrows_ordered(db):
print '%d:' % i, value
This script is essentially a cache for directories that the user wants to keep accessing often, and reorders the entries by date every time:Programs % cwork
0: /home/neil/Programs/CSE444/2009-05-07:Project3
1: /home/neil/Programs/CSE481
Programs % cwork add
Programs % cwork
0: /home/neil/Programs
1: /home/neil/Programs/CSE444/2009-05-07:Project3
2: /home/neil/Programs/CSE481
Programs % cwork 0

The last command opens up a new GNU Screen window with a shell in the directory requested.

Sunday, April 19, 2009

screensh

I finally found out how to get ssh "$HOST" screen to work! The secret is shown by this convenient Python script that launches an SSH session to a remote host and starts screen:

screensh.py

#!/usr/bin/python
from subprocess import Popen
from sys import argv, exit, stderr

if len(argv) >= 2:
options = argv[1:-1]
host = argv[-1]
Popen(['ssh', '-t'] + options + [host, 'screen']).communicate()
else:
print >>stderr, 'Usage: %s [ options ] host' % argv[0]
exit(1)

Python really is a "clean" language.

Friday, April 17, 2009

shparse

Wanting to learn the basics of how to write Python modules in C, I started on this simple function. All it does is use shell-style rules to parse a string into "arguments."

shparse.c

#include <Python.h>
#define INCS 64

enum EMODE {
EMODE_ESC = 1,
EMODE_QUOT = 2,
EMODE_DQUOT = 4
};


static PyObject * shparse_parse(PyObject * self, PyObject * args) {
const Py_UNICODE * cmdstring;
size_t i, x = 0, cplen;
char mode = 0;
PyObject * ret;
Py_UNICODE c, cp[4];

Py_UNICODE * buff = calloc(INCS, sizeof(Py_UNICODE)), * tmp;
size_t bufflen = INCS;



if(!PyArg_ParseTuple(args, "u", &cmdstring))
return NULL;

ret = PyList_New(0);
for(i = 0; cmdstring[i]; i++) {
c = cmdstring[i];
cp[0] = 0; cp[1] = 0; cp[2] = 0; cp[3] = 0; cplen = 0;
if(mode & EMODE_ESC) {
cp[0] = c;
cplen = 1;
mode &= ~EMODE_ESC;
}
else if(mode & EMODE_QUOT) {
switch(c) {
case '\'':
mode &= ~EMODE_QUOT;
break;
default:
cp[0] = c;
cplen = 1;
break;
}
}
else if(mode & EMODE_DQUOT) {
switch(c) {
case '\\':
mode |= EMODE_ESC;
break;
case '"':
mode &= ~EMODE_DQUOT;
break;
default:
cp[0] = c;
cplen = 1;
break;
}
}
else {
switch(c) {
case '\\':
mode |= EMODE_ESC;
break;
case '\'':
mode |= EMODE_QUOT;
break;
case '"':
mode |= EMODE_DQUOT;
break;
case ' ':
if(x > 0) {
x = 0;
PyList_Append(ret, Py_BuildValue("u", buff));
}
break;
default:
cp[0] = c;
cplen = 1;
break;
}
}


if(cp[0]) {
while(x + cplen + 1 >= bufflen) {
bufflen += INCS;
buff = realloc(buff, bufflen);
}
buff[x++] = cp[0];
if(cplen >= 2) {
buff[x++] = cp[1];
if(cplen >= 3) {
buff[x++] = cp[2];
if(cplen == 4)
buff[x++] = cp[2];
else
buff[x] = '\0';
}
else
buff[x] = '\0';
}
else
buff[x] = '\0';
}
}

if(x > 0)
PyList_Append(ret, Py_BuildValue("u", buff));
free(buff);


return ret;
}

static PyMethodDef Methods[] = {
{"parse", shparse_parse, METH_VARARGS, "Parse an input string."},
{NULL, NULL, 0, NULL}
};


PyMODINIT_FUNC initshparse(void) {
Py_InitModule("shparse", Methods);
}

It could be quite useful, and would most definitely be faster than equivalent Python code. By the way, the Python/C API reference is quite good.

Sunday, March 29, 2009

musicdir

I've been organizing my music in a certain way for quite some time: "$ARTIST - $ALBUM/$TRACK - $TITLE.flac". Even so, I've never taken advantage of that structure, at least programmatically. Now I have, with this program:

musicdir.c

#include <glob.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <argp.h>




typedef enum {
MODE_ALL = 1
} MODE;

struct arguments {
char * artist, * album, * title, * root;
size_t artist_len, album_len, title_len, root_len;
int track;
char mode;
} argument = {NULL, NULL, NULL, "/home/music/", 0, 0, 0, 12, -1, 0};

error_t parser(int key, char * arg, struct argp_state * state) {
int track;
switch(key) {
case 'a':
if(arg) {
argument.artist = arg;
argument.artist_len = strlen(arg);
}
break;
case 'c':
if(arg) {
argument.album = arg;
argument.album_len = strlen(arg);
}
break;
case 't':
if(arg) {
argument.title = arg;
argument.title_len = strlen(arg);
}
break;
case 'r':
if(arg) {
argument.root = arg;
argument.root_len = strlen(arg);
}
break;
case 'n':
track = atoi(arg);
if(track > 0)
argument.track = track;
else
return ARGP_ERR_UNKNOWN;
break;
case 'l':
argument.mode |= MODE_ALL;
break;
case ARGP_KEY_ARG:
return ARGP_ERR_UNKNOWN;
case ARGP_KEY_END:
break;
default:
return ARGP_ERR_UNKNOWN;
break;
}
return 0;
}


int main(int argc, char ** argv) {
static struct argp_option options[] = {
{ "artist", 'a', "artist", 0, "The artist part of the filename.", 0 },
{ "album", 'c', "album", 0, "The album part of the filename.", 0 },
{ "title", 't', "title", 0, "The title part of the filename.", 0 },
{ "track", 'n', "track", 0, "The track part of the filename, greater than zero.", 0 },
{ "root-dir", 'r', "root-dir", 0, "The root music directory. [/home/music]", 0 },
{ "list-all", 'l', NULL, 0, "List all found files rather than the default (the first file).", 0},
{ 0 }
};
static struct argp args_parsed = {
options,
&parser,
NULL,
"The music directory finder.",
NULL,
NULL
};
int arg_index;
glob_t globs;
int glob_result;
error_t result;
size_t total_len = 16, i;
char * pattern = NULL;
if((result = argp_parse(&args_parsed, argc, argv, 0, &arg_index, &argument)) == 0) {
/* printf("root=%s, artist=%s, album=%s, title=%s, track=%d\n", argument.root, argument.artist, argument.album, argument.title, argument.track); */

/* Allocate string for pattern */
if(argument.root)
total_len += argument.root_len;
if(argument.artist)
total_len += argument.artist_len;
if(argument.album)
total_len += argument.album_len;
if(argument.title)
total_len += argument.title_len;
if(argument.track >= 0)
total_len += 11;
pattern = calloc(total_len, sizeof(char));


/* Copy arguments into pattern */
strcpy(pattern, argument.root);

if(argument.artist)
sprintf(pattern + strlen(pattern), "%s - ", argument.artist);
else
strcat(pattern, "* - ");

if(argument.album)
sprintf(pattern + strlen(pattern), "%s/", argument.album);
else
strcat(pattern, "*/");


if(argument.track >= 0)
sprintf(pattern + strlen(pattern), "%d - ", argument.track);
else
strcat(pattern, "* - ");

if(argument.title)
sprintf(pattern + strlen(pattern), "%s.*", argument.title);
else
strcat(pattern, "*.*");


/* puts(pattern); */
if((glob_result = glob(pattern, GLOB_NOESCAPE, NULL, &globs)) == 0) {
if(globs.gl_pathc > 0) {
puts(*globs.gl_pathv);
if(argument.mode & MODE_ALL)
for(i = 1; i < globs.gl_pathc; i++)
puts(globs.gl_pathv[i]);
}
globfree(&globs);
}
else {
switch(glob_result) {
case GLOB_NOSPACE:
fputs("glob: Unable to allocate memory.\n", stderr);
break;
case GLOB_ABORTED:
fputs("glob: Read error.\n", stderr);
break;
case GLOB_NOMATCH:
fputs("glob: No such file or directory.\n", stderr);
break;
}
}
free(pattern);
}
else
perror("argp_parse");
return 0;
}
% ./musicdir --help
Usage: musicdir [OPTION...]
The music directory finder.

-a, --artist=artist The artist part of the filename.
-c, --album=album The album part of the filename.
-l, --list-all List all found files rather than the default (the
first file).
-n, --track=track The track part of the filename, greater than zero.

-r, --root-dir=root-dir The root music directory. [/home/music]
--root-dir=root-dir The root music directory. [/home/music]
-t, --title=title The title part of the filename.
-?, --help Give this help list
--usage Give a short usage message

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.
% ./musicdir -a aiko
/home/music/aiko - KissHug/01 - KissHug.flac

Thursday, March 19, 2009

sizeof

Even though this tool isn't really done, I've decided to release what I've built so far.

This program finds the size of a directory or file's contents based on the arguments given, then scales it down into human-readable output. It's a great tool for tracking the largest files on disk, and is far more reliable than du, despite being slower.

sizeof.c

#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <signal.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#define MAX_FLEN PATH_MAX
const char * failname = NULL;

void seg() {
perror(failname);
fprintf(stderr, "%s: Unable to recover; exiting.\n", failname);
exit(255);
}

off_t get_size(const char * dirname, ino_t dinod) {
struct stat info;
DIR * dirinfo;
struct dirent * file;
off_t total = 0;
char tmp[MAX_FLEN], * tmp_target;
memset(tmp, '\0', MAX_FLEN);
size_t tmp_target_len;
strncpy(tmp, dirname, MAX_FLEN - 1);
strcat(tmp, "/");
tmp_target = tmp + (strlen(tmp));
tmp_target_len = MAX_FLEN - strlen(tmp_target) - 2;
dirinfo = opendir(dirname);
failname = dirname;
if(dirinfo != NULL) {
while((file = readdir(dirinfo)) != NULL) {
if(file->d_name[0] != '.') {
strncpy(tmp_target, file->d_name, tmp_target_len);
failname = tmp;
if(!stat(tmp, &info)) {
if(S_ISDIR(info.st_mode)) {
if(info.st_ino != dinod) // Prevent symlinks to self
total += get_size(tmp, info.st_ino);
else
fprintf(stderr, "%s: Directory symlink to self.\n", tmp);
}
else if(S_ISREG(info.st_mode))
total += info.st_size;
}
else
perror(file->d_name);
}
}
closedir(dirinfo);
}
else
perror(dirname);
return total;
}
void scale_down(double * total, const char ** suffix) {
if(*total >= (1 << 30)) {
*total /= (1 << 30);
*suffix = "gigabytes";
}
else if(*total >= (1 << 20)) {
*total /= (1 << 20);
*suffix = "megabytes";
}
else if(*total >= (1 << 10)) {
*total /= (1 << 10);
*suffix = "kilobytes";
}
}


int main(int argc, const char ** argv) {
double total;
const char * suffix;
int i;
struct stat info;
signal(11, &seg);
if(argc > 2) {
for(i = 1; i < argc; i++) {
if(!stat(argv[i], &info)) {
suffix = "bytes";
if(S_ISDIR(info.st_mode))
total = get_size(argv[i], 0);
else if(S_ISREG(info.st_mode))
total = info.st_size;
scale_down(&total, &suffix);
printf("%-40s: %.2lf %s\n", argv[i], total, suffix);
}
else
perror(argv[i]);
}
return 0;
}
else if(argc == 2) {
if(!stat(argv[1], &info)) {
suffix = "bytes";
if(S_ISDIR(info.st_mode))
total = get_size(argv[1], 0);
else if(S_ISREG(info.st_mode))
total = info.st_size;
scale_down(&total, &suffix);
printf("%.2lf %s\n", total, suffix);
return 0;
}
else {
perror(argv[2]);
return 1;
}
}
else {
fprintf(stderr, "Usage: %s directory\n", *argv);
return 1;
}
}
% ./sizeof *
Makefile : 291.00 bytes
sizeof : 19.33 kilobytes
sizeof.c : 2.51 kilobytes
sizeof.c.html : 23.34 kilobytes
sizeof.o : 13.17 kilobytes
sizeof.s : 8.08 kilobytes
sizeof_un.s : 7.80 kilobytes