taking a step back/up/sideways (thebrain)

Around 2003, 2004 I had a bit of a mild obsession with organizing my life into digital form, creating as many mappings as I could from my everyday existence into some kind of digital form. This of course included ideas and thoughts, writings and paintings, music and movies, friends and bits of information about those friends etc. This amass of data have gone from one disjointed medium to another (.txt files in folders, emails, blog posts, napkins) without ever really achieving any chohesiveness or real improvement in my ability to actually synthesize or act on all that information.

At that time the closest tool I found that could come close to mapping ideas and thoughts in a way that made sense to me was "TheBrain" a piece of software intended for visualizing information, and as importantly, the links between that information. As a user, you add "thoughts" to your brain which become nodes in a large graph of ideas, thoughts, attributes, urls etc. You can then very quickly build child, parent and sibling links between those nodes to add more context and information. Those links can then be categorized to add even more context to the relationship between ideas which is often very important information. Unlike a lot of "mind mapping" tools I've used though, the brain does not force you into a tree structure. Your thoughts can have multiple parents and siblings or "jumps" to thoughts anywhere else in your brain, whether they are directly related or not. Hyperlinks, imagine that! I think for the actual exercise of brainstorming and free flow thought this is critical. It also mimicks how I visualize my own thoughts working. I've since tried personal wiki's which give you a lot of the same flexibility but lack the very fast keyboard entry for connections and nodes and force you to think at the level "inside" the node, by editing the page. Whereas the brain allows you to think and work at the level of the topology, which is really effective.

I stopped using the software basically because of the overhead of attempting to keep the futile mapping exercise up to date. Imagine if every idea and thought you had needed to be compulsively cataloged manually into a program in order to keep the overall picture intact. It just doesn't work, at least not for me, and not for long and detracts from my overall goals. I found that every time I opened my brain there was just too much catchup work to do in order to get things synced up and in order.

The other thing that changed for me since 2004 has been search. Between Google and Spotlight on my mac I basically stopped categorizing information in the same ways I used to have to. It becomes less and less necessary to build nested categories of programs or emails or documents etc. Search has exposed the entire hierarchy in a glance, in many cases keeping the context intact that would have derived the categories. Still there is value in the information of that structure, but in a far less visible way.

Well I've resurrected the brain and am using it in a new way that seems to be actually working for me. For starters I don't keep any notes or real content in the brain, this is one of the clumsiest facets of the tool and almost seems like an afterthought. It really is all about the topology, which for me is fine since the bulk of what I need is often in dedicated stores (subversion, sharepoint, team foundation server) I've found any attempt to use the clumsy palettes and data entry forms to just be too cumbersome to bother with.

Focusing on the nodes and connections, and learning all the keyboard shortcuts have enabled me to use the brain in a way that is a lot like I sometimes use notepad when I need to brainstorm. Except rather than dashes, asterisks and plus-signs I'm using jumps, parents and children of small snippets of text. It's really a cool feeling being able to navigate this very large graph of interrelated concerns and ideas with very little effort. Wiki's and other hypertextual forms have served me well too, but never with so little impediment.

For high level thinking and free form brainstorming this tool is the best I've used. And provided I keep it to just the free flowing jumping and navigating it's incredibly useful.

Simple Extensibility in .NET

I've used this approach a few times when I essentially need a really simple plugin / provider model within my applications so I thought I'd jot down the relevant details here for posterity using an old project for adding post commit hooks to subversion.

Consider this a somewhat simplistic approach, not suitable for production code without a bit more plumbing. If you are going all out and need true add-in's for your .NET based product I recommend checking out the managed add-in framework , very robust stuff and not that hard to implement. In a lot of cases though the isolation, discoverability, communication pipelines etc are a bit overkill. The example I'll show is a subversion hook that allows for very simple addition of new .NET "actions" to execute on PostCommit. In this case the "add-ins" are only written in house, and editing a config file to hook them up is completely acceptable etc etc.

The solution

Subversion.Contracts : This project is the bridge between our dispatcher and the plugins that will do the work.
Subversion.Plugins : Any of the actions we wish to take post commit are added here, but could just as easily be distributed across as many assemblies and projects as necessary as long as they reference the contracts.
Subversion.Dispatcher : This is the console application that actually receives the arguments from subversion and translates them into our contracts, then executes the appropriate actions (note no references to the plugins project)


The Contract

The contracts are relatively simple, but whatever you put in them this is the interface for the "plugin" that will need to implement. In our case this is IPostCommitHandler :

using System;

namespace Subversion.Contracts
{
 public interface IPostCommitHandler
 {
  void ExecuteCommand(PostCommitArgs a);
 }
}

Pretty simple, essentially just a "do whatever you want" method that passes the arguments from subversion wrapped up in a simple class. See the attached zip if you want the guts of the subversion specific stuff.


The Plugin

using System;
using Subversion.Contracts;

namespace Subversion.Plugins
{
 public class ExecuteForAllCommits : IPostCommitHandler
 {
  #region IPostCommitHandler Members

  public void ExecuteCommand(PostCommitArgs a)
  {
   SendEmailNotification.SendEmail(a.Argument, a.Revision);
  }

  #endregion
 }
}


Again, very simple and in this case we're passing off the execution to a static class that again is not shown, but what gets executed isn't all that important in this case.. simply fill in what you need.

The Dispatcher (Plugin Host)

using System;
using System.Collections;
using System.Text.RegularExpressions;
using System.Configuration;
using Subversion.Contracts;

namespace Subversion.Dispatcher
{
 ///
 /// 
 /// Summary description for PostCommit.
 /// 
 class PostCommit
 {

  private static string subversionPath = ConfigurationSettings.AppSettings["SubversionPath"];

  static void Main(string[] args)
  {
   SubversionRevision rev = ParseRevision(args);
   ArrayList commands = DispatchGlobalCommands(rev);
   DispatchNamedCommands(rev, commands);
  }

  private static SubversionRevision ParseRevision(string[] args)
  {
   SubversionRevision rev;
   if (args.Length == 2)
   {
    rev = new SubversionRevision(subversionPath, args[0], args[1]);
   }
   else
   {
    rev = new SubversionRevision(subversionPath, string.Empty, string.Empty);
   }
   return rev;
  }

  private static void DispatchNamedCommands(SubversionRevision rev, ArrayList commands)
  {
   string[] commitLines = rev.CommitLog.Split(Environment.NewLine[0]);
   // Handle Named Commands
   string registeredCommands = String.Join("|", (string[])commands.ToArray(typeof(string)));
   Regex CommandSearch = new Regex(@"(" + registeredCommands + @")\s*:\s*(.+)?", RegexOptions.IgnoreCase);
   foreach (string line in commitLines)
   {
    string lowerline = line.ToLower();
    for (Match Matches = CommandSearch.Match(lowerline); Matches.Success; Matches = Matches.NextMatch())
    {
     string handlerString = ConfigurationSettings.AppSettings["command:" + Matches.Groups[1].ToString()];
     DispatchCommand(handlerString, Matches.Groups[2].ToString(), rev);
    }
   }
  }

  private static ArrayList DispatchGlobalCommands(SubversionRevision rev)
  {
   // Handle global commands 
   ArrayList commands = new ArrayList();
   for (int i = 0; i < ConfigurationSettings.AppSettings.Count; i++)
   {
    string key = ConfigurationSettings.AppSettings.GetKey(i);
    string val = ConfigurationSettings.AppSettings.Get(i);
    string[] cmdParts = key.Split(':');
    if (cmdParts.Length == 2 && cmdParts[0] == "command")
    {
     if (cmdParts[1].StartsWith("*"))
     {
      DispatchCommand(val, cmdParts[1].Substring(cmdParts[1].IndexOf(",") + 1), rev);
     }
     else
     {
      commands.Add(cmdParts[1]);
     }
    }
   }
   return commands;
  }


  /// 
  /// Call the appropriate method for the command name given with the argument given
  /// no processing of the argument happens here. 
  /// 
private static void DispatchCommand(string handlerString, string argument, SubversionRevision rev)
  {
   // We don't want properly configured commands to stop working because of errors so trap
   // everything here...
   try
   {
    if (handlerString != null && handlerString.Length > 0)
    {
     string[] typeAndAssembly = handlerString.Split(',');
     if (typeAndAssembly.Length == 2)
     {
      System.Reflection.Assembly a = System.Reflection.Assembly.Load(typeAndAssembly[1]);
      System.Type t = a.GetType(typeAndAssembly[0], true);
      object handler = System.Activator.CreateInstance(t);
      if (handler is IPostCommitHandler)
      {
       ((IPostCommitHandler)handler).ExecuteCommand(new PostCommitArgs(argument,rev));
      }
     }
    }
   }
   catch (Exception) 
   { //TODO: log errors 
   }
  } 
 }
}


There is some plumbing in this class that isn't directly related to this post, but I've left it all anyway. Subversion will run this command every time a checkin is made, and the process ends and starts over again each time. This allows for some pretty simple handling of loaded assemblies and whatnot, if you have a longer running process or are dealing with some scale be cautious. ;-)

The Main function has two jobs, parse and create the revision, then read the application configuration file and start issueing commands for the received revision. Commands are in two parts, those defined in config to be executed always (global commands) and those that are interpreted from the subversion commit log itself, parsed out and executed with arguments from the revision log.

Here are some example commands defined in the config
<!-- Commands -->
<add key="command:*,chris" value="Subversion.Plugins.ExecuteForAllCommits,Subversion.Plugins" />
<add key="command:*,check-ins" value="Subversion.Plugins.ExecuteForAllCommits,Subversion.Plugins" />
<add key="command:bug" value="Subversion.Plugins.UpdateBugTracker,Subversion.Plugins" />
<add key="command:cc" value="Subversion.Plugins.SendEmailNotification,Subversion.Plugins" />


  • in the key we have "command:[name]" signifies a command arriving in a revision where somewhere in the revision log we'll see the command name followed by a colon, anything following the colon is then passed to the plugin as an argument. If the name is an asterisk then we simply execute for all, with an optional argument being passed to the plugin. (so the first example emails chris for all revisions, and the second emails an account named check-ins
  • the value portion here is what directs the program where to look for the appropriate plugin and class to execute. I copied the format I found in a web.config file which is to put the class name followed by the assembly name separated by a comma.

In retrospect if I were doing something similar again I'd probably create a better structured format rather than relying on all this string parsing... but old code is what it is in this case.

Finally we call DispatchCommand for each parsed out command which is the last piece of this old code that I'm attempting to document here for reuse. DispatchCommand will read the class name and assembly name, load the assembly name and attempt to instantiate the class/type named in order to call it using our IPostCommitHandler interface.

There are a few ways to do this, and for this project I'm simply calling "System.Reflection.Assembly.Load" which relies on the fact that my plugins are located in my bin directory. I've also done this using a "plugin store" which is a fancy way to say I had a dynamic path configured that I could read my assemblies from. In this case you can use LoadFile or LoadFrom, LoadFrom will load dependencies automatically while LoadFile loads just the assembly and will potentially load duplicate copies. (see the documentation) In order to get the dll's in place for this project we just simply add a post build event like so...

copy $(TargetDir)*.* $(SolutionDir)\Subversion.Dispatcher\$(OutDir)

If after instantiating the named type from the loaded assembly we actually have an IPostCommitHandler then make the call! Done.

System.Reflection.Assembly a = System.Reflection.Assembly.Load(typeAndAssembly[1]);
System.Type t = a.GetType(typeAndAssembly[0], true);
object handler = System.Activator.CreateInstance(t);
if (handler is IPostCommitHandler)
{
 ((IPostCommitHandler)handler).ExecuteCommand(new PostCommitArgs(argument,rev));
}

So that's that. You can download the code here - it should basically work as is if you are looking for a shortcut to extending subversion with .NET. I was relatively lazy with getting this posted - so if you got this far, can use the code, and have problems with it leave a comment and I'll try to help if I can.

Transcendent Man!

I read the singularity is near last year and really enjoyed it, despite a few misgivings for Kurzweil's ego and some dubious use of statistics. One of the things I found myself really intrigued by was Kurweil himself and this movie looks like a fun look at the man and his ideas.



Do I believe him? Part of me wants to, definitely. The ultimate end-game of the singularity is fascinating and wondrous, but I actually found some of the more intermediate steps in his projections to be more fascinating. Maybe that's just a factor of what I can relate to. One example of this was the idea that nano-technology will lead us to self-assembling products from base materials and an instruction set transmitted as information. So much of my life is already so information focused that the idea of being able to go 100% information based and the implications on how society is structured etc... it's mind numbingly cool. Try to imagine how much energy, time and effort we put into moving goods around this planet and how incredible it would be for all of that to end.

Anyway, looking forward to renting this one when it becomes available.