Channels ▼


Sweetening the Plain Vanilla .NET FileSystemWatcher

Ivan Porto Carrero is an independent Ruby and .NET developer. Adam Burmister is a polyglot front- and back-end developer, usability evangelist, and designer. Ivan and Adam are the authors of IronRuby in Action.

We routinely watch certain paths on the file system for changes and subsequently perform actions when something happens there. The .NET Framework provides us with a FileSystemWatcher class to help you with that task. This FileSystemWatcher can be defined on a certain path, which can be a folder, and optionally you can set the watcher instance to include the subdirectories of that path as well. But, for more advanced filtering, you're either stuck with defining different paths to watch or providing your own filter when an event is fired. Although there are some other issues involved, in the interest of keeping the code simple, this version of the DSL won't compensate for all of them because that isn't the topic of the discussion. Get ready to dive in and learn several metaprogramming tricks as we build a file-watching Domain-Specific Language (DSL).

Starting with the End Result

In the DSL, I'd like to use regular expressions for the filters as well as a limited form of glob patterns, limited in the sense that it only needs to understand the **, ?, and * wildcard operators. I would like to be able to specify multiple regular expressions for a given path. I would also like to be able to specify a blanket regular expression for a path that is used as the default filter and takes precedence over the more specific regular expressions. Listing 1 shows the end result for what I'd like to achieve.

filesystem do
  watch("/path/to/watch", /_spec.rb$/ui) do                               #A
    on_change { |args| # do stuff here with args.path or }      #B
    on_change /integration\/.*_spec.rb/ { |args| # do stuff here } 
    on_rename { |args| # do stuff here }
    on_rename "*.rb" { |args| # do stuff here } 
    on_delete { |args| # do stuff here with args.path or } 
    on_create { |args| # do stuff here with args.path or } 
    on_error  { |args| # do stuff here } 
  watch("/another/path/to/watch", /app\/.*\.rb$/ui) do
    on_change { |args| # do stuff here with args.path or } 
    on_rename { |args| # do stuff here } 
    on_delete { |args| # do stuff here with args.path or } 
    on_create { |args| # do stuff here with args.path or } 
    on_error  { |args| # do stuff here } 

#A Define a path to watch
#B Attach event handlers

Listing 1: The finished file system watching DSL

As you can see, we have an entry point called filesystem, which takes a block and then proceeds with defining new watch paths. Each watch path in turn takes another block where it defines the event handlers for this watch. This actual setup doesn't really make sense in some cases but it does show the complete syntax we're going for. For this example, we won't need any external libraries and so we can start right away with collecting the paths and filters.

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.