<commit_before>if (global.GENTLY) require = GENTLY.hijack(require);

var fs = require('fs');
var util = require('util'),
    path = require('path'),
    File = require('./file'),
    MultipartParser = require('./multipart_parser').MultipartParser,
    QuerystringParser = require('./querystring_parser').QuerystringParser,
    OctetParser       = require('./octet_parser').OctetParser,
    JSONParser = require('./json_parser').JSONParser,
    StringDecoder = require('string_decoder').StringDecoder,
    EventEmitter = require('events').EventEmitter,
    Stream = require('stream').Stream,
    os = require('os');

function IncomingForm(opts) {
  if (!(this instanceof IncomingForm)) return new IncomingForm(opts);
  EventEmitter.call(this);

  opts=opts||{};

  this.error = null;
  this.ended = false;

  this.maxFields = opts.maxFields || 0;
  this.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024;
  this.keepExtensions = opts.keepExtensions || false;
  this.uploadDir = opts.uploadDir || os.tmpDir();
  this.encoding = opts.encoding || 'utf-8';
  this.headers = null;
  this.type = null;
  this.hash = false;

  this.bytesReceived = null;
  this.bytesExpected = null;

  this._parser = null;
  this._flushing = 0;
  this._fieldsSize = 0;
  this.openedFiles = [];

  return this;
};
util.inherits(IncomingForm, EventEmitter);
exports.IncomingForm = IncomingForm;

IncomingForm.prototype.parse = function(req, cb) {
  this.pause = function() {
    try {
      req.pause();
    } catch (err) {
      // the stream was destroyed
      if (!this.ended) {
        // before it was completed, crash & burn
        this._error(err);
      }
      return false;
    }
    return true;
  };

  this.resume = function() {
    try {
      req.resume();
    } catch (err) {
      // the stream was destroyed
      if (!this.ended) {
        // before it was completed, crash & burn
        this._error(err);
      }
      return false;
    }

    return true;
  };

  var self = this;
  req
    .on('error', function(err) {
      self._error(err);
    })
    .on('aborted', function() {
      self.emit('aborted');
      self._error(new Error('Request aborted'));
    })
    .on('data', function(buffer) {
      self.write(buffer);
    })
    .on('end', function() {
      if (self.error) {
        return;
      }

      var err = self._parser.end();
      if (err) {
        self._error(err);
      }
    });

  if (cb) {
    var fields = {}, files = {};
    this
      .on('field', function(name, value) {
        fields[name] = value;
      })
      .on('file', function(name, file) {
        files[name] = file;
      })
      .on('error', function(err) {
        cb(err, fields, files);
      })
      .on('end', function() {
        cb(null, fields, files);
      });
  }

  this.writeHeaders(req.headers);

  return this;
};

IncomingForm.prototype.writeHeaders = function(headers) {
  this.headers = headers;
  this._parseContentLength();
  this._parseContentType();
};

IncomingForm.prototype.write = function(buffer) {
  if (!this._parser) {
    this._error(new Error('unintialized parser'));
    return;
  }

  this.bytesReceived += buffer.length;
  this.emit('progress', this.bytesReceived, this.bytesExpected);

  var bytesParsed = this._parser.write(buffer);
  if (bytesParsed !== buffer.length) {
    this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed'));
  }

  return bytesParsed;
};

IncomingForm.prototype.pause = function() {
  // this does nothing, unless overwritten in IncomingForm.parse
  return false;
};

IncomingForm.prototype.resume = function() {
  // this does nothing, unless overwritten in IncomingForm.parse
  return false;
};

IncomingForm.prototype.onPart = function(part) {
  // this method can be overwritten by the user
  this.handlePart(part);
};

IncomingForm.prototype.handlePart = function(part) {
  var self = this;

  if (part.filename === undefined) {
    var value = ''
      , decoder = new StringDecoder(this.encoding);

    part.on('data', function(buffer) {
      self._fieldsSize += buffer.length;
      if (self._fieldsSize > self.maxFieldsSize) {
        self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data'));
        return;
      }
      value += decoder.write(buffer);
    });

    part.on('end', function() {
      self.emit('field', part.name, value);
    });
    return;
  }

  this._flushing++;

  var file = new File({
    path: this._uploadPath(part.filename),
    name: part.filename,
    type: part.mime,
    hash: self.hash
  });

  this.emit('fileBegin', part.name, file);

  file.open();
  this.openedFiles.push(file);

  part.on('data', function(buffer) {
    self.pause();
    file.write(buffer, function() {
      self.resume();
    });
  });

  part.on('end', function() {
    file.end(function() {
      self._flushing--;
      self.emit('file', part.name, file);
      self._maybeEnd();
    });
  });
};

function dummyParser(self) {
  return {
    end: function () {
      self.ended = true;
      self._maybeEnd();
      return null;
    }
  };
}

IncomingForm.prototype._parseContentType = function() {
  if (this.bytesExpected === 0) {
    this._parser = dummyParser(this);
    return;
  }

  if (!this.headers['content-type']) {
    this._error(new Error('bad content-type header, no content-type'));
    return;
  }

  if (this.headers['content-type'].match(/octet-stream/i)) {
    this._initOctetStream();
    return;
  }

  if (this.headers['content-type'].match(/urlencoded/i)) {
    this._initUrlencoded();
    return;
  }

  if (this.headers['content-type'].match(/multipart/i)) {
    var m;
    if (m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i)) {
      this._initMultipart(m[1] || m[2]);
    } else {
      this._error(new Error('bad content-type header, no multipart boundary'));
    }
    return;
  }

  if (this.headers['content-type'].match(/json/i)) {
    this._initJSONencoded();
    return;
  }

  this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type']));
};

IncomingForm.prototype._error = function(err) {
  if (this.error || this.ended) {
    return;
  }

  this.error = err;
  this.pause();
  this.emit('error', err);

  if (Array.isArray(this.openedFiles)) {
    this.openedFiles.forEach(function(file) {
      file._writeStream.destroy();
      setTimeout(fs.unlink, 0, file.path);
    });
  }
};

IncomingForm.prototype._parseContentLength = function() {
  if (this.headers['content-length']) {
    this.bytesReceived = 0;
    this.bytesExpected = parseInt(this.headers['content-length'], 10);
    this.emit('progress', this.bytesReceived, this.bytesExpected);
  }
};

IncomingForm.prototype._newParser = function() {
  return new MultipartParser();
};

IncomingForm.prototype._initMultipart = function(boundary) {
  this.type = 'multipart';

  var parser = new MultipartParser(),
      self = this,
      headerField,
      headerValue,
      part;

  parser.initWithBoundary(boundary);

  parser.onPartBegin = function() {
    part = new Stream();
    part.readable = true;
    part.headers = {};
    part.name = null;
    part.filename = null;
    part.mime = null;

    part.transferEncoding = 'binary';
    part.transferBuffer = '';

    headerField = '';
    headerValue = '';
  };

  parser.onHeaderField = function(b, start, end) {
    headerField += b.toString(self.encoding, start, end);
  };

  parser.onHeaderValue = function(b, start, end) {
    headerValue += b.toString(self.encoding, start, end);
  };

  parser.onHeaderEnd = function() {
    headerField = headerField.toLowerCase();
    part.headers[headerField] = headerValue;

    var m;
    if (headerField == 'content-disposition') {
      if (m = headerValue.match(/\bname="([^"]+)"/i)) {
        part.name = m[1];
      }

      part.filename = self._fileName(headerValue);
    } else if (headerField == 'content-type') {
      part.mime = headerValue;
    } else if (headerField == 'content-transfer-encoding') {
      part.transferEncoding = headerValue.toLowerCase();
    }

    headerField = '';
    headerValue = '';
  };

  parser.onHeadersEnd = function() {
    switch(part.transferEncoding){
      case 'binary':
      case '7bit':
      case '8bit':
      parser.onPartData = function(b, start, end) {
        part.emit('data', b.slice(start, end));
      };

      parser.onPartEnd = function() {
        part.emit('end');
      };
      break;

      case 'base64':
      parser.onPartData = function(b, start, end) {
        part.transferBuffer += b.slice(start, end).toString('ascii');

        /*
        four bytes (chars) in base64 converts to three bytes in binary
        encoding. So we should always work with a number of bytes that
        can be divided by 4, it will result in a number of buytes that
        can be divided vy 3.
        */
        var offset = parseInt(part.transferBuffer.length / 4) * 4;
        part.emit('data', new Buffer(part.transferBuffer.substring(0, offset), 'base64'))
        part.transferBuffer = part.transferBuffer.substring(offset);
      };

      parser.onPartEnd = function() {
        part.emit('data', new Buffer(part.transferBuffer, 'base64'))
        part.emit('end');
      };
      break;

      default:
      return self._error(new Error('unknown transfer-encoding'));
    }

    self.onPart(part);
  };

  parser.onEnd = function() {
    self.ended = true;
    self._maybeEnd();
  };

  this._parser = parser;
};

IncomingForm.prototype._fileName = function(headerValue) {
  var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
  if (!m) return;

  var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
  filename = filename.replace(/%22/g, '"');
  filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
    return String.fromCharCode(code);
  });
  return filename;
};

IncomingForm.prototype._initUrlencoded = function() {
  this.type = 'urlencoded';

  var parser = new QuerystringParser(this.maxFields)
    , self = this;

  parser.onField = function(key, val) {
    self.emit('field', key, val);
  };

  parser.onEnd = function() {
    self.ended = true;
    self._maybeEnd();
  };

  this._parser = parser;
};

IncomingForm.prototype._initOctetStream = function() {
  this.type = 'octet-stream';
  var filename = this.headers['x-file-name'];
  var mime = this.headers['content-type'];

  var file = new File({
    path: this._uploadPath(filename),
    name: filename,
    type: mime
  });

  file.open();

  this.emit('fileBegin', filename, file);

  this._flushing++;

  var self = this;

  self._parser = new OctetParser();

  //Keep track of writes that haven't finished so we don't emit the file before it's done being written
  var outstandingWrites = 0;

  self._parser.on('data', function(buffer){
    self.pause();
    outstandingWrites++;

    file.write(buffer, function() {
      outstandingWrites--;
      self.resume();

      if(self.ended){
        self._parser.emit('doneWritingFile');
      }
    });
  });

  self._parser.on('end', function(){
    self._flushing--;
    self.ended = true;

    var done = function(){
      self.emit('file', 'file', file);
      self._maybeEnd();
    };

    if(outstandingWrites === 0){
      done();
    } else {
      self._parser.once('doneWritingFile', done);
    }
  });
};

IncomingForm.prototype._initJSONencoded = function() {
  this.type = 'json';

  var parser = new JSONParser()
    , self = this;

  if (this.bytesExpected) {
    parser.initWithLength(this.bytesExpected);
  }

  parser.onField = function(key, val) {
    self.emit('field', key, val);
  }

  parser.onEnd = function() {
    self.ended = true;
    self._maybeEnd();
  };

  this._parser = parser;
};

IncomingForm.prototype._uploadPath = function(filename) {
  var name = '';
  for (var i = 0; i < 32; i++) {
    name += Math.floor(Math.random() * 16).toString(16);
  }

  if (this.keepExtensions) {
    var ext = path.extname(filename);
    ext     = ext.replace(/(\.[a-z0-9]+).*/, '$1');

    name += ext;
  }

  return path.join(this.uploadDir, name);
};

IncomingForm.prototype._maybeEnd = function() {
  if (!this.ended || this._flushing || this.error) {
    return;
  }

  this.emit('end');
};

<commit_msg>Change the default to 1000, to match the new Node behaviour.<commit_after>if (global.GENTLY) require = GENTLY.hijack(require);

var fs = require('fs');
var util = require('util'),
    path = require('path'),
    File = require('./file'),
    MultipartParser = require('./multipart_parser').MultipartParser,
    QuerystringParser = require('./querystring_parser').QuerystringParser,
    OctetParser       = require('./octet_parser').OctetParser,
    JSONParser = require('./json_parser').JSONParser,
    StringDecoder = require('string_decoder').StringDecoder,
    EventEmitter = require('events').EventEmitter,
    Stream = require('stream').Stream,
    os = require('os');

function IncomingForm(opts) {
  if (!(this instanceof IncomingForm)) return new IncomingForm(opts);
  EventEmitter.call(this);

  opts=opts||{};

  this.error = null;
  this.ended = false;

  this.maxFields = opts.maxFields || 1000;
  this.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024;
  this.keepExtensions = opts.keepExtensions || false;
  this.uploadDir = opts.uploadDir || os.tmpDir();
  this.encoding = opts.encoding || 'utf-8';
  this.headers = null;
  this.type = null;
  this.hash = false;

  this.bytesReceived = null;
  this.bytesExpected = null;

  this._parser = null;
  this._flushing = 0;
  this._fieldsSize = 0;
  this.openedFiles = [];

  return this;
};
util.inherits(IncomingForm, EventEmitter);
exports.IncomingForm = IncomingForm;

IncomingForm.prototype.parse = function(req, cb) {
  this.pause = function() {
    try {
      req.pause();
    } catch (err) {
      // the stream was destroyed
      if (!this.ended) {
        // before it was completed, crash & burn
        this._error(err);
      }
      return false;
    }
    return true;
  };

  this.resume = function() {
    try {
      req.resume();
    } catch (err) {
      // the stream was destroyed
      if (!this.ended) {
        // before it was completed, crash & burn
        this._error(err);
      }
      return false;
    }

    return true;
  };

  var self = this;
  req
    .on('error', function(err) {
      self._error(err);
    })
    .on('aborted', function() {
      self.emit('aborted');
      self._error(new Error('Request aborted'));
    })
    .on('data', function(buffer) {
      self.write(buffer);
    })
    .on('end', function() {
      if (self.error) {
        return;
      }

      var err = self._parser.end();
      if (err) {
        self._error(err);
      }
    });

  if (cb) {
    var fields = {}, files = {};
    this
      .on('field', function(name, value) {
        fields[name] = value;
      })
      .on('file', function(name, file) {
        files[name] = file;
      })
      .on('error', function(err) {
        cb(err, fields, files);
      })
      .on('end', function() {
        cb(null, fields, files);
      });
  }

  this.writeHeaders(req.headers);

  return this;
};

IncomingForm.prototype.writeHeaders = function(headers) {
  this.headers = headers;
  this._parseContentLength();
  this._parseContentType();
};

IncomingForm.prototype.write = function(buffer) {
  if (!this._parser) {
    this._error(new Error('unintialized parser'));
    return;
  }

  this.bytesReceived += buffer.length;
  this.emit('progress', this.bytesReceived, this.bytesExpected);

  var bytesParsed = this._parser.write(buffer);
  if (bytesParsed !== buffer.length) {
    this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed'));
  }

  return bytesParsed;
};

IncomingForm.prototype.pause = function() {
  // this does nothing, unless overwritten in IncomingForm.parse
  return false;
};

IncomingForm.prototype.resume = function() {
  // this does nothing, unless overwritten in IncomingForm.parse
  return false;
};

IncomingForm.prototype.onPart = function(part) {
  // this method can be overwritten by the user
  this.handlePart(part);
};

IncomingForm.prototype.handlePart = function(part) {
  var self = this;

  if (part.filename === undefined) {
    var value = ''
      , decoder = new StringDecoder(this.encoding);

    part.on('data', function(buffer) {
      self._fieldsSize += buffer.length;
      if (self._fieldsSize > self.maxFieldsSize) {
        self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data'));
        return;
      }
      value += decoder.write(buffer);
    });

    part.on('end', function() {
      self.emit('field', part.name, value);
    });
    return;
  }

  this._flushing++;

  var file = new File({
    path: this._uploadPath(part.filename),
    name: part.filename,
    type: part.mime,
    hash: self.hash
  });

  this.emit('fileBegin', part.name, file);

  file.open();
  this.openedFiles.push(file);

  part.on('data', function(buffer) {
    self.pause();
    file.write(buffer, function() {
      self.resume();
    });
  });

  part.on('end', function() {
    file.end(function() {
      self._flushing--;
      self.emit('file', part.name, file);
      self._maybeEnd();
    });
  });
};

function dummyParser(self) {
  return {
    end: function () {
      self.ended = true;
      self._maybeEnd();
      return null;
    }
  };
}

IncomingForm.prototype._parseContentType = function() {
  if (this.bytesExpected === 0) {
    this._parser = dummyParser(this);
    return;
  }

  if (!this.headers['content-type']) {
    this._error(new Error('bad content-type header, no content-type'));
    return;
  }

  if (this.headers['content-type'].match(/octet-stream/i)) {
    this._initOctetStream();
    return;
  }

  if (this.headers['content-type'].match(/urlencoded/i)) {
    this._initUrlencoded();
    return;
  }

  if (this.headers['content-type'].match(/multipart/i)) {
    var m;
    if (m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i)) {
      this._initMultipart(m[1] || m[2]);
    } else {
      this._error(new Error('bad content-type header, no multipart boundary'));
    }
    return;
  }

  if (this.headers['content-type'].match(/json/i)) {
    this._initJSONencoded();
    return;
  }

  this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type']));
};

IncomingForm.prototype._error = function(err) {
  if (this.error || this.ended) {
    return;
  }

  this.error = err;
  this.pause();
  this.emit('error', err);

  if (Array.isArray(this.openedFiles)) {
    this.openedFiles.forEach(function(file) {
      file._writeStream.destroy();
      setTimeout(fs.unlink, 0, file.path);
    });
  }
};

IncomingForm.prototype._parseContentLength = function() {
  if (this.headers['content-length']) {
    this.bytesReceived = 0;
    this.bytesExpected = parseInt(this.headers['content-length'], 10);
    this.emit('progress', this.bytesReceived, this.bytesExpected);
  }
};

IncomingForm.prototype._newParser = function() {
  return new MultipartParser();
};

IncomingForm.prototype._initMultipart = function(boundary) {
  this.type = 'multipart';

  var parser = new MultipartParser(),
      self = this,
      headerField,
      headerValue,
      part;

  parser.initWithBoundary(boundary);

  parser.onPartBegin = function() {
    part = new Stream();
    part.readable = true;
    part.headers = {};
    part.name = null;
    part.filename = null;
    part.mime = null;

    part.transferEncoding = 'binary';
    part.transferBuffer = '';

    headerField = '';
    headerValue = '';
  };

  parser.onHeaderField = function(b, start, end) {
    headerField += b.toString(self.encoding, start, end);
  };

  parser.onHeaderValue = function(b, start, end) {
    headerValue += b.toString(self.encoding, start, end);
  };

  parser.onHeaderEnd = function() {
    headerField = headerField.toLowerCase();
    part.headers[headerField] = headerValue;

    var m;
    if (headerField == 'content-disposition') {
      if (m = headerValue.match(/\bname="([^"]+)"/i)) {
        part.name = m[1];
      }

      part.filename = self._fileName(headerValue);
    } else if (headerField == 'content-type') {
      part.mime = headerValue;
    } else if (headerField == 'content-transfer-encoding') {
      part.transferEncoding = headerValue.toLowerCase();
    }

    headerField = '';
    headerValue = '';
  };

  parser.onHeadersEnd = function() {
    switch(part.transferEncoding){
      case 'binary':
      case '7bit':
      case '8bit':
      parser.onPartData = function(b, start, end) {
        part.emit('data', b.slice(start, end));
      };

      parser.onPartEnd = function() {
        part.emit('end');
      };
      break;

      case 'base64':
      parser.onPartData = function(b, start, end) {
        part.transferBuffer += b.slice(start, end).toString('ascii');

        /*
        four bytes (chars) in base64 converts to three bytes in binary
        encoding. So we should always work with a number of bytes that
        can be divided by 4, it will result in a number of buytes that
        can be divided vy 3.
        */
        var offset = parseInt(part.transferBuffer.length / 4) * 4;
        part.emit('data', new Buffer(part.transferBuffer.substring(0, offset), 'base64'))
        part.transferBuffer = part.transferBuffer.substring(offset);
      };

      parser.onPartEnd = function() {
        part.emit('data', new Buffer(part.transferBuffer, 'base64'))
        part.emit('end');
      };
      break;

      default:
      return self._error(new Error('unknown transfer-encoding'));
    }

    self.onPart(part);
  };

  parser.onEnd = function() {
    self.ended = true;
    self._maybeEnd();
  };

  this._parser = parser;
};

IncomingForm.prototype._fileName = function(headerValue) {
  var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
  if (!m) return;

  var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
  filename = filename.replace(/%22/g, '"');
  filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
    return String.fromCharCode(code);
  });
  return filename;
};

IncomingForm.prototype._initUrlencoded = function() {
  this.type = 'urlencoded';

  var parser = new QuerystringParser(this.maxFields)
    , self = this;

  parser.onField = function(key, val) {
    self.emit('field', key, val);
  };

  parser.onEnd = function() {
    self.ended = true;
    self._maybeEnd();
  };

  this._parser = parser;
};

IncomingForm.prototype._initOctetStream = function() {
  this.type = 'octet-stream';
  var filename = this.headers['x-file-name'];
  var mime = this.headers['content-type'];

  var file = new File({
    path: this._uploadPath(filename),
    name: filename,
    type: mime
  });

  file.open();

  this.emit('fileBegin', filename, file);

  this._flushing++;

  var self = this;

  self._parser = new OctetParser();

  //Keep track of writes that haven't finished so we don't emit the file before it's done being written
  var outstandingWrites = 0;

  self._parser.on('data', function(buffer){
    self.pause();
    outstandingWrites++;

    file.write(buffer, function() {
      outstandingWrites--;
      self.resume();

      if(self.ended){
        self._parser.emit('doneWritingFile');
      }
    });
  });

  self._parser.on('end', function(){
    self._flushing--;
    self.ended = true;

    var done = function(){
      self.emit('file', 'file', file);
      self._maybeEnd();
    };

    if(outstandingWrites === 0){
      done();
    } else {
      self._parser.once('doneWritingFile', done);
    }
  });
};

IncomingForm.prototype._initJSONencoded = function() {
  this.type = 'json';

  var parser = new JSONParser()
    , self = this;

  if (this.bytesExpected) {
    parser.initWithLength(this.bytesExpected);
  }

  parser.onField = function(key, val) {
    self.emit('field', key, val);
  }

  parser.onEnd = function() {
    self.ended = true;
    self._maybeEnd();
  };

  this._parser = parser;
};

IncomingForm.prototype._uploadPath = function(filename) {
  var name = '';
  for (var i = 0; i < 32; i++) {
    name += Math.floor(Math.random() * 16).toString(16);
  }

  if (this.keepExtensions) {
    var ext = path.extname(filename);
    ext     = ext.replace(/(\.[a-z0-9]+).*/, '$1');

    name += ext;
  }

  return path.join(this.uploadDir, name);
};

IncomingForm.prototype._maybeEnd = function() {
  if (!this.ended || this._flushing || this.error) {
    return;
  }

  this.emit('end');
};

<|endoftext|>


<commit_before>var DocsParser = require("../docsparser");
var xregexp = require('../xregexp').XRegExp;

function PhpParser(settings) {
    DocsParser.call(this, settings);
}

PhpParser.prototype = Object.create(DocsParser.prototype);

PhpParser.prototype.setup_settings = function() {
    var shortPrimitives = this.editor_settings.short_primitives || false;
    var nameToken = '[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*'
    this.settings = {
        // curly brackets around the type information
        'curlyTypes': false,
        'typeInfo': true,
        'typeTag': 'var',
        'varIdentifier': '[$]' + nameToken + '(?:->' + nameToken + ')*',
        'fnIdentifier': nameToken,
        'fnOpener': 'function(?:\\s+' + nameToken + ')?\\s*\\(',
        'commentCloser': ' */',
        'bool': (shortPrimitives ? 'bool' : 'boolean'),
        'function': 'function'
    };
};

PhpParser.prototype.parse_function = function(line) {
    var regex = xregexp(
        'function\\s+&?(?:\\s+)?' +
        '(?P<name>' + this.settings.fnIdentifier + ')' +
        // function fnName
        // (arg1, arg2)
        '\\s*\\(\\s*(?P<args>.*?)\\)' +
        '(\\s*\\:\\s*(?P<retval>.*))?'
        );

    var matches = xregexp.exec(line, regex);
    if(matches === null)
        return null;

    return [matches.name, matches.args, matches.retval];
};

PhpParser.prototype.get_arg_type = function(arg) {
    // function add($x, $y = 1)
    var regex = xregexp(
        '(?P<name>' + this.settings.varIdentifier + ')\\s*=\\s*(?P<val>.*)'
        );

    var matches = xregexp.exec(arg, regex);
    if(matches !== null)
        return this.guess_type_from_value(matches.val);

    // function sum(Array $x)
    if(arg.search(/\S\s/) > -1) {
        matches = /^(\S+)/.exec(arg);
        return matches[1];
    }
    else
        return null;
};

PhpParser.prototype.get_arg_name = function(arg) {
    var regex = new RegExp(
        '(' + this.settings.varIdentifier + ')(?:\\s*=.*)?$'
        );
    var matches = regex.exec(arg);
    return matches[1];
};

PhpParser.prototype.parse_var = function(line) {
    /*
        var $foo = blah,
            $foo = blah;
        $baz->foo = blah;
        $baz = array(
             'foo' => blah
        )
    */
    var regex = xregexp(
        '(?P<name>' + this.settings.varIdentifier + ')\\s*=>?\\s*(?P<val>.*?)(?:[;,]|$)'
        );
    var matches = xregexp.exec(line, regex);
    if(matches !== null)
        return [matches.name, matches.val.trim()];

    regex = xregexp(
        '\\b(?:var|public|private|protected|static)\\s+(?P<name>' + this.settings.varIdentifier + ')'
        );
    matches = xregexp.exec(line, regex);
    if(matches !== null)
        return [matches.name, null];

    return null;
};

PhpParser.prototype.guess_type_from_value = function(val) {
    var short_primitives = this.editor_settings.short_primitives || false;
    if(this.is_numeric(val)) {
        if(val.indexOf('.') > -1)
            return 'float';

        return (short_primitives ? 'int' : 'integer');
    }
    if((val[0] == '"') || (val[0] == '\''))
        return 'string';
    if(val.slice(0,5) == 'array')
        return 'array';

    var values = ['true', 'false', 'filenotfound'];
    if (values.indexOf(val.toLowerCase()) !== -1) {
        return (short_primitives ? 'bool' : 'boolean');
    }

    if(val.slice(0,4) == 'new ') {
        var regex = new RegExp(
            'new (' + this.settings.fnIdentifier + ')'
            );
        var matches = regex.exec(val);
        return (matches[0] && matches[1]) || null;
    }
    return null;
};

PhpParser.prototype.get_function_return_type = function(name, retval) {
    var shortPrimitives = this.editor_settings.short_primitives || false;
    if (name.slice(0,2) == '__'){
        var values = ['__construct', '__destruct', '__set', '__unset', '__wakeup'];
        var i, len;
        for(i = 0; len = values.length, i < len; i++) {
            if(name == values[i])
                return null;
        }
        if(name == '__sleep')
            return 'array';
        if(name == '__toString')
            return 'string';
        if(name == '__isset')
            return (shortPrimitives ? 'bool' : 'boolean');
    } else if (retval !== undefined) {
        return retval;
    }
    return DocsParser.prototype.get_function_return_type.call(this, name, retval);
};

module.exports = PhpParser;
<commit_msg>Ignore return type if the type is defined as void<commit_after>var DocsParser = require("../docsparser");
var xregexp = require('../xregexp').XRegExp;

function PhpParser(settings) {
    DocsParser.call(this, settings);
}

PhpParser.prototype = Object.create(DocsParser.prototype);

PhpParser.prototype.setup_settings = function() {
    var shortPrimitives = this.editor_settings.short_primitives || false;
    var nameToken = '[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*'
    this.settings = {
        // curly brackets around the type information
        'curlyTypes': false,
        'typeInfo': true,
        'typeTag': 'var',
        'varIdentifier': '[$]' + nameToken + '(?:->' + nameToken + ')*',
        'fnIdentifier': nameToken,
        'fnOpener': 'function(?:\\s+' + nameToken + ')?\\s*\\(',
        'commentCloser': ' */',
        'bool': (shortPrimitives ? 'bool' : 'boolean'),
        'function': 'function'
    };
};

PhpParser.prototype.parse_function = function(line) {
    var regex = xregexp(
        'function\\s+&?(?:\\s+)?' +
        '(?P<name>' + this.settings.fnIdentifier + ')' +
        // function fnName
        // (arg1, arg2)
        '\\s*\\(\\s*(?P<args>.*?)\\)' +
        '(\\s*\\:\\s*(?P<retval>\\S*))?'
        );

    var matches = xregexp.exec(line, regex);
    if(matches === null)
        return null;

    return [matches.name, matches.args, matches.retval];
};

PhpParser.prototype.get_arg_type = function(arg) {
    // function add($x, $y = 1)
    var regex = xregexp(
        '(?P<name>' + this.settings.varIdentifier + ')\\s*=\\s*(?P<val>.*)'
        );

    var matches = xregexp.exec(arg, regex);
    if(matches !== null)
        return this.guess_type_from_value(matches.val);

    // function sum(Array $x)
    if(arg.search(/\S\s/) > -1) {
        matches = /^(\S+)/.exec(arg);
        return matches[1];
    }
    else
        return null;
};

PhpParser.prototype.get_arg_name = function(arg) {
    var regex = new RegExp(
        '(' + this.settings.varIdentifier + ')(?:\\s*=.*)?$'
        );
    var matches = regex.exec(arg);
    return matches[1];
};

PhpParser.prototype.parse_var = function(line) {
    /*
        var $foo = blah,
            $foo = blah;
        $baz->foo = blah;
        $baz = array(
             'foo' => blah
        )
    */
    var regex = xregexp(
        '(?P<name>' + this.settings.varIdentifier + ')\\s*=>?\\s*(?P<val>.*?)(?:[;,]|$)'
        );
    var matches = xregexp.exec(line, regex);
    if(matches !== null)
        return [matches.name, matches.val.trim()];

    regex = xregexp(
        '\\b(?:var|public|private|protected|static)\\s+(?P<name>' + this.settings.varIdentifier + ')'
        );
    matches = xregexp.exec(line, regex);
    if(matches !== null)
        return [matches.name, null];

    return null;
};

PhpParser.prototype.guess_type_from_value = function(val) {
    var short_primitives = this.editor_settings.short_primitives || false;
    if(this.is_numeric(val)) {
        if(val.indexOf('.') > -1)
            return 'float';

        return (short_primitives ? 'int' : 'integer');
    }
    if((val[0] == '"') || (val[0] == '\''))
        return 'string';
    if(val.slice(0,5) == 'array')
        return 'array';

    var values = ['true', 'false', 'filenotfound'];
    if (values.indexOf(val.toLowerCase()) !== -1) {
        return (short_primitives ? 'bool' : 'boolean');
    }

    if(val.slice(0,4) == 'new ') {
        var regex = new RegExp(
            'new (' + this.settings.fnIdentifier + ')'
            );
        var matches = regex.exec(val);
        return (matches[0] && matches[1]) || null;
    }
    return null;
};

PhpParser.prototype.get_function_return_type = function(name, retval) {
    var shortPrimitives = this.editor_settings.short_primitives || false;
    if (name.slice(0,2) == '__'){
        var values = ['__construct', '__destruct', '__set', '__unset', '__wakeup'];
        var i, len;
        for(i = 0; len = values.length, i < len; i++) {
            if(name == values[i])
                return null;
        }
        if(name == '__sleep')
            return 'array';
        if(name == '__toString')
            return 'string';
        if(name == '__isset')
            return (shortPrimitives ? 'bool' : 'boolean');
    } else if (retval === 'void') {
        return null;
    } else if (retval !== undefined) {
        return retval;
    }
    return DocsParser.prototype.get_function_return_type.call(this, name, retval);
};

module.exports = PhpParser;
<|endoftext|>


<commit_before>'use strict';

var debug = require('debug')('secc:OptionManager');
var crypto = require('crypto');
var path = require('path');
var GCC_OPTIONS = require('./GCC_OPTIONS');
//var CLANG_OPTIONS = require('./CLANG_OPTIONS');
//var getBareOption = function(option)

// FIXME : move Extnames to other shared library file.
var supportingLanguages = [ 'c', 'c++'
                          , 'objective-c', 'objective-c++'
                          , 'assembler', 'assembler-with-cpp'];
var Extname = {
  'assembler' : ['.S', '.sx'],
  'assembler Preprocessed' : ['.s'],
  'c' : ['.c'],
  'c Preprocessed' : ['.i'],
  'c++' : ['.cc','.cp','.cxx','.cpp','.CPP','.c++','.C'],
  'c++ Preprocessed' : ['.ii'],
  'objective-c' : ['.m'],
  'objective-c Preprocessed' : ['.mi'],
  'objective-c++' : ['.mm', '.M'],
  'objective-c++ Preprocessed' : ['.mii']
};

var _lastElement = function(arr){
  return (Array.isArray(arr)) ? arr[arr.length -1] : arr;
}

var OptionManager = function() {
  if (!(this instanceof OptionManager)) {
     return new OptionManager();
  }
  this.gccOptions = {};       //stored by category
  this.gccOptionsIndex = {};  //stored by key/value

  //loading OPTIONS
  for (var category in GCC_OPTIONS) {
    var options;
    options = _parsingGccOption(GCC_OPTIONS, category);
    this.gccOptions[category] = options;
  }

  //index.
  for (var category in this.gccOptions) {
    var options = this.gccOptions[category] ;
    for(var i = 0; i< options.length ; i++) {
      if (this.gccOptionsIndex.hasOwnProperty(options[i].bare))
        throw new Error(options[i].bare + ' exists in object');

      this.gccOptionsIndex[options[i].bare] = options[i];
    }
  }

};

OptionManager.prototype.gccOptionList = function() {
  return this.gccOptions;
}

OptionManager.prototype.gccOptionIndexList = function() {
  return this.gccOptionsIndex;
}

//supposed that 'thin client', 'network cost is cheap enough',
//              and 'workers are powerful'
OptionManager.prototype.analyzeArguments = function(argv, compiler, cwd, mode) {
  debug('analyzeArguments start');
  var argv = argv || [];
  var compiler = compiler || 'gcc';
  var cwd = cwd || '';
  var mode = (mode && (mode == 2)) ? '2' : '1';

  var infile = null;
  var outfile = null;
  var multipleOutfiles = false;
  var language = null;
  var useLocal = false;
  var projectId = null;
  var preprocessedInfile = false;
  var localArgv = null;
  var remoteArgv = null;
  var argvHash = null;
  var form = [];
  var formIndex = {};

  projectId = crypto.createHash('md5').update(cwd).digest("hex");

  //parsing/hashing arguments.
  for (var i = 0; i < argv.length ; i++) {
    var o = this.parseOneOption(argv[i]);
    if (o && !o.singular && argv[i+1] && (argv[i+1][0] !== '-'))
      o.following = argv[++i];
    form.push(o);

    //make an index. 'null' makes an array always for psrsing infile.
    if (!formIndex.hasOwnProperty(o.bare)) {
      if (o.bare === null)
        formIndex[o.bare] = [o];
      else
        formIndex[o.bare] = o;
    } else {
      if (Array.isArray(formIndex[o.bare]))
        formIndex[o.bare].push(o);
      else
        formIndex[o.bare] = [formIndex[o.bare], o];
    }
  }

  //language
  if (formIndex.hasOwnProperty('-x')) {
    var o = _lastElement(formIndex['-x']);

    language = o.following;
    if (supportingLanguages.indexOf[language] === -1) //un-supporting
      buildLocal = true;
  }

  //outfile
  if (formIndex.hasOwnProperty('-o')) {
    var o = _lastElement(formIndex['-o']);
    outfile = o.following;
  }
  //multipleOutfiles
  if (formIndex.hasOwnProperty('-gsplit-dwarf'))
    multipleOutfiles = true;

  //infile. o.bare === null
  //debug(formIndex);
  if (formIndex.hasOwnProperty(null)) {
    for (var i in formIndex[null]) {
      if (formIndex[null][i].option[0] === '-') continue;

      var extname = path.extname(formIndex[null][i].option);
      if (extname === '') continue;

      var __infileProcessed = false;
      for (var key in Extname) {
        if (Extname[key].indexOf(extname) !== -1) {
          __infileProcessed = true;
          infile = formIndex[null][i].option;

          if (key.indexOf('Preprocessed') !== -1)
            preprocessedInfile = true;

          if (language === null) {
            if (key === 'assembler')
              language = 'assembler-with-cpp';
            else
              language = key.replace(' Preprocessed',''); //shortcut
          }

          break;
        }
      }
      if (__infileProcessed) break;
    }
  }

  //FIXME : check un-supporting -M? flag
  for (var i = 0; i < form.length ; i++) {
    if (form[i].bare
      && form[i].bare.substring(0,2) === '-M'
      && (['-MF', '-MG', '-MP', '-MQ', '-MT', '-MD','-MMD'].indexOf(form[i].bare) === -1 )) {
      useLocal = true;
      break;
    }
  }

  //check useLocal
  if ( (infile === null)    //unable to detect the infile
    || ([ 'conftest.c'      //skip on ./configure test
        , 'conftest.cpp'
        , 'conftest.s'
        , 'conftest1.s'
        , 'dummy.c'
        ].indexOf(path.basename(infile)) !== -1))
    useLocal = true;
  if (path.dirname(infile) === '/tmp')  //might be testing in ./configure such as /tmp/ffconf.bLAhBlAh.c
    useLocal = true;
  if (!formIndex.hasOwnProperty('-c'))
    useLocal = true;
  if ( formIndex.hasOwnProperty('-###')
    || formIndex.hasOwnProperty('-S')
    || formIndex.hasOwnProperty('-E')
    || formIndex.hasOwnProperty('-M'))
    useLocal = true;
  if (cwd.indexOf('CMakeFiles') !== -1)
    useLocal = true;

  //local arguments, remote arguments
  if (!useLocal  && (mode === '1')) {
    localArgv = [];
    remoteArgv = [];
    for (var i = 0; i < form.length ; i++) {
      if (form[i].bare === '-o') continue;

      localArgv.push(form[i].option);
      if (!form[i].singular)
        localArgv.push(form[i].following);
    }
    localArgv.push('-E');

    for (var i = 0; i < form.length ; i++) {
      if (form[i].category === 'Preprocessor Options') continue;
      if (form[i].bare === '-I') {
        //form '-I' is defined as 'singular'
        //so check either '-I /path/to/...' or '-I/path/to/...'
        if (form[i].bare === form[i].option) {
          //next argument shouldn't be an option.
          if (form[i+1] && form[i+1].bare && (form[i+1].bare.indexOf['-'] == 0))
            useLocal = true;

          i++;
        }
        continue;
      }
      if (form[i].bare === '-o') continue;
      if ((form[i].bare === null) && (form[i].option === infile)) continue;

      remoteArgv.push(form[i].option);
      if (!form[i].singular)
        remoteArgv.push(form[i].following);
    }
  }

  if (!useLocal  && (mode === '2')) {
    remoteArgv = [];
    for (var i = 0; i < form.length ; i++) {
      if (form[i].bare === '-o') continue;

      remoteArgv.push(form[i].option);
      if (!form[i].singular)
        remoteArgv.push(form[i].following);
    }
  }

  argvHash = crypto.createHash('md5').update(JSON.stringify(remoteArgv)).digest("hex");

  debug('analyzeArguments end');  // start~end : 1~3 ms
  return {argvHash : argvHash,
          infile : infile,
          preprocessedInfile : preprocessedInfile,
          outfile : outfile,
          multipleOutfiles : multipleOutfiles,
          language : language,
          useLocal : useLocal,
          projectId : projectId,
          localArgv : localArgv,
          remoteArgv : remoteArgv
          //,form : form
         };
}

OptionManager.prototype.parseOneOption = function(option) {
  var o = {
            bare : null,
            singular : true,
            equals : false,
            option : option,
            rvalue : null,
            following : null,
            category : null
          };

  var str = o.option;
  if (str.indexOf('=') !== -1) {
    o.equals = true;
    str = str.substring(0, str.indexOf('=')); //FIXME : parse rvalue?
    o.rvalue = o.option.substr(str.length + 1);
  }

  if (str.indexOf('/') !== -1)  // there's no '/' in options
    str = str.substring(0, str.indexOf('/'));

  do {
    if (this.gccOptionsIndex.hasOwnProperty(str)) {
      var form = this.gccOptionsIndex[str];
      o.singular = form.singular;
      o.category = form.category;
      break;
    } else {
      str = str.slice(0,-1);
    }
  } while(str.length > 0)

  if (str.length > 0)
    o.bare = str;
  return o;
}

var _parsingGccOption = function(OPTIONS, category) {
  var options = [];

  //split by spaces/line feed, remove empty string.
  var arr = OPTIONS[category]
           .split(/\s+/)
           .filter(function(e){return e});

  for (var i = 0; i < arr.length ; i++) {
    //might be following or special case such as <source | object>
    if (arr[i][0] !== '-') {
      if ((i == 0) && (arr[i][0] === '<'))  // FIXME : one case : <object-file-name>
        continue;

      var last = options[options.length-1];
      last.singular = false;
      last.following = arr[i];
      continue;
    }

    var o = {
              bare : null,
              singular : true,
              equals : false,
              option : arr[i],
              rvalue : null,
              following : null,
              category : category
            };

    var str = arr[i].replace(/\[[,.]*\]/,''); //--help[=class[,...]]
    str = str.replace(/\[[-|*=<>a-z0-9]*\]/,'');
    if (str.indexOf('=') !== -1) {
      o.equals = true;
      str = str.substring(0, str.indexOf('=')); //FIXME : parse rvalue?
      o.rvalue = arr[i].substr(str.length + 1);
    }

    if (str.indexOf('<') !== -1) {
      str = str.substring(0, str.indexOf('<'));
    }

    o.bare = str;
    options.push(o);
  }

  return options;
}

module.exports = OptionManager;

//console.log(OptionManager().gccOptionsIndex);<commit_msg>bug fix. do not push the null following to Option Array ex, '-xobjective-c' is also valid(without a space)<commit_after>'use strict';

var debug = require('debug')('secc:OptionManager');
var crypto = require('crypto');
var path = require('path');
var GCC_OPTIONS = require('./GCC_OPTIONS');
//var CLANG_OPTIONS = require('./CLANG_OPTIONS');
//var getBareOption = function(option)

// FIXME : move Extnames to other shared library file.
var supportingLanguages = [ 'c', 'c++'
                          , 'objective-c', 'objective-c++'
                          , 'assembler', 'assembler-with-cpp'];
var Extname = {
  'assembler' : ['.S', '.sx'],
  'assembler Preprocessed' : ['.s'],
  'c' : ['.c'],
  'c Preprocessed' : ['.i'],
  'c++' : ['.cc','.cp','.cxx','.cpp','.CPP','.c++','.C'],
  'c++ Preprocessed' : ['.ii'],
  'objective-c' : ['.m'],
  'objective-c Preprocessed' : ['.mi'],
  'objective-c++' : ['.mm', '.M'],
  'objective-c++ Preprocessed' : ['.mii']
};

var _lastElement = function(arr){
  return (Array.isArray(arr)) ? arr[arr.length -1] : arr;
}

var OptionManager = function() {
  if (!(this instanceof OptionManager)) {
     return new OptionManager();
  }
  this.gccOptions = {};       //stored by category
  this.gccOptionsIndex = {};  //stored by key/value

  //loading OPTIONS
  for (var category in GCC_OPTIONS) {
    var options;
    options = _parsingGccOption(GCC_OPTIONS, category);
    this.gccOptions[category] = options;
  }

  //index.
  for (var category in this.gccOptions) {
    var options = this.gccOptions[category] ;
    for(var i = 0; i< options.length ; i++) {
      if (this.gccOptionsIndex.hasOwnProperty(options[i].bare))
        throw new Error(options[i].bare + ' exists in object');

      this.gccOptionsIndex[options[i].bare] = options[i];
    }
  }

};

OptionManager.prototype.gccOptionList = function() {
  return this.gccOptions;
}

OptionManager.prototype.gccOptionIndexList = function() {
  return this.gccOptionsIndex;
}

//supposed that 'thin client', 'network cost is cheap enough',
//              and 'workers are powerful'
OptionManager.prototype.analyzeArguments = function(argv, compiler, cwd, mode) {
  debug('analyzeArguments start');
  var argv = argv || [];
  var compiler = compiler || 'gcc';
  var cwd = cwd || '';
  var mode = (mode && (mode == 2)) ? '2' : '1';

  var infile = null;
  var outfile = null;
  var multipleOutfiles = false;
  var language = null;
  var useLocal = false;
  var projectId = null;
  var preprocessedInfile = false;
  var localArgv = null;
  var remoteArgv = null;
  var argvHash = null;
  var form = [];
  var formIndex = {};

  projectId = crypto.createHash('md5').update(cwd).digest("hex");

  //parsing/hashing arguments.
  for (var i = 0; i < argv.length ; i++) {
    var o = this.parseOneOption(argv[i]);
    if (o && !o.singular && argv[i+1] && (argv[i+1][0] !== '-'))
      o.following = argv[++i];
    form.push(o);

    //make an index. 'null' makes an array always for psrsing infile.
    if (!formIndex.hasOwnProperty(o.bare)) {
      if (o.bare === null)
        formIndex[o.bare] = [o];
      else
        formIndex[o.bare] = o;
    } else {
      if (Array.isArray(formIndex[o.bare]))
        formIndex[o.bare].push(o);
      else
        formIndex[o.bare] = [formIndex[o.bare], o];
    }
  }

  //language
  if (formIndex.hasOwnProperty('-x')) {
    var o = _lastElement(formIndex['-x']);

    language = o.following;
    if (supportingLanguages.indexOf[language] === -1) //un-supporting
      buildLocal = true;
  }

  //outfile
  if (formIndex.hasOwnProperty('-o')) {
    var o = _lastElement(formIndex['-o']);
    outfile = o.following;
  }
  //multipleOutfiles
  if (formIndex.hasOwnProperty('-gsplit-dwarf'))
    multipleOutfiles = true;

  //infile. o.bare === null
  //debug(formIndex);
  if (formIndex.hasOwnProperty(null)) {
    for (var i in formIndex[null]) {
      if (formIndex[null][i].option[0] === '-') continue;

      var extname = path.extname(formIndex[null][i].option);
      if (extname === '') continue;

      var __infileProcessed = false;
      for (var key in Extname) {
        if (Extname[key].indexOf(extname) !== -1) {
          __infileProcessed = true;
          infile = formIndex[null][i].option;

          if (key.indexOf('Preprocessed') !== -1)
            preprocessedInfile = true;

          if (language === null) {
            if (key === 'assembler')
              language = 'assembler-with-cpp';
            else
              language = key.replace(' Preprocessed',''); //shortcut
          }

          break;
        }
      }
      if (__infileProcessed) break;
    }
  }

  //FIXME : check un-supporting -M? flag
  for (var i = 0; i < form.length ; i++) {
    if (form[i].bare
      && form[i].bare.substring(0,2) === '-M'
      && (['-MF', '-MG', '-MP', '-MQ', '-MT', '-MD','-MMD'].indexOf(form[i].bare) === -1 )) {
      useLocal = true;
      break;
    }
  }

  //check useLocal
  if ( (infile === null)    //unable to detect the infile
    || ([ 'conftest.c'      //skip on ./configure test
        , 'conftest.cpp'
        , 'conftest.s'
        , 'conftest1.s'
        , 'dummy.c'
        ].indexOf(path.basename(infile)) !== -1))
    useLocal = true;
  if (path.dirname(infile) === '/tmp')  //might be testing in ./configure such as /tmp/ffconf.bLAhBlAh.c
    useLocal = true;
  if (!formIndex.hasOwnProperty('-c'))
    useLocal = true;
  if ( formIndex.hasOwnProperty('-###')
    || formIndex.hasOwnProperty('-S')
    || formIndex.hasOwnProperty('-E')
    || formIndex.hasOwnProperty('-M'))
    useLocal = true;
  if (cwd.indexOf('CMakeFiles') !== -1)
    useLocal = true;

  //local arguments, remote arguments
  if (!useLocal  && (mode === '1')) {
    localArgv = [];
    remoteArgv = [];
    for (var i = 0; i < form.length ; i++) {
      if (form[i].bare === '-o') continue;

      localArgv.push(form[i].option);
      if (!form[i].singular && form[i].following)
        localArgv.push(form[i].following);
    }
    localArgv.push('-E');

    for (var i = 0; i < form.length ; i++) {
      if (form[i].category === 'Preprocessor Options') continue;
      if (form[i].bare === '-I') {
        //form '-I' is defined as 'singular'
        //so check either '-I /path/to/...' or '-I/path/to/...'
        if (form[i].bare === form[i].option) {
          //next argument shouldn't be an option.
          if (form[i+1] && form[i+1].bare && (form[i+1].bare.indexOf['-'] == 0))
            useLocal = true;

          i++;
        }
        continue;
      }
      if (form[i].bare === '-o') continue;
      if ((form[i].bare === null) && (form[i].option === infile)) continue;

      remoteArgv.push(form[i].option);
      if (!form[i].singular && form[i].following)
        remoteArgv.push(form[i].following);
    }
  }

  if (!useLocal  && (mode === '2')) {
    remoteArgv = [];
    for (var i = 0; i < form.length ; i++) {
      if (form[i].bare === '-o') continue;

      remoteArgv.push(form[i].option);
      if (!form[i].singular)
        remoteArgv.push(form[i].following);
    }
  }

  argvHash = crypto.createHash('md5').update(JSON.stringify(remoteArgv)).digest("hex");

  debug('analyzeArguments end');  // start~end : 1~3 ms
  return {argvHash : argvHash,
          infile : infile,
          preprocessedInfile : preprocessedInfile,
          outfile : outfile,
          multipleOutfiles : multipleOutfiles,
          language : language,
          useLocal : useLocal,
          projectId : projectId,
          localArgv : localArgv,
          remoteArgv : remoteArgv
          //,form : form
         };
}

OptionManager.prototype.parseOneOption = function(option) {
  var o = {
            bare : null,
            singular : true,
            equals : false,
            option : option,
            rvalue : null,
            following : null,
            category : null
          };

  var str = o.option;
  if (str.indexOf('=') !== -1) {
    o.equals = true;
    str = str.substring(0, str.indexOf('=')); //FIXME : parse rvalue?
    o.rvalue = o.option.substr(str.length + 1);
  }

  if (str.indexOf('/') !== -1)  // there's no '/' in options
    str = str.substring(0, str.indexOf('/'));

  do {
    if (this.gccOptionsIndex.hasOwnProperty(str)) {
      var form = this.gccOptionsIndex[str];
      o.singular = form.singular;
      o.category = form.category;
      break;
    } else {
      str = str.slice(0,-1);
    }
  } while(str.length > 0)

  if (str.length > 0)
    o.bare = str;
  return o;
}

var _parsingGccOption = function(OPTIONS, category) {
  var options = [];

  //split by spaces/line feed, remove empty string.
  var arr = OPTIONS[category]
           .split(/\s+/)
           .filter(function(e){return e});

  for (var i = 0; i < arr.length ; i++) {
    //might be following or special case such as <source | object>
    if (arr[i][0] !== '-') {
      if ((i == 0) && (arr[i][0] === '<'))  // FIXME : one case : <object-file-name>
        continue;

      var last = options[options.length-1];
      last.singular = false;
      last.following = arr[i];
      continue;
    }

    var o = {
              bare : null,
              singular : true,
              equals : false,
              option : arr[i],
              rvalue : null,
              following : null,
              category : category
            };

    var str = arr[i].replace(/\[[,.]*\]/,''); //--help[=class[,...]]
    str = str.replace(/\[[-|*=<>a-z0-9]*\]/,'');
    if (str.indexOf('=') !== -1) {
      o.equals = true;
      str = str.substring(0, str.indexOf('=')); //FIXME : parse rvalue?
      o.rvalue = arr[i].substr(str.length + 1);
    }

    if (str.indexOf('<') !== -1) {
      str = str.substring(0, str.indexOf('<'));
    }

    o.bare = str;
    options.push(o);
  }

  return options;
}

module.exports = OptionManager;

//console.log(OptionManager().gccOptionsIndex);<|endoftext|>


<commit_before>import Orbit from './main';
import Notifier from './notifier';
import { assert } from './lib/assert';
import { extend } from './lib/objects';

function notifierForEvent(object, eventName, createIfUndefined) {
  var notifier = object._eventedNotifiers[eventName];
  if (!notifier && createIfUndefined) {
    notifier = object._eventedNotifiers[eventName] = new Notifier();
  }
  return notifier;
}

function removeNotifierForEvent(object, eventName) {
  delete object._eventedNotifiers[eventName];
}

/**
 The `Evented` interface uses notifiers to add events to an object. Like
 notifiers, events will send along all of their arguments to subscribed
 listeners.

 The `Evented` interface can extend an object or prototype as follows:

 ```javascript
 var source = {};
 Orbit.Evented.extend(source);
 ```

 Listeners can then register themselves for particular events with `on`:

 ```javascript
 var listener1 = function(message) {
       console.log('listener1 heard ' + message);
     },
     listener2 = function(message) {
       console.log('listener2 heard ' + message);
     };

 source.on('greeting', listener1);
 source.on('greeting', listener2);

 evented.emit('greeting', 'hello'); // logs "listener1 heard hello" and
                                    //      "listener2 heard hello"
 ```

 Listeners can be unregistered from events at any time with `off`:

 ```javascript
 source.off('greeting', listener2);
 ```

 A listener can register itself for multiple events at once:

 ```javascript
 source.on('greeting salutation', listener2);
 ```

 And multiple events can be triggered sequentially at once,
 assuming that you want to pass them all the same arguments:

 ```javascript
 source.emit('greeting salutation', 'hello', 'bonjour', 'guten tag');
 ```

 Last but not least, listeners can be polled
 (note that spaces can't be used in event names):

 ```javascript
 source.on('question', function(question) {
   if (question === 'favorite food?') return 'beer';
 });

 source.on('question', function(question) {
   if (question === 'favorite food?') return 'wasabi almonds';
 });

 source.on('question', function(question) {
   // this listener doesn't return anything, and therefore won't participate
   // in the poll
 });

 source.poll('question', 'favorite food?'); // returns ['beer', 'wasabi almonds']
 ```

 @class Evented
 @namespace Orbit
 @extension
 @constructor
 */
export default {
  /**
   Mixes the `Evented` interface into an object

   @method extend
   @param {Object} object Object to extend
   @returns {Object} Extended object
   */
  extend: function(object) {
    assert('Evented requires Orbit.Promise be defined', Orbit.Promise);

    if (object._evented === undefined) {
      extend(object, this.interface);
      object._eventedNotifiers = {};
    }
    return object;
  },

  interface: {
    _evented: true,

    on: function(eventNames, callback, binding) {
      binding = binding || this;

      eventNames.split(/\s+/).forEach(function(eventName) {
        notifierForEvent(this, eventName, true).addListener(callback, binding);
      }, this);
    },

    off: function(eventNames, callback, binding) {
      var notifier;

      binding = binding || this;

      eventNames.split(/\s+/).forEach(function(eventName) {
        notifier = notifierForEvent(this, eventName);
        if (notifier) {
          if (callback) {
            notifier.removeListener(callback, binding);
          } else {
            removeNotifierForEvent(this, eventName);
          }
        }
      }, this);
    },

    one: function(eventName, callback, binding) {
      var callOnce,
          notifier;

      binding = binding || this;

      notifier = notifierForEvent(this, eventName, true);

      callOnce = function() {
        callback.apply(binding, arguments);
        notifier.removeListener(callOnce, binding);
      };

      notifier.addListener(callOnce, binding);
    },

    emit: function(eventNames) {
      let args = Array.prototype.slice.call(arguments, 1);
      let notifier;

      eventNames.split(/\s+/).forEach(function(eventName) {
        notifier = notifierForEvent(this, eventName);
        if (notifier) {
          notifier.emit.apply(notifier, args);
        }
      }, this);
    },

    poll: function(eventNames) {
      let args = Array.prototype.slice.call(arguments, 1);
      let responses = [];
      let notifier;

      eventNames.split(/\s+/).forEach(function(eventName) {
        notifier = notifierForEvent(this, eventName);
        if (notifier) {
          responses = responses.concat(notifier.poll.apply(notifier, args));
        }
      }, this);

      return responses;
    },

    listeners: function(eventNames) {
      let listeners = [];
      let notifier;

      eventNames.split(/\s+/).forEach(function(eventName) {
        notifier = notifierForEvent(this, eventName);
        if (notifier) {
          listeners = listeners.concat(notifier.listeners);
        }
      }, this);

      return listeners;
    },

    resolve: function(eventNames) {
      let listeners = this.listeners(eventNames);
      let args = Array.prototype.slice.call(arguments, 1);

      return new Orbit.Promise(function(resolve, reject) {
        var resolveEach = function() {
          if (listeners.length === 0) {
            reject();
          } else {
            var listener = listeners.shift();
            var response = listener[0].apply(listener[1], args);

            if (response) {
              response.then(
                function(success) {
                  resolve(success);
                },
                function(error) {
                  resolveEach();
                }
              );
            } else {
              resolveEach();
            }
          }
        };

        resolveEach();
      });
    },

    settle: function(eventNames) {
      let listeners = this.listeners(eventNames);
      let args = Array.prototype.slice.call(arguments, 1);

      return new Orbit.Promise(function(resolve) {
        function settleEach() {
          if (listeners.length === 0) {
            resolve();
          } else {
            var listener = listeners.shift();
            var response;
            try {
              response = listener[0].apply(listener[1], args);
            } catch (e) {
              console.error('Orbit ignored error in event listener', eventNames);
              console.error(e.stack || e);
            }

            if (response) {
              return response
                .then(
                  function(success) {
                    settleEach();
                  },
                  function(error) {
                    settleEach();
                  }
                );
            } else {
              settleEach();
            }
          }
        }

        settleEach();
      });
    },

    series: function(eventNames) {
      let listeners = this.listeners(eventNames);
      let args = Array.prototype.slice.call(arguments, 1);

      return new Orbit.Promise(function(resolve, reject) {
        function settleEach() {
          if (listeners.length === 0) {
            resolve();
          } else {
            let listener = listeners.shift();
            let response = listener[0].apply(listener[1], args);

            if (response) {
              return response
                .then(
                  function(success) {
                    settleEach();
                  },
                  function(error) {
                    reject(error);
                  }
                );
            } else {
              settleEach();
            }
          }
        }

        settleEach();
      });
    }
  }
};
<commit_msg>Modernize Evented.<commit_after>import Orbit from './main';
import Notifier from './notifier';
import { assert } from './lib/assert';
import { extend } from './lib/objects';

function notifierForEvent(object, eventName, createIfUndefined) {
  var notifier = object._eventedNotifiers[eventName];
  if (!notifier && createIfUndefined) {
    notifier = object._eventedNotifiers[eventName] = new Notifier();
  }
  return notifier;
}

function removeNotifierForEvent(object, eventName) {
  delete object._eventedNotifiers[eventName];
}

/**
 The `Evented` interface uses notifiers to add events to an object. Like
 notifiers, events will send along all of their arguments to subscribed
 listeners.

 The `Evented` interface can extend an object or prototype as follows:

 ```javascript
 var source = {};
 Orbit.Evented.extend(source);
 ```

 Listeners can then register themselves for particular events with `on`:

 ```javascript
 var listener1 = function(message) {
       console.log('listener1 heard ' + message);
     },
     listener2 = function(message) {
       console.log('listener2 heard ' + message);
     };

 source.on('greeting', listener1);
 source.on('greeting', listener2);

 evented.emit('greeting', 'hello'); // logs "listener1 heard hello" and
                                    //      "listener2 heard hello"
 ```

 Listeners can be unregistered from events at any time with `off`:

 ```javascript
 source.off('greeting', listener2);
 ```

 A listener can register itself for multiple events at once:

 ```javascript
 source.on('greeting salutation', listener2);
 ```

 And multiple events can be triggered sequentially at once,
 assuming that you want to pass them all the same arguments:

 ```javascript
 source.emit('greeting salutation', 'hello', 'bonjour', 'guten tag');
 ```

 Last but not least, listeners can be polled
 (note that spaces can't be used in event names):

 ```javascript
 source.on('question', function(question) {
   if (question === 'favorite food?') return 'beer';
 });

 source.on('question', function(question) {
   if (question === 'favorite food?') return 'wasabi almonds';
 });

 source.on('question', function(question) {
   // this listener doesn't return anything, and therefore won't participate
   // in the poll
 });

 source.poll('question', 'favorite food?'); // returns ['beer', 'wasabi almonds']
 ```

 @class Evented
 @namespace Orbit
 @extension
 @constructor
 */
export default {
  /**
   Mixes the `Evented` interface into an object

   @method extend
   @param {Object} object Object to extend
   @returns {Object} Extended object
   */
  extend(object) {
    assert('Evented requires Orbit.Promise be defined', Orbit.Promise);

    if (object._evented === undefined) {
      extend(object, this.interface);
      object._eventedNotifiers = {};
    }
    return object;
  },

  interface: {
    _evented: true,

    on(eventNames, callback, _binding) {
      let binding = _binding || this;

      eventNames.split(/\s+/).forEach((eventName) => {
        notifierForEvent(this, eventName, true).addListener(callback, binding);
      });
    },

    off(eventNames, callback, _binding) {
      let binding = _binding || this;

      eventNames.split(/\s+/).forEach((eventName) => {
        let notifier = notifierForEvent(this, eventName);
        if (notifier) {
          if (callback) {
            notifier.removeListener(callback, binding);
          } else {
            removeNotifierForEvent(this, eventName);
          }
        }
      });
    },

    one(eventName, callback, _binding) {
      let callOnce,
          notifier;

      let binding = _binding || this;

      notifier = notifierForEvent(this, eventName, true);

      callOnce = function() {
        callback.apply(binding, arguments);
        notifier.removeListener(callOnce, binding);
      };

      notifier.addListener(callOnce, binding);
    },

    emit(eventNames, ...args) {
      eventNames.split(/\s+/).forEach((eventName) => {
        let notifier = notifierForEvent(this, eventName);
        if (notifier) {
          notifier.emit.apply(notifier, args);
        }
      });
    },

    poll(eventNames, ...args) {
      let responses = [];

      eventNames.split(/\s+/).forEach((eventName) => {
        let notifier = notifierForEvent(this, eventName);
        if (notifier) {
          responses = responses.concat(notifier.poll.apply(notifier, args));
        }
      });

      return responses;
    },

    listeners(eventNames) {
      let listeners = [];

      eventNames.split(/\s+/).forEach((eventName) => {
        let notifier = notifierForEvent(this, eventName);
        if (notifier) {
          listeners = listeners.concat(notifier.listeners);
        }
      });

      return listeners;
    },

    resolve(eventNames) {
      let listeners = this.listeners(eventNames);
      let args = Array.prototype.slice.call(arguments, 1);

      return new Orbit.Promise(function(resolve, reject) {
        function resolveEach() {
          if (listeners.length === 0) {
            reject();
          } else {
            let listener = listeners.shift();
            let response = listener[0].apply(listener[1], args);

            if (response) {
              response
                .then(success => resolve(success))
                .catch(error => resolveEach());
            } else {
              resolveEach();
            }
          }
        }

        resolveEach();
      });
    },

    settle(eventNames) {
      let listeners = this.listeners(eventNames);
      let args = Array.prototype.slice.call(arguments, 1);

      return new Orbit.Promise((resolve) => {
        function settleEach() {
          if (listeners.length === 0) {
            resolve();
          } else {
            let listener = listeners.shift();
            let response;

            try {
              response = listener[0].apply(listener[1], args);
            } catch (e) {
              console.error('Orbit ignored error in event listener', eventNames);
              console.error(e.stack || e);
            }

            if (response) {
              return response
                .then(success => settleEach())
                .catch(error => settleEach());
            } else {
              settleEach();
            }
          }
        }

        settleEach();
      });
    },

    series(eventNames) {
      let listeners = this.listeners(eventNames);
      let args = Array.prototype.slice.call(arguments, 1);

      return new Orbit.Promise((resolve, reject) => {
        function settleEach() {
          if (listeners.length === 0) {
            resolve();
          } else {
            let listener = listeners.shift();
            let response = listener[0].apply(listener[1], args);

            if (response) {
              return response
                .then(success => settleEach())
                .catch(error => reject(error));
            } else {
              settleEach();
            }
          }
        }

        settleEach();
      });
    }
  }
};
<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

"use strict";

module.metadata = {
  "stability": "experimental"
};

// Add-ons SDK
const { Class } = require("sdk/core/heritage");

// Firebug SDK
const { Trace, TraceError } = require("./core/trace.js").get(module.id);
const { TabMenu } = require("./tab-menu.js");
const { ToolboxChrome } = require("./toolbox-chrome.js");
const { EventEmitter, gDevTools } = require("./core/devtools.js");
const { PanelSidebarOverlay } = require("./panel-sidebar-overlay.js");
const { Win } = require("./core/window.js");

/**
 * Base object for all Toolbox panel overlays.
 */
const PanelOverlay = Class(
/** @lends PanelOverlay */
{
  /**
   * Executed by the framework when a panel instance is created.
   */
  initialize: function(options) {
    Trace.sysout("PanelOverlay.initialize; " + options.id, options);

    EventEmitter.decorate(this);

    this.overlayId = options.id;
    this.toolbox = options.toolbox;
    this.chrome = options.chrome;
    this.panelFrame = options.panelFrame;
    this.context = options.context;

    this.onThemeChanged = this.onThemeChanged.bind(this);
    this.themeSwitched = this.themeSwitched.bind(this);
  },

  destroy: function() {
    this.destroyTabMenu();

    if (this.sidebarOverlay) {
      this.sidebarOverlay.destroy();
    }

    ToolboxChrome.off("theme-changed", this.onThemeChanged);
    gDevTools.off("theme-switched", this.themeSwitched);
  },

  /**
   * Executed by the framework when panel's frame is loaded.
   */
  onBuild: function(options) {
    Trace.sysout("PanelOverlay.onBuild; " + this.id, options);

    if (!options.panel) {
      return;
    }

    this.panel = options.panel;
  },

  /**
   * Executed by the framework when panel's initialization is done
   * and the panel is fully ready.
   */
  onReady: function(options) {
    Trace.sysout("PanelOverlay.onReady; " + this.id, options);

    // xxxHonza: the built-in panels should all use the {@ToolSidebar}
    // object. So far it's only the Console and Inspector panels.
    // The instance of the {@ToolSidebar} should be accessible through
    // panel.sidebar property. FIX ME, REPORT BUG
    if (this.panel && this.panel.sidebar) {
      this.sidebar = this.panel.sidebar;
    }

    this.createTabMenu();

    this.sidebarOverlay = new PanelSidebarOverlay({
      owner: this,
      toolbox: this.toolbox,
      sidebar: this.sidebar
    });

    if (this.searchable && ToolboxChrome.isFirebugThemeActive()) {
      this.updateSearchBox(true);
    }

    this.setupSidePanels();
    this.setupSideOverlays();

    // Use a custom attribute to distinguish toolbox panels from standalone ones
    // (such as console panel)
    let win = this.getPanelWindow();
    win.document.documentElement.setAttribute("panel", "true");

    // Listen to theme changes. The listeners are registered here
    // in onReady, so theme changes are called after the overlay
    // is fully ready.
    ToolboxChrome.on("theme-changed", this.onThemeChanged);
    gDevTools.on("theme-switched", this.themeSwitched);

    // Theme initialization
    let currentTheme = ToolboxChrome.getCurrentTheme();
    this.onThemeChanged(currentTheme);
    this.themeSwitched("theme-switched", win, currentTheme);
  },

  setupSidePanels: function() {
    this.sidebarOverlay.setupSidePanels();
  },

  setupSideOverlays: function() {
    this.sidebarOverlay.setupSideOverlays();
  },

  // Tab Menu

  createTabMenu: function() {
    if (this.tabMenu) {
      return;
    }

    this.tabMenu = new TabMenu({
      panel: this,
      toolbox: this.toolbox,
      tabIdPrefix: "toolbox-tab-",
      doc: this.toolbox.doc,
      owner: this.toolbox
    });

    this.tabMenu.createTabMenu();
  },

  destroyTabMenu: function() {
    if (this.tabMenu) {
      this.tabMenu.destroy();
      this.tabMenu = null;
    }
  },

  // Search

  search: function (nativeSearchBoxSelector, value) {
    let doc = this.getPanelDocument();
    let win = this.getPanelWindow();

    // Inject the searched pattern to the native search box.
    // xxxHonza: The search box UI will be built-in at some point
    // see: https://bugzilla.mozilla.org/show_bug.cgi?id=1026479
    // As soon as the bug is fixed this code will change TESTME
    let nativeSearchBox = doc.querySelector(nativeSearchBoxSelector);
    nativeSearchBox.value = value;

    // Trigger the "command" event for the native search box
    // to apply the filter.
    let event = doc.createEvent("xulcommandevent");
    event.initCommandEvent("command", true, true, win, 0, false, false, false,
        false, null);
    nativeSearchBox.dispatchEvent(event);
  },

  updateSearchBox: function(apply) {
  },

  // Toolbar Buttons

  getPanelToolbarButtons: function() {
    let buttons = [];
    return buttons;
  },

  // Options

  getOptionsMenuItems: function() {
    let items = [];
    return items;
  },

  /**
   * Executed by the framework when the user clicks panel tab options
   * menu target. Returns custom menu popup for panel options.
   *
   * @returns {MenuPopup} The method can return custom <menupopup> element
   * that will be displayed when the user clicks the tab options target.
   */
  getOptionsMenuPopup: function() {
  },

  // Side Panels

  /**
   * Return list of side panels that should be created for this panel.
   */
  getSidePanels: function() {
    return [];
  },

  /**
   * Can be overridden by overlays to indicate that the (built-in)
   * panel has side panels even if there are no additional custom
   * panels appended.
   */
  hasSidePanels: function() {
    let panels = this.getSidePanels();
    return (panels && panels.length);
  },

  // Panel Selection

  /**
   * Executed by the framework when the panel is selected in the toolbox.
   */
  onShow: function() {
    Trace.sysout("PanelOverlay.show;");

    if (this.tabMenu) {
      this.tabMenu.onPanelShow();
    }

    if (this.searchable && ToolboxChrome.isFirebugThemeActive()) {
      this.updateSearchBox(true);
    }
  },

  /**
   * Executed by the framework when the panel is un-selected in the toolbox.
   */
  onHide: function() {
    Trace.sysout("PanelOverlay.hide;");

    if (this.tabMenu) {
      this.tabMenu.onPanelHide();
    }

    // Unapply search-box customization when the Inspector panel
    // is hidden (unselected). The search box is shared among
    // panels and other customization can apply.
    if (this.searchable) {
      this.updateSearchBox(false);
    }
  },

  // Theme

  onThemeChanged: function(newTheme, oldTheme) {
  },

  themeSwitched: function(eventId, win, newTheme, oldTheme) {
    let frame = this.panelFrame;
    if (frame && frame.contentWindow === win) {
      Win.loaded(win).then(() => {
        this.onThemeSwitched(win, newTheme, oldTheme);
      });
    }
  },

  onThemeSwitched: function(win, newTheme, oldTheme) {
    // xxxHonza: E.g. the StyleEditorOverlay.onApplyTheme is called twice
    // when the theme changes, so avoid that here (for all panels).
    if (this.currentTheme == newTheme) {
      return;
    }

    this.currentTheme = newTheme;

    if (newTheme == "firebug") {
      this.onApplyTheme(win, oldTheme);
    } else if (oldTheme == "firebug"){
      this.onUnapplyTheme(win, newTheme);
    }
  },

  /**
   * Executed by the framework when Firebug theme is applied.
   *
   * xxxHonza: onApplyTheme and onUnapplyTheme are not executed for
   * custom panels that don't include theme-switching.js file in
   * the content document. Note that the document must have chrome
   * privileges.
   *
   * @param {Window} Window in the iframe the theme is applied to.
   * @param {@String} ID of the previous theme.
   */
  onApplyTheme: function(win, oldTheme) {
    Trace.sysout("PanelOverlay.onApplyTheme;");

    if (this.searchable) {
      this.updateSearchBox(true);
    }

    this.createTabMenu();

    // Emit select event, so the tab-menu is visible after
    // Firebug theme is activated.
    this.toolbox.emit("select", this.toolbox.currentToolId);
  },

  /**
   * Executed by the framework when Firebug theme is unapplied.
   *
   * @param {Window} Window in the iframe the theme is applied to.
   * @param {@String} ID of the new theme.
   */
  onUnapplyTheme: function(win, newTheme) {
    Trace.sysout("PanelOverlay.onUnapplyTheme;");

    if (this.searchable) {
      this.updateSearchBox(false);
    }

    this.destroyTabMenu();
  },

  // Accessors

  /**
   * Returns unique ID of the overlay.
   */
  get id() {
    return this.overlayId;
  },

  getPanel: function() {
    return this.panel;
  },

  getPanelDocument: function() {
    return this.panelFrame.contentDocument;
  },

  getPanelWindow: function() {
    return this.panelFrame.contentWindow;
  },

  getToolbar: function() {
    let doc = this.getPanelDocument();
    return doc.querySelector("#panelToolbar");
  }
});

// Exports from this module
exports.PanelOverlay = PanelOverlay;
<commit_msg>Better error handling<commit_after>/* See license.txt for terms of usage */

"use strict";

module.metadata = {
  "stability": "experimental"
};

// Add-ons SDK
const { Class } = require("sdk/core/heritage");

// Firebug SDK
const { Trace, TraceError } = require("./core/trace.js").get(module.id);
const { TabMenu } = require("./tab-menu.js");
const { ToolboxChrome } = require("./toolbox-chrome.js");
const { EventEmitter, gDevTools } = require("./core/devtools.js");
const { PanelSidebarOverlay } = require("./panel-sidebar-overlay.js");
const { Win } = require("./core/window.js");

/**
 * Base object for all Toolbox panel overlays.
 */
const PanelOverlay = Class(
/** @lends PanelOverlay */
{
  /**
   * Executed by the framework when a panel instance is created.
   */
  initialize: function(options) {
    Trace.sysout("PanelOverlay.initialize; " + options.id, options);

    EventEmitter.decorate(this);

    this.overlayId = options.id;
    this.toolbox = options.toolbox;
    this.chrome = options.chrome;
    this.panelFrame = options.panelFrame;
    this.context = options.context;

    this.onThemeChanged = this.onThemeChanged.bind(this);
    this.themeSwitched = this.themeSwitched.bind(this);
  },

  destroy: function() {
    this.destroyTabMenu();

    if (this.sidebarOverlay) {
      this.sidebarOverlay.destroy();
    }

    ToolboxChrome.off("theme-changed", this.onThemeChanged);
    gDevTools.off("theme-switched", this.themeSwitched);
  },

  /**
   * Executed by the framework when panel's frame is loaded.
   */
  onBuild: function(options) {
    Trace.sysout("PanelOverlay.onBuild; " + this.id, options);

    if (!options.panel) {
      return;
    }

    this.panel = options.panel;
  },

  /**
   * Executed by the framework when panel's initialization is done
   * and the panel is fully ready.
   */
  onReady: function(options) {
    Trace.sysout("PanelOverlay.onReady; " + this.id, options);

    // xxxHonza: the built-in panels should all use the {@ToolSidebar}
    // object. So far it's only the Console and Inspector panels.
    // The instance of the {@ToolSidebar} should be accessible through
    // panel.sidebar property. FIX ME, REPORT BUG
    if (this.panel && this.panel.sidebar) {
      this.sidebar = this.panel.sidebar;
    }

    this.createTabMenu();

    this.sidebarOverlay = new PanelSidebarOverlay({
      owner: this,
      toolbox: this.toolbox,
      sidebar: this.sidebar
    });

    if (this.searchable && ToolboxChrome.isFirebugThemeActive()) {
      this.updateSearchBox(true);
    }

    this.setupSidePanels();
    this.setupSideOverlays();

    // Use a custom attribute to distinguish toolbox panels from standalone ones
    // (such as console panel)
    let win = this.getPanelWindow();
    win.document.documentElement.setAttribute("panel", "true");

    // Listen to theme changes. The listeners are registered here
    // in onReady, so theme changes are called after the overlay
    // is fully ready.
    ToolboxChrome.on("theme-changed", this.onThemeChanged);
    gDevTools.on("theme-switched", this.themeSwitched);

    // Theme initialization
    let currentTheme = ToolboxChrome.getCurrentTheme();
    this.onThemeChanged(currentTheme);
    this.themeSwitched("theme-switched", win, currentTheme);
  },

  setupSidePanels: function() {
    this.sidebarOverlay.setupSidePanels();
  },

  setupSideOverlays: function() {
    this.sidebarOverlay.setupSideOverlays();
  },

  // Tab Menu

  createTabMenu: function() {
    if (this.tabMenu) {
      return;
    }

    this.tabMenu = new TabMenu({
      panel: this,
      toolbox: this.toolbox,
      tabIdPrefix: "toolbox-tab-",
      doc: this.toolbox.doc,
      owner: this.toolbox
    });

    this.tabMenu.createTabMenu();
  },

  destroyTabMenu: function() {
    if (this.tabMenu) {
      this.tabMenu.destroy();
      this.tabMenu = null;
    }
  },

  // Search

  search: function (nativeSearchBoxSelector, value) {
    let doc = this.getPanelDocument();
    let win = this.getPanelWindow();

    // Inject the searched pattern to the native search box.
    // xxxHonza: The search box UI will be built-in at some point
    // see: https://bugzilla.mozilla.org/show_bug.cgi?id=1026479
    // As soon as the bug is fixed this code will change TESTME
    let nativeSearchBox = doc.querySelector(nativeSearchBoxSelector);
    nativeSearchBox.value = value;

    // Trigger the "command" event for the native search box
    // to apply the filter.
    let event = doc.createEvent("xulcommandevent");
    event.initCommandEvent("command", true, true, win, 0, false, false, false,
        false, null);
    nativeSearchBox.dispatchEvent(event);
  },

  updateSearchBox: function(apply) {
  },

  // Toolbar Buttons

  getPanelToolbarButtons: function() {
    let buttons = [];
    return buttons;
  },

  // Options

  getOptionsMenuItems: function() {
    let items = [];
    return items;
  },

  /**
   * Executed by the framework when the user clicks panel tab options
   * menu target. Returns custom menu popup for panel options.
   *
   * @returns {MenuPopup} The method can return custom <menupopup> element
   * that will be displayed when the user clicks the tab options target.
   */
  getOptionsMenuPopup: function() {
  },

  // Side Panels

  /**
   * Return list of side panels that should be created for this panel.
   */
  getSidePanels: function() {
    return [];
  },

  /**
   * Can be overridden by overlays to indicate that the (built-in)
   * panel has side panels even if there are no additional custom
   * panels appended.
   */
  hasSidePanels: function() {
    let panels = this.getSidePanels();
    return (panels && panels.length);
  },

  // Panel Selection

  /**
   * Executed by the framework when the panel is selected in the toolbox.
   */
  onShow: function() {
    Trace.sysout("PanelOverlay.show;");

    if (this.tabMenu) {
      this.tabMenu.onPanelShow();
    }

    if (this.searchable && ToolboxChrome.isFirebugThemeActive()) {
      this.updateSearchBox(true);
    }
  },

  /**
   * Executed by the framework when the panel is un-selected in the toolbox.
   */
  onHide: function() {
    Trace.sysout("PanelOverlay.hide;");

    if (this.tabMenu) {
      this.tabMenu.onPanelHide();
    }

    // Unapply search-box customization when the Inspector panel
    // is hidden (unselected). The search box is shared among
    // panels and other customization can apply.
    if (this.searchable) {
      this.updateSearchBox(false);
    }
  },

  // Theme

  onThemeChanged: function(newTheme, oldTheme) {
  },

  themeSwitched: function(eventId, win, newTheme, oldTheme) {
    let frame = this.panelFrame;
    if (frame && frame.contentWindow === win) {
      // See also this MDN article about proper handling errors.
      // https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Promise#Handling_errors_and_common_pitfalls
      Win.loaded(win).then(() => {
        this.onThemeSwitched(win, newTheme, oldTheme);
      }).then(null, err => {
        TraceError.sysout("PanelOverlay.themeSwitched; ERROR " + err, err);
      });
    }
  },

  onThemeSwitched: function(win, newTheme, oldTheme) {
    // xxxHonza: E.g. the StyleEditorOverlay.onApplyTheme is called twice
    // when the theme changes, so avoid that here (for all panels).
    if (this.currentTheme == newTheme) {
      return;
    }

    this.currentTheme = newTheme;

    if (newTheme == "firebug") {
      this.onApplyTheme(win, oldTheme);
    } else if (oldTheme == "firebug"){
      this.onUnapplyTheme(win, newTheme);
    }
  },

  /**
   * Executed by the framework when Firebug theme is applied.
   *
   * xxxHonza: onApplyTheme and onUnapplyTheme are not executed for
   * custom panels that don't include theme-switching.js file in
   * the content document. Note that the document must have chrome
   * privileges.
   *
   * @param {Window} Window in the iframe the theme is applied to.
   * @param {@String} ID of the previous theme.
   */
  onApplyTheme: function(win, oldTheme) {
    Trace.sysout("PanelOverlay.onApplyTheme;");

    if (this.searchable) {
      this.updateSearchBox(true);
    }

    this.createTabMenu();

    // Emit select event, so the tab-menu is visible after
    // Firebug theme is activated.
    this.toolbox.emit("select", this.toolbox.currentToolId);
  },

  /**
   * Executed by the framework when Firebug theme is unapplied.
   *
   * @param {Window} Window in the iframe the theme is applied to.
   * @param {@String} ID of the new theme.
   */
  onUnapplyTheme: function(win, newTheme) {
    Trace.sysout("PanelOverlay.onUnapplyTheme;");

    if (this.searchable) {
      this.updateSearchBox(false);
    }

    this.destroyTabMenu();
  },

  // Accessors

  /**
   * Returns unique ID of the overlay.
   */
  get id() {
    return this.overlayId;
  },

  getPanel: function() {
    return this.panel;
  },

  getPanelDocument: function() {
    return this.panelFrame.contentDocument;
  },

  getPanelWindow: function() {
    return this.panelFrame.contentWindow;
  },

  getToolbar: function() {
    let doc = this.getPanelDocument();
    return doc.querySelector("#panelToolbar");
  }
});

// Exports from this module
exports.PanelOverlay = PanelOverlay;
<|endoftext|>