// Utilities
import { consoleWarn } from './console'
import { chunk, padEnd } from './helpers'
import { toXYZ } from './color/transformSRGB'

// Types
import { VuetifyThemeVariant } from 'types/services/theme'

export type ColorInt = number
export type XYZ = [number, number, number]
export type LAB = [number, number, number]
export type HSV = { h: number, s: number, v: number }
export type HSVA = HSV & { a: number }
export type RGB = { r: number, g: number, b: number }
export type RGBA = RGB & { a: number }
export type HSL = { h: number, s: number, l: number }
export type HSLA = HSL & { a: number }
export type Hex = string
export type Hexa = string
export type Color = string | number | {}

export function isCssColor (color?: string | false): boolean {
  return !!color && !!color.match(/^(#|var\(--|(rgb|hsl)a?\()/)
}

export function colorToInt (color: Color): ColorInt {
  let rgb

  if (typeof color === 'number') {
    rgb = color
  } else if (typeof color === 'string') {
    let c = color[0] === '#' ? color.substring(1) : color
    if (c.length === 3) {
      c = c.split('').map(char => char + char).join('')
    }
    if (c.length !== 6) {
      consoleWarn(`'${color}' is not a valid rgb color`)
    }
    rgb = parseInt(c, 16)
  } else {
    throw new TypeError(`Colors can only be numbers or strings, recieved ${color == null ? color : color.constructor.name} instead`)
  }

  if (rgb < 0) {
    consoleWarn(`Colors cannot be negative: '${color}'`)
    rgb = 0
  } else if (rgb > 0xffffff || isNaN(rgb)) {
    consoleWarn(`'${color}' is not a valid rgb color`)
    rgb = 0xffffff
  }

  return rgb
}

export function classToHex (
  color: string,
  colors: Record<string, Record<string, string>>,
  currentTheme: Partial<VuetifyThemeVariant>,
): string {
  const [colorName, colorModifier] = color
    .toString().trim().replace('-', '').split(' ', 2) as (string | undefined)[]

  let hexColor = ''
  if (colorName && colorName in colors) {
    if (colorModifier && colorModifier in colors[colorName]) {
      hexColor = colors[colorName][colorModifier]
    } else if ('base' in colors[colorName]) {
      hexColor = colors[colorName].base
    }
  } else if (colorName && colorName in currentTheme) {
    hexColor = currentTheme[colorName] as string
  }

  return hexColor
}

export function intToHex (color: ColorInt): string {
  let hexColor: string = color.toString(16)

  if (hexColor.length < 6) hexColor = '0'.repeat(6 - hexColor.length) + hexColor

  return '#' + hexColor
}

export function colorToHex (color: Color): string {
  return intToHex(colorToInt(color))
}

/**
 * Converts HSVA to RGBA. Based on formula from https://en.wikipedia.org/wiki/HSL_and_HSV
 *
 * @param color HSVA color as an array [0-360, 0-1, 0-1, 0-1]
 */
export function HSVAtoRGBA (hsva: HSVA): RGBA {
  const { h, s, v, a } = hsva
  const f = (n: number) => {
    const k = (n + (h / 60)) % 6
    return v - v * s * Math.max(Math.min(k, 4 - k, 1), 0)
  }

  const rgb = [f(5), f(3), f(1)].map(v => Math.round(v * 255))

  return { r: rgb[0], g: rgb[1], b: rgb[2], a }
}

/**
 * Converts RGBA to HSVA. Based on formula from https://en.wikipedia.org/wiki/HSL_and_HSV
 *
 * @param color RGBA color as an array [0-255, 0-255, 0-255, 0-1]
 */
export function RGBAtoHSVA (rgba: RGBA): HSVA {
  if (!rgba) return { h: 0, s: 1, v: 1, a: 1 }

  const r = rgba.r / 255
  const g = rgba.g / 255
  const b = rgba.b / 255
  const max = Math.max(r, g, b)
  const min = Math.min(r, g, b)

  let h = 0

  if (max !== min) {
    if (max === r) {
      h = 60 * (0 + ((g - b) / (max - min)))
    } else if (max === g) {
      h = 60 * (2 + ((b - r) / (max - min)))
    } else if (max === b) {
      h = 60 * (4 + ((r - g) / (max - min)))
    }
  }

  if (h < 0) h = h + 360

  const s = max === 0 ? 0 : (max - min) / max
  const hsv = [h, s, max]

  return { h: hsv[0], s: hsv[1], v: hsv[2], a: rgba.a }
}

export function HSVAtoHSLA (hsva: HSVA): HSLA {
  const { h, s, v, a } = hsva

  const l = v - (v * s / 2)

  const sprime = l === 1 || l === 0 ? 0 : (v - l) / Math.min(l, 1 - l)

  return { h, s: sprime, l, a }
}

export function HSLAtoHSVA (hsl: HSLA): HSVA {
  const { h, s, l, a } = hsl

  const v = l + s * Math.min(l, 1 - l)

  const sprime = v === 0 ? 0 : 2 - (2 * l / v)

  return { h, s: sprime, v, a }
}

export function RGBAtoCSS (rgba: RGBA): string {
  return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
}

export function RGBtoCSS (rgba: RGBA): string {
  return RGBAtoCSS({ ...rgba, a: 1 })
}

export function RGBAtoHex (rgba: RGBA): Hex {
  const toHex = (v: number) => {
    const h = Math.round(v).toString(16)
    return ('00'.substr(0, 2 - h.length) + h).toUpperCase()
  }

  return `#${[
    toHex(rgba.r),
    toHex(rgba.g),
    toHex(rgba.b),
    toHex(Math.round(rgba.a * 255)),
  ].join('')}`
}

export function HexToRGBA (hex: Hex): RGBA {
  const rgba = chunk(hex.slice(1), 2).map((c: string) => parseInt(c, 16))

  return {
    r: rgba[0],
    g: rgba[1],
    b: rgba[2],
    a: Math.round((rgba[3] / 255) * 100) / 100,
  }
}

export function HexToHSVA (hex: Hex): HSVA {
  const rgb = HexToRGBA(hex)
  return RGBAtoHSVA(rgb)
}

export function HSVAtoHex (hsva: HSVA): Hex {
  return RGBAtoHex(HSVAtoRGBA(hsva))
}

export function parseHex (hex: string): Hex {
  if (hex.startsWith('#')) {
    hex = hex.slice(1)
  }

  hex = hex.replace(/([^0-9a-f])/gi, 'F')

  if (hex.length === 3 || hex.length === 4) {
    hex = hex.split('').map(x => x + x).join('')
  }

  if (hex.length === 6) {
    hex = padEnd(hex, 8, 'F')
  } else {
    hex = padEnd(padEnd(hex, 6), 8, 'F')
  }

  return `#${hex}`.toUpperCase().substr(0, 9)
}

export function parseGradient (
  gradient: string,
  colors: Record<string, Record<string, string>>,
  currentTheme: Partial<VuetifyThemeVariant>,
) {
  return gradient.replace(/([a-z]+(\s[a-z]+-[1-5])?)(?=$|,)/gi, x => {
    return classToHex(x, colors, currentTheme) || x
  }).replace(/(rgba\()#[0-9a-f]+(?=,)/gi, x => {
    return 'rgba(' + Object.values(HexToRGBA(parseHex(x.replace(/rgba\(/, '')))).slice(0, 3).join(',')
  })
}

export function RGBtoInt (rgba: RGBA): ColorInt {
  return (rgba.r << 16) + (rgba.g << 8) + rgba.b
}

/**
 * Returns the contrast ratio (1-21) between two colors.
 *
 * @param c1 First color
 * @param c2 Second color
 */
export function contrastRatio (c1: RGBA, c2: RGBA): number {
  const [, y1] = toXYZ(RGBtoInt(c1))
  const [, y2] = toXYZ(RGBtoInt(c2))

  return (Math.max(y1, y2) + 0.05) / (Math.min(y1, y2) + 0.05)
}


return vm.$scopedSlots[name]!(data instanceof Function ? data() : data)

} else if (vm.$slots[name] && (!data || optional)) {

} else if (vm.$slots.hasOwnProperty(name) && (!data || optional)) {

import Vue, { VNode, PropType } from 'vue'
import { getObjectValueByPath } from '../../util/helpers'
import { DataTableHeader } from 'vuetify/types'

export default Vue.extend({
  name: 'row',

  functional: true,

  props: {
    headers: Array as PropType<DataTableHeader[]>,
    hideDefaultHeader: Boolean,
    index: Number,
    item: Object,
    rtl: Boolean,
  },

  render (h, { props, slots, data }): VNode {
    const computedSlots = slots()

    const columns: VNode[] = props.headers.map((header: DataTableHeader) => {
      const classes = {
        'v-data-table__mobile-row': true,
      }

      const children = []
      const value = getObjectValueByPath(props.item, header.value)

      const slotName = header.value
      const scopedSlot = data.scopedSlots && data.scopedSlots[slotName]
      const regularSlot = computedSlots[slotName]

      if (scopedSlot) {
        children.push(scopedSlot({
          item: props.item,
          isMobile: true,
          header,
          index: props.index,
          value,
        }))
      } else if (regularSlot) {
        children.push(regularSlot)
      } else {
        children.push(value == null ? value : String(value))
      }

      const mobileRowChildren = [
        h('div', {
          staticClass: 'v-data-table__mobile-row__cell',
        }, children),
      ]

      if (header.value !== 'dataTableSelect' && !props.hideDefaultHeader) {
        mobileRowChildren.unshift(
          h('div', {
            staticClass: 'v-data-table__mobile-row__header',
          }, [header.text])
        )
      }

      return h('td', { class: classes }, mobileRowChildren)
    })

    return h('tr', { ...data, staticClass: 'v-data-table__mobile-table-row' }, columns)
  },
})


<template>
  <v-row justify="center">
    <v-date-picker v-model="picker"></v-date-picker>
  </v-row>
</template>

<script>
  export default {
    data () {
      return {
        picker: (new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)).toISOString().substr(0, 10),
      }
    },
  }
</script>


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VSparkline.ts should position labels correctly 1`] = `
<svg display="block"
     stroke-width="20"
     viewbox="0 0 300 85.5"
     class="primary--text"
>
  <defs>
    <linearGradient id="35"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <g style="font-size: 8; text-anchor: middle; dominant-baseline: mathematical; fill: currentColor;">
    <text x="8"
          y="76.25"
          font-size="7"
    >
      1
    </text>
    <text x="150"
          y="76.25"
          font-size="7"
    >
      7
    </text>
    <text x="292"
          y="76.25"
          font-size="7"
    >
      42
    </text>
  </g>
  <path d="M8 67L150 58.36585365853659S150 58.36585365853659 150 58.36585365853659L292 8"
        fill="none"
        stroke="url(#35)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render bar component with all values 0 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="39"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-39-clip">
    <rect x="48"
          y="75"
          width="4"
          height="0"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="148"
          y="75"
          width="4"
          height="0"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="248"
          y="75"
          width="4"
          height="0"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-39-clip)"
     fill="url(#39)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component and match a snapshot 1`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 75"
     class="primary--text"
>
  <defs>
    <linearGradient id="1"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <path d="M8 67L150 58.36585365853659S150 58.36585365853659 150 58.36585365853659L292 8"
        fill="none"
        stroke="url(#1)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render component with bars and auto-line-width and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="21"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-21-clip">
    <rect x="5.333333333333336"
          y="73.21428571428571"
          width="89.33333333333333"
          height="1.7857142857142858"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="105.33333333333334"
          y="62.5"
          width="89.33333333333333"
          height="12.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="205.33333333333334"
          y="0"
          width="89.33333333333333"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-21-clip)"
     fill="url(#21)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and auto-line-width with custom padding and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="27"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-27-clip">
    <rect x="8"
          y="73.21428571428571"
          width="84"
          height="1.7857142857142858"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="108"
          y="62.5"
          width="84"
          height="12.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="208"
          y="0"
          width="84"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-27-clip)"
     fill="url(#27)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and correct bar lengths 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="37"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-37-clip">
    <rect x="73"
          y="37.5"
          width="4"
          height="37.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="223"
          y="0"
          width="4"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-37-clip)"
     fill="url(#37)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and correct bar lengths 2`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="37"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-37-clip">
    <rect x="73"
          y="0"
          width="4"
          height="37.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="223"
          y="0"
          width="4"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-37-clip)"
     fill="url(#37)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and custom label size and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 97.5"
>
  <defs>
    <linearGradient id="29"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-29-clip">
    <rect x="48"
          y="73.21428571428571"
          width="4"
          height="1.7857142857142858"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="148"
          y="62.5"
          width="4"
          height="12.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="248"
          y="0"
          width="4"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g style="font-size: 8; text-anchor: middle; dominant-baseline: mathematical; fill: currentColor;">
    <text x="50"
          y="86.25"
          font-size="15"
    >
      Value 1
    </text>
    <text x="150"
          y="86.25"
          font-size="15"
    >
      Value 2
    </text>
    <text x="250"
          y="86.25"
          font-size="15"
    >
      Value 3
    </text>
  </g>
  <g clip-path="url(#sparkline-bar-29-clip)"
     fill="url(#29)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and custom padding and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="25"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-25-clip">
    <rect x="48"
          y="73.21428571428571"
          width="4"
          height="1.7857142857142858"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="148"
          y="62.5"
          width="4"
          height="12.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="248"
          y="0"
          width="4"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-25-clip)"
     fill="url(#25)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and gradient and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="17"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="rgba(80, 160, 240, 0.5)"
      >
      </stop>
      <stop offset="0.5"
            stop-color="red"
      >
      </stop>
      <stop offset="1"
            stop-color="#000"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-17-clip">
    <rect x="48"
          y="73.21428571428571"
          width="4"
          height="1.7857142857142858"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="148"
          y="62.5"
          width="4"
          height="12.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="248"
          y="0"
          width="4"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-17-clip)"
     fill="url(#17)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and labels and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 85.5"
>
  <defs>
    <linearGradient id="19"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-19-clip">
    <rect x="48"
          y="73.21428571428571"
          width="4"
          height="1.7857142857142858"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="148"
          y="62.5"
          width="4"
          height="12.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="248"
          y="0"
          width="4"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g style="font-size: 8; text-anchor: middle; dominant-baseline: mathematical; fill: currentColor;">
    <text x="50"
          y="80.25"
          font-size="7"
    >
      Value 1
    </text>
    <text x="150"
          y="80.25"
          font-size="7"
    >
      Value 2
    </text>
    <text x="250"
          y="80.25"
          font-size="7"
    >
      Value 3
    </text>
  </g>
  <g clip-path="url(#sparkline-bar-19-clip)"
     fill="url(#19)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and line width and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="23"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-23-clip">
    <rect x="46"
          y="73.21428571428571"
          width="8"
          height="1.7857142857142858"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="146"
          y="62.5"
          width="8"
          height="12.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="246"
          y="0"
          width="8"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-23-clip)"
     fill="url(#23)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="13"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-13-clip">
    <rect x="48"
          y="73.21428571428571"
          width="4"
          height="1.7857142857142858"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="148"
          y="62.5"
          width="4"
          height="12.5"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="248"
          y="0"
          width="4"
          height="75"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-13-clip)"
     fill="url(#13)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with bars and negative and match a snapshot 1`] = `
<svg display="block"
     viewbox="0 0 300 75"
>
  <defs>
    <linearGradient id="15"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <clipPath id="sparkline-bar-15-clip">
    <rect x="35.5"
          y="73.25581395348837"
          width="4"
          height="1.744186046511628"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="110.5"
          y="71.51162790697674"
          width="4"
          height="1.744186046511628"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="185.5"
          y="61.04651162790697"
          width="4"
          height="12.209302325581396"
          rx="0"
          ry="0"
    >
    </rect>
    <rect x="260.5"
          y="0"
          width="4"
          height="73.25581395348837"
          rx="0"
          ry="0"
    >
    </rect>
  </clipPath>
  <g clip-path="url(#sparkline-bar-15-clip)"
     fill="url(#15)"
  >
    <rect x="0"
          y="0"
          width="300"
          height="75"
    >
    </rect>
  </g>
</svg>
`;

exports[`VSparkline.ts should render component with gradient and match a snapshot 1`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 75"
     class="primary--text"
>
  <defs>
    <linearGradient id="9"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="rgba(80, 160, 240, 0.5)"
      >
      </stop>
      <stop offset="0.5"
            stop-color="red"
      >
      </stop>
      <stop offset="1"
            stop-color="#000"
      >
      </stop>
    </linearGradient>
  </defs>
  <path d="M8 67L150 58.36585365853659S150 58.36585365853659 150 58.36585365853659L292 8"
        fill="none"
        stroke="url(#9)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render component with label size and match a snapshot 1`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 96"
     class="primary--text"
>
  <defs>
    <linearGradient id="33"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <g style="font-size: 8; text-anchor: middle; dominant-baseline: mathematical; fill: currentColor;">
    <text x="8"
          y="81.5"
          font-size="14"
    >
      1
    </text>
    <text x="150"
          y="81.5"
          font-size="14"
    >
      7
    </text>
    <text x="292"
          y="81.5"
          font-size="14"
    >
      42
    </text>
  </g>
  <path d="M8 67L150 58.36585365853659S150 58.36585365853659 150 58.36585365853659L292 8"
        fill="none"
        stroke="url(#33)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render component with line width and match a snapshot 1`] = `
<svg display="block"
     stroke-width="42"
     viewbox="0 0 300 75"
     class="primary--text"
>
  <defs>
    <linearGradient id="7"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <path d="M8 67L150 58.36585365853659S150 58.36585365853659 150 58.36585365853659L292 8"
        fill="none"
        stroke="url(#7)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render component with padding and match a snapshot 1`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 75"
     class="primary--text"
>
  <defs>
    <linearGradient id="3"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <path d="M20 55L150 49.8780487804878S150 49.8780487804878 150 49.8780487804878L280 20"
        fill="none"
        stroke="url(#3)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render component with string labels and match a snapshot 1`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 85.5"
     class="primary--text"
>
  <defs>
    <linearGradient id="11"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <g style="font-size: 8; text-anchor: middle; dominant-baseline: mathematical; fill: currentColor;">
    <text x="8"
          y="76.25"
          font-size="7"
    >
      1
    </text>
    <text x="150"
          y="76.25"
          font-size="7"
    >
      7
    </text>
    <text x="292"
          y="76.25"
          font-size="7"
    >
      42
    </text>
  </g>
  <path d="M8 67L150 58.36585365853659S150 58.36585365853659 150 58.36585365853659L292 8"
        fill="none"
        stroke="url(#11)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render component with string labels and match a snapshot 2`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 85.5"
     class="primary--text"
>
  <defs>
    <linearGradient id="11"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <g style="font-size: 8; text-anchor: middle; dominant-baseline: mathematical; fill: currentColor;">
    <text x="8"
          y="76.25"
          font-size="7"
    >
      2
    </text>
    <text x="150"
          y="76.25"
          font-size="7"
    >
      8
    </text>
    <text x="292"
          y="76.25"
          font-size="7"
    >
      43
    </text>
  </g>
  <path d="M8 67L150 58.36585365853659S150 58.36585365853659 150 58.36585365853659L292 8"
        fill="none"
        stroke="url(#11)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render component with string labels and match a snapshot 3`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 85.5"
     class="primary--text"
>
  <defs>
    <linearGradient id="11"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <g style="font-size: 8; text-anchor: middle; dominant-baseline: mathematical; fill: currentColor;">
    <text x="8"
          y="76.25"
          font-size="7"
    >
      foo
    </text>
    <text x="150"
          y="76.25"
          font-size="7"
    >
      bar
    </text>
    <text x="292"
          y="76.25"
          font-size="7"
    >
      baz
    </text>
  </g>
  <path d="M8 67L150 58.36585365853659S150 58.36585365853659 150 58.36585365853659L292 8"
        fill="none"
        stroke="url(#11)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render component with trend and equal values and match a snapshot 1`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 75"
     class="primary--text"
>
  <defs>
    <linearGradient id="31"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <path d="M8 67L150 67L292 67"
        fill="none"
        stroke="url(#31)"
  >
  </path>
</svg>
`;

exports[`VSparkline.ts should render smooth component and match a snapshot 1`] = `
<svg display="block"
     stroke-width="4"
     viewbox="0 0 300 75"
     class="primary--text"
>
  <defs>
    <linearGradient id="5"
                    gradientunits="userSpaceOnUse"
                    x1="0"
                    y1="100%"
                    x2="0"
                    y2="0"
    >
      <stop offset="0"
            stop-color="currentColor"
      >
      </stop>
    </linearGradient>
  </defs>
  <path d="M8 67L130.03686887894946 59.57968883834626S150 58.36585365853659 168.84944344392306 51.680161334300735L292 8"
        fill="none"
        stroke="url(#5)"
  >
  </path>
</svg>
`;


// Service
import { Theme } from '../index'

// Preset
import { preset } from '../../../presets/default'

// Utilities
import { mergeDeep } from '../../../util/helpers'

// Types
import Vue from 'vue'
import {
  VuetifyParsedTheme,
  VuetifyThemeVariant,
  ThemeOptions,
} from 'vuetify/types/services/theme'

const FillVariant = (variant: Partial<VuetifyThemeVariant> = {}) => {
  return {
    primary: '#1976D2',
    secondary: '#424242',
    accent: '#82B1FF',
    error: '#FF5252',
    info: '#2196F3',
    success: '#4CAF50',
    warning: '#FFC107',
    ...variant,
  }
}

describe('Theme.ts', () => {
  function rootFactory () {
    return mergeDeep(JSON.parse(JSON.stringify(preset)), {
      theme: {
        default: 'light',
        themes: {
          dark: FillVariant(),
          light: FillVariant(),
        },
      },
    })
  }

  let mockTheme: (theme?: Partial<ThemeOptions>) => Theme
  let instance: Vue

  beforeEach(() => {
    mockTheme = (themeOptions?: Partial<ThemeOptions>) => {
      const options = { theme: themeOptions || {} }
      const theme = new Theme(mergeDeep(rootFactory(), options))
      instance = new Vue({
        beforeCreate () {
          theme.init(this)
        },
      })

      return theme
    }
  })

  afterEach(() => {
    const style = document.getElementById('vuetify-theme-stylesheet')

    style && style.remove()
  })

  it('should disable theme colors', () => {
    const theme = mockTheme({ disable: true })

    expect(theme.styleEl).toBeFalsy()
  })

  it('should generate theme and apply to document', () => {
    const theme = mockTheme({
      themes: {
        light: FillVariant({
          primary: '#000001',
          secondary: '#000002',
          accent: '#000003',
        }),
      },
    })

    const style = document.getElementById('vuetify-theme-stylesheet')
    const html = style!.innerHTML

    expect(html).toMatchSnapshot()
    expect(html.indexOf('#000001') > -1).toBe(true)
    expect(html.indexOf('#000002') > -1).toBe(true)
    expect(html.indexOf('#000003') > -1).toBe(true)
  })

  it('should apply a new theme', () => {
    const theme = mockTheme({
      default: 'light',
      themes: {
        light: FillVariant(),
        dark: FillVariant({
          primary: '#FFFFFF',
        }),
      },
    })

    const style = document.getElementById('vuetify-theme-stylesheet')
    const html = style!.innerHTML

    theme.dark = true

    expect(html).not.toEqual(style!.innerHTML)
  })

  it('should clear css', () => {
    const theme = mockTheme()
    const spy = jest.spyOn(theme, 'clearCss')

    theme.dark = true
    expect(spy).toHaveBeenCalledTimes(0)

    theme.themes.light = FillVariant()
    theme.dark = false
    expect(spy).toHaveBeenCalledTimes(0)

    theme.disabled = true
    theme.dark = true
    expect(spy).toHaveBeenCalledTimes(1)
  })

  it('should use themeCache', () => {
    const cache = new Map()
    const themeCache = {
      get: jest.fn(theme => cache.get(theme)),
      set: jest.fn((theme: VuetifyParsedTheme, css: string) => {
        cache.set(theme, css)
      }),
    }

    const theme = mockTheme({
      options: { themeCache },
    })

    expect(theme.generatedStyles).toMatchSnapshot()
    expect(themeCache.set).toHaveBeenCalledTimes(2)

    theme.applyTheme()

    expect(themeCache.get).toHaveBeenCalledTimes(3)
    expect(themeCache.set).toHaveBeenCalledTimes(3)
    expect(theme.generatedStyles).toMatchSnapshot()
  })

  it('should minify theme', () => {
    const minifyTheme = jest.fn((css: string) => css + 'foobar')

    const theme = mockTheme({
      options: { minifyTheme },
    })

    const style = document.getElementById('vuetify-theme-stylesheet')
    const html = style!.innerHTML

    expect(minifyTheme).toHaveBeenCalled()
    expect(html.indexOf('foobar') > -1).toBe(true)
    expect(theme.generatedStyles).toMatchSnapshot()
  })

  it('should add nonce to stylesheet', () => {
    const theme = mockTheme({
      options: { cspNonce: 'foobar' },
    })

    const style = document.getElementById('vuetify-theme-stylesheet')

    expect(style!.getAttribute('nonce')).toBe('foobar')
  })

  it('should initialize the theme', () => {
    const theme = mockTheme()
    const spy = jest.spyOn(theme, 'applyTheme')
    const ssrContext = { head: '' }
    theme.init(instance, ssrContext)

    expect(spy).toHaveBeenCalledTimes(1)
    expect(ssrContext.head).toBeTruthy()
    expect(ssrContext.head).toMatchSnapshot()
  })

  it('should set theme with vue-meta@1', () => {
    const theme = mockTheme()
    const anyInstance = instance as any

    anyInstance.$meta = () => ({})

    theme.init(anyInstance)

    expect(typeof anyInstance.$options.metaInfo).toBe('function')

    const metaInfo = anyInstance.$options.metaInfo()

    expect(metaInfo).toBeTruthy()
    expect(metaInfo.style).toHaveLength(1)
    expect(metaInfo.style[0].cssText).toMatchSnapshot()
  })

  it('should set theme with vue-meta@2', () => {
    const theme = mockTheme()
    const anyInstance = instance as any

    anyInstance.$meta = () => ({
      getOptions: () => ({ keyName: 'metaInfo' }),
    })

    theme.init(anyInstance)

    const metaKeyName = anyInstance.$meta().getOptions().keyName

    expect(typeof anyInstance.$options[metaKeyName]).toBe('function')

    const metaInfo = anyInstance.$options[metaKeyName]()

    expect(metaInfo).toBeTruthy()
    expect(metaInfo.style).toHaveLength(1)
    expect(metaInfo.style[0].cssText).toMatchSnapshot()
  })

  it('should react to theme changes', async () => {
    const theme = mockTheme()
    const spy = jest.spyOn(theme, 'applyTheme')

    theme.themes.light.primary = '#000000'
    await instance.$nextTick()

    theme.themes.dark.secondary = '#000000'
    await instance.$nextTick()

    theme.currentTheme.accent = '#000000'
    await instance.$nextTick()

    expect(spy).toHaveBeenCalledTimes(3)
  })

  it('should reset themes', async () => {
    const theme = mockTheme()
    const spy = jest.spyOn(theme, 'applyTheme')

    expect(theme.generatedStyles).toMatchSnapshot()
    theme.resetThemes()
    expect(theme.generatedStyles).toMatchSnapshot()
    expect(spy).toHaveBeenCalledTimes(1)
  })

  it('should set theme', () => {
    const theme = mockTheme()
    const spy = jest.spyOn(theme, 'applyTheme')

    expect(theme.generatedStyles).toMatchSnapshot()
    theme.setTheme('light', { accent: '#c0ffee' })
    expect(theme.generatedStyles).toMatchSnapshot()
    theme.setTheme('dark', { accent: '#c0ffee' })
    expect(theme.generatedStyles).toMatchSnapshot()
    expect(spy).toHaveBeenCalledTimes(2)
  })

  it('should use vue-meta@2.3 functionality', () => {
    const theme = mockTheme()
    const set = jest.fn()

    const $meta = () => ({
      addApp: () => ({ set }),
    })

    ;(instance as any).$meta = $meta as any

    theme.init(instance)

    expect(set).toHaveBeenCalled()
  })

  it('should not generate variations', () => {
    const theme = mockTheme({ options: { variations: false } })

    const style = document.getElementById('vuetify-theme-stylesheet')
    const html = style!.innerHTML

    expect(html).toMatchSnapshot()
  })
})


keep keep keep replace keep keep keep keep

script = `(async function(exports, require, module, __filename, __dirname, hexo){${script}\n});`;

import Promise from 'bluebird';
import { sep, join, dirname } from 'path';
import tildify from 'tildify';
import Database from 'warehouse';
import { magenta, underline } from 'picocolors';
import { EventEmitter } from 'events';
import { readFile } from 'hexo-fs';
import Module from 'module';
import { runInThisContext } from 'vm';
const {version} = require('../../package.json');
import logger from 'hexo-log';

import {
  Console,
  Deployer,
  Filter,
  Generator,
  Helper,
  Highlight,
  Injector,
  Migrator,
  Processor,
  Renderer,
  Tag
} from '../extend';

import Render from './render';
import registerModels from './register_models';
import Post from './post';
import Scaffold from './scaffold';
import Source from './source';
import Router from './router';
import Theme from '../theme';
import Locals from './locals';
import defaultConfig from './default_config';
import loadDatabase from './load_database';
import multiConfigPath from './multi_config_path';
import { deepMerge, full_url_for } from 'hexo-util';
let resolveSync; // = require('resolve');

const libDir = dirname(__dirname);
const dbVersion = 1;

const stopWatcher = box => { if (box.isWatching()) box.unwatch(); };

const routeCache = new WeakMap();

const castArray = obj => { return Array.isArray(obj) ? obj : [obj]; };

const mergeCtxThemeConfig = ctx => {
  // Merge hexo.config.theme_config into hexo.theme.config before post rendering & generating
  // config.theme_config has "_config.[theme].yml" merged in load_theme_config.js
  if (ctx.config.theme_config) {
    ctx.theme.config = deepMerge(ctx.theme.config, ctx.config.theme_config);
  }
};

const createLoadThemeRoute = function(generatorResult, locals, ctx) {
  const { log, theme } = ctx;
  const { path, cache: useCache } = locals;

  const layout = [...new Set(castArray(generatorResult.layout))];
  const layoutLength = layout.length;

  // always use cache in fragment_cache
  locals.cache = true;
  return () => {
    if (useCache && routeCache.has(generatorResult)) return routeCache.get(generatorResult);

    for (let i = 0; i < layoutLength; i++) {
      const name = layout[i];
      const view = theme.getView(name);

      if (view) {
        log.debug(`Rendering HTML ${name}: ${magenta(path)}`);
        return view.render(locals)
          .then(result => ctx.extend.injector.exec(result, locals))
          .then(result => ctx.execFilter('_after_html_render', result, {
            context: ctx,
            args: [locals]
          }))
          .tap(result => {
            if (useCache) {
              routeCache.set(generatorResult, result);
            }
          }).tapCatch(err => {
            log.error({ err }, `Render HTML failed: ${magenta(path)}`);
          });
      }
    }

    log.warn(`No layout: ${magenta(path)}`);
  };
};

function debounce(func: () => void, wait: number) {
  let timeout: NodeJS.Timeout;
  return function() {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(this);
    }, wait);
  };
}

interface Args {
  debug?: any;
  safe?: any;
  silent?: any;
  _?: any[];
  output?: any;
  config?: any;
}

interface Query {
  date?: any;
  published?: boolean;
}

interface Extend {
  console: Console,
  deployer: Deployer,
  filter: Filter,
  generator: Generator,
  helper: Helper,
  highlight: Highlight,
  injector: Injector,
  migrator: Migrator,
  processor: Processor,
  renderer: Renderer,
  tag: Tag
}

type DefaultConfigType = typeof defaultConfig;
interface Config extends DefaultConfigType {
  [key: string]: any;
}

// Node.js internal APIs
declare module 'module' {
  function _nodeModulePaths(path: string): string[];
  function _resolveFilename(request: string, parent: Module, isMain?: any, options?: any): string;
  const _extensions: NodeJS.RequireExtensions,
    _cache: any;
}

class Hexo extends EventEmitter {
  public base_dir: string;
  public public_dir: string;
  public source_dir: string;
  public plugin_dir: string;
  public script_dir: string;
  public scaffold_dir: string;
  public theme_dir: string;
  public theme_script_dir: string;
  public env: any;
  public extend: Extend;
  public config: Config;
  public log: ReturnType<typeof logger>;
  public render: Render;
  public route: Router;
  public post: Post;
  public scaffold: Scaffold;
  public _dbLoaded: boolean;
  public _isGenerating: boolean;
  public database: Database;
  public config_path: string;
  public source: Source;
  public theme: Theme;
  public locals: Locals;
  public version: string;
  public _watchBox: () => void;
  public page: any;
  public path: any;
  public url: any;
  public layout: any;
  public view_dir: any;
  public site: any;
  public args: any;
  public cache: any;
  public alias: any;
  public data: any;
  public lib_dir: string;
  public core_dir: string;
  static lib_dir: string;
  static core_dir: string;
  static version: string;

  constructor(base = process.cwd(), args: Args = {}) {
    super();

    this.base_dir = base + sep;
    this.public_dir = join(base, 'public') + sep;
    this.source_dir = join(base, 'source') + sep;
    this.plugin_dir = join(base, 'node_modules') + sep;
    this.script_dir = join(base, 'scripts') + sep;
    this.scaffold_dir = join(base, 'scaffolds') + sep;
    this.theme_dir = join(base, 'themes', defaultConfig.theme) + sep;
    this.theme_script_dir = join(this.theme_dir, 'scripts') + sep;

    this.env = {
      args,
      debug: Boolean(args.debug),
      safe: Boolean(args.safe),
      silent: Boolean(args.silent),
      env: process.env.NODE_ENV || 'development',
      version,
      cmd: args._ ? args._[0] : '',
      init: false
    };

    this.extend = {
      console: new Console(),
      deployer: new Deployer(),
      filter: new Filter(),
      generator: new Generator(),
      helper: new Helper(),
      highlight: new Highlight(),
      injector: new Injector(),
      migrator: new Migrator(),
      processor: new Processor(),
      renderer: new Renderer(),
      tag: new Tag()
    };

    this.config = { ...defaultConfig };

    this.log = logger(this.env);

    this.render = new Render(this);

    this.route = new Router();

    this.post = new Post(this);

    this.scaffold = new Scaffold(this);

    this._dbLoaded = false;

    this._isGenerating = false;

    // If `output` is provided, use that as the
    // root for saving the db. Otherwise default to `base`.
    const dbPath = args.output || base;

    if (/^(init|new|g|publish|s|deploy|render|migrate)/.test(this.env.cmd)) {
      this.log.d(`Writing database to ${join(dbPath, 'db.json')}`);
    }

    this.database = new Database({
      version: dbVersion,
      path: join(dbPath, 'db.json')
    });

    const mcp = multiConfigPath(this);

    this.config_path = args.config ? mcp(base, args.config, args.output)
      : join(base, '_config.yml');

    registerModels(this);

    this.source = new Source(this);
    this.theme = new Theme(this);
    this.locals = new Locals();
    this._bindLocals();
  }

  _bindLocals() {
    const db = this.database;
    const { locals } = this;

    locals.set('posts', () => {
      const query: Query = {};

      if (!this.config.future) {
        query.date = { $lte: Date.now() };
      }

      if (!this._showDrafts()) {
        query.published = true;
      }

      return db.model('Post').find(query);
    });

    locals.set('pages', () => {
      const query: Query = {};

      if (!this.config.future) {
        query.date = { $lte: Date.now() };
      }

      return db.model('Page').find(query);
    });

    locals.set('categories', () => db.model('Category'));

    locals.set('tags', () => db.model('Tag'));

    locals.set('data', () => {
      const obj = {};

      db.model('Data').forEach(data => {
        obj[data._id] = data.data;
      });

      return obj;
    });
  }

  init() {
    this.log.debug('Hexo version: %s', magenta(this.version));
    this.log.debug('Working directory: %s', magenta(tildify(this.base_dir)));

    // Load internal plugins
    require('../plugins/console')(this);
    require('../plugins/filter')(this);
    require('../plugins/generator')(this);
    require('../plugins/helper')(this);
    require('../plugins/highlight')(this);
    require('../plugins/injector')(this);
    require('../plugins/processor')(this);
    require('../plugins/renderer')(this);
    require('../plugins/tag').default(this);

    // Load config
    return Promise.each([
      'update_package', // Update package.json
      'load_config', // Load config
      'load_theme_config', // Load alternate theme config
      'load_plugins' // Load external plugins & scripts
    ], name => require(`./${name}`)(this)).then(() => this.execFilter('after_init', null, { context: this })).then(() => {
      // Ready to go!
      this.emit('ready');
    });
  }

  call(name, args, callback) {
    if (!callback && typeof args === 'function') {
      callback = args;
      args = {};
    }

    const c = this.extend.console.get(name);

    // eslint-disable-next-line no-extra-parens
    if (c) return (Reflect.apply(c, this, [args]) as any).asCallback(callback);
    return Promise.reject(new Error(`Console \`${name}\` has not been registered yet!`));
  }

  model(name, schema) {
    return this.database.model(name, schema);
  }

  resolvePlugin(name, basedir) {
    try {
      // Try to resolve the plugin with the Node.js's built-in require.resolve.
      return require.resolve(name, { paths: [basedir] });
    } catch (err) {
      try {
        // There was an error (likely the node_modules is corrupt or from early version of npm)
        // Use Hexo prior 6.0.0's behavior (resolve.sync) to resolve the plugin.
        resolveSync = resolveSync || require('resolve').sync;
        return resolveSync(name, { basedir });
      } catch (err) {
        // There was an error (likely the plugin wasn't found), so return a possibly
        // non-existing path that a later part of the resolution process will check.
        return join(basedir, 'node_modules', name);
      }
    }
  }

  loadPlugin(path: string, callback: (...args: any[]) => any) {
    return readFile(path).then(script => {
      // Based on: https://github.com/joyent/node/blob/v0.10.33/src/node.js#L516
      const module = new Module(path);
      module.filename = path;
      module.paths = Module._nodeModulePaths(path);

      function req(path) {
        return module.require(path);
      }

      req.resolve = request => Module._resolveFilename(request, module);

      req.main = require.main;
      req.extensions = Module._extensions;
      req.cache = Module._cache;

      script = `(function(exports, require, module, __filename, __dirname, hexo){${script}\n});`;

      const fn = runInThisContext(script, path);

      return fn(module.exports, req, module, path, dirname(path), this);
    }).asCallback(callback);
  }

  _showDrafts() {
    const { args } = this.env;
    return args.draft || args.drafts || this.config.render_drafts;
  }

  load(callback) {
    return loadDatabase(this).then(() => {
      this.log.info('Start processing');

      return Promise.all([
        this.source.process(),
        this.theme.process()
      ]);
    }).then(() => {
      mergeCtxThemeConfig(this);
      return this._generate({ cache: false });
    }).asCallback(callback);
  }

  watch(callback) {
    let useCache = false;
    const { cache } = Object.assign({
      cache: false
    }, this.config.server);
    const { alias } = this.extend.console;

    if (alias[this.env.cmd] === 'server' && cache) {
      // enable cache when run hexo server
      useCache = true;
    }
    this._watchBox = debounce(() => this._generate({ cache: useCache }), 100);

    return loadDatabase(this).then(() => {
      this.log.info('Start processing');

      return Promise.all([
        this.source.watch(),
        this.theme.watch()
      ]);
    }).then(() => {
      mergeCtxThemeConfig(this);

      this.source.on('processAfter', this._watchBox);
      this.theme.on('processAfter', () => {
        this._watchBox();
        mergeCtxThemeConfig(this);
      });

      return this._generate({ cache: useCache });
    }).asCallback(callback);
  }

  unwatch() {
    if (this._watchBox != null) {
      this.source.removeListener('processAfter', this._watchBox);
      this.theme.removeListener('processAfter', this._watchBox);

      this._watchBox = null;
    }

    stopWatcher(this.source);
    stopWatcher(this.theme);
  }

  _generateLocals() {
    const { config, env, theme, theme_dir } = this;
    const ctx = { config: { url: this.config.url } };
    const localsObj = this.locals.toObject();

    class Locals {
      page: {
        path: string;
      };
      path: string;
      url: string;
      config: object;
      theme: object;
      layout: string;
      env: any;
      view_dir: string;
      site: object;
      cache?: boolean;

      constructor(path, locals) {
        this.page = { ...locals };
        if (this.page.path == null) this.page.path = path;
        this.path = path;
        this.url = full_url_for.call(ctx, path);
        this.config = config;
        this.theme = theme.config;
        this.layout = 'layout';
        this.env = env;
        this.view_dir = join(theme_dir, 'layout') + sep;
        this.site = localsObj;
      }
    }

    return Locals;
  }

  _runGenerators() {
    this.locals.invalidate();
    const siteLocals = this.locals.toObject();
    const generators = this.extend.generator.list();
    const { log } = this;

    // Run generators
    return Promise.map(Object.keys(generators), key => {
      const generator = generators[key];

      log.debug('Generator: %s', magenta(key));
      return Reflect.apply(generator, this, [siteLocals]);
    }).reduce((result, data) => {
      return data ? result.concat(data) : result;
    }, []);
  }

  _routerReflesh(runningGenerators, useCache) {
    const { route } = this;
    const routeList = route.list();
    const Locals = this._generateLocals();
    Locals.prototype.cache = useCache;

    return runningGenerators.map(generatorResult => {
      if (typeof generatorResult !== 'object' || generatorResult.path == null) return undefined;

      // add Route
      const path = route.format(generatorResult.path);
      const { data, layout } = generatorResult;

      if (!layout) {
        route.set(path, data);
        return path;
      }

      return this.execFilter('template_locals', new Locals(path, data), { context: this })
        .then(locals => { route.set(path, createLoadThemeRoute(generatorResult, locals, this)); })
        .thenReturn(path);
    }).then(newRouteList => {
      // Remove old routes
      for (let i = 0, len = routeList.length; i < len; i++) {
        const item = routeList[i];

        if (!newRouteList.includes(item)) {
          route.remove(item);
        }
      }
    });
  }

  _generate(options: { cache?: boolean } = {}) {
    if (this._isGenerating) return;

    const useCache = options.cache;

    this._isGenerating = true;

    this.emit('generateBefore');

    // Run before_generate filters
    return this.execFilter('before_generate', this.locals.get('data'), { context: this })
      .then(() => this._routerReflesh(this._runGenerators(), useCache)).then(() => {
        this.emit('generateAfter');

        // Run after_generate filters
        return this.execFilter('after_generate', null, { context: this });
      }).finally(() => {
        this._isGenerating = false;
      });
  }

  exit(err) {
    if (err) {
      this.log.fatal(
        { err },
        'Something\'s wrong. Maybe you can find the solution here: %s',
        underline('https://hexo.io/docs/troubleshooting.html')
      );
    }

    return this.execFilter('before_exit', null, { context: this }).then(() => {
      this.emit('exit', err);
    });
  }

  execFilter(type, data, options) {
    return this.extend.filter.exec(type, data, options);
  }

  execFilterSync(type, data, options) {
    return this.extend.filter.execSync(type, data, options);
  }
}

Hexo.lib_dir = libDir + sep;
Hexo.prototype.lib_dir = Hexo.lib_dir;

Hexo.core_dir = dirname(libDir) + sep;
Hexo.prototype.core_dir = Hexo.core_dir;

Hexo.version = version;
Hexo.prototype.version = Hexo.version;

export = Hexo;


// Lazy require highlight.js
let highlight;

module.exports = function highlightFilter(code, options) {
  const hljsCfg = this.config.highlight || {};
  const line_threshold = options.line_threshold || hljsCfg.line_threshold || 0;
  const shouldUseLineNumbers = typeof options.line_number === 'undefined' ? hljsCfg.line_number : options.line_number;
  const surpassesLineThreshold = options.lines_length > line_threshold;
  const gutter = shouldUseLineNumbers && surpassesLineThreshold;
  const languageAttr = typeof options.language_attr === 'undefined' ? hljsCfg.language_attr : options.language_attr;

  const hljsOptions = {
    autoDetect: hljsCfg.auto_detect,
    caption: options.caption,
    firstLine: options.firstLine,
    gutter,
    hljs: hljsCfg.hljs,
    lang: options.lang,
    languageAttr,
    mark: options.mark,
    tab: hljsCfg.tab_replace,
    wrap: hljsCfg.wrap
  };
  if (hljsCfg.first_line_number === 'inline') {
    if (typeof options.firstLineNumber !== 'undefined') {
      hljsOptions.firstLine = options.firstLineNumber;
    } else {
      hljsOptions.gutter = false;
    }
  }

  if (Array.isArray(hljsCfg.exclude_languages) && hljsCfg.exclude_languages.includes(hljsOptions.lang)) {
    // Only wrap with

    hljsOptions.wrap = false;
    hljsOptions.gutter = false;
    hljsOptions.autoDetect = false;
  }

  if (!highlight) highlight = require('hexo-util').highlight;

  return highlight(code, hljsOptions);
};


import Promise from 'bluebird';

interface BaseObj {
  path: string;
  data: any;
  layout?: string;
}
type ReturnType = BaseObj | BaseObj[];
type GeneratorReturnType = ReturnType | Promise<ReturnType>;

interface GeneratorFunction {
  (locals: object): GeneratorReturnType;
}

type StoreFunctionReturn = Promise<ReturnType>;

interface StoreFunction {
  (locals: object): StoreFunctionReturn;
}

interface Store {
  [key: string]: StoreFunction
}

class Generator {
  public id: number;
  public store: Store;

  constructor() {
    this.id = 0;
    this.store = {};
  }

  list() {
    return this.store;
  }

  get(name: string) {
    return this.store[name];
  }

  register(fn: GeneratorFunction): void
  register(name: string, fn: GeneratorFunction): void
  register(name: string | GeneratorFunction, fn?: GeneratorFunction) {
    if (!fn) {
      if (typeof name === 'function') { // fn
        fn = name;
        name = `generator-${this.id++}`;
      } else {
        throw new TypeError('fn must be a function');
      }
    }

    if (fn.length > 1) fn = Promise.promisify(fn);
    this.store[name as string] = Promise.method(fn);
  }
}

export = Generator;


'use strict';

describe('Renderers', () => {
  require('./json');
  require('./plain');
  require('./yaml');
  require('./nunjucks');
});


keep keep keep add keep keep keep keep keep

'  return new Promise(resolve => resolve());',

'use strict';

const fs = require('hexo-fs');
const { join, dirname } = require('path');
const Promise = require('bluebird');

describe('Load plugins', () => {
  const Hexo = require('../../../dist/hexo');
  const hexo = new Hexo(join(__dirname, 'plugin_test'), { silent: true });
  const loadPlugins = require('../../../dist/hexo/load_plugins');

  const script = [
    'hexo._script_test = {',
    '  filename: __filename,',
    '  dirname: __dirname,',
    '  module: module,',
    '  require: require',
    '}'
  ].join('\n');

  function validate(path) {
    const result = hexo._script_test;

    result.filename.should.eql(path);
    result.dirname.should.eql(dirname(path));
    result.module.id.should.eql(path);
    result.module.filename.should.eql(path);

    delete hexo._script_test;
  }

  function createPackageFile(name, path) {
    const pkg = {
      name: 'hexo-site',
      version: '0.0.0',
      private: true,
      dependencies: {
        [name]: '*'
      }
    };

    path = path || join(hexo.base_dir, 'package.json');
    return fs.writeFile(path, JSON.stringify(pkg, null, '  '));
  }

  function createPackageFileWithDevDeps(name) {
    const pkg = {
      name: 'hexo-site',
      version: '0.0.0',
      private: true,
      dependencies: {},
      devDependencies: {
        [name]: '*'
      }
    };

    return fs.writeFile(join(hexo.base_dir, 'package.json'), JSON.stringify(pkg, null, '  '));
  }

  hexo.env.init = true;
  hexo.theme_script_dir = join(hexo.base_dir, 'themes', 'test', 'scripts');

  before(() => fs.mkdir(hexo.base_dir));

  after(() => fs.rmdir(hexo.base_dir));

  afterEach(async () => {
    await createPackageFile('hexo-another-plugin');
  });

  it('load plugins', () => {
    const name = 'hexo-plugin-test';
    const path = join(hexo.plugin_dir, name, 'index.js');

    return Promise.all([
      createPackageFile(name),
      fs.writeFile(path, script)
    ]).then(() => loadPlugins(hexo)).then(() => {
      validate(path);
      return fs.unlink(path);
    });
  });

  it('load scoped plugins', () => {
    const name = '@some-scope/hexo-plugin-test';
    const path = join(hexo.plugin_dir, name, 'index.js');

    return Promise.all([
      createPackageFile(name),
      fs.writeFile(path, script)
    ]).then(() => loadPlugins(hexo)).then(() => {
      validate(path);
      return fs.unlink(path);
    });
  });

  it('load devDep plugins', () => {
    const name = 'hexo-plugin-test';
    const path = join(hexo.plugin_dir, name, 'index.js');

    return Promise.all([
      createPackageFileWithDevDeps(name),
      fs.writeFile(path, script)
    ]).then(() => loadPlugins(hexo)).then(() => {
      validate(path);
      return fs.unlink(path);
    });
  });

  it('load plugins in the theme\'s package.json', async () => {
    const name = 'hexo-plugin-test';
    const path = join(hexo.plugin_dir, name, 'index.js');
    return Promise.all([
      createPackageFile(name, join(hexo.theme_dir, 'package.json')),
      fs.writeFile(path, script)
    ]).then(() => loadPlugins(hexo)).then(() => {
      validate(path);
      return fs.unlink(path);
    });
  });

  it('ignore plugin whose name is started with "hexo-theme-"', async () => {
    const script = 'hexo._script_test = true';
    const name = 'hexo-theme-test_theme';
    const path = join(hexo.plugin_dir, name, 'index.js');

    await Promise.all([
      createPackageFile(name),
      fs.writeFile(path, script)
    ]);
    await loadPlugins(hexo);

    should.not.exist(hexo._script_test);
    delete hexo.config.theme;
    return fs.unlink(path);
  });

  it('ignore scoped plugin whose name is started with "hexo-theme-"', async () => {
    const script = 'hexo._script_test = true';
    const name = '@hexojs/hexo-theme-test_theme';
    const path = join(hexo.plugin_dir, name, 'index.js');

    await Promise.all([
      createPackageFile(name),
      fs.writeFile(path, script)
    ]);
    await loadPlugins(hexo);

    should.not.exist(hexo._script_test);
    delete hexo.config.theme;
    return fs.unlink(path);
  });

  it('ignore plugins whose name is not started with "hexo-"', async () => {
    const script = 'hexo._script_test = true';
    const name = 'another-plugin';
    const path = join(hexo.plugin_dir, name, 'index.js');

    await Promise.all([
      createPackageFile(name),
      fs.writeFile(path, script)
    ]);
    await loadPlugins(hexo);

    should.not.exist(hexo._script_test);
    return fs.unlink(path);
  });

  it('ignore plugins which is typescript definition', () => {
    const script = 'hexo._script_test = true';
    const name = '@types/hexo-test-plugin';
    const path = join(hexo.plugin_dir, name, 'index.js');

    return Promise.all([
      createPackageFile(name),
      fs.writeFile(path, script)
    ]).then(() => loadPlugins(hexo)).then(() => {
      should.not.exist(hexo._script_test);
      return fs.unlink(path);
    });
  });

  it('ignore plugins which are in package.json but not exist actually', () => createPackageFile('hexo-plugin-test').then(() => loadPlugins(hexo)));

  it('load scripts', async () => {
    const path = join(hexo.script_dir, 'test.js');

    fs.writeFile(path, script);
    await loadPlugins(hexo);

    validate(path);
    return fs.unlink(path);
  });

  it('load theme scripts', () => {
    const path = join(hexo.theme_script_dir, 'test.js');

    return fs.writeFile(path, script).then(() => loadPlugins(hexo)).then(() => {
      validate(path);
      return fs.unlink(path);
    });
  });

  it('don\'t load plugins in safe mode', () => {
    const script = 'hexo._script_test = true';
    const path = join(hexo.script_dir, 'test.js');

    return fs.writeFile(path, script).then(() => {
      hexo.env.safe = true;
      return loadPlugins(hexo);
    }).then(() => {
      hexo.env.safe = false;
      should.not.exist(hexo._script_test);
      return fs.unlink(path);
    });
  });

  // Issue #4251
  it('load scripts with sourcemap EOF', async () => {
    const path = join(hexo.script_dir, 'test.js');

    const script = [
      '(() => {',
      '  hexo._script_test = true;',
      '})();',
      '//# sourceMappingURL=data:application/json;<redacted>'
    ].join('\n');

    fs.writeFile(path, script);
    await loadPlugins(hexo);

    hexo._script_test.should.eql(true);
    return fs.unlink(path);
  });
});


'use strict';

const { stub, assert: sinonAssert } = require('sinon');

describe('Console list', () => {
  const Hexo = require('../../../dist/hexo');
  const hexo = new Hexo();
  const Page = hexo.model('Page');
  const listPages = require('../../../dist/plugins/console/list/page').bind(hexo);

  hexo.config.permalink = ':title/';

  let logStub;

  before(() => { logStub = stub(console, 'log'); });

  afterEach(() => { logStub.reset(); });

  after(() => { logStub.restore(); });

  it('no page', () => {
    listPages();
    sinonAssert.calledWithMatch(logStub, 'Date');
    sinonAssert.calledWithMatch(logStub, 'Title');
    sinonAssert.calledWithMatch(logStub, 'Path');
    sinonAssert.calledWithMatch(logStub, 'No pages.');
  });

  it('page', async () => {
    await Page.insert({
      source: 'foo',
      title: 'Hello World',
      path: 'bar'
    });
    listPages();
    sinonAssert.calledWithMatch(logStub, 'Date');
    sinonAssert.calledWithMatch(logStub, 'Title');
    sinonAssert.calledWithMatch(logStub, 'Path');
    sinonAssert.calledWithMatch(logStub, 'Hello World');
    sinonAssert.calledWithMatch(logStub, 'foo');
  });

  it('page with unicode', async () => {
    await Page.insert({
      source: 'foo',
      title: '\u0100',
      path: 'bar'
    });
    listPages();
    sinonAssert.calledWithMatch(logStub, 'Date');
    sinonAssert.calledWithMatch(logStub, 'Title');
    sinonAssert.calledWithMatch(logStub, 'Path');
    sinonAssert.calledWithMatch(logStub, '\u0100');
    sinonAssert.calledWithMatch(logStub, 'foo');
  });
});


'use strict';

const cheerio = require('cheerio');

describe('iframe', () => {
  const iframe = require('../../../dist/plugins/tag/iframe');

  it('url', () => {
    const $ = cheerio.load(iframe(['https://zespia.tw']));

    $('iframe').attr('src').should.eql('https://zespia.tw/');
    $('iframe').attr('width').should.eql('100%');
    $('iframe').attr('height').should.eql('300');
    $('iframe').attr('frameborder').should.eql('0');
    $('iframe').attr('allowfullscreen').should.eql('');
    $('iframe').attr('loading').should.eql('lazy');
  });

  it('width', () => {
    const $ = cheerio.load(iframe(['https://zespia.tw', '500']));

    $('iframe').attr('src').should.eql('https://zespia.tw/');
    $('iframe').attr('width').should.eql('500');
    $('iframe').attr('height').should.eql('300');
    $('iframe').attr('frameborder').should.eql('0');
    $('iframe').attr('allowfullscreen').should.eql('');
    $('iframe').attr('loading').should.eql('lazy');
  });

  it('height', () => {
    const $ = cheerio.load(iframe(['https://zespia.tw', '500', '600']));

    $('iframe').attr('src').should.eql('https://zespia.tw/');
    $('iframe').attr('width').should.eql('500');
    $('iframe').attr('height').should.eql('600');
    $('iframe').attr('frameborder').should.eql('0');
    $('iframe').attr('allowfullscreen').should.eql('');
    $('iframe').attr('loading').should.eql('lazy');
  });
});


'use strict';

const util = require('hexo-util');
const cheerio = require('cheerio');

describe('code', () => {
  const Hexo = require('../../../dist/hexo');
  const hexo = new Hexo();
  require('../../../dist/plugins/highlight/')(hexo);
  const codeTag = require('../../../dist/plugins/tag/code')(hexo);
  const { escapeHTML } = util;

  const fixture = [
    'if (tired && night){',
    '  sleep();',
    '}'
  ].join('\n');

  function code(args, content) {
    return codeTag(args.split(' '), content);
  }

  function highlight(code, options) {
    return util.highlight(code, options || {})
      .replace(/{/g, '{')
      .replace(/}/g, '}');
  }

  function prism(code, options) {
    return util.prismHighlight(code, options || {})
      .replace(/{/g, '{')
      .replace(/}/g, '}');
  }

  describe('highlightjs', () => {
    it('default', () => {
      const result = code('', fixture);
      result.should.eql(highlight(fixture));
    });

    it('non standard indent', () => {
      const nonStandardIndent = [
        '  ',
        '  return x;',
        '}',
        '',
        fixture,
        '  '
      ].join('/n');
      const result = code('', nonStandardIndent);
      result.should.eql(highlight(nonStandardIndent));
    });

    it('lang', () => {
      const result = code('lang:js', fixture);
      result.should.eql(highlight(fixture, {
        lang: 'js'
      }));
    });

    it('line_number', () => {
      let result = code('line_number:false', fixture);
      result.should.eql(highlight(fixture, {
        gutter: false
      }));
      result = code('line_number:true', fixture);
      result.should.eql(highlight(fixture, {
        gutter: true
      }));
    });

    it('line_threshold', () => {
      let result = code('line_number:false line_threshold:1', fixture);
      result.should.eql(highlight(fixture, {
        gutter: false
      }));
      result = code('line_number:true line_threshold:1', fixture);
      result.should.eql(highlight(fixture, {
        gutter: true
      }));
      result = code('line_number:true line_threshold:3', fixture);
      result.should.eql(highlight(fixture, {
        gutter: false
      }));
    });

    it('highlight disable', () => {
      const result = code('highlight:false', fixture);
      result.should.eql('
' + escapeHTML(fixture) + '
');
    });

    it('title', () => {
      const result = code('Hello world', fixture);
      result.should.eql(highlight(fixture, {
        caption: 'Hello world'
      }));
    });

    it('link', () => {
      const result = code('Hello world https://hexo.io/', fixture);
      const expected = highlight(fixture, {
        caption: 'Hello worldlink'
      });

      result.should.eql(expected);
    });

    it('link text', () => {
      const result = code('Hello world https://hexo.io/ Hexo', fixture);
      const expected = highlight(fixture, {
        caption: 'Hello worldHexo'
      });

      result.should.eql(expected);
    });

    it('disabled', () => {
      hexo.config.syntax_highlighter = '';

      const result = code('', fixture);
      result.should.eql('
' + escapeHTML(fixture) + '
');

      hexo.config.syntax_highlighter = 'highlight.js';
    });

    it('first_line', () => {
      let result = code('first_line:1234', fixture);
      result.should.eql(highlight(fixture, {
        firstLine: 1234
      }));
      result = code('', fixture);
      result.should.eql(highlight(fixture, {
        firstLine: 1
      }));
    });

    it('mark', () => {
      const source = [
        'const http = require(\'http\');',
        '',
        'const hostname = \'127.0.0.1\';',
        'const port = 1337;',
        '',
        'http.createServer((req, res) => {',
        '  res.writeHead(200, { \'Content-Type\': \'text/plain\' });',
        '  res.end(\'Hello World\n\');',
        '}).listen(port, hostname, () => {',
        '  console.log(`Server running at http://${hostname}:${port}/`);',
        '});'
      ].join('\n');

      code('mark:1,7-9,11', source).should.eql(highlight(source, {
        mark: [1, 7, 8, 9, 11]
      }));

      code('mark:11,9-7,1', source).should.eql(highlight(source, {
        mark: [1, 7, 8, 9, 11]
      }));
    });

    it('# lines', () => {
      const result = code('', fixture);
      const $ = cheerio.load(result);
      $('.gutter .line').should.have.lengthOf(3);
    });

    it('wrap', () => {
      let result = code('wrap:false', fixture);
      result.should.eql(highlight(fixture, {
        wrap: false
      }));
      result = code('wrap:true', fixture);
      result.should.eql(highlight(fixture, {
        wrap: true
      }));
    });

    it('language_attr', () => {
      const result = code('lang:js language_attr:true', fixture);
      result.should.eql(highlight(fixture, {
        lang: 'js',
        languageAttr: true
      }));
    });
  });

  describe('prismjs', () => {
    beforeEach(() => {
      hexo.config.syntax_highlighter = 'prismjs';
    });

    it('default', () => {
      const result = code('', fixture);
      result.should.eql(prism(fixture));
    });

    it('non standard indent', () => {
      const nonStandardIndent = [
        '  ',
        '  return x;',
        '}',
        '',
        fixture,
        '  '
      ].join('/n');
      const result = code('', nonStandardIndent);
      result.should.eql(prism(nonStandardIndent));
    });

    it('lang', () => {
      const result = code('lang:js', fixture);
      result.should.eql(prism(fixture, {
        lang: 'js'
      }));
    });

    it('line_number', () => {
      let result = code('line_number:false', fixture);
      result.should.eql(prism(fixture, {
        lineNumber: false
      }));
      result = code('line_number:true', fixture);
      result.should.eql(prism(fixture, {
        lineNumber: true
      }));
    });

    it('line_threshold', () => {
      let result = code('line_number:false line_threshold:1', fixture);
      result.should.eql(prism(fixture, {
        lineNumber: false
      }));
      result = code('line_number:true line_threshold:1', fixture);
      result.should.eql(prism(fixture, {
        lineNumber: true
      }));
      result = code('line_number:true line_threshold:3', fixture);
      result.should.eql(prism(fixture, {
        lineNumber: false
      }));
    });

    it('highlight disable', () => {
      const result = code('highlight:false', fixture);
      result.should.eql('
' + escapeHTML(fixture) + '
');
    });

    it('disabled', () => {
      hexo.config.syntax_highlighter = '';

      const result = code('', fixture);
      result.should.eql('
' + escapeHTML(fixture) + '
');

      hexo.config.syntax_highlighter = 'highlight.js';
    });

    it('first_line', () => {
      let result = code('first_line:1234', fixture);
      result.should.eql(prism(fixture, {
        firstLine: 1234
      }));
      result = code('', fixture);
      result.should.eql(prism(fixture, {
        firstLine: 1
      }));
    });

    it('mark', () => {
      const source = [
        'const http = require(\'http\');',
        '',
        'const hostname = \'127.0.0.1\';',
        'const port = 1337;',
        '',
        'http.createServer((req, res) => {',
        '  res.writeHead(200, { \'Content-Type\': \'text/plain\' });',
        '  res.end(\'Hello World\n\');',
        '}).listen(port, hostname, () => {',
        '  console.log(`Server running at http://${hostname}:${port}/`);',
        '});'
      ].join('\n');

      code('mark:1,7-9,11', source).should.eql(prism(source, {
        mark: [1, 7, 8, 9, 11]
      }));

      code('mark:11,9-7,1', source).should.eql(prism(source, {
        mark: [1, 7, 8, 9, 11]
      }));
    });

    it('title', () => {
      const result = code('Hello world', fixture);
      result.should.eql(prism(fixture, {
        caption: 'Hello world'
      }));
    });
  });
});


const name = '@some-scope/hexo-plugin-test';

const path = join(hexo.plugin_dir, name, 'index.js');

]).then(() => loadPlugins(hexo)).then(() => {


import { encodeURL, escapeHTML } from 'hexo-util';

/**
 * Asset link tag
 *
 * Syntax:
 *   {% asset_link slug [title] [escape] %}
 */
export = ctx => {
  const PostAsset = ctx.model('PostAsset');

  return function assetLinkTag(args) {
    const slug = args.shift();
    if (!slug) return;

    const asset = PostAsset.findOne({post: this._id, slug});
    if (!asset) return;

    let escape = args[args.length - 1];
    if (escape === 'true' || escape === 'false') {
      args.pop();
    } else {
      escape = 'true';
    }

    let title = args.length ? args.join(' ') : asset.slug;
    const attrTitle = escapeHTML(title);
    if (escape === 'true') title = attrTitle;

    const link = encodeURL(new URL(asset.path, ctx.config.url).pathname);

    return `<a href="${link}" title="${attrTitle}">${title}</a>`;
  };
};


# Hexo

> A fast, simple & powerful blog framework, powered by [Node.js](https://nodejs.org).

[Website](https://hexo.io) |
[Documentation](https://hexo.io/docs/) |
[Installation Guide](https://hexo.io/docs/#Installation) |
[Contribution Guide](https://hexo.io/docs/contributing) |
[Code of Conduct](CODE_OF_CONDUCT.md) |
[API](https://hexo.io/api/) |
[Twitter](https://twitter.com/hexojs)

[![NPM version](https://badge.fury.io/js/hexo.svg)](https://www.npmjs.com/package/hexo)
![Required Node version](https://img.shields.io/node/v/hexo)
[![Build Status](https://github.com/hexojs/hexo/workflows/Tester/badge.svg)](https://github.com/hexojs/hexo/actions?query=workflow%3ATester)
[![dependencies Status](https://img.shields.io/librariesio/release/npm/hexo)](https://libraries.io/npm/hexo)
[![Coverage Status](https://coveralls.io/repos/hexojs/hexo/badge.svg?branch=master)](https://coveralls.io/r/hexojs/hexo?branch=master)
[![Gitter](https://badges.gitter.im/hexojs/hexo.svg)](https://gitter.im/hexojs/hexo)
[![Discord Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/teM2Anj)
[![Telegram Chat](https://img.shields.io/badge/chat-on%20telegram-32afed.svg)](https://t.me/hexojs)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhexojs%2Fhexo.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhexojs%2Fhexo?ref=badge_shield)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)

## Features

- Blazing fast generating
- Support for GitHub Flavored Markdown and most Octopress plugins
- One-command deploy to GitHub Pages, Heroku, etc.
- Powerful API for limitless extensibility
- Hundreds of [themes](https://hexo.io/themes/) & [plugins](https://hexo.io/plugins/)

## Quick Start

**Install Hexo**

``` bash
$ npm install hexo-cli -g
```

Install with [brew](https://brew.sh/) on macOS and Linux:

```bash
$ brew install hexo
```

**Setup your blog**

``` bash
$ hexo init blog
$ cd blog
```

**Start the server**

``` bash
$ hexo server
```

**Create a new post**

``` bash
$ hexo new "Hello Hexo"
```

**Generate static files**

``` bash
$ hexo generate
```

## More Information

- Read the [documentation](https://hexo.io/)
- Visit the [Awesome Hexo](https://github.com/hexojs/awesome-hexo) list
- Find solutions in [troubleshooting](https://hexo.io/docs/troubleshooting.html)
- Join discussion on [Google Group](https://groups.google.com/group/hexo), [Discord](https://discord.gg/teM2Anj), [Gitter](https://gitter.im/hexojs/hexo) or [Telegram](https://t.me/hexojs)
- See the [plugin list](https://hexo.io/plugins/) and the [theme list](https://hexo.io/themes/) on wiki
- Follow [@hexojs](https://twitter.com/hexojs) for latest news

## Contributing

We welcome you to join the development of Hexo. Please see [contributing document](https://hexo.io/docs/contributing). 🤗

Also, we welcome PR or issue to [official-plugins](https://github.com/hexojs).

## Contributors

[![](https://opencollective.com/Hexo/contributors.svg?width=890)](https://github.com/hexojs/hexo/graphs/contributors)

## Backers

[![Backers](https://opencollective.com/hexo/tiers/backers.svg?avatarHeight=36&width=600)](https://opencollective.com/hexo)

## Sponsors

## License

[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhexojs%2Fhexo.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhexojs%2Fhexo?ref=badge_large)


import type Hexo from '../hexo';

export interface HighlightOptions {
  lang: string | undefined,
  caption: string | undefined,
  lines_length: number,

  // plulgins/filter/before_post_render/backtick_code_block
  firstLineNumber?: string | number

  // plugins/tag/code.ts
  language_attr?: boolean | undefined;
  firstLine?: number;
  line_number?: boolean | undefined;
  line_threshold?: number | undefined;
  mark?: number[];
  wrap?: boolean | undefined;

}

interface HighlightExecArgs {
  context?: Hexo;
  args?: [string, HighlightOptions];
}

interface StoreFunction {
  (content: string, options: HighlightOptions): string;
  priority?: number;
}

interface Store {
  [key: string]: StoreFunction
}

class SyntaxHighlight {
  public store: Store;

  constructor() {
    this.store = {};
  }

  register(name: string, fn: StoreFunction) {
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    this.store[name] = fn;
  }

  query(name: string) {
    return name && this.store[name];
  }

  exec(name: string, options: HighlightExecArgs): string {
    const fn = this.store[name];

    if (!fn) throw new TypeError(`syntax highlighter ${name} is not registered`);
    const ctx = options.context;
    const args = options.args || [];

    return Reflect.apply(fn, ctx, args);
  }
}

export default SyntaxHighlight;


export type FilesEndpoint = {
GET: (params: {
roomId: IRoom['_id'];
count: number;
sort: string;
query: string;

import { useCallback, useEffect, useMemo, useState } from 'react';

import { getConfig } from '../../../../../app/ui-utils/client/config';
import { IUser } from '../../../../../definition/IUser';
import { useEndpoint } from '../../../../contexts/ServerContext';
import { useScrollableMessageList } from '../../../../hooks/lists/useScrollableMessageList';
import { useStreamUpdatesForMessageList } from '../../../../hooks/lists/useStreamUpdatesForMessageList';
import { DiscussionsList, DiscussionsListOptions } from '../../../../lib/lists/DiscussionsList';

export const useDiscussionsList = (
	options: DiscussionsListOptions,
	uid: IUser['_id'],
): {
	discussionsList: DiscussionsList;
	initialItemCount: number;
	loadMoreItems: (start: number, end: number) => void;
} => {
	const [discussionsList] = useState(() => new DiscussionsList(options));

	useEffect(() => {
		if (discussionsList.options !== options) {
			discussionsList.updateFilters(options);
		}
	}, [discussionsList, options]);

	const getDiscussions = useEndpoint('GET', 'chat.getDiscussions');

	const fetchMessages = useCallback(
		async (start, end) => {
			const { messages, total } = await getDiscussions({
				roomId: options.rid,
				text: options.text,
				offset: start,
				count: end - start,
			});

			return {
				items: messages,
				itemCount: total,
			};
		},
		[getDiscussions, options.rid, options.text],
	);

	const { loadMoreItems, initialItemCount } = useScrollableMessageList(
		discussionsList,
		fetchMessages,
		useMemo(() => {
			const discussionListSize = getConfig('discussionListSize');
			return discussionListSize ? parseInt(discussionListSize, 10) : undefined;
		}, []),
	);
	useStreamUpdatesForMessageList(discussionsList, uid, options.rid);

	return {
		discussionsList,
		loadMoreItems,
		initialItemCount,
	};
};


import { SHA256 } from 'meteor/sha';
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import ldapEscape from 'ldap-escape';

import { slug, getLdapUsername, getLdapUserUniqueID, syncUserData, addLdapUser } from './sync';
import LDAP from './ldap';
import { settings } from '../../settings';
import { callbacks } from '../../callbacks';
import { Logger } from '../../logger';

const logger = new Logger('LDAPHandler', {});

function fallbackDefaultAccountSystem(bind, username, password) {
	if (typeof username === 'string') {
		if (username.indexOf('@') === -1) {
			username = { username };
		} else {
			username = { email: username };
		}
	}

	logger.info('Fallback to default account system', username);

	const loginRequest = {
		user: username,
		password: {
			digest: SHA256(password),
			algorithm: 'sha-256',
		},
	};

	return Accounts._runLoginHandlers(bind, loginRequest);
}

Accounts.registerLoginHandler('ldap', function(loginRequest) {
	if (!loginRequest.ldap || !loginRequest.ldapOptions) {
		return undefined;
	}

	logger.info('Init LDAP login', loginRequest.username);

	if (settings.get('LDAP_Enable') !== true) {
		return fallbackDefaultAccountSystem(this, loginRequest.username, loginRequest.ldapPass);
	}

	const self = this;
	const ldap = new LDAP();
	let ldapUser;

	const escapedUsername = ldapEscape.filter`${ loginRequest.username }`;

	try {
		ldap.connectSync();
		const users = ldap.searchUsersSync(escapedUsername);

		if (users.length !== 1) {
			logger.info('Search returned', users.length, 'record(s) for', escapedUsername);
			throw new Error('User not Found');
		}

		if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
			if (ldap.isUserInGroup(escapedUsername, users[0].dn)) {
				ldapUser = users[0];
			} else {
				throw new Error('User not in a valid group');
			}
		} else {
			logger.info('Wrong password for', escapedUsername);
		}
	} catch (error) {
		logger.error(error);
	}

	if (ldapUser === undefined) {
		return fallbackDefaultAccountSystem(self, loginRequest.username, loginRequest.ldapPass);
	}

	// Look to see if user already exists
	let userQuery;

	const Unique_Identifier_Field = getLdapUserUniqueID(ldapUser);
	let user;

	if (Unique_Identifier_Field) {
		userQuery = {
			'services.ldap.id': Unique_Identifier_Field.value,
		};

		logger.info('Querying user');
		logger.debug('userQuery', userQuery);

		user = Meteor.users.findOne(userQuery);
	}

	let username;

	if (settings.get('LDAP_Username_Field') !== '') {
		username = slug(getLdapUsername(ldapUser));
	} else {
		username = slug(loginRequest.username);
	}

	if (!user) {
		userQuery = {
			username,
		};

		logger.debug('userQuery', userQuery);

		user = Meteor.users.findOne(userQuery);
	}

	// Login user if they exist
	if (user) {
		if (user.ldap !== true && settings.get('LDAP_Merge_Existing_Users') !== true) {
			logger.info('User exists without "ldap: true"');
			throw new Meteor.Error('LDAP-login-error', `LDAP Authentication succeeded, but there's already an existing user with provided username [${ username }] in Mongo.`);
		}

		logger.info('Logging user');

		syncUserData(user, ldapUser, ldap);

		if (settings.get('LDAP_Login_Fallback') === true && typeof loginRequest.ldapPass === 'string' && loginRequest.ldapPass.trim() !== '') {
			Accounts.setPassword(user._id, loginRequest.ldapPass, { logout: false });
		}
		logger.info('running afterLDAPLogin');
		callbacks.run('afterLDAPLogin', { user, ldapUser, ldap });
		return {
			userId: user._id,
		};
	}

	logger.info('User does not exist, creating', username);

	if (settings.get('LDAP_Username_Field') === '') {
		username = undefined;
	}

	if (settings.get('LDAP_Login_Fallback') !== true) {
		loginRequest.ldapPass = undefined;
	}

	// Create new user
	const result = addLdapUser(ldapUser, username, loginRequest.ldapPass, ldap);

	if (result instanceof Error) {
		throw result;
	}
	callbacks.run('afterLDAPLogin', { user: result, ldapUser, ldap });

	return result;
});

let LDAP_Enable;
settings.get('LDAP_Enable', (key, value) => {
	if (LDAP_Enable === value) {
		return;
	}
	LDAP_Enable = value;

	if (!value) {
		return callbacks.remove('beforeValidateLogin', 'validateLdapLoginFallback');
	}

	callbacks.add('beforeValidateLogin', (login) => {
		if (!login.allowed) {
			return login;
		}

		// The fallback setting should only block password logins, so users that have other login services can continue using them
		if (login.type !== 'password') {
			return login;
		}

		if (login.user.services && login.user.services.ldap && login.user.services.ldap.id) {
			login.allowed = !!settings.get('LDAP_Login_Fallback');
		}

		return login;
	}, callbacks.priority.MEDIUM, 'validateLdapLoginFallback');
});