Go,可扩展系统,可对文件进行分类

I have the idea to create a "script" that will walk a directory tree to classify files and take actions (If it is a photo, upload to Flickr. If it is a video, move to a folder, ..).

I have no knwoledge of Go but would like to change that. The file discovery and classification don't seems to be the big part. routines and channels seems to be enough to have a non blocking system.

My main concern is more about the actions to take. I will certainly code some actions like "FlickrUpload" or "Shell" (for mv, cp, ..). But I would like to have a configurable system where I can attach an action to a mime type. I think an Xml structure could be nice to configure it and seems simple to parse.

<?xml version="1.0" standalone="yes">
<configuration>
  <file type="image/jpg">
    <action name="FlickrUpload" />
    <action name="Shell">
      mv ${file} /some/absolute/path/
    </action>
    <!-- or -->
    <FlickrUpload /> 
  </file>
</configuration>

But I have no idea on how to bind the action and his arguments (from Xml) to an action that will be an interface (unless you say other).

type action interface {
  execute(path string) error
}

From an XML stream or any other textual medium, you can only obtain text. So your mv ${file} /some/absolute/path/ is essentially text.

This means you will inevitably need to parse it before actually call your real action on whatever you have parsed out.

Now you have two facets of this problem:

  1. How to call certain Go code once an action is known.
  2. How to pass "action arguments" data to it.

They are intertwined a bit—depending on how do you decide to solve (1).

The first approach to call Go function given its textual "handle" is to simply have a map of such handles to functions, like this:

type Action func (...interface{}) error

var actions = map[string]Action {
    "mv": Move,
    "flickr": UploadToFlickr,
}

func Move(args ...interface{}) error {
    // implement
}

func UploadToFlickr(args ...interface{}) error {
    // implement
}

And now you dispatch your actions like

act, ok := actions[action_name]
if !ok {
  log.Fatalf("Config error: Unsupported action: %s
', action_name)
}

err := act(action_args...)
if err != nil {
   // Handle
}

Notice these ... bits: they implement variadic functions and allow to pass a slice of values to a variadic function.

The second approach is to go more strictly typed:

  1. You define an interface:

    type Action interface {
        func Do(fname string) error
    }
    
  2. Your configuration parsing code would produce values of concrete types like, say, MoveAction, FlickrUploadAction.

    All these types must implement a method Do() of the interface Action.

    By doing this, they automagically implment the said interface. This also means your condiguration parser could return values of that interface—something like this:

    func ConfigParser() (map[string]Action, error)
    

    …where map[string]Action would map MIME types of files to values of concrete types—such as FlickrUploadAction for files of type image/png and MoveAction for files of type text/plain.

To call an action, you lookup a map produced by that imaginary ConfigParser() function using the file's MIME type and obtain a value which implements the Action interface, so what's left is simply calling its Do() method passing it the name of the actual file.

TL;DR

Read up on interfaces—they are super powerful and pervasive in the standard library (look at those io.Readers and io.Writers).