<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Button, Input, Select, Card, Alert, Spin, Icon } from 'antd'
import { autobind } from 'core-decorators';
import crossRequest from 'cross-request';
import { withRouter } from 'react-router';
import axios from 'axios';
import URL from 'url';

import {
} from '../../../actions/group.js'

import './InterfaceTest.scss'

const { TextArea } = Input;
const InputGroup = Input.Group;
const Option = Select.Option;

@connect(
  state => ({
    reqParams: state.addInterface.reqParams,
    method: state.addInterface.method,
    url: state.addInterface.url,
    seqGroup: state.addInterface.seqGroup,
    interfaceName: state.addInterface.interfaceName,
    interfaceProject: state.addInterface.project
  }),
  {
  }
)
@withRouter
export default class InterfaceTest extends Component {

  static propTypes = {
    reqParams: PropTypes.string,
    method: PropTypes.string,
    url: PropTypes.string,
    interfaceName: PropTypes.string,
    seqGroup: PropTypes.array,
    match: PropTypes.object,
    interfaceProject: PropTypes.object
  }

  state = {
    res: '',
    method: 'GET',
    domains: [],
    pathname: '',
    query: {},
    params: {},
    paramsNotJson: false,
    headers: {},
    currDomain: ''
  }

  constructor(props) {
    super(props)
  }

  componentWillMount() {
    this.getInterfaceState()
  }

  componentWillReceiveProps(nextProps) {
    this.getInterfaceState(nextProps)
  }

  @autobind
  getInterfaceState(nextProps) {
    const props = nextProps || this.props;
    const { method, url, seqGroup, interfaceProject } = props;
    const { prd_host, basepath, protocol, env } = interfaceProject;
    const pathname = (basepath + url).replace(/\/+/g, '/');

    const domains = {prd: protocol + '://' + prd_host};
    env.forEach(item => {
      domains[item.name] = item.domain;
    })

    const query = [];
    let params = {};
    let reqParams = this.props.reqParams ? this.props.reqParams : '{}';
    let paramsNotJson = false;
    try {
      reqParams = JSON.parse(reqParams)
      paramsNotJson = false;
    } catch (e) {
      paramsNotJson = true;
    }
    if (method === 'GET') {
      Object.keys(reqParams).forEach(key => {
        const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams[key]) : reqParams[key].toString();
        query.push({key, value})
      })
    } else if (method === 'POST') {
      params = reqParams;
    }

    const headers = {}
    seqGroup.forEach((headerItem) => {
      if (headerItem.name) {
        headers[headerItem.name] = headerItem.value;
      }
    })

    this.setState({
      method,
      domains,
      pathname,
      query,
      params,
      paramsNotJson,
      headers,
      currDomain: domains.prd,
      loading: false
    });
  }

  @autobind
  requestInterface() {
    const { headers, params, currDomain, method, pathname, query } = this.state;
    const urlObj = URL.parse(currDomain);

    const href = URL.format({
      protocol: urlObj.protocol || 'http',
      host: urlObj.host,
      pathname,
      query: this.getQueryObj(query)
    });

    this.setState({ loading: true })

    crossRequest({
      url: href,
      method,
      headers,
      data: params,
      success: (res, header) => {
        console.log(header)
        try {
          res = JSON.parse(res);
        } catch (e) {
          null;
        }
        this.setState({res})
        this.setState({ loading: false })
      },
      error: (err, header) => {
        console.log(header)
        this.setState({res: err || '请求失败'})
        this.setState({ loading: false })
      }
    })
  }

  @autobind
  changeDomain(value) {
    this.setState({ currDomain: value });
  }

  @autobind
  selectDomain(value) {
    this.setState({ currDomain: value });
  }

  @autobind
  changeHeader(e, key) {
    const headers = JSON.parse(JSON.stringify(this.state.headers));
    headers[key] = e.target.value;
    this.setState({ headers });
  }

  @autobind
  changeQuery(e, index, isKey) {
    const query = JSON.parse(JSON.stringify(this.state.query));
    const v = e.target.value;
    if (isKey) {
      query[index].key = v;
    } else {
      query[index].value = v;
    }
    this.setState({ query });
  }
  @autobind
  addQuery() {
    const { query } = this.state;
    this.setState({query: query.concat([{key: '', value: ''}])})
  }
  @autobind
  deleteQuery(index) {
    const { query } = this.state;
    this.setState({query: query.filter((item, i) => +index !== +i)});
  }

  @autobind
  changeParams(e, key) {
    const params = JSON.parse(JSON.stringify(this.state.params));
    params[key] = e.target.value;
    this.setState({ params });
  }

  @autobind
  changeMethod(value) {
    this.setState({ method: value });
  }

  @autobind
  changePath(e) {
    const path = e.target.value;
    const urlObj = URL.parse(path, true);
    this.setState({
      query: urlObj.query,
      pathname: urlObj.pathname
    })
  }

  hasCrossRequestPlugin() {
    const dom = document.getElementById('y-request');
    return dom.getAttribute('key') === 'yapi';
  }

  getQueryObj(query) {
    const queryObj = {};
    query.forEach(item => {
      if (item.key) {
        queryObj[item.key] = item.value || '';
      }
    })
    return queryObj;
  }

  render () {

    const { interfaceName } = this.props;
    const { method, domains, pathname, query, headers, params, paramsNotJson, currDomain } = this.state;
    const hasPlugin = this.hasCrossRequestPlugin();
    const search = decodeURIComponent(URL.format({query: this.getQueryObj(query)}));

    console.log(axios)
    window.axios = axios

    return (

          { hasPlugin ? '' :
          <Alert
            message={

                温馨提示：当前正在使用接口测试服务，请安装我们为您免费提供的

                  测试增强插件 [点击获取]！

            }
            type="warning"
          />
          }

{interfaceName}

        {/* url */}

            <InputGroup compact style={{display: 'inline-block', width: 680, border: 0, background: '#fff', marginBottom: -4}}>
              <Input value="Method" disabled style={{display: 'inline-block', width: 80, border: 0, background: '#fff'}} />
              <Input value="Domain" disabled style={{display: 'inline-block', width: 300, border: 0, background: '#fff'}} />
              <Input value="Basepath + Url + [Query]" disabled style={{display: 'inline-block', width: 300, border: 0, background: '#fff'}} />
            </InputGroup>
            <InputGroup compact style={{display: 'inline-block', width: 680}}>
              <Select value={method} style={{display: 'inline-block', width: 80}} onChange={this.changeMethod} >
                <Option value="GET">GET</Option>
                <Option value="POST">POST</Option>
              </Select>
              <Select value={currDomain} mode="combobox" filterOption={() => true} style={{display: 'inline-block', width: 300}} onChange={this.changeDomain} onSelect={this.selectDomain}>
                {
                  Object.keys(domains).map((key, index) => (<Option value={domains[key]} key={index}>{key + '：' + domains[key]}</Option>))
                }
              </Select>
              <Input value={pathname + search} onChange={this.changePath} spellCheck="false" style={{display: 'inline-block', width: 300}} />
            </InputGroup>
            <Button
              onClick={this.requestInterface}
              type="primary"
              style={{marginLeft: 10}}
              loading={this.state.loading}
            >发送</Button>
            （请求测试真实接口）

          <Card title="Query" noHovering style={{marginTop: 10}}>
            {
              query.map((item, index) => {
                return (

                    <Input value={item.key} onChange={e => this.changeQuery(e, index, true)} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                    <Input value={item.value} onChange={e => this.changeQuery(e, index)} style={{display: 'inline-block', width: 200, margin: 10}} />
                    <Icon type="close" className="icon-btn" onClick={() => this.deleteQuery(index)} />

                )
              })
            }
            <Button type="primary" icon="plus" onClick={this.addQuery}>Add query parameter</Button>
          </Card>
          <Card title="HEADERS" noHovering style={{marginTop: 10}} className={Object.keys(headers).length ? '' : 'hidden'}>

              {
                Object.keys(headers).map((key, index) => {
                  return (

                      <Input value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={headers[key]} onChange={e => this.changeHeader(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />

                  )
                })
              }
              <Button type="primary" icon="plus">Add header</Button>

          </Card>
          <Card title="Body" noHovering style={{marginTop: 10}} className={method === 'POST' ? '' : 'hidden'}>

              { paramsNotJson ?
                <TextArea
                  value={params}
                  style={{margin: 10}}
                  autosize={{ minRows: 2, maxRows: 6 }}
                ></TextArea> :
                Object.keys(params).map((key, index) => {
                  const value = typeof params[key] === 'object' ? JSON.stringify(params[key]) : params[key].toString();
                  return (

                      <Input value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={value} onChange={e => this.changeParams(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />

                  )
                })
              }
              <Button type="primary" icon="plus">Add form parameter</Button>

          </Card>

        <Card title="返回结果" noHovering style={{marginTop: 10}}>
          <Spin spinning={this.state.loading}>

                <TextArea
                  value={typeof this.state.res === 'object' ? JSON.stringify(this.state.res, null, 2) : this.state.res.toString()}
                  autosize={{ minRows: 2, maxRows: 6 }}
                ></TextArea>

          </Spin>
        </Card>

    )
  }
}
<commit_msg>feat: 完善接口测试功能<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Button, Input, Select, Card, Alert, Spin, Icon, message } from 'antd'
import { autobind } from 'core-decorators';
import crossRequest from 'cross-request';
import { withRouter } from 'react-router';
import axios from 'axios';
import URL from 'url';

import {
} from '../../../actions/group.js'

import './InterfaceTest.scss'

const { TextArea } = Input;
const InputGroup = Input.Group;
const Option = Select.Option;

@connect(
  state => ({
    reqParams: state.addInterface.reqParams,
    method: state.addInterface.method,
    url: state.addInterface.url,
    seqGroup: state.addInterface.seqGroup,
    interfaceName: state.addInterface.interfaceName,
    interfaceProject: state.addInterface.project
  }),
  {
  }
)
@withRouter
export default class InterfaceTest extends Component {

  static propTypes = {
    reqParams: PropTypes.string,
    method: PropTypes.string,
    url: PropTypes.string,
    interfaceName: PropTypes.string,
    seqGroup: PropTypes.array,
    match: PropTypes.object,
    interfaceProject: PropTypes.object
  }

  state = {
    res: '',
    method: 'GET',
    domains: [],
    pathname: '',
    query: {},
    params: {},
    paramsNotJson: false,
    headers: {},
    currDomain: '',
    paramsType: 'from'
  }

  constructor(props) {
    super(props)
  }

  componentWillMount() {
    this.getInterfaceState()
  }

  componentWillReceiveProps(nextProps) {
    this.getInterfaceState(nextProps)
  }

  @autobind
  getInterfaceState(nextProps) {
    const props = nextProps || this.props;
    const { method, url, seqGroup, interfaceProject } = props;
    const { prd_host, basepath, protocol, env } = interfaceProject;
    const pathname = (basepath + url).replace(/\/+/g, '/');

    const domains = {prd: protocol + '://' + prd_host};
    env.forEach(item => {
      domains[item.name] = item.domain;
    })

    const query = [];
    let params = [];
    let reqParams = this.props.reqParams ? this.props.reqParams : '{}';
    let paramsNotJson = false;
    try {
      reqParams = JSON.parse(reqParams);
      // paramsNotJson = false;
    } catch (e) {
      // paramsNotJson = true;
      reqParams = {};
      message.error('请求参数不是 JSON 格式');
    }
    if (method === 'GET') {
      Object.keys(reqParams).forEach(key => {
        const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams[key]) : reqParams[key].toString();
        query.push({key, value})
      })
    } else if (method === 'POST') {
      // params = reqParams;
      Object.keys(reqParams).forEach(key => {
        const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams[key]) : reqParams[key].toString();
        query.push({key, value, type: 'text'})
      })
    }

    const headers = []
    seqGroup.forEach((headerItem) => {
      if (headerItem.name) {
        headers.push({name: headerItem.name, value: headerItem.value});
      }
    })

    this.setState({
      method,
      domains,
      pathname,
      query,
      params,
      paramsNotJson,
      headers,
      currDomain: domains.prd,
      loading: false,
      paramsType: 'form'
    });
  }

  @autobind
  requestInterface() {
    const { headers, params, currDomain, method, pathname, query } = this.state;
    const urlObj = URL.parse(currDomain);

    const href = URL.format({
      protocol: urlObj.protocol || 'http',
      host: urlObj.host,
      pathname,
      query: this.getQueryObj(query)
    });

    this.setState({ loading: true })

    crossRequest({
      url: href,
      method,
      headers,
      data: params,
      success: (res, header) => {
        console.log(header)
        try {
          res = JSON.parse(res);
        } catch (e) {
          null;
        }
        this.setState({res})
        this.setState({ loading: false })
      },
      error: (err, header) => {
        console.log(header)
        this.setState({res: err || '请求失败'})
        this.setState({ loading: false })
      }
    })
  }

  @autobind
  changeDomain(value) {
    this.setState({ currDomain: value });
  }

  @autobind
  selectDomain(value) {
    this.setState({ currDomain: value });
  }

  @autobind
  changeHeader(e, index, isName) {
    const headers = JSON.parse(JSON.stringify(this.state.headers));
    const v = e.target.value;
    if (isName) {
      headers[index].name = v;
    } else {
      headers[index].value = v;
    }
    this.setState({ headers });
  }
  @autobind
  addHeader() {
    const { headers } = this.state;
    this.setState({headers: headers.concat([{name: '', value: ''}])})
  }
  @autobind
  deleteHeader(index) {
    const { headers } = this.state;
    this.setState({headers: headers.filter((item, i) => +index !== +i)});
  }

  @autobind
  changeQuery(e, index, isKey) {
    const query = JSON.parse(JSON.stringify(this.state.query));
    const v = e.target.value;
    if (isKey) {
      query[index].key = v;
    } else {
      query[index].value = v;
    }
    this.setState({ query });
  }
  @autobind
  addQuery() {
    const { query } = this.state;
    this.setState({query: query.concat([{key: '', value: ''}])})
  }
  @autobind
  deleteQuery(index) {
    const { query } = this.state;
    this.setState({query: query.filter((item, i) => +index !== +i)});
  }

  @autobind
  changeParams(e, index, type) {
    const params = JSON.parse(JSON.stringify(this.state.params));
    switch (type) {
      case 'key':
        params[index].key = e.target.value
        break;
      case 'type':
        params[index].type = e
        break;
      case 'value':
        params[index].value = e.target.value
        break;
      default:
        break;
    }
    this.setState({ params });
  }
  @autobind
  addParams() {
    const { params } = this.state;
    this.setState({params: params.concat([{key: '', value: '', type: 'text'}])})
  }
  @autobind
  deleteParams(index) {
    const { params } = this.state;
    this.setState({params: params.filter((item, i) => +index !== +i)});
  }

  @autobind
  changeMethod(value) {
    this.setState({ method: value });
  }

  @autobind
  changePath(e) {
    const path = e.target.value;
    const urlObj = URL.parse(path, true);
    this.setState({
      query: urlObj.query,
      pathname: urlObj.pathname
    })
  }

  @autobind
  changeParamsType(value) {
    this.setState({paramsType: value})
  }

  hasCrossRequestPlugin() {
    const dom = document.getElementById('y-request');
    return dom.getAttribute('key') === 'yapi';
  }

  getQueryObj(query) {
    const queryObj = {};
    query.forEach(item => {
      if (item.key) {
        queryObj[item.key] = item.value || '';
      }
    })
    return queryObj;
  }

  render () {

    const { interfaceName } = this.props;
    const { method, domains, pathname, query, headers, params, currDomain, paramsType } = this.state;
    const hasPlugin = this.hasCrossRequestPlugin();
    const search = decodeURIComponent(URL.format({query: this.getQueryObj(query)}));

    console.log(axios)
    window.axios = axios

    return (

          { hasPlugin ? '' :
          <Alert
            message={

                温馨提示：当前正在使用接口测试服务，请安装我们为您免费提供的

                  测试增强插件 [点击获取]！

            }
            type="warning"
          />
          }

{interfaceName}

        {/* url */}

            <InputGroup compact style={{display: 'inline-block', width: 680, border: 0, background: '#fff', marginBottom: -4}}>
              <Input value="Method" disabled style={{display: 'inline-block', width: 80, border: 0, background: '#fff'}} />
              <Input value="Domain" disabled style={{display: 'inline-block', width: 300, border: 0, background: '#fff'}} />
              <Input value="Basepath + Url + [Query]" disabled style={{display: 'inline-block', width: 300, border: 0, background: '#fff'}} />
            </InputGroup>
            <InputGroup compact style={{display: 'inline-block', width: 680}}>
              <Select value={method} style={{display: 'inline-block', width: 80}} onChange={this.changeMethod} >
                <Option value="GET">GET</Option>
                <Option value="POST">POST</Option>
              </Select>
              <Select value={currDomain} mode="combobox" filterOption={() => true} style={{display: 'inline-block', width: 300}} onChange={this.changeDomain} onSelect={this.selectDomain}>
                {
                  Object.keys(domains).map((key, index) => (<Option value={domains[key]} key={index}>{key + '：' + domains[key]}</Option>))
                }
              </Select>
              <Input value={pathname + search} onChange={this.changePath} spellCheck="false" style={{display: 'inline-block', width: 300}} />
            </InputGroup>
            <Button
              onClick={this.requestInterface}
              type="primary"
              style={{marginLeft: 10}}
              loading={this.state.loading}
            >发送</Button>
            （请求测试真实接口）

          <Card title="Query" noHovering style={{marginTop: 10}}>
            {
              query.map((item, index) => {
                return (

                    <Input value={item.key} onChange={e => this.changeQuery(e, index, true)} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                    <Input value={item.value} onChange={e => this.changeQuery(e, index)} style={{display: 'inline-block', width: 200, margin: 10}} />
                    <Icon type="close" className="icon-btn" onClick={() => this.deleteQuery(index)} />

                )
              })
            }
            <Button type="primary" icon="plus" onClick={this.addQuery}>Add query parameter</Button>
          </Card>
          <Card title="HEADERS" noHovering style={{marginTop: 10}} >

              {
                headers.map((item, index) => {
                  return (

                      <Input value={item.name} onChange={e => this.changeHeader(e, index, true)} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={item.value} onChange={e => this.changeHeader(e, index)} style={{display: 'inline-block', width: 200, margin: 10}} />
                      <Icon type="close" className="icon-btn" onClick={() => this.deleteHeader(index)} />

                  )
                })
              }
              <Button type="primary" icon="plus" onClick={this.addHeader}>Add header</Button>

          </Card>
          <Card title="Body" noHovering style={{marginTop: 10}}>

              <Select defaultValue={paramsType} onChange={this.changeParamsType} className={method === 'POST' ? '' : 'hidden'}>
                <Option value="text">Text</Option>
                <Option value="file">File</Option>
                <Option value="form">Form</Option>
              </Select>
              { method === 'POST' && paramsType !== 'form' && paramsType !== 'file' &&

                  <TextArea
                    value={params}
                    style={{margin: 10}}
                    autosize={{ minRows: 2, maxRows: 6 }}
                  ></TextArea>

{paramsType}

              }
              {
                method === 'POST' && paramsType === 'form' && (

                    {
                      params.map((item, index) => {
                        return (

                            <Input value={item.key} onChange={e => this.changeParams(e, index, 'key')} style={{display: 'inline-block', width: 200, margin: 10}} />
                            [<Select value={item.type} onChange={e => this.changeParams(e, index, 'type')}>
                              <Option value="file">File</Option>
                              <Option value="text">Text</Option>
                            </Select>]{' = '}
                            {item.type === 'file' ?
                              <Input type="file" style={{display: 'inline-block', width: 200, margin: 10}} /> :
                              <Input value={item.value} onChange={e => this.changeParams(e, index, 'value')} style={{display: 'inline-block', width: 200, margin: 10}} />
                            }

                        )
                      })
                    }
                    <Button type="primary" icon="plus" onClick={this.addParams}>Add form parameter</Button>

                )
              }
              {
                method === 'POST' && paramsType === 'file' && (

                    <Input type="file"></Input>

                )
              }
              {
                method !== 'POST' && (

GET 请求没有 Body。

                )
              }

          </Card>

        <Card title="返回结果" noHovering style={{marginTop: 10}}>
          <Spin spinning={this.state.loading}>

                <TextArea
                  value={typeof this.state.res === 'object' ? JSON.stringify(this.state.res, null, 2) : this.state.res.toString()}
                  autosize={{ minRows: 2, maxRows: 6 }}
                ></TextArea>

          </Spin>
        </Card>

    )
  }
}
<|endoftext|>


<commit_before>'use strict';

var async = require('async')
	, chalk = require('chalk')
	, clearIDCache = require('identify-resource').clearCache
	, clearTemplateCache = require('transfigure').clearCache
	, cnsl = require('./utils/cnsl')
	, compact = require('lodash/array/compact')
	, difference = require('lodash/array/difference')
	, distinct = require('lodash/array/unique')
	, extend = require('lodash/object/extend')
	, fileFactory = require('./file')
	, flatten = require('lodash/array/flatten')
	, fs = require('fs')
	, path = require('path')
	, pathname = require('./utils/pathname')
	, recurfs = require('recur-fs')
	, unique = require('./utils/unique')

	, debug = cnsl.debug
	, indir = recurfs.indir
	, parallel = async.parallel
	, print = cnsl.print
	, readdir = recurfs.readdir.sync
	, series = async.series
	, strong = cnsl.strong
	, warn = cnsl.warn
	, waterfall = async.waterfall;

/**
 * Target instance factory
 * @param {Object} data
 * @returns {Target}
 */
module.exports = function (data) {
	var instance = new Target(data);

	return instance;
};

/**
 * Constructor
 * @param {Object} props
 */
function Target (props) {
/* Decorated properties
	this.bootstrap = false;
	this.boilerplate = false;
	this.fileExtensions = null;
	this.hasChildren = false;
	this.input = '';
	this.inputpath = '';
	this.appServer = false;
	this.batch = false;
	this.modular = false;
	this.output = '';
	this.outputpath = '';
	this.parent = null;
	this.runtimeOptions = null;
	this.sources = null;
	this.targets = null;
	this.workflows = null;
*/
	extend(this, props);
	this.isRoot = !this.inputpath;
	this.referencedFiles = [];
	this.fileFactoryOptions = {
		fileExtensions: this.fileExtensions,
		sources: this.sources,
		runtimeOptions: this.runtimeOptions
	};

	// Prepare file filter
	if (this.fileExtensions) {
		var exts = [];

		for (var type in this.fileExtensions) {
			exts = exts.concat(this.fileExtensions[type]);
		}
		this.fileFilter = new RegExp(exts.join('$|') + '$');
	}

	// Print
	if (this.isRoot) {
		debug('created root Target instance');
	} else {
		// Handle printing long input arrays
		if (Array.isArray(this.inputpath)) {
			this.inputString = this.inputpath.map(function (input) {
				return pathname(input);
			});
			// Trim long lists
			if (this.inputString.length > 3) {
				this.inputString = this.inputString.slice(0, 3).join(', ')
					+ '...and '
					+ (this.inputString.length - 3)
					+ ' others';
			} else {
				this.inputString = this.inputString.join(', ');
			}
		} else {
			this.inputString = pathname(this.inputpath);
		}

		debug('created Target instance with input: '
			+ strong(this.inputString)
			+ ' and output: '
			+ strong(this.output), 2);
	}
}

/**
 * Run build
 * @param {Function} fn(err, filepaths)
 */
Target.prototype.build = function (fn) {
	var self = this;

	if (this.isRoot) {
		// Execute 'before' hook
		waterfall([function (done) {
			self.executeHook('before', self, done);
		},

		// Build targets
		function (done) {
			series(self.targets.map(function (target) {
				return target.build.bind(target);
			}), function (err, results) {
				done(err, flatten(results));
			});
		},

		// Execute 'after' hook
		function (filepaths, done) {
			self.executeHook('after', self, function (err) {
				done(err, filepaths);
			});
		}], fn);

	} else {
		var watching = !this.outputpath && this.runtimeOptions.watch
			, timerID = Array.isArray(this.input)
				? this.input[0]
				: this.input;

		this.referencedFiles = [];

		cnsl.start(timerID);

		print((watching ? 'watching ' : 'building ')
			+ strong(this.inputString)
			+ (this.outputpath ? ' to ' + strong(path.basename(this.outputpath)) : ''), 1);

		// Execute 'before' hook
		waterfall([function (done) {
			self.executeHook('before', self, done);
		},

		// Parse source files
		function (done) {
			done(null, self.parse(self.batch, self.inputpath, self.fileFilter, self.fileFactoryOptions));
		},

		// Process files
		function (files, done) {
			self.process(files, self.workflows, watching, done);
		},

		// Execute 'afterEach' hooks
		function (files, done) {
			parallel(files.map(function (file) {
				// Memoize
				return self.executeHook.bind(self, 'afterEach', file);
			}), function (err) {
				done(err, files);
			});
		},

		// Write writeable files
		function (files, done) {
			print('[processed '
				+ strong(self.referencedFiles.length)
				+ (self.referencedFiles.length > 1 ? ' files' : ' file')
				+ ' in '
				+ chalk.cyan((cnsl.stop(timerID) / 1000) + 's')
				+ ']', 2);
			done(null, self.write(files));
		},

		// Build child targets
		function (filepaths, done) {
			if (self.hasChildren) {
				// Lock files to prevent inclusion in downstream targets
				self.lock(self.referencedFiles);
				series(self.targets.map(function (target) {
					return target.build.bind(target);
				}), function (err, results) {
					// Add new filepaths
					filepaths = filepaths.concat(flatten(results));
					self.unlock(self.referencedFiles);
					done(err, filepaths);
				});
			} else {
				done(null, filepaths);
			}
		},

		// Execute 'after' hook
		function (filepaths, done) {
			self.executeHook('after', self, function (err) {
				done(err, filepaths);
			});
		},

		// Reset
		function (filepaths, done) {
			self.reset();
			done(null, filepaths);
		}], fn);
	}
};

/**
 * Parse 'input' source files
 * @param {Boolean} batch
 * @param {String|Array} input
 * @param {RegExp} filter
 * @param {Object} options
 * @returns {Array}
 */
Target.prototype.parse = function (batch, input, filter, options) {
	filter = filter || /./;

	var files = []
		, factory = function (filepath) {
				return fileFactory(filepath, options);
			};

	// Input is directory or array
	if (batch) {
		// Grab all files (filtered by file extension)
		var inputs = !Array.isArray(input) ? [input] : input
			, filepaths = flatten(inputs.map(function (inpt) {
					// Directory
					if (!path.extname(inpt).length) {
						return readdir(inpt, function (resource, stat) {
							return stat.isFile() && filter.test(resource);
						});
					}

					return inpt;
				}));

		// Create file instances
		files = filepaths
			.map(factory)
			.filter(function (file) {
				return file != null;
			});
		if (!files.length) warn('no valid source files found in ' + strong(input), 4);

	// Input is file
	} else {
		var file;

		// Create File instance
		if (file = factory(input)) {
			files = [file];
		} else {
			warn(strong(input) + ' not found in project sources', 4);
		}
	}

	return files;
};

/**
 * Process batch workflow 'commands' for 'files'
 * @param {Array} files
 * @param {Object} workflows
 * @param {Boolean} watching
 * @param {Function} fn(err, files)
 */
Target.prototype.process = function (files, workflows, watching, fn) {
	var self = this;

	// Parse conditional workflow tasks
	function parseWorkflow (workflow) {
		return workflow.reduce(function (result, task) {
			if (!~task.indexOf(':')) {
				result.push(task);
			// Conditional task
			} else {
				var conditions = task.split(':');

				task = conditions.pop();
				if (self[conditions[0]]
					&& (conditions.length > 1 ? self[conditions[1]] : true)) {
						result.push(task);
				}
			}
			return result;
		}, []);
	}

	// Recursive batch run 'someFiles'
	function batch (someFiles, idx, done) {
		parallel(someFiles.map(function (file) {
			// Memoize
			return file.run.bind(file, parseWorkflow(workflows[file.type][idx]), watching);
		}), function (err, dependencies) {
			if (err) return done(err);

			// Process files not yet processed
			dependencies = difference(distinct(compact(flatten(dependencies))), files);
			if (dependencies.length) {
				// Store
				files = files.concat(dependencies);
				return batch(dependencies, idx, done);
			}

			done(null, files);
		});
	}

	// Batch run first set of workflow tasks
	batch(files, 0, function (err) {
		if (err) return fn(err);

		// Store all referenced files
		self.referencedFiles = files;

		// Filter writeable files
		files = files.filter(function (file) {
			return file.getIsWriteable();
		});

		// Second set only operates on writeable files
		if (files.length) {
			batch(files, 1, fn);
		} else {
			fn(null, files);
		}
	});
};

/**
 * Write generated content
 * @param {Array} files
 * @returns {Array}
 */
Target.prototype.write = function (files) {
	var options = {
		bootstrap: this.bootstrap,
		boilerplate: this.boilerplate
	};

	// Write files in sequence
	return files.map(function (file) {
		// Don't write if no output path
		if (this.outputpath) {
			var filepath = this.outputpath;

			// Resolve output path if directory
			if (!path.extname(filepath).length) {
				var outputInSources = this.sources.some(function (source) {
					return indir(source, this.outputpath);
				}, this);

				// Swap input for output path if in sources in order to preserve relative package structure
				if (outputInSources) {
					// TODO: find common root directory in case of Array/glob etc
					var input = Array.isArray(this.inputpath) ? file.filepath : this.inputpath;

					// Resolve directory if input is a file
					if (path.extname(input).length) input = path.dirname(input);
					filepath = file.filepath.replace(input, this.outputpath);
				} else {
					var id = file.id;

					// Find id relative to source dir
					this.sources.forEach(function (source) {
						if (~file.filepath.indexOf(source)) {
							id = path.relative(source, file.filepath);
						}
					}, this);

					filepath = path.join(filepath, id);
				}

				var extension = path.extname(filepath);

				// Resolve missing extension
				if (!extension) filepath += '.' + file.type;
				if (extension != '.' + file.type) filepath = filepath.replace(extension, '.' + file.type);
			}

			// Handle generating unique paths
			if (unique.isUniquePattern(filepath)) {
				// Remove existing
				var existing = unique.find(filepath);

				if (existing && fs.existsSync(existing)) fs.unlinkSync(existing);

				// Generate unique path
				// Disable during watch otherwise css reloading won't work
				filepath = unique.generate(filepath, !this.runtimeOptions.watch ? file.content : false);
			}

			// Write file
			return file.write(filepath, options);
		}
	}, this);
};

/**
 * Set lock flag for 'files'
 * @param {Array} files
 */
Target.prototype.lock = function (files) {
	files.forEach(function (file) {
		file.isLocked = true;
	});
};

/**
 * Unset lock flag for 'files'
 * @param {Array} files
 */
Target.prototype.unlock = function (files) {
	files.forEach(function (file) {
		file.isLocked = false;
	});
};

/**
 * Determine if 'file' is a referenced file (child targets included)
 * @param {File} file
 * @returns {Boolean}
 */
Target.prototype.hasFile = function (file) {
	var referenced;

	referenced = ~this.referencedFiles.indexOf(file);
	if (referenced) {
		return true;
	} else if (this.hasChildren) {
		for (var i = 0, n = this.targets.length; i < n; i++) {
			referenced = this.targets[i].hasFile(file);
			if (referenced) return true;
		}
	}
	return false;
};

/**
 * Reset modified files
 */
Target.prototype.reset = function () {
	this.referencedFiles.forEach(function (file) {
		file.reset();
	});
	clearIDCache();
	clearTemplateCache();
};

/**
 * Execute the 'hook' function with a particular 'context'
 * @param {String} hook
 * @param {Object} context
 * @param {Function} fn(err)
 */
Target.prototype.executeHook = function (hook, context, fn) {
	if (this[hook]) {
		print('executing ' + hook + ' hook...', 2);
		// Make global objects available to the function
		this[hook](global, process, console, require, context, this.runtimeOptions, fn);
	} else {
		fn();
	}
};
<commit_msg>skip build if only watching<commit_after>'use strict';

var async = require('async')
	, chalk = require('chalk')
	, clearIDCache = require('identify-resource').clearCache
	, clearTemplateCache = require('transfigure').clearCache
	, cnsl = require('./utils/cnsl')
	, compact = require('lodash/array/compact')
	, difference = require('lodash/array/difference')
	, distinct = require('lodash/array/unique')
	, extend = require('lodash/object/extend')
	, fileFactory = require('./file')
	, flatten = require('lodash/array/flatten')
	, fs = require('fs')
	, path = require('path')
	, pathname = require('./utils/pathname')
	, recurfs = require('recur-fs')
	, unique = require('./utils/unique')

	, debug = cnsl.debug
	, indir = recurfs.indir
	, parallel = async.parallel
	, print = cnsl.print
	, readdir = recurfs.readdir.sync
	, series = async.series
	, strong = cnsl.strong
	, warn = cnsl.warn
	, waterfall = async.waterfall;

/**
 * Target instance factory
 * @param {Object} data
 * @returns {Target}
 */
module.exports = function (data) {
	var instance = new Target(data);

	return instance;
};

/**
 * Constructor
 * @param {Object} props
 */
function Target (props) {
/* Decorated properties
	this.bootstrap = false;
	this.boilerplate = false;
	this.fileExtensions = null;
	this.hasChildren = false;
	this.input = '';
	this.inputpath = '';
	this.appServer = false;
	this.batch = false;
	this.modular = false;
	this.output = '';
	this.outputpath = '';
	this.parent = null;
	this.runtimeOptions = null;
	this.sources = null;
	this.targets = null;
	this.workflows = null;
*/
	extend(this, props);
	this.isRoot = !this.inputpath;
	this.referencedFiles = [];
	this.fileFactoryOptions = {
		fileExtensions: this.fileExtensions,
		sources: this.sources,
		runtimeOptions: this.runtimeOptions
	};

	// Prepare file filter
	if (this.fileExtensions) {
		var exts = [];

		for (var type in this.fileExtensions) {
			exts = exts.concat(this.fileExtensions[type]);
		}
		this.fileFilter = new RegExp(exts.join('$|') + '$');
	}

	// Print
	if (this.isRoot) {
		debug('created root Target instance');
	} else {
		// Handle printing long input arrays
		if (Array.isArray(this.inputpath)) {
			this.inputString = this.inputpath.map(function (input) {
				return pathname(input);
			});
			// Trim long lists
			if (this.inputString.length > 3) {
				this.inputString = this.inputString.slice(0, 3).join(', ')
					+ '...and '
					+ (this.inputString.length - 3)
					+ ' others';
			} else {
				this.inputString = this.inputString.join(', ');
			}
		} else {
			this.inputString = pathname(this.inputpath);
		}

		debug('created Target instance with input: '
			+ strong(this.inputString)
			+ ' and output: '
			+ strong(this.output), 2);
	}
}

/**
 * Run build
 * @param {Function} fn(err, filepaths)
 */
Target.prototype.build = function (fn) {
	var self = this;

	if (this.isRoot) {
		// Execute 'before' hook
		waterfall([function (done) {
			self.executeHook('before', self, done);
		},

		// Build targets
		function (done) {
			series(self.targets.map(function (target) {
				return target.build.bind(target);
			}), function (err, results) {
				done(err, flatten(results));
			});
		},

		// Execute 'after' hook
		function (filepaths, done) {
			self.executeHook('after', self, function (err) {
				done(err, filepaths);
			});
		}], fn);

	// Skip if watch only and not watch build
	} else if (this.outputpath || !this.outputpath && this.runtimeOptions.watch) {
		var watching = !this.outputpath && this.runtimeOptions.watch
			, timerID = Array.isArray(this.input)
				? this.input[0]
				: this.input;

		this.referencedFiles = [];

		cnsl.start(timerID);

		print((watching ? 'watching ' : 'building ')
			+ strong(this.inputString)
			+ (this.outputpath ? ' to ' + strong(path.basename(this.outputpath)) : ''), 1);

		// Execute 'before' hook
		waterfall([function (done) {
			self.executeHook('before', self, done);
		},

		// Parse source files
		function (done) {
			done(null, self.parse(self.batch, self.inputpath, self.fileFilter, self.fileFactoryOptions));
		},

		// Process files
		function (files, done) {
			self.process(files, self.workflows, watching, done);
		},

		// Execute 'afterEach' hooks
		function (files, done) {
			parallel(files.map(function (file) {
				// Memoize
				return self.executeHook.bind(self, 'afterEach', file);
			}), function (err) {
				done(err, files);
			});
		},

		// Write writeable files
		function (files, done) {
			print('[processed '
				+ strong(self.referencedFiles.length)
				+ (self.referencedFiles.length > 1 ? ' files' : ' file')
				+ ' in '
				+ chalk.cyan((cnsl.stop(timerID) / 1000) + 's')
				+ ']', 2);
			done(null, self.write(files));
		},

		// Build child targets
		function (filepaths, done) {
			if (self.hasChildren) {
				// Lock files to prevent inclusion in downstream targets
				self.lock(self.referencedFiles);
				series(self.targets.map(function (target) {
					return target.build.bind(target);
				}), function (err, results) {
					// Add new filepaths
					filepaths = filepaths.concat(flatten(results));
					self.unlock(self.referencedFiles);
					done(err, filepaths);
				});
			} else {
				done(null, filepaths);
			}
		},

		// Execute 'after' hook
		function (filepaths, done) {
			self.executeHook('after', self, function (err) {
				done(err, filepaths);
			});
		},

		// Reset
		function (filepaths, done) {
			self.reset();
			done(null, filepaths);
		}], fn);

	} else {
		fn();
	}
};

/**
 * Parse 'input' source files
 * @param {Boolean} batch
 * @param {String|Array} input
 * @param {RegExp} filter
 * @param {Object} options
 * @returns {Array}
 */
Target.prototype.parse = function (batch, input, filter, options) {
	filter = filter || /./;

	var files = []
		, factory = function (filepath) {
				return fileFactory(filepath, options);
			};

	// Input is directory or array
	if (batch) {
		// Grab all files (filtered by file extension)
		var inputs = !Array.isArray(input) ? [input] : input
			, filepaths = flatten(inputs.map(function (inpt) {
					// Directory
					if (!path.extname(inpt).length) {
						return readdir(inpt, function (resource, stat) {
							return stat.isFile() && filter.test(resource);
						});
					}

					return inpt;
				}));

		// Create file instances
		files = filepaths
			.map(factory)
			.filter(function (file) {
				return file != null;
			});
		if (!files.length) warn('no valid source files found in ' + strong(input), 4);

	// Input is file
	} else {
		var file;

		// Create File instance
		if (file = factory(input)) {
			files = [file];
		} else {
			warn(strong(input) + ' not found in project sources', 4);
		}
	}

	return files;
};

/**
 * Process batch workflow 'commands' for 'files'
 * @param {Array} files
 * @param {Object} workflows
 * @param {Boolean} watching
 * @param {Function} fn(err, files)
 */
Target.prototype.process = function (files, workflows, watching, fn) {
	var self = this;

	// Parse conditional workflow tasks
	function parseWorkflow (workflow) {
		return workflow.reduce(function (result, task) {
			if (!~task.indexOf(':')) {
				result.push(task);
			// Conditional task
			} else {
				var conditions = task.split(':');

				task = conditions.pop();
				if (self[conditions[0]]
					&& (conditions.length > 1 ? self[conditions[1]] : true)) {
						result.push(task);
				}
			}
			return result;
		}, []);
	}

	// Recursive batch run 'someFiles'
	function batch (someFiles, idx, done) {
		parallel(someFiles.map(function (file) {
			// Memoize
			return file.run.bind(file, parseWorkflow(workflows[file.type][idx]), watching);
		}), function (err, dependencies) {
			if (err) return done(err);

			// Process files not yet processed
			dependencies = difference(distinct(compact(flatten(dependencies))), files);
			if (dependencies.length) {
				// Store
				files = files.concat(dependencies);
				return batch(dependencies, idx, done);
			}

			done(null, files);
		});
	}

	// Batch run first set of workflow tasks
	batch(files, 0, function (err) {
		if (err) return fn(err);

		// Store all referenced files
		self.referencedFiles = files;

		// Filter writeable files
		files = files.filter(function (file) {
			return file.getIsWriteable();
		});

		// Second set only operates on writeable files
		if (files.length) {
			batch(files, 1, fn);
		} else {
			fn(null, files);
		}
	});
};

/**
 * Write generated content
 * @param {Array} files
 * @returns {Array}
 */
Target.prototype.write = function (files) {
	var options = {
		bootstrap: this.bootstrap,
		boilerplate: this.boilerplate
	};

	// Write files in sequence
	return files.map(function (file) {
		// Don't write if no output path
		if (this.outputpath) {
			var filepath = this.outputpath;

			// Resolve output path if directory
			if (!path.extname(filepath).length) {
				var outputInSources = this.sources.some(function (source) {
					return indir(source, this.outputpath);
				}, this);

				// Swap input for output path if in sources in order to preserve relative package structure
				if (outputInSources) {
					// TODO: find common root directory in case of Array/glob etc
					var input = Array.isArray(this.inputpath) ? file.filepath : this.inputpath;

					// Resolve directory if input is a file
					if (path.extname(input).length) input = path.dirname(input);
					filepath = file.filepath.replace(input, this.outputpath);
				} else {
					var id = file.id;

					// Find id relative to source dir
					this.sources.forEach(function (source) {
						if (~file.filepath.indexOf(source)) {
							id = path.relative(source, file.filepath);
						}
					}, this);

					filepath = path.join(filepath, id);
				}

				var extension = path.extname(filepath);

				// Resolve missing extension
				if (!extension) filepath += '.' + file.type;
				if (extension != '.' + file.type) filepath = filepath.replace(extension, '.' + file.type);
			}

			// Handle generating unique paths
			if (unique.isUniquePattern(filepath)) {
				// Remove existing
				var existing = unique.find(filepath);

				if (existing && fs.existsSync(existing)) fs.unlinkSync(existing);

				// Generate unique path
				// Disable during watch otherwise css reloading won't work
				filepath = unique.generate(filepath, !this.runtimeOptions.watch ? file.content : false);
			}

			// Write file
			return file.write(filepath, options);
		}
	}, this);
};

/**
 * Set lock flag for 'files'
 * @param {Array} files
 */
Target.prototype.lock = function (files) {
	files.forEach(function (file) {
		file.isLocked = true;
	});
};

/**
 * Unset lock flag for 'files'
 * @param {Array} files
 */
Target.prototype.unlock = function (files) {
	files.forEach(function (file) {
		file.isLocked = false;
	});
};

/**
 * Determine if 'file' is a referenced file (child targets included)
 * @param {File} file
 * @returns {Boolean}
 */
Target.prototype.hasFile = function (file) {
	var referenced;

	referenced = ~this.referencedFiles.indexOf(file);
	if (referenced) {
		return true;
	} else if (this.hasChildren) {
		for (var i = 0, n = this.targets.length; i < n; i++) {
			referenced = this.targets[i].hasFile(file);
			if (referenced) return true;
		}
	}
	return false;
};

/**
 * Reset modified files
 */
Target.prototype.reset = function () {
	this.referencedFiles.forEach(function (file) {
		file.reset();
	});
	clearIDCache();
	clearTemplateCache();
};

/**
 * Execute the 'hook' function with a particular 'context'
 * @param {String} hook
 * @param {Object} context
 * @param {Function} fn(err)
 */
Target.prototype.executeHook = function (hook, context, fn) {
	if (this[hook]) {
		print('executing ' + hook + ' hook...', 2);
		// Make global objects available to the function
		this[hook](global, process, console, require, context, this.runtimeOptions, fn);
	} else {
		fn();
	}
};
<|endoftext|>


<commit_before>import fs from 'fs'
import path from 'path'
import pkgDir from 'pkg-dir'
import loaderUtils from 'loader-utils'
import { kebabCase, camelCase, pascalCase, getJSON, normalize } from './utils'
import COMPONENTS from 'veui/components.json'
import { sep } from 'path'

const COMPONENTS_DIRNAME = 'components'
const EXT_TYPES = {
  SCRIPT: ['js'],
  STYLE: ['css', 'less', 'styl', 'sass', 'scss']
}

let resolveCache = {}

/**
 * webpack loader to load theme modules for VEUI components
 * @param {string} content Raw file content of .vue file
 * @returns {string} Content of patched .vue file
 */
export default async function (content) {
  let callback = this.async()
  const loaderOptions = loaderUtils.getOptions(this) || {}
  let component = resolveComponent(this.resourcePath)
  if (!component) {
    callback(null, content)
    return
  }

  try {
    let result = await patchComponent(content, component, loaderOptions, path => {
      return new Promise((resolve) => {
        try {
          this.resolve(this.rootContext || this.options.context, path, (err, result) => {
            if (err) {
              resolve(false)
              return
            }
            resolve(true)
          })
        } catch (e) {
          resolve(false)
        }
      })
    })
    callback(null, result)
  } catch (e) {
    callback(e)
  }
}

/**
 * Patch the original .vue file with additional peer modules.
 * @param {string} content .vue file content
 * @param {string} component Component name
 * @param {Object} options veui-theme-loader options
 * @param {function} resolve webpack resolve function to see if target peer exists
 * @returns {string} The patched content
 */
async function patchComponent (content, component, options, resolve) {
  let {
    modules = [],
    package: pack,
    path: packPath = COMPONENTS_DIRNAME,
    transform,
    fileName
  } = options

  if (pack && fileName) {
    modules.push({ package: pack, path: packPath, transform, fileName })
  }

  if (!modules.length) {
    return content
  }

  let parts = modules.reduce(
    (
      acc,
      {
        package: pack,
        path: packPath = COMPONENTS_DIRNAME,
        transform,
        fileName
      }
    ) => {
      let peerComponent = getPeerFilename(component, {
        transform,
        template: fileName
      })
      let peerPath = path.join(pack, packPath, peerComponent)
      pushPart(acc, { path: peerPath })
      return acc
    },
    {
      script: [],
      style: []
    }
  )

  await Promise.all([...parts.script, ...parts.style].map(async item => {
    item.valid = await assurePath(item.path, resolve)
  }))

  return Object.keys(parts).reduce((content, type) => {
    return parts[type]
      .filter(({ valid }) => valid)
      .reduce((content, { path }) => {
        return patchType(content, type, path)
      }, content)
  }, content)
}

/**
 * Push peer file dependency into collected parts.
 * @param {Object} parts Collected parts containing scripts and styles
 * @param {Object} file The file to be appended
 */
function pushPart (parts, file) {
  let ext = getExtname(file.path)
  let type = Object.keys(EXT_TYPES).find(key => {
    return EXT_TYPES[key].includes(ext)
  })
  parts[type.toLowerCase()].push(file)
}

/**
 * Get extension name of a file
 * @param {string} file File path
 * @returns {string} File extension
 */
function getExtname (file) {
  return path
    .extname(file)
    .replace(/\./g, '')
    .toLowerCase()
}

const RE_SCRIPT = /<script(?:\s+[^>]*)?>/i

/**
 * Patch file content according to a given type.
 * @param {string} content Original content
 * @param {string} type Peer type, can be `script` or `style`
 * @param {string} peerPath Peer module path
 * @returns {string} The patched content
 */
function patchType (content, type, peerPath) {
  let normalizedPath = normalize(peerPath).replace(/\\/g, '\\\\')
  switch (type) {
    case 'script':
      content = content.replace(RE_SCRIPT, match => {
        return `${match}\nimport '${normalizedPath}'\n`
      })
      break
    case 'style':
      let langStr = ''
      let ext = getExtname(normalizedPath)
      if (ext !== 'css') {
        langStr = `lang="${ext}" `
      }
      content += `\n<style ${langStr}src="${normalizedPath}"></style>\n`
      break
    default:
      break
  }

  return content
}

/**
 * To test the target peer path exists or not.
 * @param {string} modulePath Peer module path
 * @param {function} resolve webpack module resolver
 * @returns {boolean} If the target peer path exists
 */
async function assurePath (modulePath, resolve) {
  if (resolveCache[modulePath] === false) {
    return
  } else if (!(modulePath in resolveCache)) {
    if (typeof resolve === 'function') {
      try {
        resolveCache[modulePath] = await resolve(modulePath)
      } catch (e) {
        resolveCache[modulePath] = false
      }
    }
  }

  return resolveCache[modulePath]
}

/**
 * Convert a component name according to file name template.
 * @param {string} name Peer module file
 * @param {Object} options Transform options
 * @param {string} options.transform Transform type for base name
 * @param {string} options.template File name template
 * @returns {string} Peer module file name
 */
function getPeerFilename (
  name,
  { transform = 'kebab-case', template = '${module}.css' }
) {
  if (!name) {
    return null
  }

  switch (transform) {
    case 'kebab-case':
      name = kebabCase(name)
      break
    case 'camelCase':
      name = camelCase(name)
      break
    case 'PascalCase':
      name = pascalCase(name)
      break
    case false:
    default:
      break
  }

  return template.replace(/\$\{module\}/g, name)
}

/**
 * Resolve the underlying component for a given file path.
 * '/dev/veui/src/components/Button.vue' → 'Button'
 * @param {string} file Absolute file path
 * @returns {?string} The resolved component name (`null` if not a VEUI component)
 */
function resolveComponent (file) {
  // make sure relative paths resolved to somewhere inside veui
  let pkg = pkgDir.sync(file)
  if (!pkg || getJSON(path.join(pkg, 'package.json')).name !== 'veui') {
    return null
  }

  // veui/${dir} or veui/src/${dir}
  let dirPath = path.join(pkg, COMPONENTS_DIRNAME) // runtime
  if (!fs.existsSync(dirPath)) {
    dirPath = path.join(pkg, 'src', COMPONENTS_DIRNAME) // dev
    if (!fs.existsSync(dirPath)) {
      return null
    }
  }

  // is VEUI component
  return getComponentName(path.relative(dirPath, file))
}

/**
 * Convert a component relative path to a component name according to
 * VEUI's component list.
 * 'Icon.vue' → 'Icon'
 * @param {string} componentPath Component path
 * @returns {?string} Component name (`null` if not a VEUI component)
 */
function getComponentName (componentPath) {
  if (!componentPath) {
    return null
  }
  let component = COMPONENTS.find(({ path }) => {
    path = path.replace(/[/\\]/g, sep)
    return path === componentPath || path.split('.')[0] === componentPath
  })

  return component ? component.name : null
}
<commit_msg>fix: use `normalize`<commit_after>import fs from 'fs'
import path from 'path'
import pkgDir from 'pkg-dir'
import loaderUtils from 'loader-utils'
import { kebabCase, camelCase, pascalCase, getJSON, normalize } from './utils'
import COMPONENTS from 'veui/components.json'

const COMPONENTS_DIRNAME = 'components'
const EXT_TYPES = {
  SCRIPT: ['js'],
  STYLE: ['css', 'less', 'styl', 'sass', 'scss']
}

let resolveCache = {}

/**
 * webpack loader to load theme modules for VEUI components
 * @param {string} content Raw file content of .vue file
 * @returns {string} Content of patched .vue file
 */
export default async function (content) {
  let callback = this.async()
  const loaderOptions = loaderUtils.getOptions(this) || {}
  let component = resolveComponent(this.resourcePath)
  if (!component) {
    callback(null, content)
    return
  }

  try {
    let result = await patchComponent(content, component, loaderOptions, path => {
      return new Promise((resolve) => {
        try {
          this.resolve(this.rootContext || this.options.context, path, (err, result) => {
            if (err) {
              resolve(false)
              return
            }
            resolve(true)
          })
        } catch (e) {
          resolve(false)
        }
      })
    })
    callback(null, result)
  } catch (e) {
    callback(e)
  }
}

/**
 * Patch the original .vue file with additional peer modules.
 * @param {string} content .vue file content
 * @param {string} component Component name
 * @param {Object} options veui-theme-loader options
 * @param {function} resolve webpack resolve function to see if target peer exists
 * @returns {string} The patched content
 */
async function patchComponent (content, component, options, resolve) {
  let {
    modules = [],
    package: pack,
    path: packPath = COMPONENTS_DIRNAME,
    transform,
    fileName
  } = options

  if (pack && fileName) {
    modules.push({ package: pack, path: packPath, transform, fileName })
  }

  if (!modules.length) {
    return content
  }

  let parts = modules.reduce(
    (
      acc,
      {
        package: pack,
        path: packPath = COMPONENTS_DIRNAME,
        transform,
        fileName
      }
    ) => {
      let peerComponent = getPeerFilename(component, {
        transform,
        template: fileName
      })
      let peerPath = path.join(pack, packPath, peerComponent)
      pushPart(acc, { path: peerPath })
      return acc
    },
    {
      script: [],
      style: []
    }
  )

  await Promise.all([...parts.script, ...parts.style].map(async item => {
    item.valid = await assurePath(item.path, resolve)
  }))

  return Object.keys(parts).reduce((content, type) => {
    return parts[type]
      .filter(({ valid }) => valid)
      .reduce((content, { path }) => {
        return patchType(content, type, path)
      }, content)
  }, content)
}

/**
 * Push peer file dependency into collected parts.
 * @param {Object} parts Collected parts containing scripts and styles
 * @param {Object} file The file to be appended
 */
function pushPart (parts, file) {
  let ext = getExtname(file.path)
  let type = Object.keys(EXT_TYPES).find(key => {
    return EXT_TYPES[key].includes(ext)
  })
  parts[type.toLowerCase()].push(file)
}

/**
 * Get extension name of a file
 * @param {string} file File path
 * @returns {string} File extension
 */
function getExtname (file) {
  return path
    .extname(file)
    .replace(/\./g, '')
    .toLowerCase()
}

const RE_SCRIPT = /<script(?:\s+[^>]*)?>/i

/**
 * Patch file content according to a given type.
 * @param {string} content Original content
 * @param {string} type Peer type, can be `script` or `style`
 * @param {string} peerPath Peer module path
 * @returns {string} The patched content
 */
function patchType (content, type, peerPath) {
  let normalizedPath = normalize(peerPath).replace(/\\/g, '\\\\')
  switch (type) {
    case 'script':
      content = content.replace(RE_SCRIPT, match => {
        return `${match}\nimport '${normalizedPath}'\n`
      })
      break
    case 'style':
      let langStr = ''
      let ext = getExtname(normalizedPath)
      if (ext !== 'css') {
        langStr = `lang="${ext}" `
      }
      content += `\n<style ${langStr}src="${normalizedPath}"></style>\n`
      break
    default:
      break
  }

  return content
}

/**
 * To test the target peer path exists or not.
 * @param {string} modulePath Peer module path
 * @param {function} resolve webpack module resolver
 * @returns {boolean} If the target peer path exists
 */
async function assurePath (modulePath, resolve) {
  if (resolveCache[modulePath] === false) {
    return
  } else if (!(modulePath in resolveCache)) {
    if (typeof resolve === 'function') {
      try {
        resolveCache[modulePath] = await resolve(modulePath)
      } catch (e) {
        resolveCache[modulePath] = false
      }
    }
  }

  return resolveCache[modulePath]
}

/**
 * Convert a component name according to file name template.
 * @param {string} name Peer module file
 * @param {Object} options Transform options
 * @param {string} options.transform Transform type for base name
 * @param {string} options.template File name template
 * @returns {string} Peer module file name
 */
function getPeerFilename (
  name,
  { transform = 'kebab-case', template = '${module}.css' }
) {
  if (!name) {
    return null
  }

  switch (transform) {
    case 'kebab-case':
      name = kebabCase(name)
      break
    case 'camelCase':
      name = camelCase(name)
      break
    case 'PascalCase':
      name = pascalCase(name)
      break
    case false:
    default:
      break
  }

  return template.replace(/\$\{module\}/g, name)
}

/**
 * Resolve the underlying component for a given file path.
 * '/dev/veui/src/components/Button.vue' → 'Button'
 * @param {string} file Absolute file path
 * @returns {?string} The resolved component name (`null` if not a VEUI component)
 */
function resolveComponent (file) {
  // make sure relative paths resolved to somewhere inside veui
  let pkg = pkgDir.sync(file)
  if (!pkg || getJSON(path.join(pkg, 'package.json')).name !== 'veui') {
    return null
  }

  // veui/${dir} or veui/src/${dir}
  let dirPath = path.join(pkg, COMPONENTS_DIRNAME) // runtime
  if (!fs.existsSync(dirPath)) {
    dirPath = path.join(pkg, 'src', COMPONENTS_DIRNAME) // dev
    if (!fs.existsSync(dirPath)) {
      return null
    }
  }

  // is VEUI component
  return getComponentName(path.relative(dirPath, file))
}

/**
 * Convert a component relative path to a component name according to
 * VEUI's component list.
 * 'Icon.vue' → 'Icon'
 * @param {string} componentPath Component path
 * @returns {?string} Component name (`null` if not a VEUI component)
 */
function getComponentName (componentPath) {
  if (!componentPath) {
    return null
  }
  let component = COMPONENTS.find(({ path }) => {
    path = normalize(path)
    return path === componentPath || path.split('.')[0] === componentPath
  })

  return component ? component.name : null
}
<|endoftext|>


<commit_before>import * as dom from 'shared/utils/dom';
import * as keys from 'shared/utils/keys';

export default class InputComponent {
  constructor(target) {
    this.pressed = {};
    this.onKeyListeners = [];

    target.addEventListener('keypress', this.onKeyPress.bind(this));
    target.addEventListener('keydown', this.onKeyDown.bind(this));
    target.addEventListener('keyup', this.onKeyUp.bind(this));
  }

  onKey(cb) {
    this.onKeyListeners.push(cb);
  }

  onKeyPress(e) {
    if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
      return;
    }
    this.pressed[e.key] = 'keypress';
    this.capture(e);
  }

  onKeyDown(e) {
    if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
      return;
    }
    this.pressed[e.key] = 'keydown';
    this.capture(e);
  }

  onKeyUp(e) {
    delete this.pressed[e.key];
  }

  capture(e) {
    if (this.fromInput(e)) {
      if (e.key === 'Escape' && e.target.blur) {
        e.target.blur();
      }
      return;
    }
    if (['Shift', 'Control', 'Alt', 'OS'].includes(e.key)) {
      // pressing only meta key is ignored
      return;
    }

    let key = keys.fromKeyboardEvent(e);

    for (let listener of this.onKeyListeners) {
      let stop = listener(key);
      if (stop) {
        e.preventDefault();
        e.stopPropagation();
        break;
      }
    }
  }

  fromInput(e) {
    if (!e.target) {
      return false;
    }
    return e.target instanceof HTMLInputElement ||
      e.target instanceof HTMLTextAreaElement ||
      e.target instanceof HTMLSelectElement ||
      dom.isContentEditable(e.target);
  }
}
<commit_msg>Cancel key input by <C-[><commit_after>import * as dom from 'shared/utils/dom';
import * as keys from 'shared/utils/keys';

const cancelKey = (e) => {
  return e.key === 'Escape' || e.key === '[' && e.ctrlKey;
};

export default class InputComponent {
  constructor(target) {
    this.pressed = {};
    this.onKeyListeners = [];

    target.addEventListener('keypress', this.onKeyPress.bind(this));
    target.addEventListener('keydown', this.onKeyDown.bind(this));
    target.addEventListener('keyup', this.onKeyUp.bind(this));
  }

  onKey(cb) {
    this.onKeyListeners.push(cb);
  }

  onKeyPress(e) {
    if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
      return;
    }
    this.pressed[e.key] = 'keypress';
    this.capture(e);
  }

  onKeyDown(e) {
    if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
      return;
    }
    this.pressed[e.key] = 'keydown';
    this.capture(e);
  }

  onKeyUp(e) {
    delete this.pressed[e.key];
  }

  capture(e) {
    if (this.fromInput(e)) {
      if (cancelKey(e) && e.target.blur) {
        e.target.blur();
      }
      return;
    }
    if (['Shift', 'Control', 'Alt', 'OS'].includes(e.key)) {
      // pressing only meta key is ignored
      return;
    }

    let key = keys.fromKeyboardEvent(e);

    for (let listener of this.onKeyListeners) {
      let stop = listener(key);
      if (stop) {
        e.preventDefault();
        e.stopPropagation();
        break;
      }
    }
  }

  fromInput(e) {
    if (!e.target) {
      return false;
    }
    return e.target instanceof HTMLInputElement ||
      e.target instanceof HTMLTextAreaElement ||
      e.target instanceof HTMLSelectElement ||
      dom.isContentEditable(e.target);
  }
}
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { generateUUID } from 'react-native-database';

import { getNextNumber, NUMBER_SEQUENCE_KEYS } from './numberSequenceUtilities';

const {
  CUSTOMER_INVOICE_NUMBER,
  INVENTORY_ADJUSTMENT_SERIAL_NUMBER,
  REQUISITION_SERIAL_NUMBER,
  REQUISITION_REQUESTER_REFERENCE,
  STOCKTAKE_SERIAL_NUMBER,
  SUPPLIER_INVOICE_NUMBER,
} = NUMBER_SEQUENCE_KEYS;

/**
 * Create a customer invoice associated with a given customer.
 *
 * @param   {Realm}        database
 * @param   {Name}         customer  Customer associated with invoice.
 * @return  {Transaction}
 */
const createCustomerInvoice = (database, customer, user) => {
  const currentDate = new Date();
  const invoice = database.create('Transaction', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, CUSTOMER_INVOICE_NUMBER),
    entryDate: currentDate,
    confirmDate: currentDate,
    type: 'customer_invoice',
    // Mobile customer invoices are always 'confirmed' for easy stock tracking.
    status: 'confirmed',
    comment: '',
    otherParty: customer,
    enteredBy: user,
  });

  database.save('Transaction', invoice);
  customer.addTransaction(invoice);
  database.save('Name', customer);

  return invoice;
};

/**
 * Create a new number sequence.
 *
 * @param   {Realm}           database
 * @param   {string}          sequenceKey
 * @return  {NumberSequence}
 */
const createNumberSequence = (database, sequenceKey) =>
  database.create('NumberSequence', {
    id: generateUUID(),
    sequenceKey,
  });

/**
 * Create a number attached to a sequence.
 *
 * @param  {Realm}           database
 * @param  {NumberSequence}  numberSequence  Sequence to attach the number to.
 * @param  {number}          number
 */
const createNumberToReuse = (database, numberSequence, number) => {
  const numberToReuse = database.create('NumberToReuse', {
    id: generateUUID(),
    numberSequence,
    number,
  });

  numberSequence.addNumberToReuse(numberToReuse);
};

/**
 * Create a transaction representing an inventory adjustment.
 *
 * @param   {Realm}        database    Local database.
 * @param   {Name}         user        Currently logged in user.
 * @param   {Date}         date        Current date.
 * @param   {bool}         isAddition  If true, adjustment is an increase,
 *                                     else, adjustment is a decrease.
 * @return  {Transaction}
 */
const createInventoryAdjustment = (database, user, date, isAddition) =>
  database.create('Transaction', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, INVENTORY_ADJUSTMENT_SERIAL_NUMBER),
    entryDate: date,
    confirmDate: date,
    type: isAddition ? 'supplier_invoice' : 'supplier_credit',
    status: 'confirmed',
    comment: '',
    enteredBy: user,
    otherParty: database.objects('Name').find(name => name.type === 'inventory_adjustment'),
  });

/**
 * Create a new empty batch for a given item.
 *
 * @param  {Realm}      database
 * @param  {Item}       item         Item assocated with new batch.
 * @param  {string}     batchString
 * @return {ItemBatch}
 */
const createItemBatch = (database, item, batchString) => {
  // Handle cross-reference items.
  const { realItem } = item;

  const itemBatch = database.create('ItemBatch', {
    id: generateUUID(),
    item: realItem,
    batch: batchString,
    packSize: 1,
    numberOfPacks: 0,
    costPrice: realItem.defaultPrice ? realItem.defaultPrice : 0,
    sellPrice: realItem.defaultPrice ? realItem.defaultPrice : 0,
  });

  realItem.addBatch(itemBatch);
  database.save('Item', realItem);

  return itemBatch;
};

/**
 * Create a new requisition.
 *
 * @param   {Realm}        database
 * @param   {User}         user            User creating requisition.
 * @param   {Name}         otherStoreName  Name of other store (e.g. supplying store).
 * @return  {Requisition}
 */
const createRequisition = (database, user, otherStoreName) =>
  database.create('Requisition', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, REQUISITION_SERIAL_NUMBER),
    requesterReference: getNextNumber(database, REQUISITION_REQUESTER_REFERENCE),
    status: 'suggested',
    type: 'request',
    entryDate: new Date(),
    daysToSupply: 30,
    enteredBy: user,
    otherStoreName,
  });

/**
 * Create a new requisition item.
 *
 * @param   {Database}        database
 * @param   {Requisition}     requisition  Parent requisition.
 * @param   {Item}            item         Item to add to requisition.
 * @param   {double}          dailyUsage   Daily usage of item.
 * @return  {RequisitionItem}
 */
const createRequisitionItem = (database, requisition, item, dailyUsage) => {
  // Handle cross reference items.
  const { realItem } = item;

  const requisitionItem = database.create('RequisitionItem', {
    id: generateUUID(),
    item: realItem,
    requisition,
    stockOnHand: realItem.totalQuantity,
    dailyUsage: dailyUsage || realItem.dailyUsage,
    requiredQuantity: 0,
    comment: '',
    sortIndex: requisition.items.length + 1,
  });

  requisition.addItem(requisitionItem);
  database.save('Requisition', requisition);

  return requisitionItem;
};

/**
 * Create a new stocktake.
 *
 * @param   {Realm}      database
 * @param   {User}       user      User creating stocktake.
 * @return  {Stocktake}
 */
const createStocktake = (database, user) => {
  const date = new Date();

  const stocktake = database.create('Stocktake', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, STOCKTAKE_SERIAL_NUMBER),
    name: '',
    createdDate: date,
    stocktakeDate: date,
    status: 'suggested',
    comment: '',
    createdBy: user,
  });

  return stocktake;
};

/**
 * Create a new stocktake item.
 *
 * @param   {Realm}          database
 * @param   {Stocktake}      stocktake  Parent stocktake.
 * @param   {Item}           item       Real item to create stocktake item from.
 * @return  {StocktakeItem}
 */
const createStocktakeItem = (database, stocktake, item) => {
  // Handle cross reference items.
  const { realItem } = item;

  const stocktakeItem = database.create('StocktakeItem', {
    id: generateUUID(),
    item: realItem,
    stocktake,
  });

  stocktake.items.push(stocktakeItem);
  database.save('Stocktake', stocktake);

  return stocktakeItem;
};

/**
 * Create a new stocktake batch.
 *
 * @param   {Realm}          database
 * @param   {Item}           stocktakeItem  Batch item.
 * @param   {ItemBatch}      itemBatch      Item batch to associate with stocktake batch.
 * @return  {StocktakeBatch}
 */
const createStocktakeBatch = (database, stocktakeItem, itemBatch) => {
  const { numberOfPacks, packSize, expiryDate, batch, costPrice, sellPrice } = itemBatch;

  const stocktakeBatch = database.create('StocktakeBatch', {
    id: generateUUID(),
    stocktake: stocktakeItem.stocktake,
    itemBatch,
    snapshotNumberOfPacks: numberOfPacks,
    packSize,
    expiryDate,
    batch,
    costPrice,
    sellPrice,
    sortIndex: stocktakeItem.stocktake ? stocktakeItem.stocktake.numberOfBatches : 0,
  });

  stocktakeItem.addBatch(stocktakeBatch);
  database.save('StocktakeItem', stocktakeItem);

  return stocktakeBatch;
};

/**
 * Create a new supplier invoice.
 *
 * @param   {Realm}       database
 * @param   {Name}        supplier   Name of supplier being invoiced.
 * @param   {User}        enteredBy  User who creating invoice.
 * @return  {Transaction}
 */
const createSupplierInvoice = (database, supplier, user) => {
  const currentDate = new Date();

  const invoice = database.create('Transaction', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, SUPPLIER_INVOICE_NUMBER),
    entryDate: currentDate,
    type: 'supplier_invoice',
    status: 'new',
    comment: '',
    otherParty: supplier,
    enteredBy: user,
  });

  database.save('Transaction', invoice);
  supplier.addTransaction(invoice);

  return invoice;
};

/**
 * Create a new transaction batch.
 *
 * @param   {Realm}             database
 * @param   {TransactionItem}   transactionItem  Batch item.
 * @param   {ItemBatch}         itemBatch        Item batch to associate with transaction batch.
 * @return  {TransactionBatch}
 */
const createTransactionBatch = (database, transactionItem, itemBatch) => {
  const { item, batch, expiryDate, packSize, costPrice, sellPrice } = itemBatch;

  const transactionBatch = database.create('TransactionBatch', {
    id: generateUUID(),
    itemId: item.id,
    itemName: item.name,
    itemBatch,
    batch,
    expiryDate,
    packSize,
    numberOfPacks: 0,
    costPrice,
    sellPrice,
    transaction: transactionItem.transaction,
    sortIndex: transactionItem.transaction ? transactionItem.transaction.numberOfBatches : 0,
  });

  transactionItem.addBatch(transactionBatch);
  database.save('TransactionItem', transactionItem);
  itemBatch.addTransactionBatch(transactionBatch);
  database.save('ItemBatch', itemBatch);

  return transactionBatch;
};

/**
 * Create a new transaction item.
 *
 * @param   {Realm}            database
 * @param   {Transaction}      transaction  Parent transaction.
 * @param   {Item}             item         Real item to create transaction item from.
 * @return  {TransactionItem}
 */
const createTransactionItem = (database, transaction, item) => {
  // Handle cross reference items.
  const { realItem } = item;

  const transactionItem = database.create('TransactionItem', {
    id: generateUUID(),
    item: realItem,
    transaction,
  });

  transaction.addItem(transactionItem);
  database.save('Transaction', transaction);

  return transactionItem;
};

/**
 * Create a record of the given type, taking care of linkages, generating IDs, serial
 * numbers, current dates, and inserting sensible defaults.
 *
 * @param   {Realm}         database
 * @param   {string}        type      The type of record to create (not 1:1 with schema types).
 * @param   {args}          ...args   Any specific arguments the record type will need.
 * @return  {Realm.Object}            The new database record.
 */
export const createRecord = (database, type, ...args) => {
  switch (type) {
    case 'CustomerInvoice':
      return createCustomerInvoice(database, ...args);
    case 'NumberSequence':
      return createNumberSequence(database, ...args);
    case 'NumberToReuse':
      return createNumberToReuse(database, ...args);
    case 'ItemBatch':
      return createItemBatch(database, ...args);
    case 'Requisition':
      return createRequisition(database, ...args);
    case 'RequisitionItem':
      return createRequisitionItem(database, ...args);
    case 'Stocktake':
      return createStocktake(database, ...args);
    case 'StocktakeItem':
      return createStocktakeItem(database, ...args);
    case 'StocktakeBatch':
      return createStocktakeBatch(database, ...args);
    case 'SupplierInvoice':
      return createSupplierInvoice(database, ...args);
    case 'InventoryAdjustment':
      return createInventoryAdjustment(database, ...args);
    case 'TransactionItem':
      return createTransactionItem(database, ...args);
    case 'TransactionBatch':
      return createTransactionBatch(database, ...args);
    default:
      throw new Error(`Cannot create a record with unsupported type: ${type}`);
  }
};

export default createRecord;
<commit_msg>Add ProgramRequisition method to createRecord<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { generateUUID } from 'react-native-database';

import { getNextNumber, NUMBER_SEQUENCE_KEYS } from './numberSequenceUtilities';

const {
  CUSTOMER_INVOICE_NUMBER,
  INVENTORY_ADJUSTMENT_SERIAL_NUMBER,
  REQUISITION_SERIAL_NUMBER,
  REQUISITION_REQUESTER_REFERENCE,
  STOCKTAKE_SERIAL_NUMBER,
  SUPPLIER_INVOICE_NUMBER,
} = NUMBER_SEQUENCE_KEYS;

/**
 * Create a customer invoice associated with a given customer.
 *
 * @param   {Realm}        database
 * @param   {Name}         customer  Customer associated with invoice.
 * @return  {Transaction}
 */
const createCustomerInvoice = (database, customer, user) => {
  const currentDate = new Date();
  const invoice = database.create('Transaction', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, CUSTOMER_INVOICE_NUMBER),
    entryDate: currentDate,
    confirmDate: currentDate,
    type: 'customer_invoice',
    // Mobile customer invoices are always 'confirmed' for easy stock tracking.
    status: 'confirmed',
    comment: '',
    otherParty: customer,
    enteredBy: user,
  });

  database.save('Transaction', invoice);
  customer.addTransaction(invoice);
  database.save('Name', customer);

  return invoice;
};

/**
 * Create a new number sequence.
 *
 * @param   {Realm}           database
 * @param   {string}          sequenceKey
 * @return  {NumberSequence}
 */
const createNumberSequence = (database, sequenceKey) =>
  database.create('NumberSequence', {
    id: generateUUID(),
    sequenceKey,
  });

/**
 * Create a number attached to a sequence.
 *
 * @param  {Realm}           database
 * @param  {NumberSequence}  numberSequence  Sequence to attach the number to.
 * @param  {number}          number
 */
const createNumberToReuse = (database, numberSequence, number) => {
  const numberToReuse = database.create('NumberToReuse', {
    id: generateUUID(),
    numberSequence,
    number,
  });

  numberSequence.addNumberToReuse(numberToReuse);
};

/**
 * Create a transaction representing an inventory adjustment.
 *
 * @param   {Realm}        database    Local database.
 * @param   {Name}         user        Currently logged in user.
 * @param   {Date}         date        Current date.
 * @param   {bool}         isAddition  If true, adjustment is an increase,
 *                                     else, adjustment is a decrease.
 * @return  {Transaction}
 */
const createInventoryAdjustment = (database, user, date, isAddition) =>
  database.create('Transaction', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, INVENTORY_ADJUSTMENT_SERIAL_NUMBER),
    entryDate: date,
    confirmDate: date,
    type: isAddition ? 'supplier_invoice' : 'supplier_credit',
    status: 'confirmed',
    comment: '',
    enteredBy: user,
    otherParty: database.objects('Name').find(name => name.type === 'inventory_adjustment'),
  });

/**
 * Create a new empty batch for a given item.
 *
 * @param  {Realm}      database
 * @param  {Item}       item         Item assocated with new batch.
 * @param  {string}     batchString
 * @return {ItemBatch}
 */
const createItemBatch = (database, item, batchString) => {
  // Handle cross-reference items.
  const { realItem } = item;

  const itemBatch = database.create('ItemBatch', {
    id: generateUUID(),
    item: realItem,
    batch: batchString,
    packSize: 1,
    numberOfPacks: 0,
    costPrice: realItem.defaultPrice ? realItem.defaultPrice : 0,
    sellPrice: realItem.defaultPrice ? realItem.defaultPrice : 0,
  });

  realItem.addBatch(itemBatch);
  database.save('Item', realItem);

  return itemBatch;
};

/**
 * Create a new requisition.
 *
 * @param   {Realm}        database
 * @param   {User}         user            User creating requisition.
 * @param   {Name}         otherStoreName  Name of other store (e.g. supplying store).
 * @return  {Requisition}
 */
const createRequisition = (database, user, otherStoreName) =>
  database.create('Requisition', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, REQUISITION_SERIAL_NUMBER),
    requesterReference: getNextNumber(database, REQUISITION_REQUESTER_REFERENCE),
    status: 'suggested',
    type: 'request',
    entryDate: new Date(),
    daysToSupply: 30,
    enteredBy: user,
    otherStoreName,
  });

/**
 * Create a new requisition item.
 *
 * @param   {Database}        database
 * @param   {Requisition}     requisition  Parent requisition.
 * @param   {Item}            item         Item to add to requisition.
 * @param   {double}          dailyUsage   Daily usage of item.
 * @return  {RequisitionItem}
 */
const createRequisitionItem = (database, requisition, item, dailyUsage) => {
  // Handle cross reference items.
  const { realItem } = item;

  const requisitionItem = database.create('RequisitionItem', {
    id: generateUUID(),
    item: realItem,
    requisition,
    stockOnHand: realItem.totalQuantity,
    dailyUsage: dailyUsage || realItem.dailyUsage,
    requiredQuantity: 0,
    comment: '',
    sortIndex: requisition.items.length + 1,
  });

  requisition.addItem(requisitionItem);
  database.save('Requisition', requisition);

  return requisitionItem;
};

/**
 * Create a new stocktake.
 *
 * @param   {Realm}      database
 * @param   {User}       user      User creating stocktake.
 * @return  {Stocktake}
 */
const createStocktake = (database, user) => {
  const date = new Date();

  const stocktake = database.create('Stocktake', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, STOCKTAKE_SERIAL_NUMBER),
    name: '',
    createdDate: date,
    stocktakeDate: date,
    status: 'suggested',
    comment: '',
    createdBy: user,
  });

  return stocktake;
};

/**
 * Create a new stocktake item.
 *
 * @param   {Realm}          database
 * @param   {Stocktake}      stocktake  Parent stocktake.
 * @param   {Item}           item       Real item to create stocktake item from.
 * @return  {StocktakeItem}
 */
const createStocktakeItem = (database, stocktake, item) => {
  // Handle cross reference items.
  const { realItem } = item;

  const stocktakeItem = database.create('StocktakeItem', {
    id: generateUUID(),
    item: realItem,
    stocktake,
  });

  stocktake.items.push(stocktakeItem);
  database.save('Stocktake', stocktake);

  return stocktakeItem;
};

/**
 * Create a new stocktake batch.
 *
 * @param   {Realm}          database
 * @param   {Item}           stocktakeItem  Batch item.
 * @param   {ItemBatch}      itemBatch      Item batch to associate with stocktake batch.
 * @return  {StocktakeBatch}
 */
const createStocktakeBatch = (database, stocktakeItem, itemBatch) => {
  const { numberOfPacks, packSize, expiryDate, batch, costPrice, sellPrice } = itemBatch;

  const stocktakeBatch = database.create('StocktakeBatch', {
    id: generateUUID(),
    stocktake: stocktakeItem.stocktake,
    itemBatch,
    snapshotNumberOfPacks: numberOfPacks,
    packSize,
    expiryDate,
    batch,
    costPrice,
    sellPrice,
    sortIndex: stocktakeItem.stocktake ? stocktakeItem.stocktake.numberOfBatches : 0,
  });

  stocktakeItem.addBatch(stocktakeBatch);
  database.save('StocktakeItem', stocktakeItem);

  return stocktakeBatch;
};

/**
 * Create a new supplier invoice.
 *
 * @param   {Realm}       database
 * @param   {Name}        supplier   Name of supplier being invoiced.
 * @param   {User}        enteredBy  User who creating invoice.
 * @return  {Transaction}
 */
const createSupplierInvoice = (database, supplier, user) => {
  const currentDate = new Date();

  const invoice = database.create('Transaction', {
    id: generateUUID(),
    serialNumber: getNextNumber(database, SUPPLIER_INVOICE_NUMBER),
    entryDate: currentDate,
    type: 'supplier_invoice',
    status: 'new',
    comment: '',
    otherParty: supplier,
    enteredBy: user,
  });

  database.save('Transaction', invoice);
  supplier.addTransaction(invoice);

  return invoice;
};

/**
 * Create a new transaction batch.
 *
 * @param   {Realm}             database
 * @param   {TransactionItem}   transactionItem  Batch item.
 * @param   {ItemBatch}         itemBatch        Item batch to associate with transaction batch.
 * @return  {TransactionBatch}
 */
const createTransactionBatch = (database, transactionItem, itemBatch) => {
  const { item, batch, expiryDate, packSize, costPrice, sellPrice } = itemBatch;

  const transactionBatch = database.create('TransactionBatch', {
    id: generateUUID(),
    itemId: item.id,
    itemName: item.name,
    itemBatch,
    batch,
    expiryDate,
    packSize,
    numberOfPacks: 0,
    costPrice,
    sellPrice,
    transaction: transactionItem.transaction,
    sortIndex: transactionItem.transaction ? transactionItem.transaction.numberOfBatches : 0,
  });

  transactionItem.addBatch(transactionBatch);
  database.save('TransactionItem', transactionItem);
  itemBatch.addTransactionBatch(transactionBatch);
  database.save('ItemBatch', itemBatch);

  return transactionBatch;
};

/**
 * Create a new transaction item.
 *
 * @param   {Realm}            database
 * @param   {Transaction}      transaction  Parent transaction.
 * @param   {Item}             item         Real item to create transaction item from.
 * @return  {TransactionItem}
 */
const createTransactionItem = (database, transaction, item) => {
  // Handle cross reference items.
  const { realItem } = item;

  const transactionItem = database.create('TransactionItem', {
    id: generateUUID(),
    item: realItem,
    transaction,
  });

  transaction.addItem(transactionItem);
  database.save('Transaction', transaction);

  return transactionItem;
};

const createProgramRequisition = (database, requisitionValues) => {
  const requisition = database.create('Requisition', {
    id: generateUUID(),
    otherStoreName: requisitionValues.supplier,
    ...requisitionValues,
  });
  database.save('Requisition', requisition);
  return requisition;
};

/**
 * Create a record of the given type, taking care of linkages, generating IDs, serial
 * numbers, current dates, and inserting sensible defaults.
 *
 * @param   {Realm}         database
 * @param   {string}        type      The type of record to create (not 1:1 with schema types).
 * @param   {args}          ...args   Any specific arguments the record type will need.
 * @return  {Realm.Object}            The new database record.
 */
export const createRecord = (database, type, ...args) => {
  switch (type) {
    case 'CustomerInvoice':
      return createCustomerInvoice(database, ...args);
    case 'NumberSequence':
      return createNumberSequence(database, ...args);
    case 'NumberToReuse':
      return createNumberToReuse(database, ...args);
    case 'ItemBatch':
      return createItemBatch(database, ...args);
    case 'Requisition':
      return createRequisition(database, ...args);
    case 'RequisitionItem':
      return createRequisitionItem(database, ...args);
    case 'Stocktake':
      return createStocktake(database, ...args);
    case 'StocktakeItem':
      return createStocktakeItem(database, ...args);
    case 'StocktakeBatch':
      return createStocktakeBatch(database, ...args);
    case 'SupplierInvoice':
      return createSupplierInvoice(database, ...args);
    case 'InventoryAdjustment':
      return createInventoryAdjustment(database, ...args);
    case 'TransactionItem':
      return createTransactionItem(database, ...args);
    case 'TransactionBatch':
      return createTransactionBatch(database, ...args);
    case 'ProgramRequisition':
      return createProgramRequisition(database, ...args);
    default:
      throw new Error(`Cannot create a record with unsupported type: ${type}`);
  }
};

export default createRecord;
<|endoftext|>


<commit_before>import React from 'react';//import react
import PropTypes from 'prop-types';
import { Button, Header, Icon, Modal, Form } from 'semantic-ui-react';

const LoginModal = ({ open, closeHandler }) => (
  <Modal id="login__modal" size="mini" open={open} onClose={() => closeHandler('loginModal')}>

    <Modal.Content>
      <Form>
        <Form.Field>
          api key
          <input placeholder="api key" id="key" />
        </Form.Field>
      </Form>
    </Modal.Content>
    <Modal.Actions>
      <Button color="grey" onClick={() => closeHandler('loginModal')}>
        <Icon name="cancel" /> cancel
      </Button>
      <Button color="black" >
        <Icon name="fort awesome" /> submit
      </Button>
    </Modal.Actions>
  </Modal>
);

LoginModal.propTypes = {
  closeHandler: PropTypes.func.isRequired,
  open: PropTypes.bool.isRequired
};

export default LoginModal;
<commit_msg>no message<commit_after>import React from 'react'; // import react
import PropTypes from 'prop-types';
import { Button, Header, Icon, Modal, Form } from 'semantic-ui-react';

const LoginModal = ({ open, closeHandler }) => (
  <Modal id="login__modal" size="mini" open={open} onClose={() => closeHandler('loginModal')}>

    <Modal.Content>
      <Form>
        <Form.Field>
          api key
          <input placeholder="api key" id="key" />
        </Form.Field>
      </Form>
    </Modal.Content>
    <Modal.Actions>
      <Button color="grey" onClick={() => closeHandler('loginModal')}>
        <Icon name="cancel" /> cancel
      </Button>
      <Button color="black" >
        <Icon name="fort awesome" /> submit
      </Button>
    </Modal.Actions>
  </Modal>
);

LoginModal.propTypes = {
  closeHandler: PropTypes.func.isRequired,
  open: PropTypes.bool.isRequired
};

export default LoginModal;
<|endoftext|>