Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy

BoudinFatal's Gamercard

Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

FluentPath 1.0

(c) Bertrand Le Roy 2010Last Sunday, I published version 1.0 of my little FluentPath library.

This library, which is a fluent wrapper around System.IO, started as a little experiment / code sample and has been met with some enthusiasm from some of you so I spent quite a bit of time over the last few months polishing it and trying to raise it to a level of quality where it can safely be used in real applications (although my lawyer is telling me that it’s still at your own risk, as specified in the license).

I’ve started using it myself on a few projects and it’s now my default way of accessing the file system.

So what’s in there and why should you use it?

It’s small!

36kB dll, no dependencies.

It’s a NuGet!

You can now install FluentPath with NuGet:Installing FluentPath with NuGet

It’s robust!

Since the first release of FluentPath, I’ve spent the most time on writing unit and functional tests for the library. At 143 tests and more than 80% coverage, I’m pretty confident that this can be safely used in real projects (even if my lawyer isn’t).

Kind of.

Well, it’s still really designed for the manipulation of relatively light files: it does not always use buffered streams and doesn’t do asynchrony. I think that’s all right for most uses but if you’re in a case where you can’t afford that, just continue to use System.IO instead.

Of course, if someone wants to contribute patches, that’s fine too.

It’s fun!

Consider the following piece of code:

Path.Current
    .Files(
        p => new[] {
            ".avi", ".m4v", ".wmv",
            ".mp4", ".dvr-ms", ".mpg", ".mkv"
        }.Contains(p.Extension))
    .CreateDirectories(
        p => p.Parent()
              .Combine(p.FileNameWithoutExtension))
    .End()
    .Move(
        p => p.Parent()
              .Combine(p.FileNameWithoutExtension)
              .Combine(p.FileName));

This selects all video files in the current directory and then moves them into their own directory. So for example if you have a WingsOfDesire.avi file, it will create a WingsOfDesire folder and move the file in there. And it will do so for each of the files in there.

It is left as an exercise to the reader to write the same code with just System.IO and to compare with this.

It’s extensible!

Now that’s nice but there is so much more you can do with files. Zipping and unzipping for example. There are good libraries for that, such as SharpZipLib. But SharpZipLib is rather low-level and it’s not fluent. Not that everything has to be, but wouldn’t it be neat to have a fluent API for zipping and unzipping, one that integrates well with FluentPath?

Well, as an experiment, I built exactly that around SharpZipLib, enabling that sort of thing:

Path.Current
    .Files("*.txt", true)
    .Zip((Path) "textfiles.zip", p => (Path) p.FileName);

Those extra methods are just extension methods for Fluent.IO.Path that can be imported by referencing the new Fluent.Zip.dll assembly and using the Fluent.Zip namespace. For example, here’s the code for that particular Zip overload:

/// <summary>
/// Zips all files in the path to the target.
/// </summary>
/// <param name="path">The files to compress.</param>
/// <param name="target">
/// The path of the target zip file.
/// If target has more than one file, only the first
/// one is used.
</param> /// <param name="fileSystemToZip"> /// A function that maps the paths of the files and
/// directories to zip into relative paths inside the zip.
/// </param> /// <returns>The zipped path.</returns> public static Path Zip(this Path path, Path target,
Func<Path, Path> fileSystemToZip) {
var files = path.AllFiles() .ToDictionary( fileSystemToZip, p => p); Zip(target, new Path(files.Keys), p => files[p].ReadBytes()); return target; }

This shows how it’s very easy to add your own extensions to FluentPath and for example capture in an easy method a specific process that you happen to repeat a lot in your code.

What’s new?

The main change in this new version is that instead of having one Path class to represent a single path, and a PathCollection class to represent a collection of Path objects, I’ve coalesced both classes, jQuery-style and now there is only Path, which behaves both like a single path and like a collection of paths. This makes it really natural to work with sets as if they were single items, which is where fluent APIs really shine.

Another important and breaking change is that Path and string are no longer implicitly cast to each other. Now you need to be explicit about switching from string to Path and back. It’s a little less convenient but the previous ambiguity actually wasn’t sustainable. This way, things are a lot clearer.

And of course, I’ve added lots of nice methods such as Grep. And lots and lots of bug fixes, which is what happens when you start writing tests…

What’s next?

The next big change I want to bring to FluentPath is an additional layer of abstraction in the form of a pluggable file system. This should enable you to test code that is using FluentPath without having to physically write to the file system but instead to write to a stub or a mock. Potentially, it also opens a few interesting scenarios where you manipulate a file-system-like structure using these same APIs and switch from one abstraction to the other without code modifications.

I also want smaller changes such as exposing all file attributes and metadata directly on Path, or change monitoring.

Where is it?

Some links and previous posts about FluentPath:

The original post:
http://weblogs.asp.net/bleroy/archive/2010/03/10/fluentpath-a-fluent-wrapper-around-system-io.aspx

Writing the tests:
http://weblogs.asp.net/bleroy/archive/2010/05/28/writing-the-tests-for-fluentpath.aspx

The project:
http://fluentpath.codeplex.com/

Comments

Damian Edwards said:

Nice. Would be great to see it extended with some async/Task<T> goodness so those fluent calls could build async execution trees.

# November 19, 2010 12:11 PM

Bertrand Le Roy said:

@Damian: yeah, I'll probably do that once C# 5.0 is out.

# November 19, 2010 12:17 PM

Michael said:

Really great to hear you're planning mockable layer of abstraction.

# November 24, 2010 1:47 AM

RichB said:

> p => new[] {

>            ".avi", ".m4v", ".wmv",

>            ".mp4", ".dvr-ms", ".mpg", ".mkv"

>        }.Contains(p.Extension))

I wish C# had an 'in' operator similar to SQL. It would make this much more readable.

p => p.Extension in new [] {".avi", ".m4v", ".wmv", ".mp4", ".dvr-ms", ".mpg", ".mkv"}

# November 24, 2010 4:57 AM

DBJDBJ said:

Bite the bullet BLR ;o)

Use CallStream ... this lib is perfect for it ...

Thanks: DBJDBJ

# November 24, 2010 10:23 AM

Bertrand Le Roy said:

@RichB: you can get something fairly close with an extension method, something like p.Extension.In(new[]{".avi", ".m4v", ".wmv", ".mp4", ".dvr-ms", ".mpg", ".mkv"}).

@DBJDBJ: I disagree.

# November 24, 2010 1:16 PM

Mikael Henriksson said:

This is awesome! I see my self using this lib quite a lot in the future. Has been looking forward to it since the first post you wrote.

# November 26, 2010 11:18 AM

Tony Hinkley said:

Bertrand, great library, already finding places to put it.  As for the In method, I always write an extension for this early and use it lots, I find a params array saves unnecessary array definitions in the caller though.

Thanks for the code.

Tony

# November 29, 2010 4:49 AM