Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move common command line code into CommandLineUtils #201

Merged
merged 1 commit into from
Mar 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Microsoft.Extensions.CommandLineUtils
{
/// <summary>
/// A utility for escaping arguments for new processes.
/// </summary>
internal static class ArgumentEscaper
{
/// <summary>
/// Undo the processing which took place to create string[] args in Main, so that the next process will
/// receive the same string[] args.
/// </summary>
/// <remarks>
/// See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
/// </remarks>
/// <param name="args"></param>
/// <returns></returns>
public static string EscapeAndConcatenate(IEnumerable<string> args)
=> string.Join(" ", args.Select(EscapeSingleArg));

private static string EscapeSingleArg(string arg)
{
var sb = new StringBuilder();

var needsQuotes = ShouldSurroundWithQuotes(arg);
var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg);

if (needsQuotes)
{
sb.Append('"');
}

for (int i = 0; i < arg.Length; ++i)
{
var backslashes = 0;

// Consume all backslashes
while (i < arg.Length && arg[i] == '\\')
{
backslashes++;
i++;
}

if (i == arg.Length && isQuoted)
{
// Escape any backslashes at the end of the arg when the argument is also quoted.
// This ensures the outside quote is interpreted as an argument delimiter
sb.Append('\\', 2 * backslashes);
}
else if (i == arg.Length)
{
// At then end of the arg, which isn't quoted,
// just add the backslashes, no need to escape
sb.Append('\\', backslashes);
}
else if (arg[i] == '"')
{
// Escape any preceding backslashes and the quote
sb.Append('\\', (2 * backslashes) + 1);
sb.Append('"');
}
else
{
// Output any consumed backslashes and the character
sb.Append('\\', backslashes);
sb.Append(arg[i]);
}
}

if (needsQuotes)
{
sb.Append('"');
}

return sb.ToString();
}

private static bool ShouldSurroundWithQuotes(string argument)
{
// Don't quote already quoted strings
if (IsSurroundedWithQuotes(argument))
{
return false;
}

// Only quote if whitespace exists in the string
return ContainsWhitespace(argument);
}

private static bool IsSurroundedWithQuotes(string argument)
{
if (argument.Length <= 1)
{
return false;
}

return argument[0] == '"' && argument[argument.Length - 1] == '"';
}

private static bool ContainsWhitespace(string argument)
=> argument.IndexOfAny(new [] { ' ', '\t', '\n' }) >= 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

// System.AppContext.GetData is not available in these frameworks
#if !NET451 && !NET452 && !NET46 && !NET461

using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Microsoft.Extensions.CommandLineUtils
{
/// <summary>
/// Utilities for finding the "dotnet.exe" file from the currently running .NET Core application
/// </summary>
internal static class DotNetMuxer
{
private const string MuxerName = "dotnet";

static DotNetMuxer()
{
MuxerPath = TryFindMuxerPath();
}

/// <summary>
/// The full filepath to the .NET Core muxer.
/// </summary>
public static string MuxerPath { get; }

/// <summary>
/// Finds the full filepath to the .NET Core muxer,
/// or returns a string containing the default name of the .NET Core muxer ('dotnet').
/// </summary>
/// <returns>The path or a string named 'dotnet'</returns>
public static string MuxerPathOrDefault()
=> MuxerPath ?? MuxerName;

private static string TryFindMuxerPath()
{
var fileName = MuxerName;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
fileName += ".exe";
}

var fxDepsFile = AppContext.GetData("FX_DEPS_FILE") as string;

if (string.IsNullOrEmpty(fxDepsFile))
{
return null;
}

var muxerDir = new FileInfo(fxDepsFile) // Microsoft.NETCore.App.deps.json
.Directory? // (version)
.Parent? // Microsoft.NETCore.App
.Parent? // shared
.Parent; // DOTNET_HOME

if (muxerDir == null)
{
return null;
}

var muxer = Path.Combine(muxerDir.FullName, fileName);
return File.Exists(muxer)
? muxer
: null;
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Xunit;

namespace Microsoft.Extensions.CommandLineUtils
{
public class ArgumentEscaperTests
{
[Theory]
[InlineData(new[] { "one", "two", "three" }, "one two three")]
[InlineData(new[] { "line1\nline2", "word1\tword2" }, "\"line1\nline2\" \"word1\tword2\"")]
[InlineData(new[] { "with spaces" }, "\"with spaces\"")]
[InlineData(new[] { @"with\backslash" }, @"with\backslash")]
[InlineData(new[] { @"""quotedwith\backslash""" }, @"\""quotedwith\backslash\""")]
[InlineData(new[] { @"C:\Users\" }, @"C:\Users\")]
[InlineData(new[] { @"C:\Program Files\dotnet\" }, @"""C:\Program Files\dotnet\\""")]
[InlineData(new[] { @"backslash\""preceedingquote" }, @"backslash\\\""preceedingquote")]
public void EscapesArguments(string[] args, string expected)
=> Assert.Equal(expected, ArgumentEscaper.EscapeAndConcatenate(args));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#if !NET452
using System.IO;
using Xunit;

namespace Microsoft.Extensions.CommandLineUtils
{
public class DotNetMuxerTests
{
[Fact]
public void FindsTheMuxer()
{
var muxerPath = DotNetMuxer.MuxerPath;
Assert.NotNull(muxerPath);
Assert.True(File.Exists(muxerPath), "The file did not exist");
}
}
}
#endif