
Congratulations to Stephen Christenson, who is the first person to finish jrpg, or at least the first to inform me of doing so. According to the savefile he sent me it didn't take that much kanji demon bashing to complete all quests (at least the crystal ball thinks so), so I should probably add a few more maps and quests.
In the last few months I got multiple feature requests for jrpg. Some of them like kana keyboard support seem relatively straightforward (if the operating system, SDL and PyGame support them). Fixing wrapping of long examples was requested often, and it shouldn't be that hard. The most popular request that simply cannot be done in a reasonable amount of work is providing translations for all words in the game.
It's really nice hearing from people who enjoy jrpg. Have fun and good learning.
The best kittens, technology, and video games blog in the world.
Wednesday, November 28, 2007
Congratulations to the first person to finish jrpg
Saturday, November 17, 2007
Mac vs Ubuntu

I wiped out OS X on my Macbook and installed kUbuntu. I just couldn't stand it any more.
The good:
- Packaging system is so massively better. Installing mysql is
sudo apt-get install mysql-server-5.0not spend a few hours on it. Upgrading actually works. Packages don't fail in the middle of installation, possibly fucking the entire system. - No need to register at random websites to have gcc. Did you know that they will spam you if you register, and "Unsubscribe" link in their spam won't work ? Now you do know. Oh and the spam is not even about Macs, it's about some crappy phones.
- Window manager is way more functional. It manages focus of windows not applications. Managing focus of applications is simply braindead when application is something like Terminal or Textmate or Finder, windows of which live completely independent life. As far as my experience go, almost every single application with multiple windows should have them managed independently.
- Middle click for the win ! Command-C, Command-V is so annoying. Unfortunately crappy Mac trackpad has only one button so I cannot middle-click it even with the standard "press both buttons" trick. Well, It's not like I planned to buy any more Apple hardware every.
- Sane terminal emulator, with all keys working properly.
- The system feels much faster, almost as if I upgraded the hardware.
- Many applications are massively better. Amarok instead of some horrible piece of crap that I didn't use anyway because even command line music playing with mplayer was more convenient. Xchat instead of Colloquy. Nobody uses Safari even on OS X so Firefox doesn't really count as a change.
- Polish Dvorak keyboard works.
- All packages are reasonably up to date. I don't have to choose between ancient Ruby and working Java (Tiger), or recent Ruby and broken Java (Leopard), or spending way too much time to get it all working properly.
- Font rendering in kUbuntu is very ugly compared to OS X. They should really fix it.
- No TextMate any more. I will need to find a decent replacement.
- Interface looks somewhat less pretty. It doesn't have to look like Mac, but some nicer themes would be good.
- I couldn't get secondary monitory working other than in shadowing mode. If I try to put secondary 1680x1050 monitor side by side to 1280x800 laptop screen I get error that total screen size would be bigger than the allowed maximum of 1680x1680. GPU driver issue ?
- Wireless doesn't work out of the box, needed downloading some driver. Not very difficult, but downloading things when internet is down is an unnecessary complication.
- Hibernate-on-lid-closure doesn't work out of the box.
- Ubuntu doesn't autodetect FireWire-connected external hard drive (500GB LaCie). The same hard drive works when connected by USB 2.0. I blame Apple for including only 2 USB and 1 FireWire instead of at least 4 USBs. Mouse and keyboard alone take two slots for fuck's sake.
- KDE used to have all configuration in one place - Control Center and it was good. Now it's randomly divided into Menu/System Settings, Menu/Settings, Menu/System.
- It will take some time to get used to keyboard shortcuts being on Alt/Control instead of Command/Control/Alt system.
Posted by
taw
at
17:47
29
comments
Wednesday, October 17, 2007
Dinosaurs

I'm not talking about Fortran, I'm talking about the actual reptiles here. Or protobirds. For as long as I can remember I believed that fossilized bones are just a poor substitute for real genetic information and people use it only because dinosaurs happened to die out without living too many descendant lines. But having seen this BBC documentary I was shocked how much behavioral information they could get from just a bunch of bones plus some experimentation. In addition to scientific value it's simply awesome. Links: part 1, part 2.
Tuesday, October 09, 2007
Two months with Ruby on Rails

I've been coding Ruby on Rails for the last two months and this rant is long overdue. There are just so many thing that are wrong with Ruby on Rails. Being better than PHP or J2EE is not enough to get away from a quick bashing on my blog.
Views
I don't hate HAML any more. Total hatred was my first reaction to it but I more or less got used to it. The main problem with HAML, RHTML and probably all other solutions is not providing any sort of XSS protection. Hand-escaping all strings in views is almost PHP SQL injection hell all over again. The few times one needs to insert raw HTML in the output are far outweighed by the huge security problems caused by "insecure by default" model. And it wouldn't be hard to implement secure templating - just make a subclass of
String meant for raw html and make default String and anything wichh to_ses get HTML-escaped.Both HAML and RHTML are very powerful as templating languages. Ruby is simply very well suited for the job. Completely unlike Python and Java which needs hundreds of lame templating languages. With a few partials, helpers and RJS snippets it's usually hard to imagine a shorter and more natural way of writing it all.
One more nice thing about ERB - it can be used pretty much everywhere, like in
database.yml for switching database adapter depending on whether it's JRuby or matz's Ruby, or in SQL snippets meant for initializing database. Maybe that's not a huge deal but there's not other language where it's so natural to do.Controllers
Controllers are basically small bags of actions which actually do stuff. Separation between controllers and views has one huge wart - flash. It doesn't clearly belong in either. And if you want things like markup and links inside flash messages - a perfectly reasonable thing to do - it gets uglier that Perl on a bad day. I'm not sure what's the right way to do it, flash partials maybe ? Or full set of link helpers available inside controller.
Functional testing reuses controller objects without cleaning out their instance variables between requests. That's just wrong. It also reuses request and response objects for no particular reason. Oh, and it silently ignores 403s,
flash[:error], doesn't follow redirects, and relies on cross-site request forgery to get any testing done - if forms include a security token in hidden field you cannot test by directly posting, you must use the actual form ! This is probably the most broken part of Ruby on Rails.A good functional test would look something like that:
def test_that_edit_respects_item_ownershit
login
get :edit, :id => your_item
form.fill_and_sumbit :x => "Foo", :y => "Bar"
assert_raises Error403 {
get :edit, :id => somebody_elses_item
form.fill_and_sumbit :x => "Foo", :y => "Bar"
}
assert_equal "This item belongs to someone else.", flash[:error]
endbut it's a long way to get anywhere near that point. form-test-helper is probably a good start.
Models
Models in Ruby on Rails are based on an idea that you can either have a very high way view or go raw SQL but nothing in between.
Raw SQL wouldn't be that bad if they at least handled security somehow (
execute accepting "%" would be a good start), strings returned from SQL were converted to Ruby objects (surprisingly timestamps get returned as Ruby objects in some databases), and results of execute supported map method. Or wait - it would be bad, it's SQL after all. Why are we still using RDBMSes in 2007, weren't they supposed to die together with Fortran or something ?Good thing about models is how easy it is to move code between controllers and models. This barrier is much more permeable than controller-view barrier, resulting in easier refactoring and code looking better. Controller-view refactoring is usually much harder.
There's a lot of stuff that doesn't clearly fit in MVC like extensions to core classes, objects that are not backed by database, helpers and so on. It would be nice if it there was a place for putting tests of it.
Routes
There are two ways of doing routes, both bad. One is the old way as seen on screencasts. It ends with paths like
/posts/123/destroy which are then fetched by web spiders deleting all your database. The new way is trying to make every controller fit REST model, so you end with DELETE /post_sharabilities/456 or something as stupid. If there is a good way of routing stuff I haven't seen it yet.A good thing is that you can pretty much ignore it, use simple routes and filter out GETs in the controller. Controller filters are simply awesome, model filters are pretty good too. You can use them to handle things like authorization. One thing they unfortunately cannot handle is data integrity. Unfortunately Active Record hooks are too weak to handle things like ensuring that each person has exactly one primary email address. Why cannot RDBMSes just die ?
Testing
The first annoying thing about Ruby on Rails testing are fixtures. Each test runs inside a transaction so why are they wiped out and reapplied once for each test class ? And they really do not scale. There must be a better solution but I'm not sure what is it. One thing is certain - while mocha is great mocks aren't it, as often hundreds of objects must exist at the same time for testing to be useful.
Permeability of model-controller barrier also means that many things are only tested in controller tests (called "functional" tests in Ruby on Rals but I'm not sure if I like this abuse of terminology). The result - 90%+ in rcov report while half of the model methods are not tested in isolation.
Rake and Capistrano
Rake is simply awesome. It is to other building tools what Rails is to J2EE. Capistrano on the other hand, I have no idea why wasn't simply implemented on top of rake. Maybe it's time to take a look at Vlad the Deployer.
Plugins
Another nice thing about Rails is great hackability. Most behaviors can be easily hacked and most hacks can be easily extracted to plugins. A few things like schema dumper weren't that easily extensible but overall most of the stuff I wanted to hack was very simple to hack. It's also a great thing how 30 independently developed plugins each monkeypatching some Ruby or Rails behavior can work together with almost no conflicts.
Documentaton and console
Code grepping is usually the best documentation. api.rubyonrails.org was sometimes helpful but not always. Trying things out in
script/console was usually enough to explore and debug model. Unfortunately it doesn't work with controllers as path helpers and controller action runners are simply not defined there, so I cannot jump from a failing test to console to find out what's going on.Other stuff
TextMate is great. Usually I hate every program I spend more than half an hour with. In this case I only somewhat dislike some parts of it, what probably means less fussy programmers will just love it ;-)
Monday, August 27, 2007
Resurrection of libgmp-ruby

I've just republished tarball of libgmp-ruby (Ruby bindings to GNU Multiple Precision Arithmetic Library). It's a very old package (literally Ruby 1.6 old), but the server hosting it died and I never quite got to republishing it before.
It is available for download in tarball format. To compile the package use:
$ ./extconf.rb
$ make
$ make installIt used to build Debian packages. I don't know if they still build or if some tweaking is necessary. GEMs are not provided, as the package is older than Ruby Gems. Some day I'll get to updatng it and providing DEBs and GEMs.
Posted by
taw
at
15:22
3
comments
Monday, August 20, 2007
Dynamically typed road traffic
I moved to London a few weeks ago. I live at 82 Mildmay Road, Islington, London, N1 4NG, I code Ruby on Rails at Trampoline Systems, I have a beautiful kitten girl Cloud (the blue-eyed white furry creature above), and I use a MacBook.
Let's start from the culture shock part. The British start working at saner hours than Polish, somewhere between 9:30 and 12:00 instead of 7:00 to 8:00. They start their day by eating "English breakfast", which consists of fried eggs, oversalted beacon, sausage made of 50% recycled plastic bottles and 50% soy protein isolate (and definitely no meat), baked beans, half-cooked mushrooms, black pudding (no idea what it is made of), tomatoes prepared in a way that makes them lose the tomato taste, semi-sweet toasts, and a few other weird things. The whole thing is huge, hard to digest, and completely unsuitable for a breakfast. At least that was what I thought at first - now I kinda like eggs, baked beans and semi-sweat toasts.
The next culture shock was in moving around. Pedestrians don't care about traffic lights. On the continent it's expected for people to wait till the light is green before crossing the street. In London nobody does so - people just check if the road is free and if it is they go. As most streets in the city center seem to have pedestrian islands in the middle, it is enough if just a single lane is free from traffic. At first I thought it will certainly lead to huge increase in traffic accidents, but it seems the British roads are actually safer than most of the continent. That's lot like static versus dynamic typing - instead of statically checking "type" of road (RedRoad or GreenRoad) you check if it responds correctly to :pedestrian message and cross if to does. Much more efficient after some getting used to.
Switching from Ubuntu Linux to Mac was weird. Macs have one big advantage over Linuxes - TextMate. As far as I can tell it's the only advantage. Other than that:
- They lack single package management system like
apt-get. One needs to use a mix offink,port,gems, binary packages, hand-compiled packages, and I still couldn't install Amarok. - No copy & paste by select and middle-click is annoying.
- Safari sucks almost as much as IE4, Macs are pretty much unusable without Firefox.
- Macs are not a very good Unix. Packages are outdated and unupgradable (Ruby 1.8.2 from 2004 on a laptop sold in 2007 - wtf?). Basic utilities like
findandcpdon't accept standard GNU flags. Locale is very annoyingly not UTF-8 without some work. There's no good terminal (neither the builtin one nor iTerm are anywhere near konsole). Filesystem is case-insensitive (yuck). There's nostraceand debugging options are limited compared to Linux. - There's no good music player. iTunes is a stinky pile of donkey shit compared to the most awesome Amarok.
- There's no good iPod client. iTunes sucks compared to even gtkpod. iTunes sucks compared to everything.
- MacBook screen is very small. MacBook trackpad is horrible (not unlike trackpads in all other laptops). Control vs Command distinction is annoying even after a few weeks (Control-D but Command-C, huh ?).
TextMate is far better than any of them. Maybe even good enough to make me say on Mac.
Monday, July 16, 2007
Who reads my blog - Redditers and Googlers

More than a year ago when I started this blog and I had no idea that anybody would actually read it, but it seems to be doing quite well. According to Google Analytics over the year there were over 90 thousand page views by over 50 thousand visitors. Recently there are about 420 page views daily, or one every three and half minutes. I don't think I have that many friends are relatives, so who reads my blog ?
There seem to be two distinct populations - Redditers, and Googlers. Excluding "direct traffic", which simply means that for whatever reason referrer was not recorder, 35% of visitors come from Google, and 32% from Reddit. The next three sources DZone, Daring Fireball and del.icio.us provide only 6.6%, 3.4% and 1.7% of visits, respectively.
The full story of article's readership look something like that:
- Article is published. I submit it to del.icio.us and usually also to reddit
- If Redditers like the article it gets to the main page. I have absolutely no idea which articles Redditers will like and which they won't. Actually I less than no idea - things I consider very interesting almost invariably get downvoted, while random rants I wrote when angry or bored get tens of points. So I submit pretty much everything programming-related and let them decide. My karma from doing so is highly positive, so it's probably not considered a very abusive practice
- In the next day or two it gets a lot of views from Redditers
- People submit it to other reddit-like websites, or write answers to it, and it stays popular for a few more days
- There's a sudden drop in popularity, as people move on to other things
- Google indexes the article, and a steady flow of Google visits starts. The flow is not wide, but it seems to last pretty much indefinitely
require 'time'
$cookie = File.read("/home/taw/ga_cookie").chomp
def wget(url, fn)
system 'wget', '--header', $cookie, url, '-O', fn unless File.exists?(fn)
File.read(fn)
end
def each_day(first_day)
day = Time.now.gmtime
day_number = 0
while true
day_s = day.strftime('%Y%m%d')
break if day_s < first_day
yield day_s, day_number
day_number += 1
day -= 24*60*60
end
end
def get_data_for(day)
url = "https://www.google.com/analytics/reporting/export?fmt=3&id=1222880&pdr=#{day}-#{day}&cmp=average&rpt=TopContentReport&trows=500"
fn = "results-#{day}"
res = wget(url, fn)
header_finished = false
res.each{|line|
unless header_finished
header_finished = true if line =~ /\AURL\tPage Views\tUnique Page Views\t/
next
end
url, page_views, unique_page_views, = line.split(/\t/)
next unless page_views # Skip the final line
next unless url =~ %r[\A/\d{4}/\d{2}/]
next if url =~ /\?/
yield(url, page_views.to_i)
}
end
$stats = {}
each_day('20060923') {|date, day_number|
get_data_for(date){|url, page_views|
$stats[url] ||= []
$stats[url][day_number] = page_views
}
}
$stats_by_post_age = []
$stats.each{|url, stats|
stats.reverse.each_with_index{|page_views, age|
page_views ||= 0
$stats_by_post_age[age] ||= 0
$stats_by_post_age[age] += page_views
}
}
total_page_views = $stats_by_post_age.inject{|a,b| a+b}
p $stats_by_post_age.map{|x| 0.01 * (10000 * x.to_f/total_page_views).to_i}
And the not very surprising results:
- 22.26% of page views are in the day article is published. As the article could have been published on any time of the day (just after midnight to just before midnight), on average that's article's first 12 hours.
- It falls rapidly to 11.47% and 4.28% over the next two days
- In the following ten days the numbers are 2.03%, 1.82%, 1.46%, 1.49%, 1.25%, 0.99%, 0.86%, 0.72%, 0.95%, 0.81%. By that time more than half visits occurred.
- In the following weeks the number gradually decreases, but I think it's more due to many posts not being online long enough than due to actual popularity loss. Maybe I'll run some statistics to test this hypothesis some day.
Sunday, July 15, 2007
Short rant on video game usability and 3D acceleration

There's one thing that pretty much every PC game does, and what I really hate. It's using "constant rendering quality" paradigm instead of "constant FPS" paradigm.
PC hardware differs a lot, with some people using older hardware and wanting to play games even if the rendering is only so-so, while other who have just bought shiny new graphics cards demanding really awesome effects from them, more to impress their friends and stimulate graphics card manufacturing than to actually improve gameplay. What pretty much everybody wants is the highest rendering quality that still gives them reasonably FPS rate.
That's what game engines should do - monitor FPS and increase or decrease rendering quality if FPS is not in some predefined range. But not a single game I know does so. Instead they all opt for providing "constant rendering quality" - maintaining some level of rendering quality whether the game gets unusably slow, or has a lot of free GPU cycles. Often both situations happen as player moves from one location to another. Changing graphics setup every few minutes would distract too much from playing, so most old hardware owners either set the quality low enough that they always have good FPS, even if for 90% of the game GPU is half idle, or accept occasional low FPS in exchange for better rendering quality. Or they solve this software problem in hardware and buy a better graphics card.
Oh, and the graphics setup. Instead of having one big "I want that many FPS" slider and then the game filling in details, there are usually dozens of confusing options - some of them affecting rendering speed considerably, others barely at all.
Time to get a new card, the one bought year ago isn't good enough any more.
Saturday, July 14, 2007
Truth, falsehood and voidness in dynamic languages

One of the things which different dynamic languages do differently is how truth, falsehood, and voidness are handled. I checked how it's done in 9 most popular dynamic languages - Common Lisp, JavaScript, Lua, Perl, PHP, Python, Ruby, Scheme, and Smalltalk.
The first question - does the language has dedicated booleans ? That is - do questions like 2 > 1 return special booleans or something else ?
- Ruby, Lua, Smalltalk, JavaScript - Yes (
trueandfalse) - Python - Yes (
TrueandFalse) - Scheme - Yes (
#tand#f) - Common Lisp - No, it returns symbol
tfor true and empty list (nil) for false. - Perl - No, it return
1for true, andundeffor false. - PHP - Kinda. Since PHP4 there are booleans
trueandfalse, but their behavior is full of hacks -print trueprints1,print falseprints nothing,false == 0,false == NULL,true == 1, eventrue == 42.
- Ruby, Scheme, Lua - all are true
- Perl, PHP, Python - all are false
- JavaScript - empty list is true, others are false
- Common Lisp - empty list is false, others are true
- Smalltalk -
NonBooloanReceiverexception is raised if anything but booleans is used in boolean context.
"0" false ?- PHP, Perl - unfortunately
"0"is false, and this is a huge source of nasty bugs - Ruby, Scheme, Lua, JavaScript, Python, Common Lisp -
"0"is true - Smalltalk -
NonBooloanReceiverexception is raised
- Ruby, Lua -
nil, accessing nonexistent elements returns it - JavaScript -
undefined, accessing nonexistent elements returns it - Perl -
undef, accessing nonexistent elements returns it - PHP -
NULL, accessing nonexistent elements returns it - Python -
None, accessing nonexistent elements throws an exception - Smalltalk -
nil, accessing nonexistent elements throws an exception - Scheme - there isn't one, accessing nonexistent values is an error
- Common Lisp - there isn't one, but empty list acts as one in most contexts, it is also returned when accessing nonexistent elements
- Ruby, Lua, JavaScript, Perl, PHP, Python, Common Lisp - it is false
- Scheme - there is no nonexistent value marker
- Smalltalk -
NonBooloanReceiverexception is raised
"0") are treated as true, while absence marker is treated as false.There is no clear consensus whether
0, 0.0, "", and empty list should be treated as true or false. Personally I think it's better to make them all true. Otherwise either libraries can define other false objects (like decimal 0.00, various empty containers, and so on) what complicates the language, or they cannot what makes it feel inconsistent.Is most languages accessing nonexistent elements of an array returns an absence marker instead of throwing an exception, and in my opinion that's the right way and it makes the code look much more natural.
Wednesday, July 11, 2007
Using home directory as GTD inbox - version 2

The GTD software I described a few weeks ago evolved quite significantly since then.
Fortunately my inbox is still empty:
$ inbox_size
Your inbox is empty.
It can be used in two modes - either single-shot report of inbox contents with
inbox_size, or continuous screening mode plus UI notification with inbox_size_notify. inbox_size.rb is a library (symlinked from /home/taw/local/bin/inbox_size) which finds all items in all my inboxes. It also handles special items:- Unread emails in Gmail inbox
- Uncommitted changes to one of the repositories
- Music log not committed to last.fm
- Passwords file chanced since last encrypted copy
- Last backup older than 3 days
- Any things I wanted to be informed about
The code
The main code is in
inbox_size.rb:require 'time'
require 'magic_xml'
$offline = false
def inbox_ls
items_whitelist = %w[
/home/taw/Desktop
/home/taw/ebooks
/home/taw/everything
/home/taw/img
/home/taw/ipoddb
/home/taw/local
/home/taw/movies
/home/taw/music
/home/taw/ref
/home/taw/website
/home/taw/website_snapshot
]
files = (Dir["/home/taw/*"] +
Dir["/home/taw/Desktop/*"] +
Dir["/home/taw/movies/complete/*"] -
items_whitelist)
items = files.map{|x|x.sub(%r[\A/home/taw/],"")}
# Code for handling special inbox items goes here
# ...
return items.sort.map{|item| "* #{item}"}
end
if $0 == __FILE__
if ARGV[0] == '--offline'
ARGV.shift
$offline = true
end
items = inbox_ls
if items.empty?
puts "Your inbox is empty."
else
puts "#{items.size} items in your inbox:", *items
end
end
inbox_size_notify which scans the inbox continuouly and displays UI notifications if it's not empty is:require 'inbox_size'
max_displayed = 30
big_timer = 5
old_items = []
while true
items = inbox_ls
next if items == []
if items == old_items
big_timer -= 1
sleep 60
next unless big_timer == 0
end
big_timer = 5
if items.size > max_displayed
displayed_items = items.sort_by{rand}[0, max_displayed].sort + ["* ..."]
else
displayed_items = items
end
system "notify", "Inbox is not processed", "#{items.size} items in your inbox:", *displayed_items
sleep 60
old_items = items
end
Script which displays KDE notifications is:
header = "Notification"
msg = ARGV.join("\n") # "All your base\nAre belong to us"
system 'dcop', 'knotify', 'Notify', 'notify', 'notify', header, msg, 'nosound', 'nofile', '16', '0'
Backup reminder
Since my disk died I became more serious about backups. I indent to have at least regular rsync of my SVK repository and some important files. Here's a script which rsyncs these files from
shanti (my main box) to ishida (an old laptop).t0 = Time.now
rv = system 'rsync -rL ~/.mirrorme/ taw@ishida:/home/taw/shanti_mirror/'
unless rv
STDERR.puts "Error trying to rsync"
exit 1
end
t1 = Time.now
File.open('/home/taw/.last_backup', 'w') {|fh|
fh.puts t1
}
puts "Started: #{t0}"
puts "Started: #{t1}"
puts "Time: #{t1-t0}s"
If backup was successful a time stamp is saved to
/home/taw/.last_backup. inbox_size.rb reminds me if I didn't backup for more than 3 days:# Time since last rsync
time_since_last_rsync = Time.now - Time.parse(File.read("/home/taw/.last_backup").chomp)
if time_since_last_rsync > 3 * 24 * 60 * 60
items << "Over 3 days since the last backup"
end
Tickler file
The "tickler file" (
/home/taw/.tickler) contains all things I want to be reminded about. Appointments, deadlines, new episodes of The Colbert Report, whatever. Of course usually I want to be reminded before the deadline, not on the deadline, so the date must be some time before the event of interest. Entries in the tickler file look something like that:Sat Jul 21 05:49:14 +0200 2007
15 days to Wikimedia Foundation validation deadline
It can be edited as a text file, but it's more convenient to add new entries with
add_tickler script:$ add_tickler 24h "New TCR episode will be available"
unless ARGV.size == 2
STDERR.puts "Usage: #{$0} 'due' 'msg'"
exit 1
end
due = ARGV.shift
msg = ARGV.shift
due_sec = case due
when /\A(\d+)s\Z/
$1.to_i
when /\A(\d+)m\Z/
$1.to_i * 60
when /\A(\d+)h\Z/
$1.to_i * 60 * 60
when /\A(\d+)d\Z/
$1.to_i * 60 * 60 * 24
else
STDERR.puts <<EOF
Usage: #{$0} 'due' 'msg'
Due can be:
* 15s
* 15m
* 15h
* 15d
EOF
exit 1
end
due_time = Time.now + due_sec
File.open("/home/taw/.tickler", "a") {|fh|
fh.puts due_time
fh.puts msg
}
The tickler file is checked by the following code in
inbox_size.rb:# Tickler items
tickler = File.readlines("/home/taw/.tickler")
while not tickler.empty?
deadline = Time.parse(tickler.shift.chomp)
msg = tickler.shift
if Time.now > deadline
items << msg
end
end
The passwords file
Pretty much every website requires an account nowadays. I don't want to reuse password on multiple website, so I generate them randomly (
cat /dev/urandom | perl -ple 's/[^a-zA-Z0-9]//g' | head) and keep them in unencrypted file /home/taw/.passwords which I simply grep if I want to login to some weird website again (normally Firefox remembers these passwords anyway, but sometimes it's necessary).As it would suck to lose all accounts, I AES-256-CBC encrypt this file and keep encrypted copies in
/home/taw/ref/skrt/, which is mirrored to multiple servers. As I need to enter my password to encrypt the file, it cannot be done automatically. The most inbox_size.rb can do is reminding me if there's no up-to-date skrt file:# skrt up to date ?
pwtm = File.mtime("/home/taw/.passwords")
last_skrt_tm = Dir["/home/taw/ref/skrt/*"].map{|fn| File.mtime(fn)}.max
if pwtm > last_skrt_tm
items << "No up-to-date skrt available"
end
In which case I run the following
skrt_new script:t = Time.now
fn = sprintf "skrt-%04d-%02d-%02d", t.year, t.month, t.day
system "openssl aes-256-cbc /home/taw/ref/skrt/#{fn}
Music log
The iPod-last.fm bridge consists of two parts - one which extracts the log from an iPod, and one which submits the data to last.fm. They communicate using very simple format, with lines like that (time is local):
Sumptuastic ; Cisza (Radio Edit) ; Cisza (Single) ; 185 ; 2007-07-11 17:51:27
Nothing in the format is iPod-specific, so I wrote a wrapper around mplayer which logs music it plays to
/home/taw/.music_log. It can also randomize songs and search for them recursively in directories. It uses a few extra programs - id3v2 to get song title, artist and album (from either ID3v2 or ID3v1 tags), and mp3info to get playing time.def mp3_get_metadata(file_name)
song_info = `id3v2 -l "#{file_name}"`
artist = nil
title = nil
album = nil
if song_info =~ /^TPE1 \(Lead performer\(s\)\/Soloist\(s\)\): (.*)$/
artist = $1
elsif song_info =~ /^Title : .{31} Artist: (.*?)\s*$/
artist = $1
end
if song_info =~ /^TIT2 \(Title\/songname\/content description\): (.*)$/
title = $1
elsif song_info =~ /^Title : (.{0,31}?)\s+ Artist: .*$/
title = $1
end
if song_info =~ /^TALB \(Album\/Movie\/Show title\): (.*)$/
album = $1
elsif song_info =~ /^Album : (.{0,31}?)\s+ Year:/
album = $1
end
return [artist, title, album]
end
def mp3_get_length(file_name)
`mp3info -F -p "%S" "#{file_name}"`.to_i
end
def with_timer
time_start = Time.now
yield
return [time_start, Time.now - time_start]
end
randomize = true
if ARGV[0] == "-s" # --sequential
randomize = false
ARGV.shift
end
songs = ARGV.map{|fn| if File.directory?(fn) then Dir["#{fn}/**/*.mp3"] else fn end}.flatten
songs = songs.sort_by{rand} if randomize
songs.each{|song|
time_start, time_elapsed = with_timer do
rv = system "mplayer", song
exit unless rv
end
artist, title, album = *mp3_get_metadata(song)
length = mp3_get_length(song)
next unless length >= 90 and (time_elapsed >= 240 or time_elapsed >= 0.5 * length)
date = time_start.strftime("%Y-%m-%d %H:%M:%S")
File.open("/home/taw/.music_log", "a") {|fh|
fh.puts "#{artist} ; #{title} ; #{album} ; #{length} ; #{date}"
}
}
It's a good idea to commit the log to last.fm often, but I'm not doing it automatically yet, as network problems with last.fm are too frequent. Instead
inbox_size.rb reminds me if there are old uncommitted entries in the log:# .music_log not empty and older than one hour
if File.size("/home/taw/.music_log") > 0 and File.mtime("/home/taw/.music_log") < Time.now - 60*60
items << "Music log not clean"
end
Uncommitted stuff in repositories
I sometimes get distracted by some interruption and forget to commit things to repositories.
I wrote
uncommitted_changes script which checks local checkouts of all repositories I use (currently 1 SVK and 2 SVN repositories) if there are any uncommitted changes. I use svn/svk diff instead of svn/svk status as the latter finds all kinds of temporary files, and I always svn/svk add all new files when I start coding anyway.Dir.chdir("/home/taw/everything/") { system "svk diff" }
Dir.chdir("/home/taw/everything/rf-rlisp/") { system "svn diff" }
Dir.chdir("/home/taw/everything/gna_tawbot/") { system "svn diff" }inbox_size.rb simply checks that output of this script is empty: # Uncommitted changes
uc = `uncommitted_changes`
unless uc == ""
items << "There are uncommitted changes in the repository"
end
Unread Gmail emails
The last kind of inbox items tracked by
inbox_size.rb are email inbox items. Google APIs are almost invariably ugly Java-centric blobs of suckiness, so instead of using Gmail API I simply get the list from RSS, parsed using magic/xml.# Unread Gmail messages
unless $offline
gmail_passwd = File.read("/home/taw/.gmail_passwd").chomp
url = "https://Tomasz.Wegrzanowski:#{gmail_passwd}@mail.google.com/mail/feed/atom"
XML.load(url).children(:entry, :title).each{|title|
items << "Email: #{title.text}"
}
end

