Contents
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.
- 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.
- Type or press the Browse... button and select the source path.
- Press Start zipping my code.
- 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 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.
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.
The ViewModels are implemented with a new way of writing properties. We usually write a ViewModel property like this:
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:
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
:
protected T Property<T>(Expression<Func<T>> nameExpression)
{
PropertyItem p;
if (properties.TryGetValue(nameExpression.ToString(), out p))
{
return (T)p.Value;
}
return default(T);
}
protected void Property<T>(Expression<Func<T>> nameExpression, T value)
{
string key = nameExpression.ToString();
PropertyItem p;
if (properties.TryGetValue(key, out p))
{
if ((p.Value == null && value == null) ||
(p.Value != null && p.Value.Equals(value)))
{
return;
}
p.Value = value;
}
else
{
p = new PropertyItem
{
Name = GetPropertyName(nameExpression),
Value = value
};
properties.Add(key, p);
}
OnPropertyChanged(new PropertyChangedEventArgs(p.Name));
}
private static string GetPropertyName<T>(Expression<Func<T>> nameExpression)
{
UnaryExpression unaryExpression = nameExpression.Body as UnaryExpression;
MemberExpression memberExpression = unaryExpression != null ?
(MemberExpression)unaryExpression.Operand :
(MemberExpression)nameExpression.Body;
return memberExpression.Member.Name;
}
class PropertyItem
{
public string Name { get; set; }
public object Value { get; set; }
}
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:
private IEnumerable<string> RecursiveGetFiles(DirectoryInfo directory,
string[] excludePatterns)
{
IEnumerable<FileInfo> files =
(from file in directory.GetFiles()
select file)
.Except(GetExcludedFiles(directory, excludePatterns), new FileInfoEqualityComparer());
foreach (FileInfo file in files)
{
yield return file.FullName;
}
IEnumerable<DirectoryInfo> subDirectories =
(from subDirectory in directory.GetDirectories()
select subDirectory)
.Except(GetExcludedDirectories(directory, excludePatterns),
new DirectoryInfoEqualityComparer());
foreach (DirectoryInfo subDirectory in subDirectories)
{
foreach (string file in RecursiveGetFiles(subDirectory, excludePatterns))
{
yield return file;
}
}
}
private IEnumerable<FileInfo> GetExcludedFiles(DirectoryInfo directory,
string[] excludePatterns)
{
return
from excludePattern in excludePatterns
from filesMatchingExcludePattern in directory.GetFiles(excludePattern)
select filesMatchingExcludePattern;
}
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();
}
}
- 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)