Home Reference Source

src/collection.js

import Document from './document'
import DocumentImporter from './importer'
import { propertyHider } from './util'
import snakeCase from 'lodash/snakeCase'
import camelCase from 'lodash/camelCase'
import isAbsolutePath from 'path-is-absolute'
import { resolve } from 'path'
import flatten from 'lodash/flatten'
import pathToRegexp from 'path-to-regexp'

const PACKAGE = 'PACKAGE'
const SCRIPT = 'SCRIPT'
const DOCUMENT = 'DOCUMENT'
const SETTINGS_FILE = 'SETTINGS_FILE'
const COPY_FILE = 'COPY_FILE'
const PROJECT = 'PROJECT'
const VECTOR = 'VECTOR'

export const PatternMapping = {
  'Package': ['**/package.json'],
  'Document': ['**/*.md'],
  'Script': ['**/*.js'],
  'SettingsFile': ['**/*.yml'],
  'CopyFile': ['**/*.yml'],
  'Vector': ['**/*.svg'],
  'Project': ['**/package.json', '**/*.js', '**/*.md', '**/*.yml']
}

export function create(options = {}) {
  return Collection.create(options)
}

export class Collection {
  static mount (base = process.cwd(), options = {}) {
    if (base.startsWith('.')) {
      base = base.replace(/^\./, process.cwd())
    }

    if (!isAbsolutePath(base)) {
      base = resolve(process.cwd(), base)
    }

    return create({
      cwd: base,
      base,
      ...options,
    }).imported.cached
  }

  static create (options = {}) {
    return new Collection(options)
  }

  constructor (options = {}) {
    const hide = propertyHider(this, true)

    hide('hide', hide)
    hide('base', options.base || process.cwd())
    hide('files', [])
    hide('options', { ignore: [], ...options })

    this.importer = DocumentImporter.create(
      this.base,
      this.patterns,
    )

    const cache = {}
    hide('cache', cache)

    const scopes = {}
    hide('_scopes', scopes)
  }

  get scopes() {
    const collection = this
    const base = {
      available: Object.keys(collection._scopes),
    }

    base.available.forEach(
      scopeName => Object.defineProperty(base, scopeName, {
        configurable: false,
        false: true,
        get: function() {
          const cfg = collection.scope(scopeName)
          return collection.files.filter(file => file.path.match(cfg.route))
        }
      })
    )

    return base
  }

  get patterns() {
    return this.typePatterns.concat(this.ignorePatterns)
  }

  get ignorePatterns () {
    return this.options.ignore.map(p => p.startsWith('!') ? p : `!${p}`).concat('!node_modules/**')
  }

  get imported() {
    if(!this.filesLoaded) {
      this.loadFiles(this.importer.readFilesSync(false))
    }

    return this.cached
  }

  loadFiles (files) {
    this.files.length = 0

    this.files.push(
      ...files.map(file => {
        return file
      })
    )

    this.filesLoaded = true

    return this.files
  }

  runImporter(cacheDocuments = true) {
    return this.importer.readFiles(false)
      .then(files => this.files.push(...files))
      .then(() => cacheDocuments ? this.cached : this)
      .catch(error => error)
  }

  get scope() {
    let fn = this.defineScope.bind(this)

    return Object.assign(fn, this.scopes)

    return fn
  }

  defineScope (scopeName, routePattern, options) {
    const name = camelCase(scopeName)

    if (this._scopes[name] && !routePattern && !options) {
      return this._scopes[name]
    }

    return this._scopes[name] = {
      route: pathToRegexp(routePattern),
      key: name,
      name: scopeName,
      ...(options || {}),
    }
  }

  get ready() {
    return this.importer.status === 'LOADED'
      ? Promise.resolve(this)
      : this.runImporter().then(importer => importer)
  }

  get documentOptions() {
    return {
      base: this.base,
      collectionType: this.options.type,
    }
  }

  get cached() {
    this.files.forEach(file => {
      this.cache[file.path] = this.cache[file.path]
      || new Document(file.path, this.documentOptions)
    })

    return this
  }

  get documents() {
    return Object.values(this.cache)
  }

  get type() {
    return snakeCase(this.options.type || 'Project').replace(/s$/i, '')
  }

  get typePatterns () {
    switch (this.type.toUpperCase()) {
        case PACKAGE:
          return PatternMapping.Package
          break
        case SCRIPT:
          return PatternMapping.Script
          break
        case SETTINGS_FILE, COPY_FILE:
          return PatternMapping.SettingsFile
          break
        case DOCUMENT:
          return PatternMapping.Document
          break
        case VECTOR:
          return PatternMapping.Vector
          break
        case PROJECT:
        default:
          return PatternMapping.Project
    }
  }
}

export default Collection