Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
madelson committed Jul 4, 2014
1 parent 3f88849 commit 9e19746
Show file tree
Hide file tree
Showing 14 changed files with 886 additions and 0 deletions.
52 changes: 52 additions & 0 deletions .gitignore
@@ -0,0 +1,52 @@
#OS junk files
Thumbs.db
*.DS_Store

#Visual Studio files
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*.vssscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log*
*.lib
*.sbr
*.sdf
ipch/
obj/
[Bb]in
[Dd]ebug*/
[Rr]elease*/

#upgrade from 2010
Backup/*
_UpgradeReport_Files/*
UpgradeLog.*

#Tooling
_ReSharper*/
[Tt]est[Rr]esult*
*.dotCover

#StyleCop
StyleCop.Cache

#Project files
[Bb]uild/

#Nuget Files
*.nupkg
#MA: using nuget package restore!
packages/
20 changes: 20 additions & 0 deletions License.txt
@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2014 Michael Adelson

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 changes: 22 additions & 0 deletions MedallionShell.sln
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Express 2013 for Windows Desktop
VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MedallionShell", "MedallionShell\MedallionShell.csproj", "{15AF2EC0-F7B2-4206-B92A-DD1F3DC25F30}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{15AF2EC0-F7B2-4206-B92A-DD1F3DC25F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15AF2EC0-F7B2-4206-B92A-DD1F3DC25F30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15AF2EC0-F7B2-4206-B92A-DD1F3DC25F30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15AF2EC0-F7B2-4206-B92A-DD1F3DC25F30}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
56 changes: 56 additions & 0 deletions MedallionShell/Async/AsyncLock.cs
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Medallion.Shell.Async
{
// http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx
internal sealed class AsyncLock
{
private readonly AsyncSemaphore semaphore = new AsyncSemaphore(initialCount: 1, maxCount: 1);
private readonly Task<Releaser> cachedReleaserTask;

public AsyncLock()
{
this.cachedReleaserTask = Task.FromResult(new Releaser(this));
}

public Task<Releaser> AcquireAsync()
{
var wait = this.semaphore.WaitAsync();
return wait.IsCompleted
? this.cachedReleaserTask
: wait.ContinueWith(
CreateReleaserFunc,
this,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default
);
}

private static Releaser CreateReleaser(Task ignored, object asyncLock)
{
return new Releaser((AsyncLock)asyncLock);
}
private static Func<Task, object, Releaser> CreateReleaserFunc = CreateReleaser;

public struct Releaser : IDisposable
{
private readonly AsyncLock @lock;

internal Releaser(AsyncLock @lock)
{
this.@lock = @lock;
}

public void Dispose()
{
this.@lock.semaphore.Release();
}
}
}
}
72 changes: 72 additions & 0 deletions MedallionShell/Async/AsyncSemaphore.cs
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Medallion.Shell.Async
{
// based on http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266983.aspx
internal sealed class AsyncSemaphore
{
private static readonly Task CachedCompletedTask = Task.FromResult(true);
private readonly Queue<TaskCompletionSource<bool>> waiters = new Queue<TaskCompletionSource<bool>>();
private readonly int? maxCount;
private int count;

public AsyncSemaphore(int initialCount, int? maxCount = null)
{
Throw.IfOutOfRange(initialCount, "initialCount", min: 0);
if (maxCount.HasValue)
{
Throw.IfOutOfRange(maxCount.Value, "maxCount", min: 0);
Throw.IfOutOfRange(initialCount, "initialCount", max: maxCount);
this.maxCount = maxCount;
}
this.count = initialCount;
}

public Task WaitAsync()
{
lock (this.waiters)
{
if (this.count > 0)
{
--this.count;
return CachedCompletedTask;
}
else
{
var waiter = new TaskCompletionSource<bool>();
this.waiters.Enqueue(waiter);
return waiter.Task;
}
}
}

public void Release()
{
TaskCompletionSource<bool> toRelease = null;
lock (this.waiters)
{
if (this.waiters.Count > 0)
{
toRelease = this.waiters.Dequeue();
}
else if (this.maxCount.HasValue && this.count == this.maxCount)
{
throw new InvalidOperationException("Max count value exceeded on the semaphore");
}
else
{
++this.count;
}
}
if (toRelease != null)
{
toRelease.SetResult(true);
}
}
}
}
110 changes: 110 additions & 0 deletions MedallionShell/Command.cs
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Medallion.Shell
{
public abstract partial class Command
{
// TODO do we want this in the base class?
public abstract Process Process { get; }
public abstract IReadOnlyList<Process> Processes { get; }

public abstract Stream StandardInputStream { get; }
public abstract Stream StandardOutputStream { get; }
public abstract Stream StandardErrorStream { get; }

public abstract Task<CommandResult> Task { get; }

public Command PipeTo(Command command)
{
return this | command;
}

public Command PipeStandardOutputTo(Stream stream)
{
return this > stream;
}

public Command PipeStandardErrorTo(Stream stream)
{
Throw.IfNull(stream, "stream");
// TODO
return this;
}

public Command PipeStandardInputFrom(Stream stream)
{
return this < stream;
}

#region ---- Operator overloads ----
public static Command operator |(Command first, Command second)
{
return new PipedCommand(first, second);
}

public static Command operator >(Command command, Stream stream)
{
Throw.IfNull(command, "command");
Throw.IfNull(stream, "stream");

// TODO

return command;
}

public static Command operator <(Command command, Stream stream)
{
Throw.IfNull(command, "command");
Throw.IfNull(stream, "stream");

// TODO

return command;
}

public static Command operator >(Command command, string path)
{
Throw.IfNull(command, "command");
Throw.IfNull(path, "path");

// used over File.OpenWrite to get read file share, which seems potentially useful and
// not that harmful
var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
return command > stream;
}

public static Command operator <(Command command, string path)
{
Throw.IfNull(command, "command");
Throw.IfNull(path, "path");

return command > File.OpenRead(path);
}

public static bool operator true(Command command)
{
Throw.IfNull(command, "command");

return command.Task.Result.Success;
}

public static bool operator false(Command command)
{
Throw.IfNull(command, "command");

return !command.Task.Result.Success;
}

public static Command operator &(Command @this, Command that)
{
throw new NotSupportedException("Bitwise & is not supported. It exists only to enable '&&'")
}
#endregion
}
}
24 changes: 24 additions & 0 deletions MedallionShell/CommandResult.cs
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Medallion.Shell
{
public sealed class CommandResult
{
internal CommandResult(int exitCode)
{
this.exitCode = exitCode;
}

private readonly int exitCode;
public int ExitCode { get; }

/// <summary>
/// Returns true iff the exit code is 0 (indicating success)
/// </summary>
public bool Success { get { return this.ExitCode == 0; } }
}
}

0 comments on commit 9e19746

Please sign in to comment.