<commit_before>import {
  pathTempalteToParameterNames
} from './_/path-template-to-parameter-names';
import {
  ParametersFn,
  ParameterPatterns,
  PathTemplate
} from './types';

type PathPattern = RegExp;

interface Pattern {
  pathPattern: PathPattern;
  parameters: ParameterPattern[];
}

interface ParameterPattern {
  name: string;
  pattern: RegExp | null;
}

const params = (
  template: PathTemplate,
  patterns?: ParameterPatterns
): ParametersFn => {
  const notFound = null;
  const { pathPattern, parameters } = inputToPattern(template, patterns);
  return (pathname) => {
    const m = pathname.match(pathPattern);
    if (m === null) return notFound;
    const parameterValues = m.slice(1);
    const npvs = parameterValues
      .map((value) => decodeURIComponent(value))
      .map((value, i) => {
        const { name, pattern } = parameters[i];
        return { name, pattern, value }; // npv
      });
    const hasUnmatchParameter = npvs.some(({ pattern, value }) => {
      return pattern !== null && value.match(pattern) === null;
    });
    if (hasUnmatchParameter) return notFound;
    const hasInvalidDuplicatedParameter = npvs.some(({ name, value }) => {
      return npvs.some(({ name: n, value: v }) => n === name && v !== value);
    });
    if (hasInvalidDuplicatedParameter) return notFound;
    return npvs
      .map(({ name, value }) => ({ [name]: value }))
      .reduce((a, x) => Object.assign(a, x), {});
  };
};

// '/users/{id}', { 'id': /^\d+$/ }
// -> { pathPattern: /^\/users\/([^\/]*)$/
//    , parameters: [{ name: 'id', pattern: /^\d+$/ }]
//    }
const inputToPattern = (
  template: PathTemplate,
  patterns: ParameterPatterns | undefined
): Pattern => {
  const userParameterPatterns = ensureUserParameterPatterns(patterns);
  const pathPattern = pathTemplateToPathPattern(template);
  const parameterNames = pathTempalteToParameterNames(template);
  const parameters = parameterNames.map((name) => {
    const userPattern = userParameterPatterns.find(({ name: n }) => n === name);
    const pattern = typeof userPattern === 'undefined'
      ? null
      : userPattern.pattern;
    return { name, pattern };
  });
  return { pathPattern, parameters };
};

const ensureUserParameterPatterns = (
  patterns: ParameterPatterns | undefined
): ParameterPattern[] => {
  if (typeof patterns === 'undefined') return [];
  return Object
    .keys(patterns)
    .map((name) => {
      const patternOrUndefined = patterns[name];
      const pattern = typeof patternOrUndefined === 'undefined'
        ? null
        : patternOrUndefined;
      return { name, pattern };
    });
};

// '/users/{id}' -> /^\/users\/[^\/]*$/
const pathTemplateToPathPattern = (template: PathTemplate): PathPattern => {
  return new RegExp(
    '^' + template.replace(/\{[A-Za-z0-9_]+\}/g, '([^\\/]*)') + '$'
  );
};

export { params };<commit_msg>refactor params<commit_after>import {
  pathTempalteToParameterNames
} from './_/path-template-to-parameter-names';
import {
  ParameterPatterns,
  Parameters,
  ParametersFn,
  Path,
  PathTemplate
} from './types';

type PathPattern = RegExp;
type ParameterPattern = NP[];
type NP = { name: string; } & { pattern: RegExp | null; };
type V = { value: string; };
type NPV = NP & V;

const params = (
  template: PathTemplate,
  patterns?: ParameterPatterns
): ParametersFn => {
  const notFound = null;
  const pathPattern = toPathPattern(template);
  const parameterPattern = toParameterPattern(template, patterns);
  return (pathname) => {
    const vs = matchPathPattern(pathname, pathPattern);
    if (vs === null) return notFound;
    const npvs = matchParameterPattern(vs, parameterPattern);
    if (npvs === null) return notFound;
    return toParameters(npvs);
  };
};

const toParameters = (npvs: NPV[]): Parameters => {
  return npvs
    .map(({ name, value }) => ({ [name]: value }))
    .reduce((a, x) => Object.assign(a, x), {});
};

const matchParameterPattern = (
  parameterValues: V[],
  parameterPattern: ParameterPattern
): NPV[] | null => {
  const npvs = parameterValues
    .map(({ value }) => decodeURIComponent(value))
    .map((value, i) => {
      const { name, pattern } = parameterPattern[i];
      return { name, pattern, value };
    });
  const hasUnmatchParameter = npvs.some(({ pattern, value }) => {
    return pattern !== null && value.match(pattern) === null;
  });
  if (hasUnmatchParameter) return null;
  const hasInvalidDuplicatedParameter = npvs.some(({ name, value }) => {
    return npvs.some(({ name: n, value: v }) => n === name && v !== value);
  });
  if (hasInvalidDuplicatedParameter) return null;
  return npvs;
};

const matchPathPattern = (
  pathname: Path,
  pathPattern: PathPattern
): V[] | null => {
  const m = pathname.match(pathPattern);
  if (m === null) return null;
  const vs = m.slice(1).map((value) => ({ value }));
  return vs;
};

// '/users/{id}', { 'id': /^\d+$/ }
// -> parameters: [{ name: 'id', pattern: /^\d+$/ }]
const toParameterPattern = (
  template: PathTemplate,
  patterns: ParameterPatterns | undefined
): ParameterPattern => {
  const userParameterPatterns = typeof patterns === 'undefined'
    ? []
    : Object
      .keys(patterns)
      .map((name) => {
        const patternOrUndefined = patterns[name];
        const pattern = typeof patternOrUndefined === 'undefined'
          ? null
          : patternOrUndefined;
        return { name, pattern };
      });
  const parameterNames = pathTempalteToParameterNames(template);
  const parameters = parameterNames.map((name) => {
    const userPattern = userParameterPatterns.find(({ name: n }) => n === name);
    const pattern = typeof userPattern === 'undefined'
      ? null
      : userPattern.pattern;
    return { name, pattern };
  });
  return parameters;
};

// '/users/{id}' -> /^\/users\/[^\/]*$/
const toPathPattern = (template: PathTemplate): PathPattern => {
  return new RegExp(
    '^' + template.replace(/\{[A-Za-z0-9_]+\}/g, '([^\\/]*)') + '$'
  );
};

export { params };
{'fraction_non_alphanumeric': 0.16888962987662554, 'fraction_numerical': 0.0010003334444814939, 'mean_word_length': 3.4142752023546725, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 40, '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': '6762549', 'n_tokens_mistral': 1833, 'n_tokens_neox': 1828, 'n_words': 450}