Click here to Skip to main content
15,879,096 members
Articles / Desktop Programming / WPF

Zip My Code

Rate me:
Please Sign up or sign in to vote.
4.78/5 (17 votes)
20 Dec 2009CPOL3 min read 71.6K   2K   48   29
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)


Written By
Software Developer Axis Communications
Sweden Sweden
Got my first computer in the 90's and loved it even though it sounded like a coffeemaker.

Now getting paid for designing cool applications, and drinks the coffee instead of listening to it being made.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Sergey Alexandrovich Kryukov9-Jan-11 13:58
mvaSergey Alexandrovich Kryukov9-Jan-11 13:58 
GeneralRe: My vote of 5 Pin
FantasticFiasco10-Jan-11 7:56
FantasticFiasco10-Jan-11 7:56 
GeneralAlternative to Plossum Pin
Sergey Alexandrovich Kryukov9-Jan-11 13:56
mvaSergey Alexandrovich Kryukov9-Jan-11 13:56 
GeneralRe: Alternative to Plossum Pin
FantasticFiasco10-Jan-11 7:55
FantasticFiasco10-Jan-11 7:55 
GeneralRe: Alternative to Plossum Pin
Sergey Alexandrovich Kryukov10-Jan-11 13:32
mvaSergey Alexandrovich Kryukov10-Jan-11 13:32 
QuestionMisuse of SVN? Pin
Sergey Alexandrovich Kryukov25-Oct-09 14:34
mvaSergey Alexandrovich Kryukov25-Oct-09 14:34 
AnswerRe: Misuse of SVN? Pin
FantasticFiasco26-Oct-09 1:45
FantasticFiasco26-Oct-09 1:45 
GeneralRe: Misuse of SVN? Pin
Sergey Alexandrovich Kryukov10-Nov-09 8:32
mvaSergey Alexandrovich Kryukov10-Nov-09 8:32 
GeneralRe: Misuse of SVN? Pin
FantasticFiasco11-Nov-09 8:22
FantasticFiasco11-Nov-09 8:22 
GeneralRe: Misuse of SVN? Pin
Sergey Alexandrovich Kryukov11-Nov-09 12:26
mvaSergey Alexandrovich Kryukov11-Nov-09 12:26 
GeneralWhat to improve [modified] Pin
Sergey Alexandrovich Kryukov12-Nov-09 6:05
mvaSergey Alexandrovich Kryukov12-Nov-09 6:05 
Ok, I took a look at you code. Not bad. It's accurately structured and simple.
I can see number of problems, mostly minor ones, as well as some room for improvement.

1) I would highly prefer console application only: configuration file on input (list of excluded file name patterns), zip on output goes to working directory; name of the zip file is constructed out of the name of working directory. Remember, you users are programmers. We know where our stuff is and used to working with texts.
As a big bonus, you will make your application running on Linux with no re-compilation -- via Mono (not just Linux but a lot more). You could also make it running on Mono also with System.Windows.Forms, but this would take some more effort.
Again, main benefit of console application is convenience.

2) Do you have source code of ICSharpCode.SharpZipLib.dll? I believe it is available in SharpDevelop. You need to provide a clear reference to the source code and give them credit.

3) Why not trying 7z? The problem with ZIP is patent issues; that is why Microsoft.NET does not provide ZIP. I heard ZIP is also available from J# run-time library. We would probably need to convince CodeProject editorial board to accept 7z. Alternatively, 7z code could be used for ZIP format. This is just the idea.

4) Now, if you really want WPF, there are issues with UI.

4a) The product does not have installation. This is good! But it would be nice to have un-installation, like “un-install and terminate”. During run-time the application leave some footprint where most recent user's configuration is stored. There must be an option to remove this.

4b) Desktop is a bad place for a default directory for user's project directory. (Desktops are for the lamer only, aren't they?) I would suggest using Working Directory for default. Again, remember your users are programmers.

4c) Your “Source path” control should be editable edit control. Again, remember your users are programmers. We would better off copying the path we need and pasting it there. Of course, then you will need to handle invalid directories.

4d) You need to use underscored label names to provide hot key navigation using “Alt+...”. Also, check up tab navigation: it should be intuitive.

4c) You have a problem with initial focus in main form. It should be set to “Source Path” (again, editable) control. And don't assume everyone will be happy clicking mouse. Programmers use keyboards much more then others.

4d) You have pretty bad XAMLs in terms of design time behavior. TaskDialog.xaml does not even open. This is fairly common with XAMLs (I hope next versions of VS will improve XAML things), but you need to check up all XAMLs in design time before you publish. Another design time problem is that your XAML windows break apart if you change window size. This is an Open Source product, not just a demo, so it should do well in design time. It is actually easier for you to make in a more accurate way, especially for your support. To achieve that, either use SizeToContent=WidthAndHeight for containers, or fill containers with content using proper margins and alignment. After you do this accurately, everything will be automatically adjusted to sizes. This effort will pay off in large.

Good luck!

Sergey A Kryukov

GeneralRe: What to improve Pin
FantasticFiasco21-Nov-09 9:02
FantasticFiasco21-Nov-09 9:02 
GeneralRe: What to improve Pin
FantasticFiasco20-Dec-09 11:53
FantasticFiasco20-Dec-09 11:53 
GeneralVery nice (Re: What to improve) Pin
Sergey Alexandrovich Kryukov27-Dec-09 16:19
mvaSergey Alexandrovich Kryukov27-Dec-09 16:19 
GeneralRe: Very nice (Re: What to improve) Pin
FantasticFiasco28-Dec-09 5:23
FantasticFiasco28-Dec-09 5:23 
GeneralTwo more improvements Pin
Sergey Alexandrovich Kryukov12-Nov-09 6:57
mvaSergey Alexandrovich Kryukov12-Nov-09 6:57 
GeneralRe: Two more improvements Pin
FantasticFiasco12-Nov-09 9:17
FantasticFiasco12-Nov-09 9:17 
GeneralRe: Two more improvements Pin
FantasticFiasco21-Nov-09 9:03
FantasticFiasco21-Nov-09 9:03 
Generalthe analog Pin
drweb8620-Jul-09 2:30
drweb8620-Jul-09 2:30 
GeneralRe: the analog Pin
FantasticFiasco20-Jul-09 2:55
FantasticFiasco20-Jul-09 2:55 
Generalworks BUT complicated!! Pin
SurlyCanuck14-Jul-09 8:36
professionalSurlyCanuck14-Jul-09 8:36 
GeneralRe: works BUT complicated!! Pin
FantasticFiasco14-Jul-09 9:06
FantasticFiasco14-Jul-09 9:06 
GeneralRe: works BUT complicated!! Pin
SurlyCanuck14-Jul-09 10:46
professionalSurlyCanuck14-Jul-09 10:46 
GeneralFails to download Pin
Johnno9-Jul-09 8:54
Johnno9-Jul-09 8:54 
GeneralRe: Fails to download Pin
FantasticFiasco9-Jul-09 9:53
FantasticFiasco9-Jul-09 9:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.