<commit_before>﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wyam.Common.Execution;

namespace Wyam.Common.IO
{
    // Initially based on code from Cake (http://cakebuild.net/)
    /// <summary>
    /// Provides properties and instance methods for working with paths.
    /// </summary>
    public abstract class NormalizedPath : IComparable<NormalizedPath>, IComparable, IEquatable<NormalizedPath>
    {
        /// <summary>
        /// The default file provider.
        /// </summary>
        public static readonly Uri DefaultProvider = new Uri("file:///", UriKind.Absolute);

        /// <summary>
        /// Gets the full path.
        /// </summary>
        /// <value>The full path.</value>
        public string FullPath { get; }

        /// <summary>
        /// Gets a value indicating whether this path is relative.
        /// </summary>
        /// <value>
        /// <c>true</c> if this path is relative; otherwise, <c>false</c>.
        /// </value>
        public bool IsRelative => !IsAbsolute;

        /// <summary>
        /// Gets or sets a value indicating whether this path is absolute.
        /// </summary>
        /// <value>
        /// <c>true</c> if this path is absolute; otherwise, <c>false</c>.
        /// </value>
        public bool IsAbsolute { get; }

        /// <summary>
        /// Gets the segments making up the path.
        /// </summary>
        /// <value>The segments making up the path.</value>
        public string[] Segments { get; }

        /// <summary>
        /// Gets the provider for this path. If this is a relative path,
        /// the provider will always be <c>null</c>. If this is an absolute
        /// path and the provider is <c>null</c> it indicates the path
        /// is not intended for use with an actual file provider.
        /// </summary>
        /// <value>
        /// The provider for this path.
        /// </value>
        public Uri Provider { get; }

        /// <summary>
        /// Gets the root of this path or "." if this is a relative path
        /// or there is no root.
        /// </summary>
        /// <value>
        /// The root of this path.
        /// </value>
        public DirectoryPath Root
        {
            get
            {
                string directory = IsAbsolute ? System.IO.Path.GetPathRoot(FullPath) : ".";
                if (string.IsNullOrWhiteSpace(directory))
                {
                    directory = ".";
                }
                return new DirectoryPath(Provider, directory);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NormalizedPath" /> class.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="absolute">Indicates if the path should be explicitly considered absolute.</param>
        protected NormalizedPath(string path, bool? absolute)
            : this(GetProviderAndPath(path), absolute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NormalizedPath" /> class
        /// with the given provider.
        /// </summary>
        /// <param name="provider">The provider for this path.</param>
        /// <param name="path">The path.</param>
        /// <param name="absolute">Indicates if the path should be explicitly considered absolute.</param>
        protected NormalizedPath(Uri provider, string path, bool? absolute)
            : this(Tuple.Create(provider, path), absolute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NormalizedPath" /> class. The new path
        /// will be absolute if the specified URI is absolute, otherwise it will be relative.
        /// </summary>
        /// <param name="path">The path as a URI.</param>
        protected NormalizedPath(Uri path)
            : this(GetProviderAndPath(path.ToString()), path.IsAbsoluteUri)
        {
        }

        /// <summary>
        /// Gets the provider and path from a path string. Implemented as a static
        /// so it can be used in a constructor chain.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns>The provider (item 1) and path (item 2).</returns>
        internal static Tuple<Uri, string> GetProviderAndPath(string path)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            Uri provider = DefaultProvider;
            
            // Did we get a delimiter?
            int delimiterIndex = path.IndexOf("|", StringComparison.Ordinal);
            if (delimiterIndex != -1)
            {
                // Path contains a provider delimiter, try to parse the provider
                string pathProvider = path.Substring(0, delimiterIndex);
                path = path.Length == delimiterIndex + 1 ? string.Empty : path.Substring(delimiterIndex + 1);
                if (!Uri.TryCreate(pathProvider, UriKind.Absolute, out provider))
                {
                    // Couldn't create the provider as a URI, try it as just a scheme
                    if (Uri.CheckSchemeName(pathProvider))
                    {
                        provider = new Uri($"{pathProvider}:///", UriKind.Absolute);
                    }
                    else
                    {
                        throw new ArgumentException("The provider URI is not valid");
                    }
                }
            }
            else if (Uri.TryCreate(path, UriKind.Absolute, out provider))
            {
                // No delimiter, but the path itself is a URI
                provider = new Uri(provider.GetLeftPart(UriPartial.Authority));
                path = provider.PathAndQuery + provider.Fragment;
            }

            return Tuple.Create(provider, path);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NormalizedPath" /> class.
        /// </summary>
        /// <param name="providerAndPath">The provider and path as a Tuple so it can
        /// be passed from both of the other constructors.</param>
        /// <param name="absolute">Indicates if the path should be explicitly considered absolute.</param>
        private NormalizedPath(Tuple<Uri, string> providerAndPath, bool? absolute)
        {
            Uri provider = providerAndPath.Item1;
            string path = providerAndPath.Item2;

            if (path == null)
            {<commit_msg>[WIP] Some more checks for NormalizedPath URIs to throw if query or fragment is used, #277<commit_after>﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wyam.Common.Execution;

namespace Wyam.Common.IO
{
    // Initially based on code from Cake (http://cakebuild.net/)
    /// <summary>
    /// Provides properties and instance methods for working with paths.
    /// </summary>
    public abstract class NormalizedPath : IComparable<NormalizedPath>, IComparable, IEquatable<NormalizedPath>
    {
        /// <summary>
        /// The default file provider.
        /// </summary>
        public static readonly Uri DefaultProvider = new Uri("file:///", UriKind.Absolute);

        /// <summary>
        /// Gets the full path.
        /// </summary>
        /// <value>The full path.</value>
        public string FullPath { get; }

        /// <summary>
        /// Gets a value indicating whether this path is relative.
        /// </summary>
        /// <value>
        /// <c>true</c> if this path is relative; otherwise, <c>false</c>.
        /// </value>
        public bool IsRelative => !IsAbsolute;

        /// <summary>
        /// Gets or sets a value indicating whether this path is absolute.
        /// </summary>
        /// <value>
        /// <c>true</c> if this path is absolute; otherwise, <c>false</c>.
        /// </value>
        public bool IsAbsolute { get; }

        /// <summary>
        /// Gets the segments making up the path.
        /// </summary>
        /// <value>The segments making up the path.</value>
        public string[] Segments { get; }

        /// <summary>
        /// Gets the provider for this path. If this is a relative path,
        /// the provider will always be <c>null</c>. If this is an absolute
        /// path and the provider is <c>null</c> it indicates the path
        /// is not intended for use with an actual file provider.
        /// </summary>
        /// <value>
        /// The provider for this path.
        /// </value>
        public Uri Provider { get; }

        /// <summary>
        /// Gets the root of this path or "." if this is a relative path
        /// or there is no root.
        /// </summary>
        /// <value>
        /// The root of this path.
        /// </value>
        public DirectoryPath Root
        {
            get
            {
                string directory = IsAbsolute ? System.IO.Path.GetPathRoot(FullPath) : ".";
                if (string.IsNullOrWhiteSpace(directory))
                {
                    directory = ".";
                }
                return new DirectoryPath(Provider, directory);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NormalizedPath" /> class.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="absolute">Indicates if the path should be explicitly considered absolute.</param>
        protected NormalizedPath(string path, bool? absolute)
            : this(GetProviderAndPath(path), absolute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NormalizedPath" /> class
        /// with the given provider.
        /// </summary>
        /// <param name="provider">The provider for this path.</param>
        /// <param name="path">The path.</param>
        /// <param name="absolute">Indicates if the path should be explicitly considered absolute.</param>
        protected NormalizedPath(Uri provider, string path, bool? absolute)
            : this(Tuple.Create(provider, path), absolute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NormalizedPath" /> class. The new path
        /// will be absolute if the specified URI is absolute, otherwise it will be relative.
        /// </summary>
        /// <param name="path">The path as a URI.</param>
        protected NormalizedPath(Uri path)
            : this(GetProviderAndPath(path.ToString()), path.IsAbsoluteUri)
        {
        }

        /// <summary>
        /// Gets the provider and path from a path string. Implemented as a static
        /// so it can be used in a constructor chain.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns>The provider (item 1) and path (item 2).</returns>
        internal static Tuple<Uri, string> GetProviderAndPath(string path)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }
            
            // Did we get a delimiter?
            Uri provider;
            int delimiterIndex = path.IndexOf("|", StringComparison.Ordinal);
            if (delimiterIndex != -1)
            {
                // Path contains a provider delimiter, try to parse the provider
                string pathProvider = path.Substring(0, delimiterIndex);
                path = path.Length == delimiterIndex + 1 ? string.Empty : path.Substring(delimiterIndex + 1);
                if (!Uri.TryCreate(pathProvider, UriKind.Absolute, out provider))
                {
                    // Couldn't create the provider as a URI, try it as just a scheme
                    if (Uri.CheckSchemeName(pathProvider))
                    {
                        provider = new Uri($"{pathProvider}:///", UriKind.Absolute);
                    }
                    else
                    {
                        throw new ArgumentException("The provider URI is not valid");
                    }
                }
            }
            else if (Uri.TryCreate(path, UriKind.Absolute, out provider))
            {
                // No delimiter, but the path itself is a URI
                if (!string.IsNullOrEmpty(provider.Query))
                {
                    throw new ArgumentException("Query components are not allowed for use in the path");
                }
                if (!string.IsNullOrEmpty(provider.Fragment))
                {
                    throw new ArgumentException("Fragment components are not allowed for use in the path");
                }
                path = provider.AbsolutePath;
                provider = new Uri(provider.GetLeftPart(UriPartial.Authority));
            }
            else
            {
                provider = DefaultProvider;
            }

            return Tuple.Create(provider, path);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NormalizedPath" /> class.
        /// </summary>
        /// <param name="providerAndPath">The provider and path as a Tuple so it can
        /// be passed from both of the other constructors.</param>
        /// <param name="absolute">Indicates if the path should be explicitly considered absolute.</param>
        private NormalizedPath(Tuple<Uri, string> providerAndPath, bool? absolute)
        {
            Uri provider = providerAndPath.Item1;
            string path = providerAndPath.Item2;

            if (path == null)
            {
{'fraction_non_alphanumeric': 0.1160902037560728, 'fraction_numerical': 0.0013777101007903705, 'mean_word_length': 1.5876172607879926, 'pattern_counts': {'":': 0, '<': 159, '<?xml version=': 0, '>': 161, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7540982', 'n_tokens_mistral': 3455, 'n_tokens_neox': 3326, 'n_words': 1136}