Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Zip My Code

4.78/5 (17 votes)
20 Dec 2009CPOL3 min read 74.2K   2K  
A utility stripping your source code to the essential core and then compressing it to a nice CodeProject article attachment.

Image 1

Image 2

Contents

Introduction

This article will present you with a utility that may come in handy if you are a contributor on CodeProject. The utility eases stripping down the source to the pure essential of what article code attachments should be. It is also very handy to do so, using simple drag-and-drop or the console interface. If enough people find it useful, it may earn its place as an author tool on the Free Tools for Authors page, which according to me is rather scant.

I am aware that this article won't get any good ratings or popularity, it is after all a very basic application. It focuses on solving a minor problem rather than presenting you with really cool code. There are, however, some grains of gold buried within the application that might interest some of you.

Credits

  • First and foremost credit goes to CodeProject member SAKryukov for taking his time to review the article and give extremely constructive feedback.
  • MADEBITS for their NETZ, a utility I use for compressing and packing ZipMyCode into one assembly.
  • The ICSharpCode team for their SharpZipLib, an assembly I use for Zip compression.
  • CodeProject member Peter Palotas for his Plossum command line parser.

How to Use the Utility

The UI Part

  1. Type or press the Browse... button and select the source path.
  2. Press Start zipping my code.
  3. A compressed zip archive will be created alongside your source.

You have the option to add, edit, and remove exclude patterns by expanding Options.

The Console Part

The application has, in addition to the UI, a command line interface:

C:\>ZipMyCode /?
ZipMyCode  version 1.0.2.0
Copyright © 2009

Usage:
   ZipMyCode path [/c:file] [/e:value[+value]] [/o:file]
   ZipMyCode /u

Strip and compress:
   /c,                 Specifies the configuration file with exclude
   /configuration      patterns, one pattern per line. If file is
                       specified, the default exclude patterns are ignored.
   /e, /exclude        Specifies the list of exclude patterns. If patterns are
                       specified, the default exclude patterns are ignored.
   /o, /output         Specifies the name of the compressed output file.

Uninstallation:
   /u, /uninstall      Removes saved settings, i.e. cleans
                       up the application footprint.

Help:
   /?, /h, /help       Displays this help text.

The interface deliberately takes a single path as argument, because you are then capable of dropping any folder on top of the application (or shortcut, if you make one) using Windows drag-and-drop, since the first argument passed to the application is the dropped folder path.

Grains of Gold

Built Using MVVM and the Dialog Service

The application is built using MVVM, which seems to be on everybody's lips for the moment. Opening dialogs is accomplished with the dialog service from my previous article: Showing Dialogs When Using the MVVM Pattern. This time, the service features a FolderBrowserDialog instead of a OpenFileDialog. The idea of a having a service opening the dialogs still holds for these small applications, no clouds on the horizon yet. Feel free to give the article a glance, and give me your opinion on the idea.

New Way of Writing Properties

The ViewModels are implemented with a new way of writing properties. We usually write a ViewModel property like this:

C#
private string someText;

public string SomeText
{
  get { return someText; }
  set
  {
    if (someText != value)
    {
      someText = value;
      OnPropertyChanged("SomeText");
    }
  }
}

With a little help from some methods, we now can write:

C#
public string SomeText
{
  get { return Property(() => SomeText); }
  set { Property(() => SomeText, value); }
}

We no longer have to write the repetitive setter code, and Visual Studio IntelliSense is better at picking up this format than the first containing a string in the OnPropertyChanged method.

The code supporting this new format exists in ViewModelBase:

C#
/// <summary>
/// Gets the value of a property matching the given expression.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="nameExpression">The expression pointing to the property.</param>
/// <returns>The property value if existing; otherwise default.</returns>
protected T Property<T>(Expression<Func<T>> nameExpression)
{
  PropertyItem p;
  if (properties.TryGetValue(nameExpression.ToString(), out p))
  {
    return (T)p.Value;
  }

  return default(T);
}

/// <summary>
/// Sets the value of a property matching the given expression.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="nameExpression">The expression pointing to the property.</param>
/// <param name="value">The value to set.</param>
protected void Property<T>(Expression<Func<T>> nameExpression, T value)
{
  // Get the key of the property
  string key = nameExpression.ToString();

  PropertyItem p;
  if (properties.TryGetValue(key, out p))
  {
    // Make sure the property value has changed
    if ((p.Value == null && value == null) || 
        (p.Value != null && p.Value.Equals(value)))
    {
      return;
    }

    // Set the new value
    p.Value = value;
  }
  else
  {
    // Create the new property item
    p = new PropertyItem
    {
      Name = GetPropertyName(nameExpression),
      Value = value
    };

    // Add the new propery item
    properties.Add(key, p);
  }

  // Raise property changed event
  OnPropertyChanged(new PropertyChangedEventArgs(p.Name));
}

/// <summary>
/// Gets the property name of the expression.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="nameExpression">The name expression.</param>
/// <returns>The property name of the expression.</returns>
private static string GetPropertyName<T>(Expression<Func<T>> nameExpression)
{
  UnaryExpression unaryExpression = nameExpression.Body as UnaryExpression;

  // Convert name expression into MemberExpression
  MemberExpression memberExpression = unaryExpression != null ?
    (MemberExpression)unaryExpression.Operand :
    (MemberExpression)nameExpression.Body;

  return memberExpression.Member.Name;
}

/// <summary>
/// Class wrapping up the essential parts of a property.
/// </summary>
class PropertyItem
{
  /// <summary>
  /// Gets or sets the name.
  /// </summary>
  public string Name { get; set; }

  /// <summary>
  /// Gets or sets the value.
  /// </summary>

  public object Value { get; set; }
}

Algorithm Searching Files and Folders with Exclude Pattern

The .NET framework supports searching for files and folders using DirectoryInfo.GetFiles(string searchPattern) and DirectoryInfo.GetDirectories(string searchPattern), but it doesn't feature one to search using an exclude pattern. When it came to deciding what files and folders are acceptable in a compressed source attachment, I decided that it was easier for a user to state what he/she didn't want rather than specifying what he/she did want.

The following code exists in GetFilesTask and uses LINQ to solve the problem:

C#
/// <summary>
/// Recursive method finding all files from a directory and its sub-directory.
/// </summary>
/// <param name="directory">The directory.</param>
/// <param name="excludePatterns">The file and directory exclude pattern.</param>
private IEnumerable<string> RecursiveGetFiles(DirectoryInfo directory,
  string[] excludePatterns)
{
  // Find files not matching the exclude patterns
  IEnumerable<FileInfo> files =
    (from file in directory.GetFiles()
     select file)
    .Except(GetExcludedFiles(directory, excludePatterns), new FileInfoEqualityComparer());

  // Yield files not matching the exclude patterns
  foreach (FileInfo file in files)
  {
    yield return file.FullName;
  }

  // Find directories not matching the exclude patterns
  IEnumerable<DirectoryInfo> subDirectories =
    (from subDirectory in directory.GetDirectories()
     select subDirectory)
    .Except(GetExcludedDirectories(directory, excludePatterns),
      new DirectoryInfoEqualityComparer());

  // Search files in sub-directories not matching the exclude pattern
  foreach (DirectoryInfo subDirectory in subDirectories)
  {
    // Yield all files not matching the exclude patterns
    foreach (string file in RecursiveGetFiles(subDirectory, excludePatterns))
    {
      yield return file;
    }
  }
}

/// <summary>
/// Gets the excluded files in a specified directory.
/// </summary>
/// <param name="directory">The directory.</param>
/// <param name="excludePatterns">The file exclude pattern.</param>
private IEnumerable<FileInfo> GetExcludedFiles(DirectoryInfo directory,
  string[] excludePatterns)
{
  return
    from excludePattern in excludePatterns
    from filesMatchingExcludePattern in directory.GetFiles(excludePattern)
    select filesMatchingExcludePattern;
}

/// <summary>

/// Gets the excluded sub-directories in a specified directory.
/// </summary>
/// <param name="directory">The directory.</param>
/// <param name="excludePatterns">The directory exclude pattern.</param>
private IEnumerable<DirectoryInfo> GetExcludedDirectories(DirectoryInfo directory,
  string[] excludePatterns)
{
  return
    from excludePattern in excludePatterns
    from directoriesMatchingExcludePattern in directory.GetDirectories(excludePattern)
    select directoriesMatchingExcludePattern;
}

class FileInfoEqualityComparer : IEqualityComparer<FileInfo>

{
  public bool Equals(FileInfo x, FileInfo y)
  {
    return x.FullName.Equals(y.FullName);
  }

  public int GetHashCode(FileInfo obj)
  {
    return obj.FullName.GetHashCode();
  }
}

class DirectoryInfoEqualityComparer : IEqualityComparer<DirectoryInfo>
{
  public bool Equals(DirectoryInfo x, DirectoryInfo y)
  {
    return x.FullName.Equals(y.FullName);
  }

  public int GetHashCode(DirectoryInfo obj)
  {
    return obj.FullName.GetHashCode();
  }
}

History

  • 1.0.2.0 (20 December 2009)
    • Console
      • Implemented a console interface
      • Implemented uninstall
    • UI
      • Source path is editable and handles validation
      • Changed default path
      • Fixed initial focus
      • Added additional default ignore patterns
  • 1.0.1.0 (25 October 2009)
  • 1.0.0.0 (7 July 2009)
    • Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)