Task

Write a simple tool to track a small set of data.

The tool should have a command-line interface to enter at least two different values.

The entered data should be stored in a structured format and saved to disk.

It does not matter what kind of data is being tracked.   It could be a collection (CDs, coins, baseball cards, books), a diary, an electronic organizer (birthdays/anniversaries/phone numbers/addresses), etc.



You should track the following details:

 A description of the item. (e.g., title, name)
 A category or tag (genre, topic, relationship such as “friend” or “family”)
 A date (either the date when the entry was made or some other date that is meaningful, like the birthday); the date may be generated or entered manually
 Other optional fields


The command should support the following Command-line arguments to run:

 Add a new entry
 Print the latest entry
 Print the latest entry for each category
 Print all entries sorted by a date


The category may be realized as a tag or as structure (by making all entries in that category subitems)

The file format on disk should be human readable, but it need not be standardized.   A natively available format that doesn't need an external library is preferred.   Avoid developing your own format if you can use an already existing one.   If there is no existing format available, pick one of:

   JSON
   S-Expressions
   YAML
   others


Related task

   Take notes on the command line 


mapping db = ([]);
 
mapping make_episode(string series, string title, string episode, array date)
{
    return ([ "series":series, "episode":episode, "title":title, "date":date ]);
}
 
void print_episode(mapping episode)
{
    write("  %-30s %10s %-30s (%{%d.%})\n", 
          episode->series, episode->episode, episode->title, episode->date);
}
 
void print_series(mapping series)
{
    write("%-30s %-10s\n", series->series, series->status);
    map(series->episodes, print_episode);
}
 
void dump_db(mapping database)
{
    foreach(database; string name; mapping series)
    {
        print_series(series);
    }
}
 
array get_latest(mapping database)
{
    array latest = ({});
    foreach(database; string name; mapping series)
    {
       latest += ({ series->episodes[0] });
    }
    return latest;
}
 
int(0..1) compare_date(array a, array b)
{
    if (!arrayp(a) && !arrayp(b)) return false;
    if (!arrayp(a) || !sizeof(a)) return arrayp(b) && sizeof(b);
    if (!arrayp(b) || !sizeof(b)) return arrayp(a) && sizeof(a);
    if (a[0] == b[0]) return compare_date(a[1..], b[1..]);
    return a[0] < b[0];
}
 
int(0..1) compare_by_date(mapping a, mapping b)
{
    return compare_date(reverse(a->date), reverse(b->date));
}
 
void watch_list(mapping database)
{
    map(Array.sort_array(get_latest(database), compare_by_date), 
        print_episode);
}
 
string prompt_read(string prompt)
{
    write("%s: ", prompt);
    return Stdio.stdin.gets();
}
 
array parse_date(string date)
{
    return (array(int))(date/".");
}
 
mapping prompt_for_episode()
{
    return make_episode(prompt_read("Series"), 
                        prompt_read("Title"), 
                        prompt_read("Episode"), 
                        parse_date(prompt_read("Date watched")));
}
 
// pike offers encode_value() and decode_value() as standard ways 
// to save and read data, but that is not a human readable format.
// therefore we are instead printing the structure as debug-output 
// which is a readable form as long as it only contains integers, 
// strings, mappings, arrays and multisets this format can be read by pike.
// to read it we are creating a class that contains the data as a value, 
// which is then compiled and instantiated to allow us to pull the data out.
void save_db(string filename, mapping database)
{
    Stdio.write_file(filename, sprintf("%O", database));
}
 
void watch_save()
{
    save_db("pwatch", db);
}
 
mapping load_db(string filename)
{
    if (file_stat(filename))
        return compile_string("mixed data = " + 
                              Stdio.read_file(filename) + ";")()->data;
    else return ([]);
}
 
mapping get_series(string name, mapping database)
{
    return database[name];
}
 
array get_episode_list(string series, mapping database)
{
    return database[series]->episodes;
}
 
void watch_new_series(string name, string status, mapping database)
{
    database[name] = (["series":name, "status":status, "episodes":({}) ]);
}
 
mapping get_or_add_series(string name, mapping database)
{
    if (!database[name])
    {
        string answer = prompt_read("Add new series? [y/n]: ");
        if (answer == "y")
            watch_new_series(name, "active", database);
    }
    return database[name];
}
 
void watch_add(mapping database)
{
    mapping episode = prompt_for_episode();
    string series_name = episode->series;
    mapping series = get_or_add_series(series_name, database);
    if (!series)
        watch_add(database);
    else
        series->episodes = Array.unshift(series->episodes, episode);
}
 
void watch_load()
{
    db = load_db("pwatch");
}
 
int main(int argc, array argv)
{
    watch_load();
    if (argc>1 && argv[1] == "add")
    {
        watch_add(db);
        watch_save();
    }
    else
        watch_list(db);
}