<commit_before>const { graphql, GraphQLString, GraphQLObjectType } = require(`graphql`)

const { inferObjectStructureFromNodes } = require(`../infer-graphql-type`)
const nodeConnections = require(`../build-node-connections`)
const { buildNodesSchema } = require(`../index`)
const {
  inferInputObjectStructureFromNodes,
} = require(`../infer-graphql-input-fields`)
const { clearTypeExampleValues } = require(`../data-tree-utils`)
const { store } = require(`../../redux`)
require(`../../db/__tests__/fixtures/ensure-loki`)()

function queryResult(nodes, query, { types = [] } = {}) {
  for (const node of nodes) {
    store.dispatch({ type: `CREATE_NODE`, payload: node })
  }
  const nodeObjectType = new GraphQLObjectType({
    name: `Node`,
    fields: inferObjectStructureFromNodes({
      nodes,
      types: [{ name: `Node` }, ...types],
    }),
  })
  const processedType = {
    nodes,
    name: `Node`,
    nodeObjectType,
    fieldsFromPlugins: [],
  }
  const fields = nodeConnections.buildFieldConfigMap(processedType)
  const schema = buildNodesSchema(fields)
  return graphql(schema, query)
}

beforeEach(() => {
  clearTypeExampleValues()
})

describe(`GraphQL Input args`, () => {
  beforeEach(() => {
    store.dispatch({ type: `DELETE_CACHE` })
  })
  it(`filters out null example values`, async () => {
    let result = await queryResult(
      [{ id: `1`, internal: { type: `Bar` }, foo: null, bar: `baz` }],
      `
        {
          allNode(foo: { eq: "bar" }) {
            edges { node { bar } }
          }
        }
      `
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Unknown argument "foo" on field "allNode"`
    )
  })

  it(`filters out empty objects`, async () => {
    let result = await queryResult(
      [{ id: `1`, internal: { type: `Bar` }, foo: {}, bar: `baz` }],
      `
        {
          allNode(foo: { eq: "bar" }) {
            edges { node { bar } }
          }
        }
      `
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Unknown argument "foo" on field "allNode"`
    )
  })

  it(`filters out empty arrays`, async () => {
    let result = await queryResult(
      [{ id: `1`, internal: { type: `Bar` }, foo: [], bar: `baz` }],
      `
        {
          allNode(foo: { eq: "bar" }) {
            edges { node { bar } }
          }
        }
      `
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Unknown argument "foo" on field "allNode"`
    )
  })

  it(`filters out sparse arrays`, async () => {
    let result = await queryResult(
      [
        {
          id: `1`,
          internal: { type: `Bar` },
          foo: [undefined, null, null],
          bar: `baz`,
        },
      ],
      `
        {
          allNode(foo: { eq: "bar" }) {
            edges { node { bar } }
          }
        }
      `
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Unknown argument "foo" on field "allNode"`
    )
  })

  it(`uses correct keys for linked fields`, async () => {
    const { store } = require(`../../redux`)
    let types = [{ name: `Bar`, nodeObjectType: GraphQLString }]

    store.dispatch({
      type: `CREATE_NODE`,
      payload: { id: `baz`, internal: { type: `Bar` } },
    })

    let result = await queryResult(
      [
        {
          id: `1`,
          internal: { type: `Bar` },
          linked___NODE: `baz`,
          foo: `bar`,
        },
      ],
      `
        {
          allNode(linked___NODE: { eq: "baz" }) {
            edges { node { linked } }
          }
        }
      `,
      { types }
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Unknown argument "linked___NODE" on field "allNode"`
    )
  })

  it(`Replaces unsupported values in keys`, () => {
    // Add a key with unsupported values to test
    // if they're replaced.
    let fields = inferInputObjectStructureFromNodes({
      nodes: [
        {
          parent: `parent`,
          children: [`bar`],
          foo: {
            parent: `parent`,
            children: [`bar`],
            "foo-moo": `tasty`,
          },
        },
      ],
    }).inferredFields

    expect(Object.keys(fields.foo.type.getFields())[2]).toEqual(`foo_moo`)
  })
  it(`Removes specific root fields`, () => {
    let fields = inferInputObjectStructureFromNodes({
      nodes: [
        {
          parent: `parent`,
          children: [`bar`],
          foo: {
            parent: `parent`,
            children: [`bar`],
          },
        },
      ],
    }).inferredFields

    expect(Object.keys(fields)).toHaveLength(1)
    expect(Object.keys(fields.foo.type.getFields())).toHaveLength(2)
  })

  it(`infers number types`, () => {
    const fields = inferInputObjectStructureFromNodes({
      nodes: [
        {
          int32: 42,
          float: 2.5,
          longint: 3000000000,
        },
      ],
    }).inferredFields
    expect(fields.int32.type.name.endsWith(`Integer`)).toBe(true)
    expect(fields.float.type.name.endsWith(`Float`)).toBe(true)
    expect(fields.longint.type.name.endsWith(`Float`)).toBe(true)
  })
})
<commit_msg>Fix input filter tests (#11202)<commit_after>const { graphql, GraphQLString, GraphQLObjectType } = require(`graphql`)

const { inferObjectStructureFromNodes } = require(`../infer-graphql-type`)
const nodeConnections = require(`../build-node-connections`)
const { buildNodesSchema } = require(`../index`)
const {
  inferInputObjectStructureFromNodes,
} = require(`../infer-graphql-input-fields`)
const { clearTypeExampleValues } = require(`../data-tree-utils`)
const { store } = require(`../../redux`)
require(`../../db/__tests__/fixtures/ensure-loki`)()

function queryResult(nodes, query, { types = [] } = {}) {
  for (const node of nodes) {
    store.dispatch({ type: `CREATE_NODE`, payload: node })
  }
  const nodeObjectType = new GraphQLObjectType({
    name: `Node`,
    fields: inferObjectStructureFromNodes({
      nodes,
      types: [{ name: `Node` }, ...types],
    }),
  })
  const processedType = {
    nodes,
    name: `Node`,
    nodeObjectType,
    fieldsFromPlugins: [],
  }
  const fields = nodeConnections.buildFieldConfigMap(processedType)
  const schema = buildNodesSchema(fields)
  return graphql(schema, query)
}

beforeEach(() => {
  clearTypeExampleValues()
})

describe(`GraphQL Input args`, () => {
  beforeEach(() => {
    store.dispatch({ type: `DELETE_CACHE` })
  })

  it(`filters out null example values`, async () => {
    let result = await queryResult(
      [{ id: `1`, internal: { type: `Bar` }, foo: null, bar: `baz` }],
      `
        {
          allNode(filter: { foo: { eq: "bar" } }) {
            edges { node { bar } }
          }
        }
      `
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Field "foo" is not defined by type filterNode.`
    )
  })

  it(`filters out empty objects`, async () => {
    let result = await queryResult(
      [{ id: `1`, internal: { type: `Bar` }, foo: {}, bar: `baz` }],
      `
        {
          allNode(filter: { foo: { eq: "bar" } }) {
            edges { node { bar } }
          }
        }
      `
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Field "foo" is not defined by type filterNode.`
    )
  })

  it(`filters out empty arrays`, async () => {
    let result = await queryResult(
      [{ id: `1`, internal: { type: `Bar` }, foo: [], bar: `baz` }],
      `
        {
          allNode(filter: { foo: { eq: "bar" } }) {
            edges { node { bar } }
          }
        }
      `
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Field "foo" is not defined by type filterNode.`
    )
  })

  it(`filters out sparse arrays`, async () => {
    let result = await queryResult(
      [
        {
          id: `1`,
          internal: { type: `Bar` },
          foo: [undefined, null, null],
          bar: `baz`,
        },
      ],
      `
        {
          allNode(filter: { foo: { eq: "bar" } }) {
            edges { node { bar } }
          }
        }
      `
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Field "foo" is not defined by type filterNode.`
    )
  })

  it(`uses correct keys for linked fields`, async () => {
    const { store } = require(`../../redux`)
    let types = [{ name: `Bar`, nodeObjectType: GraphQLString }]

    store.dispatch({
      type: `CREATE_NODE`,
      payload: { id: `baz`, internal: { type: `Bar` } },
    })

    let result = await queryResult(
      [
        {
          id: `1`,
          internal: { type: `Bar` },
          linked___NODE: `baz`,
          foo: `bar`,
        },
      ],
      `
        {
          allNode(filter: { linked___NODE: { eq: "baz" } }) {
            edges { node { linked } }
          }
        }
      `,
      { types }
    )
    expect(result.errors.length).toEqual(1)
    expect(result.errors[0].message).toMatch(
      `Field "linked___NODE" is not defined by type filterNode.`
    )
  })

  it(`Replaces unsupported values in keys`, () => {
    // Add a key with unsupported values to test
    // if they're replaced.
    let fields = inferInputObjectStructureFromNodes({
      nodes: [
        {
          parent: `parent`,
          children: [`bar`],
          foo: {
            parent: `parent`,
            children: [`bar`],
            "foo-moo": `tasty`,
          },
        },
      ],
    }).inferredFields

    expect(Object.keys(fields.foo.type.getFields())[2]).toEqual(`foo_moo`)
  })
  it(`Removes specific root fields`, () => {
    let fields = inferInputObjectStructureFromNodes({
      nodes: [
        {
          parent: `parent`,
          children: [`bar`],
          foo: {
            parent: `parent`,
            children: [`bar`],
          },
        },
      ],
    }).inferredFields

    expect(Object.keys(fields)).toHaveLength(1)
    expect(Object.keys(fields.foo.type.getFields())).toHaveLength(2)
  })

  it(`infers number types`, () => {
    const fields = inferInputObjectStructureFromNodes({
      nodes: [
        {
          int32: 42,
          float: 2.5,
          longint: 3000000000,
        },
      ],
    }).inferredFields
    expect(fields.int32.type.name.endsWith(`Integer`)).toBe(true)
    expect(fields.float.type.name.endsWith(`Float`)).toBe(true)
    expect(fields.longint.type.name.endsWith(`Float`)).toBe(true)
  })
})
<|endoftext|>


<commit_before>import Scenario from './Scenario'
import Bugsnag from '@bugsnag/react-native'

export class FeatureFlagsScenario extends Scenario {
  constructor (configuration, extraData, jsConfig) {
    super()
    this.extraData = extraData

    if (extraData && extraData.includes('callback')) {
      jsConfig.onError = (event) => {
        event.addFeatureFlag('sample_group', 'a')
      }
    }
  }

  run () {
    Bugsnag.addFeatureFlag('demo_mode')

    Bugsnag.addFeatureFlags([
      { name: 'should_not_be_reported_1' },
      { name: 'should_not_be_reported_2' },
      { name: 'should_not_be_reported_3' }
    ])

    Bugsnag.clearFeatureFlag('should_not_be_reported_3')
    Bugsnag.clearFeatureFlag('should_not_be_reported_2')
    Bugsnag.clearFeatureFlag('should_not_be_reported_1')

    if (this.extraData && this.extraData.includes('cleared')) {
      Bugsnag.clearFeatureFlags()
    }

    if (this.extraData && this.extraData.includes('unhandled')) {
      throw new Error('FeatureFlagScenario unhandled')
    } else {
      Bugsnag.notify(new Error('FeatureFlagScenario handled'))
    }
  }
}
<commit_msg>Reorder arguments<commit_after>import Scenario from './Scenario'
import Bugsnag from '@bugsnag/react-native'

export class FeatureFlagsScenario extends Scenario {
  constructor (configuration, jsConfig, extraData) {
    super()
    this.extraData = extraData

    if (extraData && extraData.includes('callback')) {
      jsConfig.onError = (event) => {
        event.addFeatureFlag('sample_group', 'a')
      }
    }
  }

  run () {
    Bugsnag.addFeatureFlag('demo_mode')

    Bugsnag.addFeatureFlags([
      { name: 'should_not_be_reported_1' },
      { name: 'should_not_be_reported_2' },
      { name: 'should_not_be_reported_3' }
    ])

    Bugsnag.clearFeatureFlag('should_not_be_reported_3')
    Bugsnag.clearFeatureFlag('should_not_be_reported_2')
    Bugsnag.clearFeatureFlag('should_not_be_reported_1')

    if (this.extraData && this.extraData.includes('cleared')) {
      Bugsnag.clearFeatureFlags()
    }

    if (this.extraData && this.extraData.includes('unhandled')) {
      throw new Error('FeatureFlagScenario unhandled')
    } else {
      Bugsnag.notify(new Error('FeatureFlagScenario handled'))
    }
  }
}
<|endoftext|>


<commit_before>/* G. Hemingway Copyright @2014
 * Context for the overall CAD assembly
 */
"use strict";

import Assembly from './assembly';
import Annotation          from './annotation';
import DataLoader from './data_loader';
import Shell from './shell';
import {makeGeometry, processKeyframe, processDelta} from './nc_delta';

/*************************************************************************/

export default class NC extends THREE.EventDispatcher {
    constructor(project, workingstep, timeIn, loader) {
        super();
        this.app = loader._app;
        this.project = project;
        this._workingstep = workingstep;
        this._timeIn = timeIn;
        this._loader = loader;
        this._objects = {};
        this.type = 'nc';
        this.raycaster = new THREE.Raycaster();
        this._object3D = new THREE.Object3D();
        this._overlay3D = new THREE.Object3D();
        this._annotation3D = new THREE.Object3D();
        this.state = {
            selected:       false,
            highlighted:    false,
            visible:        true,
            opacity:        1.0,
            explodeDistance: 0,
            collapsed:      false
        }
    }

    addModel(model, usage, type, id, transform, bbox) {
        // console.log('Add Model(' + usage + '): ' + id);
        // Setup 3D object holder
        let obj = {
            model: model,
            usage: usage,
            type: type,
            id: id,
            rendered: true,
            object3D: new THREE.Object3D(),
            transform: (new THREE.Matrix4()).copy(transform),
            bbox: bbox,
            getID: function() { return this.id; },
            getNamedParent: function() { return this },
            getBoundingBox: function() { return this },
            toggleHighlight: function() { },
            toggleVisibility: function() {this.object3D.visible = !this.object3D.visible; },
            setInvisible: function() {this.object3D.visible = false; },
            setVisible: function() {this.object3D.visible = true; },
            toggleOpacity: function() { },
            toggleSelection: function() { },
            toggleCollapsed: function() { },
            explode: function() { }
        };
        obj.object3D.applyMatrix(obj.transform);
        obj.object3D.updateMatrixWorld();
        obj.overlay3D = obj.object3D.clone();
        obj.annotation3D = obj.object3D.clone();
        // Save the object
        this._objects[id] = obj;
        this._object3D.add(obj.object3D);
        this._overlay3D.add(obj.overlay3D);
        this._annotation3D.add(obj.annotation3D);
        if (type === 'shell') {
            model.addEventListener('shellEndLoad', (event) => {
                //This is where the shell gets sent when its loaded so that the full mesh can be added to the 3D objects
                let material = new THREE.ShaderMaterial(new THREE.VelvetyShader());
                let mesh = new THREE.Mesh(event.shell.getGeometry(), material, false);

                if (obj.bbox.isEmpty()) {
                    obj.bbox = event.shell.getBoundingBox();
                }

                mesh.castShadow = true;
                mesh.receiveShadow = true;
                mesh.userData = obj;
                obj.object3D.add(mesh);
                obj.version = 0;
                if (usage === 'asis') {
                    // TODO: add selector for displaying asis geometry or not
                    obj.rendered = false;
                    obj.setInvisible();
                }
            });
        } else if (type === 'polyline') {
            model.addEventListener('annotationEndLoad', (event) => {
                let lineGeometries = event.annotation.getGeometry();
                let material = new THREE.LineBasicMaterial({
                    vertexColors: THREE.VertexColors,
                    //color: 0xffffff,
                    linewidth: 1
                });
                model._addedGeometry = [];
                for (let i = 0; i < lineGeometries.length; i++) {
                    let lines = new THREE.Line(lineGeometries[i], material);
                    lines.visible = true;
                    obj.annotation3D.add(lines);
                    model._addedGeometry.push(lines);
                }
            });
            model.addEventListener("annotationMakeVisible", (event)=>{
              _.each(model._addedGeometry, (line)=>{
                obj.annotation3D.add(line);
              });
            });

            let removeAnno = () => {
                model.removeEventListener('annotationEndLoad', removeAnno);
                _.each(model._addedGeometry, (line)=>{
                    obj.annotation3D.remove(line);
                });
            };

            model.addEventListener("annotationMakeNonVisible", (event)=>{
              if (!model._addedGeometry || model._addedGeometry.length === 0) {
                  model.addEventListener('annotationEndLoad', removeAnno);
              } else {
                  removeAnno();
              }
            });
        }
    }

    getObject3D() {
        return this._object3D;
    };

    getObjects() {
        return this._objects;
    };

    getOverlay3D() {
        return this._overlay3D;
    };

    getAnnotation3D() {
        return this._annotation3D;
    };

    getBoundingBox() {
        if (!this.boundingBox) {
            this.boundingBox = new THREE.Box3();
            let keys = _.keys(this._objects);
            _.each(keys, (key) => {
                let object = this._objects[key];
                if (object.type !== 'polyline') {
                    this.boundingBox.union(object.bbox);
                }
            });
        }
        return this.boundingBox.clone();
    }

    calcBoundingBox() {

        this._overlay3D.remove(this.bbox);
        this.boundingBox = new THREE.Box3();
        let keys = _.keys(this._objects);
        _.each(keys, (key) => {
            let object = this._objects[key];
            if (object.rendered !== false && object.type !== 'polyline') {
                let newBox = new THREE.Box3().setFromObject(object.object3D);
                if (!newBox.isEmpty()) {
                    object.bbox = newBox;
                }
                this.boundingBox.union(object.bbox);
            }
        });
        let bounds = this.boundingBox;

        this.bbox = Assembly.buildBoundingBox(bounds);
        if (this.bbox && this.state.selected) {
            this._overlay3D.add(this.bbox);
        }
    }

    clearHighlights() {
        this.dispatchEvent({ type: "_clearHighlights" });
    }

    hideAllBoundingBoxes() {
        this.dispatchEvent({ type: "_hideBounding" });
    }

    getNamedParent() {
        return this;
    }

    select(camera, mouseX, mouseY) {
        let mouse = new THREE.Vector2();
        mouse.x = (mouseX / window.innerWidth) * 2 - 1;
        mouse.y = -(mouseY / window.innerHeight) * 2 + 1;
        this.raycaster.setFromCamera(mouse, camera);

        let objs = _.map(_.values(this._objects), (obj) => obj.object3D);
        let intersections = this.raycaster.intersectObjects(objs, true);
        // Did we hit anything?
        let object = undefined;
        if (intersections.length > 0) {
            let hit = undefined;
            for (let i = 0; i < intersections.length; i++) {
                if (intersections[i].object.visible) {
                    if (!hit || intersections[i].distance < hit.distance) {
                        hit = intersections[i];
                    }
                }
            }
            if (hit) {
                object = hit.object.userData;
            }
        }
        return object;
    }
    handleInprocessGeom(geom){
        // Two types of changes: Keyframe and delta.
        // Keyframe has version property and doesn't have prev_version
        if (geom.hasOwnProperty('version') && !geom.hasOwnProperty('prev_version')) {
            //console.log(geom.version + ' - Keyframe');
            let obj = this._objects[geom.id];
            if (obj !== undefined) {
                // Process new geometry
                let geometry = makeGeometry(processKeyframe(geom));
                // Remove all old geometry -- mesh's only
                obj.object3D.traverse(function(child) {
                    if (child.type === "Mesh") {
                        obj.object3D.remove(child);
                    }
                });
                // Add in new geometry
                let material = new THREE.ShaderMaterial(new THREE.VelvetyShader());
                let mesh = new THREE.Mesh(geometry, material);
                mesh.castShadow = true;
                mesh.receiveShadow = true;
                mesh.userData = obj;
                obj.object3D.add(mesh);
                // Make sure to update the model geometry
                obj.model.setGeometry(geometry);
                obj.version = geom.version;
                obj.precision = geom.precision;
            }
            return true;
            // Delta changes have prev_version and not version fields
        } else if (geom.hasOwnProperty('prev_version') && geom.hasOwnProperty('base_version')) {
            //console.log(geom.version + ' - Delta');
            // Are we moving the geometry or modifying it
            if (!geom.hasOwnProperty('remove')) {
                console.log('Delta: just moving things');
            } else {
                let obj = this._objects[geom.id];
                if (obj !== undefined) {
                    // Process new data
                    let geometry = makeGeometry(processDelta(geom, obj));
                    // Remove all old geometry -- mesh's only
                    obj.object3D.traverse(function (child) {
                        if (child.type === "Mesh") {
                            obj.object3D.remove(child);
                        }
                    });
                    // Create new modified geometry and add to obj
                    let material = new THREE.ShaderMaterial(new THREE.VelvetyShader());
                    let mesh = new THREE.Mesh(geometry, material);
                    mesh.castShadow = true;
                    mesh.receiveShadow = true;
                    mesh.userData = obj;
                    obj.object3D.add(mesh);
                    // Make sure to update the model geometry
                    obj.model.setGeometry(geometry);
                }
            }
            return true;
            // Don't know what kind of update this is
        }
    }

    applyDelta(delta) {
        let alter = false;
        //There are two types of 'State' that we get- KeyState or DeltaState.
        //KeyState - {'project':string,'workingstep':uint,'time_in_workingstep':0,'geom':geom[{shell,polyline,deltaShell}]}
        //DeltaState - {'project':string,'workingstep':uint,'time_in_workingstep':0,'prev','geom':[{shell,polyline,deltaShell}]}
        //shell- {'id':uuid,'shell':uuid+'.json','xform':int[16],'bbox',double[6],'usage':{'cutter','asis','tobe','removal','fixture','machine'}}
        //polyline- {'id':uuid,'polyline':uuid+'.json','xform':int[16],'bbox',double[6],'usage':'toolpath'}
        //deltaShell- {'id':uuid,'shell':uuid+'.json','xform':int[16],'bbox',double[6],'usage':'inprocess'}

        //If we get a KeyState, we need to re-render the scene.
        //If we get a DeltaState, we need to update the scene.
        //First we handle KeyState.
        if (!delta.hasOwnProperty('prev')){
            //For keyframes, we need to hide the currently drawn but unused geometry,
            //Unhide anything we have that is needed but hidden,
            //And load and display any new things we don't have.
            // Hide existing Stuff. Keep it around in case we need to use it later.
            var oldgeom = _.filter(_.values(this._objects), ['type','shell']);
            _.each(oldgeom,(geom)=> {
                //this._object3D.remove(geom.object3D);
                //this._overlay3D.remove(geom.object3D);
                geom.rendered = false;
                geom.object3D.visible = false;
            });

            var oldannotations =_.values(this._loader._annotations);
            _.each(oldannotations, (oldannotation) => {
                oldannotation.removeFromScene();
            });

            //Load new Stuff.
            var toolpaths = _.filter(delta.geom, (geom) => (geom.usage == 'toolpath' || (_.has(geom, 'polyline') && geom.usage =="tobe")));
            var geoms = _.filter(delta.geom, (geom) => (
                geom.usage =='cutter' || (geom.usage =='tobe' && _.has(geom, 'shell')) ||
                geom.usage =="asis"||geom.usage=='machine' || geom.usage=="fixture")
            );
            let inproc = _.filter(delta.geom, ['usage','inprocess']);
            _.each(inproc, this.handleInprocessGeom(inproc));
            _.each(toolpaths, (geomData) => {
                let name = geomData.polyline.split('.')[0];
                if (!this._loader._annotations[name]){
                    let annotation = new Annotation(geomData.id, this, this);
                    let transform = DataLoader.parseXform(geomData.xform, true);
                    this.addModel(annotation, geomData.usage, 'polyline', geomData.id, transform, undefined);
                    // Push the annotation for later completion
                    this._loader._annotations[name] = annotation;
                    var url = '/v3/nc/';
                    this._loader.addRequest({
                        path: name,
                        baseURL: url,
                        type: 'annotation'
                    });
                } else {
                    this._loader._annotations[name].addToScene();
                }
            });

            _.each(geoms, (geomData)=>{
                let name = geomData.id;
                //Don't show as-is geom of fixture
                if(geomData.usage =='asis' || (this.app.services.machine === null && geomData.usage == 'fixture')) return;

                if(this._objects[name]) {
                    let obj = this._objects[name];
                    if (!obj.rendered) {
                        //this._overlay3D.add(obj.object3D);
                        obj.rendered = true;
                        obj.visible = true;
                        obj.setVisible();
                        this._objects[name] = obj;
                    }
                }
                else {
                    let color = DataLoader.parseColor("7d7d7d");
                    if(geomData.usage =="cutter"){
                        color = DataLoader.parseColor("FF530D");
                    }
                    let transform = DataLoader.parseXform(geomData.xform,true);
                    let boundingBox = DataLoader.parseBoundingBox(geomData.bbox);
                    let shell = new Shell(geomData.id,this,this,geomData.size,color,boundingBox);
                    this.addModel(shell,geomData.usage,'shell',geomData.id,transform,boundingBox);
                    this._loader._shells[geomData.shell]=shell;
                    var url = "/v3/nc/";
                    this._loader.addRequest({
                        path: name,
                        baseURL: url,
                        type: "shell"
                    })
                }
            });

            this._loader.runLoadQueue();
            alter = true;
            this.app.actionManager.emit('change-workingstep', delta.workingstep);
            //  let lineGeometries = event.annotation.getGeometry();
        }
        else {
            // Handle each geom update in the delta
            // This is usually just a tool movement (and volume removal update).
            _.each(delta.geom, (geom) => {
		    if(geom.usage ==='inprocess') return this.handleInprocessGeom(geom);
                if (!window.geom || window.geom.length < 100){
                    window.geom = window.geom || [];
                    window.geom.push(geom);
                }
                let obj = this._objects[geom.id];
                if(obj !== undefined) {
                    if (obj.rendered !== false) {
                        let transform = new THREE.Matrix4();
                        if (!geom.xform) return;
                        transform.fromArray(geom.xform);
                        let position = new THREE.Vector3();
                        let quaternion = new THREE.Quaternion();
                        let scale = new THREE.Vector3();
                        transform.decompose(position, quaternion, scale);
                        // we need to update all 3D properties so that
                        // annotations, overlays and objects are all updated
                        obj.object3D.position.copy(position);
                        obj.object3D.quaternion.copy(quaternion);
                        obj.annotation3D.position.copy(position);
                        obj.annotation3D.quaternion.copy(quaternion);
                        obj.overlay3D.position.copy(position);
                        obj.overlay3D.quaternion.copy(quaternion);
                        alter = true;
                    }
                }
            });
        }
        return alter;
    }

    getSelected() {
       if(this.state.selected)
        return [this];
        else
            return [];
    }
    getID() { return this.id; }
    toggleHighlight() { }
    toggleVisibility() { }
    toggleOpacity() { }

    toggleSelection() {
        // On deselection
        if(this.state.selected) {
            // Hide the bounding box
            this._overlay3D.remove(this.bbox);
            // On selection
        } else {
            let bounds = this.getBoundingBox(false);
            if (!this.bbox && !bounds.isEmpty()) {
                this.bbox = Assembly.buildBoundingBox(bounds);
            }
            if (this.bbox) {
                // Add the BBox to our overlay object
                this._overlay3D.add(this.bbox);
            }
        }
        this.state.selected = !this.state.selected;
    }

    toggleCollapsed() { }
    explode() { }
}
<commit_msg>refactored in-process geometry handler, should use updates/full dynamicshell loads properly now.<commit_after>/* G. Hemingway Copyright @2014
 * Context for the overall CAD assembly
 */
"use strict";

import Assembly from './assembly';
import Annotation          from './annotation';
import DataLoader from './data_loader';
import Shell from './shell';
import {makeGeometry, processKeyframe, processDelta} from './nc_delta';

/*************************************************************************/

export default class NC extends THREE.EventDispatcher {
    constructor(project, workingstep, timeIn, loader) {
        super();
        this.app = loader._app;
        this.project = project;
        this._workingstep = workingstep;
        this._timeIn = timeIn;
        this._loader = loader;
        this._objects = {};
        this.type = 'nc';
        this.raycaster = new THREE.Raycaster();
        this._object3D = new THREE.Object3D();
        this._overlay3D = new THREE.Object3D();
        this._annotation3D = new THREE.Object3D();
        this.state = {
            selected:       false,
            highlighted:    false,
            visible:        true,
            opacity:        1.0,
            explodeDistance: 0,
            collapsed:      false
        }
    }

    addModel(model, usage, type, id, transform, bbox) {
        // console.log('Add Model(' + usage + '): ' + id);
        // Setup 3D object holder
        let obj = {
            model: model,
            usage: usage,
            type: type,
            id: id,
            rendered: true,
            object3D: new THREE.Object3D(),
            transform: (new THREE.Matrix4()).copy(transform),
            bbox: bbox,
            getID: function() { return this.id; },
            getNamedParent: function() { return this },
            getBoundingBox: function() { return this },
            toggleHighlight: function() { },
            toggleVisibility: function() {this.object3D.visible = !this.object3D.visible; },
            setInvisible: function() {this.object3D.visible = false; },
            setVisible: function() {this.object3D.visible = true; },
            toggleOpacity: function() { },
            toggleSelection: function() { },
            toggleCollapsed: function() { },
            explode: function() { }
        };
        obj.object3D.applyMatrix(obj.transform);
        obj.object3D.updateMatrixWorld();
        obj.overlay3D = obj.object3D.clone();
        obj.annotation3D = obj.object3D.clone();
        // Save the object
        this._objects[id] = obj;
        this._object3D.add(obj.object3D);
        this._overlay3D.add(obj.overlay3D);
        this._annotation3D.add(obj.annotation3D);
        if (type === 'shell') {
            model.addEventListener('shellEndLoad', (event) => {
                //This is where the shell gets sent when its loaded so that the full mesh can be added to the 3D objects
                let material = new THREE.ShaderMaterial(new THREE.VelvetyShader());
                let mesh = new THREE.Mesh(event.shell.getGeometry(), material, false);

                if (obj.bbox.isEmpty()) {
                    obj.bbox = event.shell.getBoundingBox();
                }

                mesh.castShadow = true;
                mesh.receiveShadow = true;
                mesh.userData = obj;
                obj.object3D.add(mesh);
                obj.version = 0;
                if (usage === 'asis') {
                    // TODO: add selector for displaying asis geometry or not
                    obj.rendered = false;
                    obj.setInvisible();
                }
            });
        } else if (type === 'polyline') {
            model.addEventListener('annotationEndLoad', (event) => {
                let lineGeometries = event.annotation.getGeometry();
                let material = new THREE.LineBasicMaterial({
                    vertexColors: THREE.VertexColors,
                    //color: 0xffffff,
                    linewidth: 1
                });
                model._addedGeometry = [];
                for (let i = 0; i < lineGeometries.length; i++) {
                    let lines = new THREE.Line(lineGeometries[i], material);
                    lines.visible = true;
                    obj.annotation3D.add(lines);
                    model._addedGeometry.push(lines);
                }
            });
            model.addEventListener("annotationMakeVisible", (event)=>{
              _.each(model._addedGeometry, (line)=>{
                obj.annotation3D.add(line);
              });
            });

            let removeAnno = () => {
                model.removeEventListener('annotationEndLoad', removeAnno);
                _.each(model._addedGeometry, (line)=>{
                    obj.annotation3D.remove(line);
                });
            };

            model.addEventListener("annotationMakeNonVisible", (event)=>{
              if (!model._addedGeometry || model._addedGeometry.length === 0) {
                  model.addEventListener('annotationEndLoad', removeAnno);
              } else {
                  removeAnno();
              }
            });
        }
    }

    getObject3D() {
        return this._object3D;
    };

    getObjects() {
        return this._objects;
    };

    getOverlay3D() {
        return this._overlay3D;
    };

    getAnnotation3D() {
        return this._annotation3D;
    };

    getBoundingBox() {
        if (!this.boundingBox) {
            this.boundingBox = new THREE.Box3();
            let keys = _.keys(this._objects);
            _.each(keys, (key) => {
                let object = this._objects[key];
                if (object.type !== 'polyline') {
                    this.boundingBox.union(object.bbox);
                }
            });
        }
        return this.boundingBox.clone();
    }

    calcBoundingBox() {

        this._overlay3D.remove(this.bbox);
        this.boundingBox = new THREE.Box3();
        let keys = _.keys(this._objects);
        _.each(keys, (key) => {
            let object = this._objects[key];
            if (object.rendered !== false && object.type !== 'polyline') {
                let newBox = new THREE.Box3().setFromObject(object.object3D);
                if (!newBox.isEmpty()) {
                    object.bbox = newBox;
                }
                this.boundingBox.union(object.bbox);
            }
        });
        let bounds = this.boundingBox;

        this.bbox = Assembly.buildBoundingBox(bounds);
        if (this.bbox && this.state.selected) {
            this._overlay3D.add(this.bbox);
        }
    }

    clearHighlights() {
        this.dispatchEvent({ type: "_clearHighlights" });
    }

    hideAllBoundingBoxes() {
        this.dispatchEvent({ type: "_hideBounding" });
    }

    getNamedParent() {
        return this;
    }

    select(camera, mouseX, mouseY) {
        let mouse = new THREE.Vector2();
        mouse.x = (mouseX / window.innerWidth) * 2 - 1;
        mouse.y = -(mouseY / window.innerHeight) * 2 + 1;
        this.raycaster.setFromCamera(mouse, camera);

        let objs = _.map(_.values(this._objects), (obj) => obj.object3D);
        let intersections = this.raycaster.intersectObjects(objs, true);
        // Did we hit anything?
        let object = undefined;
        if (intersections.length > 0) {
            let hit = undefined;
            for (let i = 0; i < intersections.length; i++) {
                if (intersections[i].object.visible) {
                    if (!hit || intersections[i].distance < hit.distance) {
                        hit = intersections[i];
                    }
                }
            }
            if (hit) {
                object = hit.object.userData;
            }
        }
        return object;
    }
    handleInprocessGeom(geom){
        let parseDynamicUpdate = (geom,obj)=> {
            let geometry = makeGeometry(processDelta(geom, obj));
            // Remove all old geometry -- mesh's only
            obj.object3D.traverse(function (child) {
                if (child.type === "Mesh") {
                    obj.object3D.remove(child);
                }
            });
            // Create new modified geometry and add to obj
            let material = new THREE.ShaderMaterial(new THREE.VelvetyShader());
            let mesh = new THREE.Mesh(geometry, material);
            mesh.castShadow = true;
            mesh.receiveShadow = true;
            mesh.userData = obj;
            obj.object3D.add(mesh);
            // Make sure to update the model geometry
            obj.model.setGeometry(geometry);
        };
        let parseDynamicFull = (geom,obj)=>{
            let geometry = makeGeometry(processKeyframe(geom));
            // Remove all old geometry -- mesh's only
            obj.object3D.traverse(function(child) {
                if (child.type === "Mesh") {
                    obj.object3D.remove(child);
                }
            });
            // Add in new geometry
            let material = new THREE.ShaderMaterial(new THREE.VelvetyShader());
            let mesh = new THREE.Mesh(geometry, material);
            mesh.castShadow = true;
            mesh.receiveShadow = true;
            mesh.userData = obj;
            obj.object3D.add(mesh);
            // Make sure to update the model geometry
            obj.model.setGeometry(geometry);
            obj.version = geom.version;
            obj.precision = geom.precision;
            return true;
        };
        let existingobj = this._objects[geom.id];
        if(existingobj === undefined) { //Need a full dynamic shell.
            //Setup the memory
            let color = DataLoader.parseColor("BE17FF");
            let boundingBox = DataLoader.parseBoundingBox(geom.bbox);
            let transform =DataLoader.parseXform(geom.xform,true);
            let shell = new Shell(geom.id,this,this,geom.size,color,boundingBox);
            this.addModel(shell,geom.usage,'shell',geom.id,transform,boundingBox);
            existingobj = this._objects[geom.id];

            request.get('/v3/nc/geometry/delta/-1')
                .end((err,res)=>{
                    let fulldynamic = JSON.parse(res.text);
                    parseDynamicFull(fulldynamic,existingobj);
                });
        }
        else{ //Need an updated dynamic shell.
            request.get('/v3/nc/geometry/delta/'+existingobj.version)
                .end((err,res)=> {
                    let updateddynamic = JSON.parse(res.text);
                    parseDynamicUpdate(updateddynamic,existingobj);
                });
        }
        return true;
        // Don't know what kind of update this is
    }

    applyDelta(delta) {
        let alter = false;
        //There are two types of 'State' that we get- KeyState or DeltaState.
        //KeyState - {'project':string,'workingstep':uint,'time_in_workingstep':0,'geom':geom[{shell,polyline,deltaShell}]}
        //DeltaState - {'project':string,'workingstep':uint,'time_in_workingstep':0,'prev','geom':[{shell,polyline,deltaShell}]}
        //shell- {'id':uuid,'shell':uuid+'.json','xform':int[16],'bbox',double[6],'usage':{'cutter','asis','tobe','removal','fixture','machine'}}
        //polyline- {'id':uuid,'polyline':uuid+'.json','xform':int[16],'bbox',double[6],'usage':'toolpath'}
        //deltaShell- {'id':uuid,'shell':uuid+'.json','xform':int[16],'bbox',double[6],'usage':'inprocess'}

        //If we get a KeyState, we need to re-render the scene.
        //If we get a DeltaState, we need to update the scene.
        //First we handle KeyState.
        if (!delta.hasOwnProperty('prev')){
            //For keyframes, we need to hide the currently drawn but unused geometry,
            //Unhide anything we have that is needed but hidden,
            //And load and display any new things we don't have.
            // Hide existing Stuff. Keep it around in case we need to use it later.
            var oldgeom = _.filter(_.values(this._objects), ['type','shell']);
            _.each(oldgeom,(geom)=> {
                //this._object3D.remove(geom.object3D);
                //this._overlay3D.remove(geom.object3D);
                geom.rendered = false;
                geom.object3D.visible = false;
            });

            var oldannotations =_.values(this._loader._annotations);
            _.each(oldannotations, (oldannotation) => {
                oldannotation.removeFromScene();
            });

            //Load new Stuff.
            var toolpaths = _.filter(delta.geom, (geom) => (geom.usage == 'toolpath' || (_.has(geom, 'polyline') && geom.usage =="tobe")));
            var geoms = _.filter(delta.geom, (geom) => (
                geom.usage =='cutter' || (geom.usage =='tobe' && _.has(geom, 'shell')) ||
                geom.usage =="asis"||geom.usage=='machine' || geom.usage=="fixture")
            );
            let inproc = _.filter(delta.geom, ['usage','inprocess']);
            _.each(inproc, this.handleInprocessGeom(inproc));
            _.each(toolpaths, (geomData) => {
                let name = geomData.polyline.split('.')[0];
                if (!this._loader._annotations[name]){
                    let annotation = new Annotation(geomData.id, this, this);
                    let transform = DataLoader.parseXform(geomData.xform, true);
                    this.addModel(annotation, geomData.usage, 'polyline', geomData.id, transform, undefined);
                    // Push the annotation for later completion
                    this._loader._annotations[name] = annotation;
                    var url = '/v3/nc/';
                    this._loader.addRequest({
                        path: name,
                        baseURL: url,
                        type: 'annotation'
                    });
                } else {
                    this._loader._annotations[name].addToScene();
                }
            });

            _.each(geoms, (geomData)=>{
                let name = geomData.id;
                //Don't show as-is geom of fixture
                if(geomData.usage =='asis' || (this.app.services.machine === null && geomData.usage == 'fixture')) return;

                if(this._objects[name]) {
                    let obj = this._objects[name];
                    if (!obj.rendered) {
                        //this._overlay3D.add(obj.object3D);
                        obj.rendered = true;
                        obj.visible = true;
                        obj.setVisible();
                        this._objects[name] = obj;
                    }
                }
                else {
                    let color = DataLoader.parseColor("7d7d7d");
                    if(geomData.usage =="cutter"){
                        color = DataLoader.parseColor("FF530D");
                    }
                    let transform = DataLoader.parseXform(geomData.xform,true);
                    let boundingBox = DataLoader.parseBoundingBox(geomData.bbox);
                    let shell = new Shell(geomData.id,this,this,geomData.size,color,boundingBox);
                    this.addModel(shell,geomData.usage,'shell',geomData.id,transform,boundingBox);
                    this._loader._shells[geomData.shell]=shell;
                    var url = "/v3/nc/";
                    this._loader.addRequest({
                        path: name,
                        baseURL: url,
                        type: "shell"
                    })
                }
            });

            this._loader.runLoadQueue();
            alter = true;
            this.app.actionManager.emit('change-workingstep', delta.workingstep);
            //  let lineGeometries = event.annotation.getGeometry();
        }
        else {
            // Handle each geom update in the delta
            // This is usually just a tool movement (and volume removal update).
            _.each(delta.geom, (geom) => {
		    if(geom.usage ==='inprocess') return this.handleInprocessGeom(geom);
                if (!window.geom || window.geom.length < 100){
                    window.geom = window.geom || [];
                    window.geom.push(geom);
                }
                let obj = this._objects[geom.id];
                if(obj !== undefined) {
                    if (obj.rendered !== false) {
                        let transform = new THREE.Matrix4();
                        if (!geom.xform) return;
                        transform.fromArray(geom.xform);
                        let position = new THREE.Vector3();
                        let quaternion = new THREE.Quaternion();
                        let scale = new THREE.Vector3();
                        transform.decompose(position, quaternion, scale);
                        // we need to update all 3D properties so that
                        // annotations, overlays and objects are all updated
                        obj.object3D.position.copy(position);
                        obj.object3D.quaternion.copy(quaternion);
                        obj.annotation3D.position.copy(position);
                        obj.annotation3D.quaternion.copy(quaternion);
                        obj.overlay3D.position.copy(position);
                        obj.overlay3D.quaternion.copy(quaternion);
                        alter = true;
                    }
                }
            });
        }
        return alter;
    }

    getSelected() {
       if(this.state.selected)
        return [this];
        else
            return [];
    }
    getID() { return this.id; }
    toggleHighlight() { }
    toggleVisibility() { }
    toggleOpacity() { }

    toggleSelection() {
        // On deselection
        if(this.state.selected) {
            // Hide the bounding box
            this._overlay3D.remove(this.bbox);
            // On selection
        } else {
            let bounds = this.getBoundingBox(false);
            if (!this.bbox && !bounds.isEmpty()) {
                this.bbox = Assembly.buildBoundingBox(bounds);
            }
            if (this.bbox) {
                // Add the BBox to our overlay object
                this._overlay3D.add(this.bbox);
            }
        }
        this.state.selected = !this.state.selected;
    }

    toggleCollapsed() { }
    explode() { }
}
<|endoftext|>


<commit_before>'use strict';

module.exports = function(gulp, $, paths, env) {
  // minify html for prod
  gulp.task('html:prod', function() {
    var options = {
      empty: true,
      spare: true,
      quotes: true
    };

    return gulp.src(paths.prod.root + '/index.html')
      .pipe($.minifyHtml(options))
      .pipe(gulp.dest(paths.prod.root));
  });
};
<commit_msg>log size of html<commit_after>'use strict';

module.exports = function(gulp, $, paths, env) {
  // minify html for prod
  gulp.task('html:prod', function() {
    var options = {
      empty: true,
      spare: true,
      quotes: true
    };

    return gulp.src(paths.prod.root + '/index.html')
      .pipe($.minifyHtml(options))
      .pipe($.size({title: 'HTML'}))
      .pipe(gulp.dest(paths.prod.root));
  });
};
<|endoftext|>


<commit_before>var gulp = require('gulp'),
  gulpLoadPlugins = require('gulp-load-plugins'),
  request = require('request'),
  karmaServer = require('karma').Server,
  _ = require('lodash'),
  fs = require('fs'),
  assets = require('../config/assets.json');
var plugins = gulpLoadPlugins();

process.env.NODE_ENV = 'test';

gulp.task('test', ['startServer', 'stopServer']);
gulp.task('startServer', function(done) {
  var promise = require('../server.js');

  promise.then(function(app){done();});
});
gulp.task('stopServer', ['runKarma'], function() {
  process.exit();
});
gulp.task('runMocha', ['startServer'], function () {
  return gulp.src('./packages/**/server/tests/**/*.spec.js', {read: false})
    .pipe(plugins.mocha({
      reporter: 'spec'
    }));
});
gulp.task('runKarma', ['runMocha'], function (done) {
  request('http://localhost:3001/api/aggregatedassets', function(error, response, body) {
    var aggregatedassets = JSON.parse(body);
    aggregatedassets = processIncludes(aggregatedassets.footer.js);

    var karma = new karmaServer({
      configFile: __dirname + '/../karma.conf.js',
      singleRun: true,
      files: aggregatedassets.concat(['packages/**/public/tests/**/*.js', 'packages/**/public/**/*.html'])
    }, function () {
      done();
    });

    karma.start();
  });
});

function processIncludes(aggregatedAssets) {
  for(var i = 0; i < aggregatedAssets.length; ++i) {
    aggregatedAssets[i] = aggregatedAssets[i].slice(1);
    if(aggregatedAssets[i].indexOf('bower_components/') == -1) {
      var index = aggregatedAssets[i].indexOf('/') + 1;
      aggregatedAssets[i] = aggregatedAssets[i].substring(0, index) + "public/" + aggregatedAssets[i].substring(index);
    }
    try {
      var stats = fs.lstatSync(__dirname + '/../packages/core/' + aggregatedAssets[i]);
      aggregatedAssets[i] = 'packages/core/' + aggregatedAssets[i];
      continue;
    } catch(e) {
      // Not a file
    }
    try {
      stats = fs.lstatSync(__dirname + '/../packages/custom/' + aggregatedAssets[i]);
      aggregatedAssets[i] = 'packages/custom/' + aggregatedAssets[i];
    } catch (e) {
      // Not a file
    }
  }
  return aggregatedAssets;
}<commit_msg>Applied fix for gulp hanging on error.<commit_after>var gulp = require('gulp'),
  gulpLoadPlugins = require('gulp-load-plugins'),
  request = require('request'),
  karmaServer = require('karma').Server,
  _ = require('lodash'),
  fs = require('fs'),
  assets = require('../config/assets.json');
var plugins = gulpLoadPlugins();

process.env.NODE_ENV = 'test';

gulp.task('test', ['startServer', 'stopServer']);
gulp.task('startServer', function(done) {
  var promise = require('../server.js');

  promise.then(function(app){done();});
});
gulp.task('stopServer', ['runKarma'], function() {
  process.exit();
});
gulp.task('runMocha', ['startServer'], function () {
  return gulp.src('./packages/**/server/tests/**/*.spec.js', {read: false})
    .pipe(plugins.mocha({
      reporter: 'spec'
    }))
    .on('error', function(error){
      console.error(errro);
      this.emit('end');
    });
});
gulp.task('runKarma', ['runMocha'], function (done) {
  request('http://localhost:3001/api/aggregatedassets', function(error, response, body) {
    var aggregatedassets = JSON.parse(body);
    aggregatedassets = processIncludes(aggregatedassets.footer.js);

    var karma = new karmaServer({
      configFile: __dirname + '/../karma.conf.js',
      singleRun: true,
      files: aggregatedassets.concat(['packages/**/public/tests/**/*.js', 'packages/**/public/**/*.html'])
    }, function () {
      done();
    });

    karma.start();
  });
});

function processIncludes(aggregatedAssets) {
  for(var i = 0; i < aggregatedAssets.length; ++i) {
    aggregatedAssets[i] = aggregatedAssets[i].slice(1);
    if(aggregatedAssets[i].indexOf('bower_components/') == -1) {
      var index = aggregatedAssets[i].indexOf('/') + 1;
      aggregatedAssets[i] = aggregatedAssets[i].substring(0, index) + "public/" + aggregatedAssets[i].substring(index);
    }
    try {
      var stats = fs.lstatSync(__dirname + '/../packages/core/' + aggregatedAssets[i]);
      aggregatedAssets[i] = 'packages/core/' + aggregatedAssets[i];
      continue;
    } catch(e) {
      // Not a file
    }
    try {
      stats = fs.lstatSync(__dirname + '/../packages/custom/' + aggregatedAssets[i]);
      aggregatedAssets[i] = 'packages/custom/' + aggregatedAssets[i];
    } catch (e) {
      // Not a file
    }
  }
  return aggregatedAssets;
}<|endoftext|>


<commit_before>/**
 * @fileOverview A wrapper for the Jetty HTTP server.
 */

var log = require('ringo/logging').getLogger(module.id);
var Parser = require('ringo/args').Parser;
var system = require('system');
var {JavaEventEmitter} = require('ringo/events');
var {WebSocket, WebSocketServlet} = org.eclipse.jetty.websocket;

export('Server', 'main', 'init', 'start', 'stop', 'destroy');

var options,
    server,
    parser,
    started = false;

/**
 * Create a Jetty HTTP server with the given options. The options may
 * either define properties to be used with the default jetty.xml, or define
 * a custom configuration file.
 *
 * @param {Object} options A javascript object with any of the following
 * properties (default values in parentheses):
 *

 *
jettyConfig ('config/jetty.xml')

 *
port (8080)

 *
host (undefined)

 *

 *
 * For convenience, the constructor supports the definition of a JSGI application
 * and static resource mapping in the options object using the following properties:
 *

 *
virtualHost (undefined)

 *
mountpoint ('/')

 *
staticDir ('static')

 *
staticMountpoint ('/static')

 *
appModule ('main')

 *
appName ('app')

 *

 */
function Server(options) {

    if (!(this instanceof Server)) {
        return new Server(options);
    }

    // the jetty server instance
    var jetty;
    var defaultContext;
    var contextMap = {};
    var xmlconfig;

    /**
     * Get the server's default context. The default context is the
     * context that is created when the server is created.
     * @since: 0.6
     * @returns the default context
     */
    this.getDefaultContext = function() {
        return defaultContext;
    };

    /**
     * Get a servlet application context for the given path and virtual hosts, creating
     * it if it doesn't exist.
     * @param {string} path the context root path such as "/" or "/app"
     * @param {string|array} virtualHosts optional single or multiple virtual host names.
     *   A virtual host may start with a "*." wildcard.
     * @param {Object} options may have the following properties:
     *   sessions: true to enable sessions for this context, false otherwise
     *   security: true to enable security for this context, false otherwise
     * @since: 0.6
     * @returns a Context object
     */
    this.getContext = function(path, virtualHosts, options) {
        var idMap = xmlconfig.getIdMap();
        options = options || {};
        var contextKey = virtualHosts ? String(virtualHosts) + path : path;
        var cx = contextMap[contextKey];
        if (!cx) {
            var contexts = idMap.get("Contexts");
            var sessions = Boolean(options.sessions);
            var security = Boolean(options.security);
            cx = new org.eclipse.jetty.servlet.ServletContextHandler(contexts, path, sessions, security);
            if (virtualHosts) {
                cx.setVirtualHosts(Array.isArray(virtualHosts) ? virtualHosts : [String(virtualHosts)]);
            }
            contextMap[contextKey] = cx;
            if (jetty.isRunning()) {
                cx.start();
            }
        }

        return {
            /**
             * Map this context to a JSGI application.
             * @param {function|object} app a JSGI application, either as a function
             *   or an object with properties appModule and
             *   appName defining the application.
             *
{ appModule: 'main', appName: 'app' }

             * @param {RhinoEngine} engine optional RhinoEngine instance for
             *   multi-engine setups
             * @since: 0.6
             * @name Context.instance.serveApplication
             */
            serveApplication: function(app, engine) {
                log.debug("Adding JSGI application:", cx, "->", app);
                engine = engine || require('ringo/engine').getRhinoEngine();
                var isFunction = typeof app === "function";
                var servlet = isFunction ?
                              new JsgiServlet(engine, app) :
                              new JsgiServlet(engine);
                var jpkg = org.eclipse.jetty.servlet;
                var servletHolder = new jpkg.ServletHolder(servlet);
                if (!isFunction) {
                    servletHolder.setInitParameter('app-module', app.appModule || 'main');
                    servletHolder.setInitParameter('app-name', app.appName || 'app');
                }
                cx.addServlet(servletHolder, "/*");
            },
            /**
             * Map this context to a directory containing static resources.
             * @param {string} dir the directory from which to serve static resources
             * @since: 0.6
             * @name Context.instance.serveStatic
             */
            serveStatic: function(dir) {
                log.debug("Adding static handler:", cx, "->", dir);
                var repo = getRepository(dir);
                cx.setResourceBase(repo.exists() ? repo.getPath() : dir);
                var jpkg = org.eclipse.jetty.servlet;
                var servletHolder = new jpkg.ServletHolder(jpkg.DefaultServlet);
                cx.addServlet(servletHolder, "/*");
            },
            /**
             * Map a request path within this context to the given servlet.
             * @param {string} servletPath the servlet path
             * @param {Servlet} servlet a java object implementing the
             *     javax.servlet.Servlet interface.
             * @param {Object} initParams optional object containing servlet
             *     init parameters
             * @since: 0.6
             * @name Context.instance.addServlet
             */
            addServlet: function(servletPath, servlet, initParams) {
                log.debug("Adding Servlet:", servletPath, "->", servlet);
                var jpkg = org.eclipse.jetty.servlet;
                var servletHolder = new jpkg.ServletHolder(servlet);
                for (var p in initParams) {
                    servletHolder.setInitParameter(p, initParams[p])
                }
                cx.addServlet(servletHolder, servletPath);
            },
            /**
             * Start accepting WebSocket connections in this context context.
             *
             * @param {String} path The URL path on which to accept WebSocket connections
             * @param {Function} onconnect a function called for each new WebSocket connection
             *        with the WebSocket object as argument.
             * @since 0.8
             * @see #WebSocket
             * @name Context.instance.addWebSocket
             */
            addWebSocket: function(path, onconnect) {
                log.info("Starting websocket support");
                this.addServlet(path, new WebSocketServlet({
                    doWebSocketConnect : function(request, protocol) {
                        log.debug("new websocket");

                        var conn;
                        /**
                         * Provides support for WebSockets in the HTTP server.
                         *
                         * WebSocket is an event emitter that supports the
                         * following events:
                         *
                         *  * **open**: called when a new websocket connection is accepted
                         *  * **message**: Called with a complete text message when all fragments have been received.
                         *  * **close**: called when an established websocket connection closes
                         *
                         * @name WebSocket
                         */
                        var socket = {
                            /**
                             * Closes the WebSocket connection.
                             * @name WebSocket.instance.close
                             * @function
                             */
                            close: function() {
                                if (conn) {
                                    conn.disconnect();
                                }
                            },
                            /**
                             * Send a string over the WebSocket.
                             * @param msg a string
                             * @name WebSocket.instance.send
                             * @function
                             */
                            send: function(msg) {
                                if (conn) {
                                    conn.sendMessage(msg);
                                }
                            },
                            /**
                             * Check whether the WebSocket is open.
                             * @name WebSocket.instance.isOpen
                             * @return {Boolean} true if the connection is open
                             * @function
                             */
                            isOpen: function() {
                                return conn && conn.isOpen();
                            }

                        };

                        // make socket a java event-emitter (mixin)
                        JavaEventEmitter.call(socket, [WebSocket.OnTextMessage,
                                                       WebSocket.OnBinaryMessage]);

                        socket.addListener("open", function(connection) {
                            conn = connection;
                        });

                        if (typeof onconnect === "function") {
                            onconnect(socket, request, protocol);
                        }

                        return socket.impl;
                    }
                }));
            }
        };
    };

    /**
     * Start the HTTP server.
     */
    this.start = function() {
        jetty.start();
        log.info('Server on http://' + (props.get('host') || 'localhost') +
                ':' + props.get('port') + ' started.');
    };

    /**
     * Stop the HTTP server.
     */
    this.stop = function() {
        jetty.stop();
        contextMap = {};
    };

    /**
     * Destroy the HTTP server, freeing its resources.
     */
    this.destroy = function() {
        jetty.destroy();
    };

    /**
     * Checks whether this server is currently running.
     * @returns true if the server is running, false otherwise.
     */
    this.isRunning = function() {
        return jetty != null && jetty.isRunning();
    };

    /**
     * Get the Jetty server instance
     * @returns the Jetty Server instance
     */
    this.getJetty = function() {
        return jetty;
    };

    options = options || {};

    var jettyFile = options.jettyConfig || 'config/jetty.xml';
    var jettyConfig = getResource(jettyFile);
    if (!jettyConfig.exists()) {
        throw Error('Resource "' + jettyFile + '" not found');
    }
    var XmlConfiguration = org.eclipse.jetty.xml.XmlConfiguration;
    var JsgiServlet = org.ringojs.jsgi.JsgiServlet;
    jetty = new org.eclipse.jetty.server.Server();
    xmlconfig = new XmlConfiguration(jettyConfig.inputStream);

    // port config is done via properties
    var props = xmlconfig.getProperties();
    props.put('port', (options.port || 8080).toString());
    if (options.host) props.put('host', options.host);
    xmlconfig.configure(jetty);

    // create default context
    defaultContext = this.getContext(options.mountpoint || "/", options.virtualHost, {
        security: true,
        sessions: true
    });

    // If options defines an application mount it
    if (typeof options.app === "function") {
        defaultContext.serveApplication(options.app);
    } else if (options.appModule && options.appName) {
        defaultContext.serveApplication(options);
    }

    // Allow definition of app/static mappings in server config for convenience
    if (options.staticDir) {
        var files = require('ringo/utils/files');
        var staticContext = this.getContext(options.staticMountpoint || '/static', options.virtualHost);
        staticContext.serveStatic(files.resolveId(options.appModule, options.staticDir));
    }

    // Start listeners. This allows us to run on privileged port 80 under jsvc
    // even as non-root user if the constructor is called with root privileges
    // while start() is called with the user we will actually run as
    var connectors = jetty.getConnectors();
    for each (var connector in connectors) {
        connector.open();
    }

}

function parseOptions(arguments, defaults) {
    // parse command line options
    parser = new Parser();
    parser.addOption("a", "app-name", "APP", "The exported property name of the JSGI app (default: 'app')");
    parser.addOption("j", "jetty-config", "PATH", "The jetty xml configuration file (default. 'config/jetty.xml')");
    parser.addOption("H", "host", "ADDRESS", "The IP address to bind to (default: 0.0.0.0)");
    parser.addOption("m", "mountpoint", "PATH", "The URI path where to mount the application (default: /)");
    parser.addOption("p", "port", "PORT", "The TCP port to listen on (default: 80)");
    parser.addOption("s", "static-dir", "DIR", "A directory with static resources to serve");
    parser.addOption("S", "static-mountpoint", "PATH", "The URI path where ot mount the static resources");
    parser.addOption("v", "virtual-host", "VHOST", "The virtual host name (default: undefined)");
    parser.addOption("h", "help", null, "Print help message to stdout");
    options = parser.parse(arguments, defaults);
    if (options.port && !isFinite(options.port)) {
        var port = parseInt(options.port, 10);
        if (isNaN(port) || port < 1) {
            throw "Invalid value for port: " + options.port;
        }
        options.port = port;
    }
    return options;
}

/**
 * Daemon life cycle function invoked by init script. Creates a new Server with
 * the application at `path`. If the application exports a function called
 * `init`, it will be invoked with the new server as argument.
 *
 * @param path {string} optional path to the application. If undefined,
 *     the path will be taken from `system.args`.
 * @returns {Server} the Server instance.
 */
function init(path) {
    // protect against module reloading
    if (started) {
        return server;
    }
    // parse command line options
    var cmd = system.args.shift();
    try {
        options = parseOptions(system.args, {
            appName: "app"
        });
    } catch (error) {
        log.error("Error parsing options", error);
        system.exit(1);
    }

    if (options.help) {
        print("Usage:");
        print("", cmd, "[OPTIONS]", "[PATH]");
        print("Options:");
        print(parser.help());
        system.exit(0);
    }

    var appDir = "";
    // if no explicit path is given use first command line argument
    path = path || system.args[0];
    var fs = require("fs");
    if (path) {
        // use argument as app module
        options.appModule = path;
        appDir = fs.directory(path);
    } else {
        // look for `main` module in current path as app module
        appDir = fs.workingDirectory();
        options.appModule = fs.join(appDir, "main");
    }

    // logging module is already loaded and configured, check if app provides
    // its own log4j configuration file and apply it if so.
    var logConfig = getResource(fs.join(appDir, "config/log4j.properties"));
    if (logConfig.exists()) {
        require("./logging").setConfig(logConfig);
    }
    log.info("Set app module:", options.appModule);

    server = new Server(options);
    var app = require(options.appModule);
    if (typeof app.init === "function") {
        app.init(server);
    }
    return server;
}

/**
 * Daemon life cycle function invoked by init script. Starts the Server created
 * by `init()`. If the application exports a function called `start`, it will be
 * invoked with the server as argument immediately after it has started.
 *
 * @returns {Server} the Server instance.
 */
function start() {
    server.start();
    started = true;
    var app = require(options.appModule);
    if (typeof app.start === "function") {
        app.start(server);
    }
    return server;
}

/**
 * Daemon life cycle function invoked by init script. Stops the Server started
 * by `start()`.
 * @returns {Server} the Server instance. If the application exports a function
 * called `stop`, it will be invoked with the server as argument immediately
 * before it is stopped.
 *
 * @returns {Server} the Server instance.
 */
function stop() {
    var app = require(options.appModule);
    if (typeof app.stop === "function") {
        app.stop(server);
    }
    server.stop();
    started = false;
    return server;
}

/**
 * Daemon life cycle function invoked by init script. Frees any resources
 * occupied by the Server instance.  If the application exports a function
 * called `destroy`, it will be invoked with the server as argument.
 *
 * @returns {Server} the Server instance.
 */
function destroy() {
    var app = require(options.appModule);
    if (typeof app.destroy === "function") {
        app.destroy(server);
    }
    server.destroy();
    try {
        return server;
    } finally {
        server = null;
    }
}

/**
 * Main webapp startup function.
 * @param {String} path optional path to the web application directory or module.
 * @returns {Server} the Server instance.
 */
function main(path) {
    init(path);
    start();
    // return the server instance
    return server;
}

if (require.main == module) {
    main();
}
<commit_msg>Improve doc and inline comments, rename argument<commit_after>/**
 * @fileOverview A wrapper for the Jetty HTTP server.
 */

var log = require('ringo/logging').getLogger(module.id);
var Parser = require('ringo/args').Parser;
var system = require('system');
var {JavaEventEmitter} = require('ringo/events');
var {WebSocket, WebSocketServlet} = org.eclipse.jetty.websocket;

export('Server', 'main', 'init', 'start', 'stop', 'destroy');

var options,
    server,
    parser,
    started = false;

/**
 * Create a Jetty HTTP server with the given options. The options may
 * either define properties to be used with the default jetty.xml, or define
 * a custom configuration file.
 *
 * @param {Object} options A javascript object with any of the following
 * properties (default values in parentheses):
 *

 *
jettyConfig ('config/jetty.xml')

 *
port (8080)

 *
host (undefined)

 *

 *
 * For convenience, the constructor supports the definition of a JSGI application
 * and static resource mapping in the options object using the following properties:
 *

 *
virtualHost (undefined)

 *
mountpoint ('/')

 *
staticDir ('static')

 *
staticMountpoint ('/static')

 *
appModule ('main')

 *
appName ('app')

 *

 */
function Server(options) {

    if (!(this instanceof Server)) {
        return new Server(options);
    }

    // the jetty server instance
    var jetty;
    var defaultContext;
    var contextMap = {};
    var xmlconfig;

    /**
     * Get the server's default context. The default context is the
     * context that is created when the server is created.
     * @since: 0.6
     * @returns the default context
     */
    this.getDefaultContext = function() {
        return defaultContext;
    };

    /**
     * Get a servlet application context for the given path and virtual hosts, creating
     * it if it doesn't exist.
     * @param {string} path the context root path such as "/" or "/app"
     * @param {string|array} virtualHosts optional single or multiple virtual host names.
     *   A virtual host may start with a "*." wildcard.
     * @param {Object} options may have the following properties:
     *   sessions: true to enable sessions for this context, false otherwise
     *   security: true to enable security for this context, false otherwise
     * @since: 0.6
     * @returns a Context object
     */
    this.getContext = function(path, virtualHosts, options) {
        var idMap = xmlconfig.getIdMap();
        options = options || {};
        var contextKey = virtualHosts ? String(virtualHosts) + path : path;
        var cx = contextMap[contextKey];
        if (!cx) {
            var contexts = idMap.get("Contexts");
            var sessions = Boolean(options.sessions);
            var security = Boolean(options.security);
            cx = new org.eclipse.jetty.servlet.ServletContextHandler(contexts, path, sessions, security);
            if (virtualHosts) {
                cx.setVirtualHosts(Array.isArray(virtualHosts) ? virtualHosts : [String(virtualHosts)]);
            }
            contextMap[contextKey] = cx;
            if (jetty.isRunning()) {
                cx.start();
            }
        }

        return {
            /**
             * Map this context to a JSGI application.
             * @param {function|object} app a JSGI application, either as a function
             *   or an object with properties appModule and
             *   appName defining the application.
             *
{ appModule: 'main', appName: 'app' }

             * @param {RhinoEngine} engine optional RhinoEngine instance for
             *   multi-engine setups
             * @since: 0.6
             * @name Context.instance.serveApplication
             */
            serveApplication: function(app, engine) {
                log.debug("Adding JSGI application:", cx, "->", app);
                engine = engine || require('ringo/engine').getRhinoEngine();
                var isFunction = typeof app === "function";
                var servlet = isFunction ?
                              new JsgiServlet(engine, app) :
                              new JsgiServlet(engine);
                var jpkg = org.eclipse.jetty.servlet;
                var servletHolder = new jpkg.ServletHolder(servlet);
                if (!isFunction) {
                    servletHolder.setInitParameter('app-module', app.appModule || 'main');
                    servletHolder.setInitParameter('app-name', app.appName || 'app');
                }
                cx.addServlet(servletHolder, "/*");
            },
            /**
             * Map this context to a directory containing static resources.
             * @param {string} dir the directory from which to serve static resources
             * @since: 0.6
             * @name Context.instance.serveStatic
             */
            serveStatic: function(dir) {
                log.debug("Adding static handler:", cx, "->", dir);
                var repo = getRepository(dir);
                cx.setResourceBase(repo.exists() ? repo.getPath() : dir);
                var jpkg = org.eclipse.jetty.servlet;
                var servletHolder = new jpkg.ServletHolder(jpkg.DefaultServlet);
                cx.addServlet(servletHolder, "/*");
            },
            /**
             * Map a request path within this context to the given servlet.
             * @param {string} servletPath the servlet path
             * @param {Servlet} servlet a java object implementing the
             *     javax.servlet.Servlet interface.
             * @param {Object} initParams optional object containing servlet
             *     init parameters
             * @since: 0.6
             * @name Context.instance.addServlet
             */
            addServlet: function(servletPath, servlet, initParams) {
                log.debug("Adding Servlet:", servletPath, "->", servlet);
                var jpkg = org.eclipse.jetty.servlet;
                var servletHolder = new jpkg.ServletHolder(servlet);
                for (var p in initParams) {
                    servletHolder.setInitParameter(p, initParams[p])
                }
                cx.addServlet(servletHolder, servletPath);
            },
            /**
             * Start accepting WebSocket connections in this context context.
             *
             * @param {String} path The URL path on which to accept WebSocket connections
             * @param {Function} onconnect a function called for each new WebSocket connection
             *        with the WebSocket object as argument.
             * @since 0.8
             * @see #WebSocket
             * @name Context.instance.addWebSocket
             */
            addWebSocket: function(path, onconnect) {
                log.info("Starting websocket support");
                this.addServlet(path, new WebSocketServlet({
                    doWebSocketConnect : function(request, protocol) {
                        log.debug("new websocket");

                        var conn;
                        /**
                         * Provides support for WebSockets in the HTTP server.
                         *
                         * WebSocket is an event emitter that supports the
                         * following events:
                         *
                         *  * **open**: called when a new websocket connection is accepted
                         *  * **message**: Called with a complete text message when all fragments have been received.
                         *  * **close**: called when an established websocket connection closes
                         *
                         * @name WebSocket
                         */
                        var socket = {
                            /**
                             * Closes the WebSocket connection.
                             * @name WebSocket.instance.close
                             * @function
                             */
                            close: function() {
                                if (conn) {
                                    conn.disconnect();
                                }
                            },
                            /**
                             * Send a string over the WebSocket.
                             * @param msg a string
                             * @name WebSocket.instance.send
                             * @function
                             */
                            send: function(msg) {
                                if (conn) {
                                    conn.sendMessage(msg);
                                }
                            },
                            /**
                             * Check whether the WebSocket is open.
                             * @name WebSocket.instance.isOpen
                             * @return {Boolean} true if the connection is open
                             * @function
                             */
                            isOpen: function() {
                                return conn && conn.isOpen();
                            }

                        };

                        // make socket a java event-emitter (mixin)
                        JavaEventEmitter.call(socket, [WebSocket.OnTextMessage,
                                                       WebSocket.OnBinaryMessage]);

                        socket.addListener("open", function(connection) {
                            conn = connection;
                        });

                        if (typeof onconnect === "function") {
                            onconnect(socket, request, protocol);
                        }

                        return socket.impl;
                    }
                }));
            }
        };
    };

    /**
     * Start the HTTP server.
     */
    this.start = function() {
        jetty.start();
        log.info('Server on http://' + (props.get('host') || 'localhost') +
                ':' + props.get('port') + ' started.');
    };

    /**
     * Stop the HTTP server.
     */
    this.stop = function() {
        jetty.stop();
        contextMap = {};
    };

    /**
     * Destroy the HTTP server, freeing its resources.
     */
    this.destroy = function() {
        jetty.destroy();
    };

    /**
     * Checks whether this server is currently running.
     * @returns true if the server is running, false otherwise.
     */
    this.isRunning = function() {
        return jetty != null && jetty.isRunning();
    };

    /**
     * Get the Jetty server instance
     * @returns the Jetty Server instance
     */
    this.getJetty = function() {
        return jetty;
    };

    options = options || {};

    var jettyFile = options.jettyConfig || 'config/jetty.xml';
    var jettyConfig = getResource(jettyFile);
    if (!jettyConfig.exists()) {
        throw Error('Resource "' + jettyFile + '" not found');
    }
    var XmlConfiguration = org.eclipse.jetty.xml.XmlConfiguration;
    var JsgiServlet = org.ringojs.jsgi.JsgiServlet;
    jetty = new org.eclipse.jetty.server.Server();
    xmlconfig = new XmlConfiguration(jettyConfig.inputStream);

    // port config is done via properties
    var props = xmlconfig.getProperties();
    props.put('port', (options.port || 8080).toString());
    if (options.host) props.put('host', options.host);
    xmlconfig.configure(jetty);

    // create default context
    defaultContext = this.getContext(options.mountpoint || "/", options.virtualHost, {
        security: true,
        sessions: true
    });

    // If options defines an application mount it
    if (typeof options.app === "function") {
        defaultContext.serveApplication(options.app);
    } else if (options.appModule && options.appName) {
        defaultContext.serveApplication(options);
    }

    // Allow definition of app/static mappings in server config for convenience
    if (options.staticDir) {
        var files = require('ringo/utils/files');
        var staticContext = this.getContext(options.staticMountpoint || '/static', options.virtualHost);
        staticContext.serveStatic(files.resolveId(options.appModule, options.staticDir));
    }

    // Start listeners. This allows us to run on privileged port 80 under jsvc
    // even as non-root user if the constructor is called with root privileges
    // while start() is called with the user we will actually run as
    var connectors = jetty.getConnectors();
    for each (var connector in connectors) {
        connector.open();
    }

}

function parseOptions(arguments, defaults) {
    // parse command line options
    parser = new Parser();
    parser.addOption("a", "app-name", "APP", "The exported property name of the JSGI app (default: 'app')");
    parser.addOption("j", "jetty-config", "PATH", "The jetty xml configuration file (default. 'config/jetty.xml')");
    parser.addOption("H", "host", "ADDRESS", "The IP address to bind to (default: 0.0.0.0)");
    parser.addOption("m", "mountpoint", "PATH", "The URI path where to mount the application (default: /)");
    parser.addOption("p", "port", "PORT", "The TCP port to listen on (default: 80)");
    parser.addOption("s", "static-dir", "DIR", "A directory with static resources to serve");
    parser.addOption("S", "static-mountpoint", "PATH", "The URI path where ot mount the static resources");
    parser.addOption("v", "virtual-host", "VHOST", "The virtual host name (default: undefined)");
    parser.addOption("h", "help", null, "Print help message to stdout");
    options = parser.parse(arguments, defaults);
    if (options.port && !isFinite(options.port)) {
        var port = parseInt(options.port, 10);
        if (isNaN(port) || port < 1) {
            throw "Invalid value for port: " + options.port;
        }
        options.port = port;
    }
    return options;
}

/**
 * Daemon life cycle function invoked by init script. Creates a new Server with
 * the application at `appPath`. If the application exports a function called
 * `init`, it will be invoked with the new server as argument.
 *
 * @param appPath {string} optional application file name or module id.
 *     If undefined, the first command line argument will be used as application.
 *     If there are no command line arguments, module `main` in the current
 *     working directory is used.
 * @returns {Server} the Server instance.
 */
function init(appPath) {
    // protect against module reloading
    if (started) {
        return server;
    }
    // parse command line options
    var cmd = system.args.shift();
    try {
        options = parseOptions(system.args, {
            appName: "app"
        });
    } catch (error) {
        log.error("Error parsing options", error);
        system.exit(1);
    }

    if (options.help) {
        print("Usage:");
        print("", cmd, "[OPTIONS]", "[PATH]");
        print("Options:");
        print(parser.help());
        system.exit(0);
    }

    var appDir = "";
    // if no explicit app is given use first command line argument
    appPath = appPath || system.args[0];
    var fs = require("fs");
    if (appPath) {
        // use argument as app module
        options.appModule = appPath;
        appDir = fs.directory(appPath);
    } else {
        // look for `main` module in current working directory as app module
        appDir = fs.workingDirectory();
        options.appModule = fs.join(appDir, "main");
    }

    // logging module is already loaded and configured, check if app provides
    // its own log4j configuration file and apply it if so.
    var logConfig = getResource(fs.join(appDir, "config/log4j.properties"));
    if (logConfig.exists()) {
        require("./logging").setConfig(logConfig);
    }
    log.info("Set app module:", options.appModule);

    server = new Server(options);
    var app = require(options.appModule);
    if (typeof app.init === "function") {
        app.init(server);
    }
    return server;
}

/**
 * Daemon life cycle function invoked by init script. Starts the Server created
 * by `init()`. If the application exports a function called `start`, it will be
 * invoked with the server as argument immediately after it has started.
 *
 * @returns {Server} the Server instance.
 */
function start() {
    server.start();
    started = true;
    var app = require(options.appModule);
    if (typeof app.start === "function") {
        app.start(server);
    }
    return server;
}

/**
 * Daemon life cycle function invoked by init script. Stops the Server started
 * by `start()`.
 * @returns {Server} the Server instance. If the application exports a function
 * called `stop`, it will be invoked with the server as argument immediately
 * before it is stopped.
 *
 * @returns {Server} the Server instance.
 */
function stop() {
    var app = require(options.appModule);
    if (typeof app.stop === "function") {
        app.stop(server);
    }
    server.stop();
    started = false;
    return server;
}

/**
 * Daemon life cycle function invoked by init script. Frees any resources
 * occupied by the Server instance.  If the application exports a function
 * called `destroy`, it will be invoked with the server as argument.
 *
 * @returns {Server} the Server instance.
 */
function destroy() {
    var app = require(options.appModule);
    if (typeof app.destroy === "function") {
        app.destroy(server);
    }
    server.destroy();
    try {
        return server;
    } finally {
        server = null;
    }
}

/**
 * Main function to start an HTTP server from the command line.
 * @param {String} appPath optional application file name or module id.
 * @returns {Server} the Server instance.
 */
function main(appPath) {
    init(appPath);
    start();
    // return the server instance
    return server;
}

if (require.main == module) {
    main();
}
<|endoftext|>