<commit_before>/* @flow */

import {CompositeDisposable, Emitter} from 'atom'
import FileDiff from './file-diff'
import GitService from './git-service'
import EventTransactor from './event-transactor'

import type {ObjectMap} from './common'
import type {Disposable} from 'atom'
import type HunkLine from './hunk-line'
import type DiffHunk from './diff-hunk'

// FileList contains a collection of FileDiff objects
export default class FileList {
  gitService: GitService;
  emitter: Emitter;
  transactor: EventTransactor;
  fileCache: ObjectMap<FileDiff>;
  files: Array<FileDiff>;
  fileSubscriptions: CompositeDisposable;
  isSyncing: boolean;

  constructor (files: Array<FileDiff>, gitService: GitService, {stageOnChange}: {stageOnChange?: boolean} = {}) {
    // TODO: Rather than this `stageOnChange` bool, there should probably be a
    // new object that just handles the connection to nodegit. Cause there are
    // several of these objects in the system, but only one of them handles
    // writing to the index.
    this.gitService = gitService
    this.emitter = new Emitter()
    this.transactor = new EventTransactor(this.emitter, {fileList: this})
    this.fileCache = {}
    this.setFiles(files || [])
    if (stageOnChange) {
      this.onDidChange(this.handleDidChange.bind(this))
    }
  }

  onDidChange (callback: Function): Disposable {
    return this.emitter.on('did-change', callback)
  }

  didChange (event: ?Object) {
    this.transactor.didChange(event)
  }

  onDidUserChange (callback: Function): Disposable {
    return this.emitter.on('did-user-change', callback)
  }

  didUserChange () {
    this.emitter.emit('did-user-change')
  }

  async handleDidChange (event: Object): Promise<void> {
    if (!(event && event.events && event.fileList === this)) return
    if (this.isSyncingState()) return

    console.log(event)

    const extractedLines = this.extractLinesFromEventByHunk(event)
    if (extractedLines) {
      const {stagableLinesByHunk, fileDiff, stage} = extractedLines
      await this.stageLines(fileDiff, stagableLinesByHunk, stage)
      this.didUserChange()
      return
    }

    const rootEvent = event.events[0]
    if (rootEvent && rootEvent.property === 'stageStatus' && rootEvent.file) {
      const file: FileDiff = rootEvent.file
      // In both cases we end up here (renamed, mode change), we know we have a
      // new file name.
      // $FlowSilence
      await this.gitService.stagePath(file.getNewFileName())
      this.didUserChange()
      return
    }
  }

  async stageLines (fileDiff: FileDiff, stagableLinesPerHunk: ObjectMap<{linesToStage: Array<HunkLine>, linesToUnstage: Array<HunkLine>}>, stage: boolean): Promise<void> {
    const patches = await this.gitService.calculatePatchTexts(stagableLinesPerHunk, stage)
    await this.stagePatches(fileDiff, patches, stage)
  }

  extractLinesFromEventByHunk (event: Object): ?{stagableLinesByHunk: ObjectMap<{linesToStage: Array<HunkLine>, linesToUnstage: Array<HunkLine>}>, fileDiff: FileDiff, stage: boolean} {
    let stagableLinesByHunk = {}
    let fileDiff = null
    let stage = true
    for (let fileEvent of event.events) {
      if (!fileEvent) continue

      fileDiff = fileEvent.file

      const fileEvents = fileEvent.events
      if (!fileEvents) {
        return null
      }

      for (let hunkEvent of fileEvents) {
        if (!hunkEvent) continue

        const hunk = hunkEvent.hunk
        if (!stagableLinesByHunk[hunk]) {
          stagableLinesByHunk[hunk] = {
            linesToStage: [],
            linesToUnstage: []
          }
        }
        const stagableLines = stagableLinesByHunk[hunk]
        for (let lineEvent of hunkEvent.events) {
          if (lineEvent.line && lineEvent.property === 'isStaged') {
            if (lineEvent.line.isStaged()) {
              stagableLines.linesToStage.push(lineEvent.line)
              stage = true
            } else {
              stagableLines.linesToUnstage.push(lineEvent.line)
              stage = false
            }
          }

          if (stagableLines.linesToStage.length > 0 && stagableLines.linesToUnstage.length > 0) {
            throw new Error("Can't toggle stagedness on a selection that has mixed stage (yet) :(")
          }
        }
      }
    }

    // $FlowSilence: We know `fileDiff` won't be null by the time we get here.
    return {stagableLinesByHunk, fileDiff, stage}
  }

  openFile (file: FileDiff): Promise<void> {
    const pathName = file.getNewPathName()
    if (pathName) {
      return atom.workspace.open(pathName, {pending: true})
    } else {
      return Promise.resolve()
    }
  }

  openFileDiff (file: FileDiff): Promise<void> {
    return file.openDiff({pending: true})
  }

  setFiles (files: Array<FileDiff>) {
    if (this.fileSubscriptions) {
      this.fileSubscriptions.dispose()
    }
    this.fileSubscriptions = new CompositeDisposable()
    this.files = files
    for (const file of files) {
      this.fileSubscriptions.add(file.onDidChange(this.didChange.bind(this)))
      this.addFileToCache(file)
    }
    this.didChange()
  }

  getFiles (): Array<FileDiff> {
    return this.files
  }

  // position = [0, 2] will get you the third hunk in the first file.
  // position = [0, 2, 1] will get you the 2nd line in the third hunk in the first file.
  getObjectAtPosition (position: [number, number, ?number]): HunkLine | DiffHunk {
    const [fileIndex, hunkIndex, lineIndex] = position

    const file = this.getFiles()[fileIndex]
    const hunk = file.getHunks()[hunkIndex]
    if (lineIndex != null) {
      return hunk.getLines()[lineIndex]
    } else {
      return hunk
    }
  }

  // The file cache should allow all UI elements usage of the same FileDiff
  // models. Sometimes it's a bit of a chicken and egg problem, and it happens
  // when a tab is deserialized.
  //
  // * Let's say there is a Diff tab being deserialized for `config.js`.
  // * The deserializer runs before nodegit knows the state of things, but the
  // tab needs a model. The tab will use `getOrCreateFileFromPathName` to get
  // the model.
  // * Then and the FileDiff::loadFromGitUtils is called and there are changes
  // in `config.js`
  // * `loadFromGitUtils` will use
  // `getOrCreateFileFromPathName('config.js')`, which will grab the same model
  // that the Diff tab is using.
  // * The model will be updated from the nodegit state and the Diff tab will
  // update properly.
  addFileToCache (file: FileDiff) {
    const oldPathName = file.getOldPathName()
    if (oldPathName !== file.getNewPathName() && oldPathName && this.fileCache[oldPathName]) {
      delete this.fileCache[oldPathName]
    }

    const newPathName = file.getNewPathName()
    if (newPathName) {
      this.fileCache[newPathName] = file
    }
  }

  getFileFromPathName (pathName: string): ?FileDiff {
    return this.fileCache[pathName]
  }

  getOrCreateFileFromPathName (pathName: string): FileDiff {
    let file = this.getFileFromPathName(pathName)
    if (!file) {
      file = new FileDiff({newPathName: pathName, oldPathName: pathName})
      this.addFileToCache(file)
    }
    return file
  }

  isSyncingState (): boolean {
    if (this.isSyncing) {
      return true
    }

    for (let file of this.files) {
      if (file.isSyncingState()) {
        return true
      }
    }

    return false
  }

  toString (): string {
    return this.files.map(file => file.toString()).join('\n')
  }

  async stagePatches (fileDiff: FileDiff, patches: Array<string>, stage: boolean): Promise<number> {
    if (stage) {
      return this.gitService.stagePatches(fileDiff, patches)
    } else {
      return this.gitService.unstagePatches(fileDiff, patches)
    }
  }

  async loadFromGitUtils (): Promise<void> {
    let files = []
    this.isSyncing = true

    // FIXME: for now, we need to get the stati for the diff stuff to work. :/
    this.gitService.getStatuses()
    let unifiedDiffs = await this.gitService.getDiffs('all')
    // TODO: It's a bummer these lines happen sequentially
    const stagedDiffs = await this.gitService.getDiffs('staged')
    const unstagedDiffs = await this.gitService.getDiffs('unstaged')

    const stagedDiffsByName = {}
    for (const diff of stagedDiffs) {
      // TODO: Old path is probably not always right.
      // $FlowFixMe
      stagedDiffsByName[diff.oldFile().path()] = diff
    }

    const unstagedDiffsByName = {}
    for (const diff of unstagedDiffs) {
      // TODO: Old path is probably not always right.
      // $FlowFixMe
      unstagedDiffsByName[diff.oldFile().path()] = diff
    }

    for (let diff of unifiedDiffs) {
      // $FlowFixMe
      let fileDiff = this.getOrCreateFileFromPathName(diff.newFile().path())
      // $FlowFixMe
      const stagedDiff = stagedDiffsByName[diff.newFile().path()]
      // $FlowFixMe
      const unstagedDiff = unstagedDiffsByName[diff.oldFile().path()]
      await fileDiff.fromGitUtilsObject({diff, stagedDiff, unstagedDiff})
      files.push(fileDiff)
    }

    this.transactor.transact(() => {
      this.setFiles(files)
    })
    this.isSyncing = false
  }
}
<commit_msg>Stop logging these events.<commit_after>/* @flow */

import {CompositeDisposable, Emitter} from 'atom'
import FileDiff from './file-diff'
import GitService from './git-service'
import EventTransactor from './event-transactor'

import type {ObjectMap} from './common'
import type {Disposable} from 'atom'
import type HunkLine from './hunk-line'
import type DiffHunk from './diff-hunk'

// FileList contains a collection of FileDiff objects
export default class FileList {
  gitService: GitService;
  emitter: Emitter;
  transactor: EventTransactor;
  fileCache: ObjectMap<FileDiff>;
  files: Array<FileDiff>;
  fileSubscriptions: CompositeDisposable;
  isSyncing: boolean;

  constructor (files: Array<FileDiff>, gitService: GitService, {stageOnChange}: {stageOnChange?: boolean} = {}) {
    // TODO: Rather than this `stageOnChange` bool, there should probably be a
    // new object that just handles the connection to nodegit. Cause there are
    // several of these objects in the system, but only one of them handles
    // writing to the index.
    this.gitService = gitService
    this.emitter = new Emitter()
    this.transactor = new EventTransactor(this.emitter, {fileList: this})
    this.fileCache = {}
    this.setFiles(files || [])
    if (stageOnChange) {
      this.onDidChange(this.handleDidChange.bind(this))
    }
  }

  onDidChange (callback: Function): Disposable {
    return this.emitter.on('did-change', callback)
  }

  didChange (event: ?Object) {
    this.transactor.didChange(event)
  }

  onDidUserChange (callback: Function): Disposable {
    return this.emitter.on('did-user-change', callback)
  }

  didUserChange () {
    this.emitter.emit('did-user-change')
  }

  async handleDidChange (event: Object): Promise<void> {
    if (!(event && event.events && event.fileList === this)) return
    if (this.isSyncingState()) return

    const extractedLines = this.extractLinesFromEventByHunk(event)
    if (extractedLines) {
      const {stagableLinesByHunk, fileDiff, stage} = extractedLines
      await this.stageLines(fileDiff, stagableLinesByHunk, stage)
      this.didUserChange()
      return
    }

    const rootEvent = event.events[0]
    if (rootEvent && rootEvent.property === 'stageStatus' && rootEvent.file) {
      const file: FileDiff = rootEvent.file
      // In both cases we end up here (renamed, mode change), we know we have a
      // new file name.
      // $FlowSilence
      await this.gitService.stagePath(file.getNewFileName())
      this.didUserChange()
      return
    }
  }

  async stageLines (fileDiff: FileDiff, stagableLinesPerHunk: ObjectMap<{linesToStage: Array<HunkLine>, linesToUnstage: Array<HunkLine>}>, stage: boolean): Promise<void> {
    const patches = await this.gitService.calculatePatchTexts(stagableLinesPerHunk, stage)
    await this.stagePatches(fileDiff, patches, stage)
  }

  extractLinesFromEventByHunk (event: Object): ?{stagableLinesByHunk: ObjectMap<{linesToStage: Array<HunkLine>, linesToUnstage: Array<HunkLine>}>, fileDiff: FileDiff, stage: boolean} {
    let stagableLinesByHunk = {}
    let fileDiff = null
    let stage = true
    for (let fileEvent of event.events) {
      if (!fileEvent) continue

      fileDiff = fileEvent.file

      const fileEvents = fileEvent.events
      if (!fileEvents) {
        return null
      }

      for (let hunkEvent of fileEvents) {
        if (!hunkEvent) continue

        const hunk = hunkEvent.hunk
        if (!stagableLinesByHunk[hunk]) {
          stagableLinesByHunk[hunk] = {
            linesToStage: [],
            linesToUnstage: []
          }
        }
        const stagableLines = stagableLinesByHunk[hunk]
        for (let lineEvent of hunkEvent.events) {
          if (lineEvent.line && lineEvent.property === 'isStaged') {
            if (lineEvent.line.isStaged()) {
              stagableLines.linesToStage.push(lineEvent.line)
              stage = true
            } else {
              stagableLines.linesToUnstage.push(lineEvent.line)
              stage = false
            }
          }

          if (stagableLines.linesToStage.length > 0 && stagableLines.linesToUnstage.length > 0) {
            throw new Error("Can't toggle stagedness on a selection that has mixed stage (yet) :(")
          }
        }
      }
    }

    // $FlowSilence: We know `fileDiff` won't be null by the time we get here.
    return {stagableLinesByHunk, fileDiff, stage}
  }

  openFile (file: FileDiff): Promise<void> {
    const pathName = file.getNewPathName()
    if (pathName) {
      return atom.workspace.open(pathName, {pending: true})
    } else {
      return Promise.resolve()
    }
  }

  openFileDiff (file: FileDiff): Promise<void> {
    return file.openDiff({pending: true})
  }

  setFiles (files: Array<FileDiff>) {
    if (this.fileSubscriptions) {
      this.fileSubscriptions.dispose()
    }
    this.fileSubscriptions = new CompositeDisposable()
    this.files = files
    for (const file of files) {
      this.fileSubscriptions.add(file.onDidChange(this.didChange.bind(this)))
      this.addFileToCache(file)
    }
    this.didChange()
  }

  getFiles (): Array<FileDiff> {
    return this.files
  }

  // position = [0, 2] will get you the third hunk in the first file.
  // position = [0, 2, 1] will get you the 2nd line in the third hunk in the first file.
  getObjectAtPosition (position: [number, number, ?number]): HunkLine | DiffHunk {
    const [fileIndex, hunkIndex, lineIndex] = position

    const file = this.getFiles()[fileIndex]
    const hunk = file.getHunks()[hunkIndex]
    if (lineIndex != null) {
      return hunk.getLines()[lineIndex]
    } else {
      return hunk
    }
  }

  // The file cache should allow all UI elements usage of the same FileDiff
  // models. Sometimes it's a bit of a chicken and egg problem, and it happens
  // when a tab is deserialized.
  //
  // * Let's say there is a Diff tab being deserialized for `config.js`.
  // * The deserializer runs before nodegit knows the state of things, but the
  // tab needs a model. The tab will use `getOrCreateFileFromPathName` to get
  // the model.
  // * Then and the FileDiff::loadFromGitUtils is called and there are changes
  // in `config.js`
  // * `loadFromGitUtils` will use
  // `getOrCreateFileFromPathName('config.js')`, which will grab the same model
  // that the Diff tab is using.
  // * The model will be updated from the nodegit state and the Diff tab will
  // update properly.
  addFileToCache (file: FileDiff) {
    const oldPathName = file.getOldPathName()
    if (oldPathName !== file.getNewPathName() && oldPathName && this.fileCache[oldPathName]) {
      delete this.fileCache[oldPathName]
    }

    const newPathName = file.getNewPathName()
    if (newPathName) {
      this.fileCache[newPathName] = file
    }
  }

  getFileFromPathName (pathName: string): ?FileDiff {
    return this.fileCache[pathName]
  }

  getOrCreateFileFromPathName (pathName: string): FileDiff {
    let file = this.getFileFromPathName(pathName)
    if (!file) {
      file = new FileDiff({newPathName: pathName, oldPathName: pathName})
      this.addFileToCache(file)
    }
    return file
  }

  isSyncingState (): boolean {
    if (this.isSyncing) {
      return true
    }

    for (let file of this.files) {
      if (file.isSyncingState()) {
        return true
      }
    }

    return false
  }

  toString (): string {
    return this.files.map(file => file.toString()).join('\n')
  }

  async stagePatches (fileDiff: FileDiff, patches: Array<string>, stage: boolean): Promise<number> {
    if (stage) {
      return this.gitService.stagePatches(fileDiff, patches)
    } else {
      return this.gitService.unstagePatches(fileDiff, patches)
    }
  }

  async loadFromGitUtils (): Promise<void> {
    let files = []
    this.isSyncing = true

    // FIXME: for now, we need to get the stati for the diff stuff to work. :/
    this.gitService.getStatuses()
    let unifiedDiffs = await this.gitService.getDiffs('all')
    // TODO: It's a bummer these lines happen sequentially
    const stagedDiffs = await this.gitService.getDiffs('staged')
    const unstagedDiffs = await this.gitService.getDiffs('unstaged')

    const stagedDiffsByName = {}
    for (const diff of stagedDiffs) {
      // TODO: Old path is probably not always right.
      // $FlowFixMe
      stagedDiffsByName[diff.oldFile().path()] = diff
    }

    const unstagedDiffsByName = {}
    for (const diff of unstagedDiffs) {
      // TODO: Old path is probably not always right.
      // $FlowFixMe
      unstagedDiffsByName[diff.oldFile().path()] = diff
    }

    for (let diff of unifiedDiffs) {
      // $FlowFixMe
      let fileDiff = this.getOrCreateFileFromPathName(diff.newFile().path())
      // $FlowFixMe
      const stagedDiff = stagedDiffsByName[diff.newFile().path()]
      // $FlowFixMe
      const unstagedDiff = unstagedDiffsByName[diff.oldFile().path()]
      await fileDiff.fromGitUtilsObject({diff, stagedDiff, unstagedDiff})
      files.push(fileDiff)
    }

    this.transactor.transact(() => {
      this.setFiles(files)
    })
    this.isSyncing = false
  }
}
<|endoftext|>


<commit_before>var fs = require("fs"),
    mergesort = require("./mergesort");

module.exports = function(options, allPaths, printCb, done) {
  var dirKeys = mergesort(Object.keys(allPaths)), parent = "";

  if (options.list) {
    var results = "";
    for (var d = 0, dLength = dirKeys.length; d < dLength; d++) {
      parent = allPaths[dirKeys[d]];
      results += mergesort(parent).join("\n");
    }

    printCb(results);
  }
  else {
    var allFiles = [];

    for (var d = 0, dLength = dirKeys.length; d < dLength; d++) {
      parent = allPaths[dirKeys[d]];
      allFiles = mergesort(parent);

      for (var m = 0, allFilesLength = allFiles.length; m < allFilesLength; m++) {
        var path = allFiles[m],
            contents = fs.readFileSync(path, "utf8");

          if (options.queryClean.test(contents)) {
            var contentsSplit = contents.split("\n"),
            lines = "",
            totalMatches = contents.match(options.query).length,
            matchCount = totalMatches;

            if (options.replacement) {
              var replacedContents = contents.replace(options.query, options.replacement);
              fs.writeFile(path, replacedContents, "utf8");
            }

            for (var i = 0, strLength = contentsSplit.length; matchCount && i < strLength; i++) {
              if (options.queryClean.test(contentsSplit[i])) {
                var matchLine = (options.replacement ? contentsSplit[i].replace(options.query, options.replacement) : contentsSplit[i]);

                if (!options.ackmate) {
                  lines += "\t" + (i+1) + ": " + matchLine + "\n";
                }
                else { // slow due to match()
                  var result = options.queryClean.exec(matchLine);
                  lines += (i+1) + ";" + result.index + " " + result[0].length + ": " + matchLine + "\n";
                }

                matchCount--;
              }
            }

            printCb(options.ackmate ? ":" + path : path + ":", lines, totalMatches);
        }
      }
    }
    done();
  }
}<commit_msg>Okay that's apparently important<commit_after>var fs = require("fs"),
    mergesort = require("./mergesort");

module.exports = function(options, allPaths, printCb, done) {
  var dirKeys = mergesort(Object.keys(allPaths)), parent = "";

  if (options.list) {
    var results = "";
    for (var d = 0, dLength = dirKeys.length; d < dLength; d++) {
      parent = allPaths[dirKeys[d]];
      var sort = mergesort(parent);
      if (sort.length)
        results += sort.join("\n") + "\n";
    }

    printCb(results);
  }
  else {
    var allFiles = [];

    for (var d = 0, dLength = dirKeys.length; d < dLength; d++) {
      parent = allPaths[dirKeys[d]];
      allFiles = mergesort(parent);

      for (var m = 0, allFilesLength = allFiles.length; m < allFilesLength; m++) {
        var path = allFiles[m],
            contents = fs.readFileSync(path, "utf8");

          if (options.queryClean.test(contents)) {
            var contentsSplit = contents.split("\n"),
            lines = "",
            totalMatches = contents.match(options.query).length,
            matchCount = totalMatches;

            if (options.replacement) {
              var replacedContents = contents.replace(options.query, options.replacement);
              fs.writeFile(path, replacedContents, "utf8");
            }

            for (var i = 0, strLength = contentsSplit.length; matchCount && i < strLength; i++) {
              if (options.queryClean.test(contentsSplit[i])) {
                var matchLine = (options.replacement ? contentsSplit[i].replace(options.query, options.replacement) : contentsSplit[i]);

                if (!options.ackmate) {
                  lines += "\t" + (i+1) + ": " + matchLine + "\n";
                }
                else { // slow due to match()
                  var result = options.queryClean.exec(matchLine);
                  lines += (i+1) + ";" + result.index + " " + result[0].length + ": " + matchLine + "\n";
                }

                matchCount--;
              }
            }

            printCb(options.ackmate ? ":" + path : path + ":", lines, totalMatches);
        }
      }
    }
    done();
  }
}<|endoftext|>


<commit_before>// lib/finishers.js
//
// Functions for adding extra flags and stream data to API output
//
// Copyright 2012, StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var _ = require("underscore"),
    Step = require("step"),
    ActivityObject = require("../lib/model/activityobject").ActivityObject,
    Edge = require("../lib/model/edge").Edge,
    Favorite = require("../lib/model/favorite").Favorite,
    Share = require("../lib/model/share").Share,
    FilteredStream = require("../lib/filteredstream").FilteredStream,
    filters = require("../lib/filters"),
    recipientsOnly = filters.recipientsOnly,
    objectRecipientsOnly = filters.objectRecipientsOnly,
    objectPublicOnly = filters.objectPublicOnly,
    publicOnly = filters.publicOnly;

// finisher that adds followed flag to stuff

var addFollowedFinisher = function(req, collection, callback) {

    // Ignore for non-users

    if (!req.remoteUser) {
        callback(null);
        return;
    }

    addFollowed(req.remoteUser.profile, _.pluck(collection.items, "object"), callback);
};

var addFollowed = function(profile, objects, callback) {

    var edgeIDs;

    // Ignore for non-users

    if (!profile) {
        callback(null);
        return;
    }

    edgeIDs = objects.map(function(object) {
        return Edge.id(profile.id, object.id);
    });

    Step(
        function() {
            Edge.readAll(edgeIDs, this);
        },
        function(err, edges) {
            if (err) {
                callback(err);
            } else {
                _.each(objects, function(object, i) {
                    var edgeID = edgeIDs[i];
                    if (!_.has(object, "pump_io")) {
                        object.pump_io = {};
                    }
                    if (_.has(edges, edgeID) && _.isObject(edges[edgeID])) {
                        object.pump_io.followed = true;
                    } else {
                        object.pump_io.followed = false;
                    }
                });
                callback(null);
            }
        }
    );
};

// finisher that adds shared flag to stuff

var addSharedFinisher = function(req, collection, callback) {

    // Ignore for non-users

    if (!req.remoteUser) {
        callback(null);
        return;
    }

    addShared(req.remoteUser.profile, _.pluck(collection.items, "object"), callback);
};

var addShared = function(profile, objects, callback) {

    var shareIDs;

    // Ignore for non-users

    if (!profile) {
        callback(null);
        return;
    }

    shareIDs = objects.map(function(object) {
        return Share.id(profile, object);
    });

    Step(
        function() {
            Share.readAll(shareIDs, this);
        },
        function(err, shares) {
            if (err) {
                callback(err);
            } else {
                _.each(objects, function(object, i) {
                    var shareID = shareIDs[i];
                    if (!_.has(object, "pump_io")) {
                        object.pump_io = {};
                    }
                    if (_.has(shares, shareID) && _.isObject(shares[shareID])) {
                        object.pump_io.shared = true;
                    } else {
                        object.pump_io.shared = false;
                    }
                });
                callback(null);
            }
        }
    );
};

// finisher that adds liked flag to stuff

var addLikedFinisher = function(req, collection, callback) {

    // Ignore for non-users

    if (!req.remoteUser) {
        callback(null);
        return;
    }

    addLiked(req.remoteUser.profile, _.pluck(collection.items, "object"), callback);
};

var addLiked = function(profile, objects, callback) {

    var faveIDs;

    // Ignore for non-users

    if (!profile) {
        callback(null);
        return;
    }

    faveIDs = objects.map(function(object) {
        return Favorite.id(profile.id, object.id);
    });

    Step(
        function() {
            Favorite.readAll(faveIDs, this);
        },
        function(err, faves) {
            if (err) {
                callback(err);
            } else {
                _.each(objects, function(object, i) {
                    var faveID = faveIDs[i];
                    if (_.has(faves, faveID) && _.isObject(faves[faveID])) {
                        object.liked = true;
                    } else {
                        object.liked = false;
                    }
                });
                callback(null);
            }
        }
    );
};

var firstFewRepliesFinisher = function(req, collection, callback) {

    var profile = (req.remoteUser) ? req.remoteUser.profile : null,
        objects = _.pluck(collection.items, "object");

    firstFewReplies(profile, objects, callback);
};

var firstFewReplies = function(profile, objs, callback) {

    var getReplies = function(obj, callback) {

        if (!_.has(obj, "replies") ||
            !_.isObject(obj.replies) ||
            (_.has(obj.replies, "totalItems") && obj.replies.totalItems === 0)) {
            callback(null);
            return;
        }

        Step(
            function() {
                obj.getRepliesStream(this);
            },
            function(err, str) {
                var filtered;
                if (err) throw err;
                if (!profile) {
                    filtered = new FilteredStream(str, objectPublicOnly);
                } else {
                    filtered = new FilteredStream(str, objectRecipientsOnly(profile));
                }
                filtered.getObjects(0, 4, this);
            },
            function(err, refs) {
                var group = this.group();
                if (err) throw err;
                _.each(refs, function(ref) {
                    ActivityObject.getObject(ref.objectType, ref.id, group());
                });
            },
            function(err, objs) {
                if (err) {
                    callback(err);
                } else {
                    obj.replies.items = objs;
                    _.each(obj.replies.items, function(item) {
                        item.sanitize();
                    });
                    callback(null);
                }
            }
        );
    };

    Step(
        function() {
            var group = this.group();
            _.each(objs, function(obj) {
                getReplies(obj, group());
            });
        },
        callback
    );
};

var firstFewSharesFinisher = function(req, collection, callback) {

    var profile = (req.remoteUser) ? req.remoteUser.profile : null,
        objects = _.pluck(collection.items, "object");

    firstFewShares(profile, objects, callback);
};

var firstFewShares = function(profile, objs, callback) {

    var getShares = function(obj, callback) {

        if (!_.has(obj, "shares") ||
            !_.isObject(obj.shares) ||
            (_.has(obj.shares, "totalItems") && obj.shares.totalItems === 0)) {
            callback(null);
            return;
        }

        Step(
            function() {
                obj.getSharesStream(this);
            },
            function(err, str) {
                if (err) throw err;
                str.getObjects(0, 4, this);
            },
            function(err, refs) {
                var group = this.group();
                if (err) throw err;
                _.each(refs, function(ref) {
                    ActivityObject.getObject(ref.objectType, ref.id, group());
                });
            },
            function(err, objs) {
                if (err) {
                    callback(err);
                } else {
                    obj.shares.items = objs;
                    _.each(obj.shares.items, function(item) {
                        item.sanitize();
                    });
                    callback(null);
                }
            }
        );
    };

    Step(
        function() {
            var group = this.group();
            _.each(objs, function(obj) {
                getShares(obj, group());
            });
        },
        callback
    );
};

// finisher that adds followed flag to stuff

var addLikersFinisher = function(req, collection, callback) {

    // Ignore for non-users

    addLikers((req.remoteUser) ? req.remoteUser.profile : null,
              _.pluck(collection.items, "object"),
              callback);
};

var addLikers = function(profile, objects, callback) {

    var liked = _.filter(objects, function(object) {
        return _.has(object, "likes") &&
            _.isObject(object.likes) &&
            _.has(object.likes, "totalItems") &&
            _.isNumber(object.likes.totalItems) &&
            object.likes.totalItems > 0;
    });

    Step(
        function() {
            var group = this.group();

            _.each(liked, function(object) {
                object.getFavoriters(0, 4, group());
            });
        },
        function(err, likers) {
            if (err) {
                callback(err);
            } else {
                _.each(liked, function(object, i) {
                    object.likes.items = likers[i];
                });
                callback(null);
            }
        }
    );
};

var doFinishers = function(finishers) {
    return function(req, collection, callback) {
        Step(
            function() {
                var group = this.group();
                _.each(finishers, function(finisher) {
                    finisher(req, collection, group());
                });
            },
            callback
        );
    };
};

exports.addFollowedFinisher = addFollowedFinisher;
exports.addFollowed = addFollowed;
exports.addLikedFinisher = addLikedFinisher;
exports.addLiked = addLiked;
exports.firstFewRepliesFinisher = firstFewRepliesFinisher;
exports.firstFewReplies = firstFewReplies;
exports.firstFewSharesFinisher = firstFewSharesFinisher;
exports.firstFewShares = firstFewShares;
exports.doFinishers = doFinishers;
exports.addLikersFinisher = addLikersFinisher;
exports.addLikers = addLikers;
exports.addSharedFinisher = addSharedFinisher;
exports.addShared = addShared;
<commit_msg>Use principal rather than remoteUser in finishers<commit_after>// lib/finishers.js
//
// Functions for adding extra flags and stream data to API output
//
// Copyright 2012, StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var _ = require("underscore"),
    Step = require("step"),
    ActivityObject = require("../lib/model/activityobject").ActivityObject,
    Edge = require("../lib/model/edge").Edge,
    Favorite = require("../lib/model/favorite").Favorite,
    Share = require("../lib/model/share").Share,
    FilteredStream = require("../lib/filteredstream").FilteredStream,
    filters = require("../lib/filters"),
    recipientsOnly = filters.recipientsOnly,
    objectRecipientsOnly = filters.objectRecipientsOnly,
    objectPublicOnly = filters.objectPublicOnly,
    publicOnly = filters.publicOnly;

// finisher that adds followed flag to stuff

var addFollowedFinisher = function(req, collection, callback) {

    // Ignore for non-users

    if (!req.principal) {
        callback(null);
        return;
    }

    addFollowed(req.principal, _.pluck(collection.items, "object"), callback);
};

var addFollowed = function(profile, objects, callback) {

    var edgeIDs;

    // Ignore for non-users

    if (!profile) {
        callback(null);
        return;
    }

    edgeIDs = objects.map(function(object) {
        return Edge.id(profile.id, object.id);
    });

    Step(
        function() {
            Edge.readAll(edgeIDs, this);
        },
        function(err, edges) {
            if (err) {
                callback(err);
            } else {
                _.each(objects, function(object, i) {
                    var edgeID = edgeIDs[i];
                    if (!_.has(object, "pump_io")) {
                        object.pump_io = {};
                    }
                    if (_.has(edges, edgeID) && _.isObject(edges[edgeID])) {
                        object.pump_io.followed = true;
                    } else {
                        object.pump_io.followed = false;
                    }
                });
                callback(null);
            }
        }
    );
};

// finisher that adds shared flag to stuff

var addSharedFinisher = function(req, collection, callback) {

    // Ignore for non-users

    if (!req.principal) {
        callback(null);
        return;
    }

    addShared(req.principal, _.pluck(collection.items, "object"), callback);
};

var addShared = function(profile, objects, callback) {

    var shareIDs;

    // Ignore for non-users

    if (!profile) {
        callback(null);
        return;
    }

    shareIDs = objects.map(function(object) {
        return Share.id(profile, object);
    });

    Step(
        function() {
            Share.readAll(shareIDs, this);
        },
        function(err, shares) {
            if (err) {
                callback(err);
            } else {
                _.each(objects, function(object, i) {
                    var shareID = shareIDs[i];
                    if (!_.has(object, "pump_io")) {
                        object.pump_io = {};
                    }
                    if (_.has(shares, shareID) && _.isObject(shares[shareID])) {
                        object.pump_io.shared = true;
                    } else {
                        object.pump_io.shared = false;
                    }
                });
                callback(null);
            }
        }
    );
};

// finisher that adds liked flag to stuff

var addLikedFinisher = function(req, collection, callback) {

    // Ignore for non-users

    if (!req.principal) {
        callback(null);
        return;
    }

    addLiked(req.principal, _.pluck(collection.items, "object"), callback);
};

var addLiked = function(profile, objects, callback) {

    var faveIDs;

    // Ignore for non-users

    if (!profile) {
        callback(null);
        return;
    }

    faveIDs = objects.map(function(object) {
        return Favorite.id(profile.id, object.id);
    });

    Step(
        function() {
            Favorite.readAll(faveIDs, this);
        },
        function(err, faves) {
            if (err) {
                callback(err);
            } else {
                _.each(objects, function(object, i) {
                    var faveID = faveIDs[i];
                    if (_.has(faves, faveID) && _.isObject(faves[faveID])) {
                        object.liked = true;
                    } else {
                        object.liked = false;
                    }
                });
                callback(null);
            }
        }
    );
};

var firstFewRepliesFinisher = function(req, collection, callback) {

    var profile = req.principal,
        objects = _.pluck(collection.items, "object");

    firstFewReplies(profile, objects, callback);
};

var firstFewReplies = function(profile, objs, callback) {

    var getReplies = function(obj, callback) {

        if (!_.has(obj, "replies") ||
            !_.isObject(obj.replies) ||
            (_.has(obj.replies, "totalItems") && obj.replies.totalItems === 0)) {
            callback(null);
            return;
        }

        Step(
            function() {
                obj.getRepliesStream(this);
            },
            function(err, str) {
                var filtered;
                if (err) throw err;
                if (!profile) {
                    filtered = new FilteredStream(str, objectPublicOnly);
                } else {
                    filtered = new FilteredStream(str, objectRecipientsOnly(profile));
                }
                filtered.getObjects(0, 4, this);
            },
            function(err, refs) {
                var group = this.group();
                if (err) throw err;
                _.each(refs, function(ref) {
                    ActivityObject.getObject(ref.objectType, ref.id, group());
                });
            },
            function(err, objs) {
                if (err) {
                    callback(err);
                } else {
                    obj.replies.items = objs;
                    _.each(obj.replies.items, function(item) {
                        item.sanitize();
                    });
                    callback(null);
                }
            }
        );
    };

    Step(
        function() {
            var group = this.group();
            _.each(objs, function(obj) {
                getReplies(obj, group());
            });
        },
        callback
    );
};

var firstFewSharesFinisher = function(req, collection, callback) {

    var profile = req.principal,
        objects = _.pluck(collection.items, "object");

    firstFewShares(profile, objects, callback);
};

var firstFewShares = function(profile, objs, callback) {

    var getShares = function(obj, callback) {

        if (!_.has(obj, "shares") ||
            !_.isObject(obj.shares) ||
            (_.has(obj.shares, "totalItems") && obj.shares.totalItems === 0)) {
            callback(null);
            return;
        }

        Step(
            function() {
                obj.getSharesStream(this);
            },
            function(err, str) {
                if (err) throw err;
                str.getObjects(0, 4, this);
            },
            function(err, refs) {
                var group = this.group();
                if (err) throw err;
                _.each(refs, function(ref) {
                    ActivityObject.getObject(ref.objectType, ref.id, group());
                });
            },
            function(err, objs) {
                if (err) {
                    callback(err);
                } else {
                    obj.shares.items = objs;
                    _.each(obj.shares.items, function(item) {
                        item.sanitize();
                    });
                    callback(null);
                }
            }
        );
    };

    Step(
        function() {
            var group = this.group();
            _.each(objs, function(obj) {
                getShares(obj, group());
            });
        },
        callback
    );
};

// finisher that adds followed flag to stuff

var addLikersFinisher = function(req, collection, callback) {

    // Ignore for non-users

    addLikers(req.principal,
              _.pluck(collection.items, "object"),
              callback);
};

var addLikers = function(profile, objects, callback) {

    var liked = _.filter(objects, function(object) {
        return _.has(object, "likes") &&
            _.isObject(object.likes) &&
            _.has(object.likes, "totalItems") &&
            _.isNumber(object.likes.totalItems) &&
            object.likes.totalItems > 0;
    });

    Step(
        function() {
            var group = this.group();

            _.each(liked, function(object) {
                object.getFavoriters(0, 4, group());
            });
        },
        function(err, likers) {
            if (err) {
                callback(err);
            } else {
                _.each(liked, function(object, i) {
                    object.likes.items = likers[i];
                });
                callback(null);
            }
        }
    );
};

var doFinishers = function(finishers) {
    return function(req, collection, callback) {
        Step(
            function() {
                var group = this.group();
                _.each(finishers, function(finisher) {
                    finisher(req, collection, group());
                });
            },
            callback
        );
    };
};

exports.addFollowedFinisher = addFollowedFinisher;
exports.addFollowed = addFollowed;
exports.addLikedFinisher = addLikedFinisher;
exports.addLiked = addLiked;
exports.firstFewRepliesFinisher = firstFewRepliesFinisher;
exports.firstFewReplies = firstFewReplies;
exports.firstFewSharesFinisher = firstFewSharesFinisher;
exports.firstFewShares = firstFewShares;
exports.doFinishers = doFinishers;
exports.addLikersFinisher = addLikersFinisher;
exports.addLikers = addLikers;
exports.addSharedFinisher = addSharedFinisher;
exports.addShared = addShared;
<|endoftext|>


<commit_before>var CombinedStream = require('combined-stream');
var util = require('util');
var path = require('path');
var http = require('http');
var https = require('https');
var parseUrl = require('url').parse;
var fs = require('fs');
var mime = require('mime');
var async = require('async');

module.exports = FormData;
function FormData() {
  this._overheadLength = 0;
  this._valueLength = 0;
  this._lengthRetrievers = [];

  CombinedStream.call(this);
}
util.inherits(FormData, CombinedStream);

FormData.LINE_BREAK = '\r\n';

FormData.prototype.append = function(field, value) {
  var append = CombinedStream.prototype.append.bind(this);

  // all that streamy business can't handle numbers
  if (typeof value == 'number') value = ''+value;

  var header = this._multiPartHeader(field, value);
  var footer = this._multiPartFooter(field, value);

  append(header);
  append(value);
  append(footer);

  this._trackLength(header, value)
};

FormData.prototype._trackLength = function(header, value) {
  var valueLength = 0;
  if (Buffer.isBuffer(value)) {
    valueLength = value.length;
  } else if (typeof value === 'string') {
    valueLength = Buffer.byteLength(value);
  }

  this._valueLength += valueLength;
  this._overheadLength +=
    Buffer.byteLength(header) +
    + FormData.LINE_BREAK.length;

  // empty or ethier doesn't have path or not an http response
  if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) {
    return;
  }

  this._lengthRetrievers.push(function(next) {

    // check if it's local file
    if (value.hasOwnProperty('fd')) {
      fs.stat(value.path, function(err, stat) {
        if (err) {
          next(err);
          return;
        }

        next(null, stat.size);
      });

    // or http response
    } else if (value.hasOwnProperty('httpVersion')) {
      next(null, +value.headers['content-length']);

    // or request stream http://github.com/mikeal/request
    } else if (value.hasOwnProperty('httpModule')) {
      // wait till response come back
      value.on('response', function(response) {
        value.pause();
        next(null, +response.headers['content-length']);
      });
      value.resume();

    // something else
    } else {
      next('Unknown stream');
    }
  });
};

FormData.prototype._multiPartHeader = function(field, value) {
  var boundary = this.getBoundary();
  var header =
    '--' + boundary + FormData.LINE_BREAK +
    'Content-Disposition: form-data; name="' + field + '"';

  // fs- and request- streams have path property
  // TODO: Use request's response mime-type
  if (value.path) {
    header +=
      '; filename="' + path.basename(value.path) + '"' + FormData.LINE_BREAK +
      'Content-Type: ' + mime.lookup(value.path);

  // http response has not
  } else if (value.readable && value.hasOwnProperty('httpVersion')) {
    header +=
      '; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK +
      'Content-Type: ' + value.headers['content-type'];
  }

  header += FormData.LINE_BREAK + FormData.LINE_BREAK;
  return header;
};

FormData.prototype._multiPartFooter = function(field, value) {
  return function(next) {
    var footer = FormData.LINE_BREAK;

    var lastPart = (this._streams.length === 0);
    if (lastPart) {
      footer += this._lastBoundary();
    }

    next(footer);
  }.bind(this);
};

FormData.prototype._lastBoundary = function() {
  return '--' + this.getBoundary() + '--';
};

FormData.prototype.getHeaders = function(userHeaders) {
  var formHeaders = {
    'content-type': 'multipart/form-data; boundary=' + this.getBoundary()
  };

  for (var header in userHeaders) {
    formHeaders[header.toLowerCase()] = userHeaders[header];
  }

  return formHeaders;
}

FormData.prototype.getCustomHeaders = function(contentType) {
    contentType = contentType ? contentType : 'multipart/form-data';

    var formHeaders = {
        'content-type': contentType + '; boundary=' + this.getBoundary(),
        'content-length': this.getLengthSync()
    };

    return formHeaders;
}

FormData.prototype.getBoundary = function() {
  if (!this._boundary) {
    this._generateBoundary();
  }

  return this._boundary;
};

FormData.prototype._generateBoundary = function() {
  // This generates a 50 character boundary similar to those used by Firefox.
  // They are optimized for boyer-moore parsing.
  var boundary = '--------------------------';
  for (var i = 0; i < 24; i++) {
    boundary += Math.floor(Math.random() * 10).toString(16);
  }

  this._boundary = boundary;
};

FormData.prototype.getLengthSync = function() {
    var knownLength = this._overheadLength + this._valueLength;

    if (this._streams.length) {
        knownLength += this._lastBoundary().length;
    }

    return knownLength;
};

FormData.prototype.getLength = function(cb) {
  var knownLength = this._overheadLength + this._valueLength;

  if (this._streams.length) {
    knownLength += this._lastBoundary().length;
  }

  if (!this._lengthRetrievers.length) {
    process.nextTick(cb.bind(this, null, knownLength));
    return;
  }

  async.parallel(this._lengthRetrievers, function(err, values) {
    if (err) {
      cb(err);
      return;
    }

    values.forEach(function(length) {
      knownLength += length;
    });

    cb(null, knownLength);
  });
};

FormData.prototype.submit = function(url, cb) {
  this.getLength(function(err, length) {
    var request
      , parsedUrl = parseUrl(url)
      , options = {
          method: 'post',
          port: parsedUrl.port || 80,
          path: parsedUrl.pathname,
          headers: this.getHeaders({'Content-Length': length}),
          host: parsedUrl.hostname
        };

    if (parsedUrl.protocol == 'https:') {
      // override default port
      if (!parsedUrl.port) options.port = 443;
      request = https.request(options);
    } else {
      request = http.request(options);
    }

    this.pipe(request);
    if (cb) {
      request.on('error', cb);
      request.on('response', cb.bind(this, null));
    }

    return request;
  }.bind(this));
};
<commit_msg>Update lib/form_data.js<commit_after>var CombinedStream = require('combined-stream');
var util = require('util');
var path = require('path');
var http = require('http');
var https = require('https');
var parseUrl = require('url').parse;
var fs = require('fs');
var mime = require('mime');
var async = require('async');

module.exports = FormData;
function FormData() {
  this._overheadLength = 0;
  this._valueLength = 0;
  this._lengthRetrievers = [];

  CombinedStream.call(this);
}
util.inherits(FormData, CombinedStream);

FormData.LINE_BREAK = '\r\n';

FormData.prototype.append = function(field, value) {
  var append = CombinedStream.prototype.append.bind(this);

  // all that streamy business can't handle numbers
  if (typeof value == 'number') value = ''+value;

  var header = this._multiPartHeader(field, value);
  var footer = this._multiPartFooter(field, value);

  append(header);
  append(value);
  append(footer);

  this._trackLength(header, value)
};

FormData.prototype._trackLength = function(header, value) {
  var valueLength = 0;
  if (Buffer.isBuffer(value)) {
    valueLength = value.length;
  } else if (typeof value === 'string') {
    valueLength = Buffer.byteLength(value);
  }

  this._valueLength += valueLength;
  this._overheadLength +=
    Buffer.byteLength(header) +
    + FormData.LINE_BREAK.length;

  // empty or ethier doesn't have path or not an http response
  if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) {
    return;
  }

  this._lengthRetrievers.push(function(next) {

    // check if it's local file
    if (value.hasOwnProperty('fd')) {
      fs.stat(value.path, function(err, stat) {
        if (err) {
          next(err);
          return;
        }

        next(null, stat.size);
      });

    // or http response
    } else if (value.hasOwnProperty('httpVersion')) {
      next(null, +value.headers['content-length']);

    // or request stream http://github.com/mikeal/request
    } else if (value.hasOwnProperty('httpModule')) {
      // wait till response come back
      value.on('response', function(response) {
        value.pause();
        next(null, +response.headers['content-length']);
      });
      value.resume();

    // something else
    } else {
      next('Unknown stream');
    }
  });
};

FormData.prototype._multiPartHeader = function(field, value) {
  var boundary = this.getBoundary();
  var header =
    '--' + boundary + FormData.LINE_BREAK +
    'Content-Disposition: form-data; name="' + field + '"';

  // fs- and request- streams have path property
  // TODO: Use request's response mime-type
  if (value.path) {
    header +=
      '; filename="' + path.basename(value.path) + '"' + FormData.LINE_BREAK +
      'Content-Type: ' + mime.lookup(value.path);

  // http response has not
  } else if (value.readable && value.hasOwnProperty('httpVersion')) {
    header +=
      '; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK +
      'Content-Type: ' + value.headers['content-type'];
  }

  header += FormData.LINE_BREAK + FormData.LINE_BREAK;
  return header;
};

FormData.prototype._multiPartFooter = function(field, value) {
  return function(next) {
    var footer = FormData.LINE_BREAK;

    var lastPart = (this._streams.length === 0);
    if (lastPart) {
      footer += this._lastBoundary();
    }

    next(footer);
  }.bind(this);
};

FormData.prototype._lastBoundary = function() {
  return '--' + this.getBoundary() + '--';
};

FormData.prototype.getHeaders = function(userHeaders) {
  var formHeaders = {
    'content-type': 'multipart/form-data; boundary=' + this.getBoundary()
  };

  for (var header in userHeaders) {
    formHeaders[header.toLowerCase()] = userHeaders[header];
  }

  return formHeaders;
}

FormData.prototype.getCustomHeaders = function(contentType) {
    contentType = contentType ? contentType : 'multipart/form-data';

    var formHeaders = {
        'content-type': contentType + '; boundary=' + this.getBoundary(),
        'content-length': this.getLengthSync()
    };

    return formHeaders;
}

FormData.prototype.getBoundary = function() {
  if (!this._boundary) {
    this._generateBoundary();
  }

  return this._boundary;
};

FormData.prototype._generateBoundary = function() {
  // This generates a 50 character boundary similar to those used by Firefox.
  // They are optimized for boyer-moore parsing.
  var boundary = '--------------------------';
  for (var i = 0; i < 24; i++) {
    boundary += Math.floor(Math.random() * 10).toString(16);
  }

  this._boundary = boundary;
};

FormData.prototype.getLengthSync = function() {
    var knownLength = this._overheadLength + this._valueLength;

    if (this._streams.length) {
        knownLength += this._lastBoundary().length;
    }

    return knownLength;
};

FormData.prototype.getLength = function(cb) {
  var knownLength = this._overheadLength + this._valueLength;

  if (this._streams.length) {
    knownLength += this._lastBoundary().length;
  }

  if (!this._lengthRetrievers.length) {
    process.nextTick(cb.bind(this, null, knownLength));
    return;
  }

  async.parallel(this._lengthRetrievers, function(err, values) {
    if (err) {
      cb(err);
      return;
    }

    values.forEach(function(length) {
      knownLength += length;
    });

    cb(null, knownLength);
  });
};

FormData.prototype.submit = function(url, cb) {
  this.getLength(function(err, length) {
    var request
      , parsedUrl = parseUrl(url)
      , options = {
          method: 'post',
          port: parsedUrl.port || 80,
          path: parsedUrl.path,
          headers: this.getHeaders({'Content-Length': length}),
          host: parsedUrl.hostname
        };

    if (parsedUrl.protocol == 'https:') {
      // override default port
      if (!parsedUrl.port) options.port = 443;
      request = https.request(options);
    } else {
      request = http.request(options);
    }

    this.pipe(request);
    if (cb) {
      request.on('error', cb);
      request.on('response', cb.bind(this, null));
    }

    return request;
  }.bind(this));
};
<|endoftext|>


<commit_before>'use strict';

var childProcess = require('child_process');
var last = require('101/last');
var fs = require('fs');
var path = require('path');
var isString = require('101/is-string');

/**
 * Driver for performing filesystem operations.
 * @module fs-transform:fs-driver
 * @author Ryan Sandor Richards
 */
module.exports = FsDriver;

/**
 * Creates a new FsDriver that performs operations relative to a given root
 * directory.
 *
 * @example
 * // Create a new filesystem driver relative to /tmp
 * var driver = new FsDriver('/tmp');
 *
 * @class
 * @param {string} root Path to use for operations.
 */
function FsDriver(root) {
  if (!root) {
    root = '';
  }

  if (last(root) === '/') {
    this.root = path.resolve(root.substr(0, root.length - 1));
  }
  else {
    this.root = path.resolve(root);
  }
}

/**
 * Makes a relative path absolute to the root directory of the driver. Returns
 * the original path if it is already absolute.
 *
 * @example
 * var driver = new FsDriver('/root/dir');
 * // returns '/root/dir/example.txt'
 * driver.absolutePath('example.txt');
 *
 * // returns '/awesome/sauce' (already an absolute path)
 * driver.absolutePath('/awesome/sauce');
 *
 * @param {string} path Path to make absolute.
 * @return {string} An absolute path relative to the root for a relative path,
 *  or just the path if it was already absolute.
 */
FsDriver.prototype.absolutePath = function (path) {
  if (!isString(path)) {
    return null;
  }
  if (path.charAt(0) === '/') {
    return path;
  }
  return this.root + '/' + path;
};

/**
 * Moves a file.
 *
 * @example
 * // Has the same behavior as `mv srcfile dstfile` on the command-line.
 * driver.move('srcfile', 'dstfile', function (err, result) {
 *   // err will only be present if something went wrong
 *   // result will contain information concerning the move
 * })
 *
 * @param {string} source Source file path.
 * @param {string} dest Destination file path.
 * @param {function} cb Callback to execute with the results of the file move.
 * @return {string} The move command to be executed.
 */
FsDriver.prototype.move = function (source, dest, cb) {
  var command = [
    'mv',
    this.absolutePath(source),
    this.absolutePath(dest)
  ].join(' ');
  childProcess.exec(command, cb);
  return command;
};

/**
 * Copies a file.
 *
 * @example
 * // Has the same behavior as `cp src dest` on the command-line.
 * driver.copy('src', 'dest', function (err, result) {
 *   // ...
 * });
 *
 * @param {string} source Source file path.
 * @param {string} dest Destination file path.
 * @param {function} cb Callback to execute with the results of the file copy.
 * @return {string} The copy command to be executed.
 */
FsDriver.prototype.copy = function (source, dest, cb) {
  var command = [
    'cp',
    this.absolutePath(source),
    this.absolutePath(dest)
  ].join(' ');
  childProcess.exec(command, cb);
  return command;
};

/**
 * Performs a text search in the root directory and recursively for all
 * subdirectories.
 *
 * @example
 * // Recursively search for all text file occurances of the word 'foo'
 * driver.grep('foo', function (err, result) {
 *  // ...
 * });
 *
 * @param {string} text Text to search for.
 * @param {function} cb Callback to execute after the search has been performed.
 * @return {string} The grep command that is to be executed.
 */
FsDriver.prototype.grep = function (text, cb) {
  var escaped = text
    .replace('\\', '\\\\\\\\')
    .replace('"', '\\\\"');
  var command = [
    'grep -rn',
    '"' + escaped + '"',
    this.root
  ].join(' ');
  childProcess.exec(command, cb);
  return command;
};

/**
 * Applies in-place text replacements.
 *
 * @example
 * // Replaces 'foo' with 'bar' on line 42 of '/path/to/file'
 * fsDriver('foo', 'bar', '/path/to/file', 42, function (err, result) {
 *   // ...
 * });
 *
 * @param {string} search Search text to replace.
 * @param {string} replace Text used for replacements.
 * @param {string} path Path to the file.
 * @param {Number} line Line number on which to perform the replacement.
 * @param {function} cb Callback to execute when the replacement has been made.
 * @return {string} The sed command that is to be executed.
 */
FsDriver.prototype.sed = function (search, replace, name, line, cb) {
  var pattern = [
    line + 's',
    search.replace(/ /g, '\\ ').replace(/\//, '\\/').replace('"', '\\"'),
    replace.replace(/ /g, '\\ ').replace(/\//, '\\/').replace('"', '\\"'),
    'g'
  ].join('/');

  var command = [
    'sed -i.last ', pattern, ' ',
    this.absolutePath(name)
  ].join('');

  childProcess.exec(command, cb);

  return command;
};

/**
 * Determines if a path exists.
 * @param {string} path Absolute or relative path to check.
 * @return {boolean} `true` if the absolute path exists, false otherwise.
 */
FsDriver.prototype.exists = function (path) {
  return fs.existsSync(this.absolutePath(path));
};
<commit_msg>Made escaping a little easier.<commit_after>'use strict';

var childProcess = require('child_process');
var last = require('101/last');
var fs = require('fs');
var path = require('path');
var isString = require('101/is-string');

/**
 * Driver for performing filesystem operations.
 * @module fs-transform:fs-driver
 * @author Ryan Sandor Richards
 */
module.exports = FsDriver;

/**
 * Creates a new FsDriver that performs operations relative to a given root
 * directory.
 *
 * @example
 * // Create a new filesystem driver relative to /tmp
 * var driver = new FsDriver('/tmp');
 *
 * @class
 * @param {string} root Path to use for operations.
 */
function FsDriver(root) {
  if (!root) {
    root = '';
  }

  if (last(root) === '/') {
    this.root = path.resolve(root.substr(0, root.length - 1));
  }
  else {
    this.root = path.resolve(root);
  }
}

/**
 * Makes a relative path absolute to the root directory of the driver. Returns
 * the original path if it is already absolute.
 *
 * @example
 * var driver = new FsDriver('/root/dir');
 * // returns '/root/dir/example.txt'
 * driver.absolutePath('example.txt');
 *
 * // returns '/awesome/sauce' (already an absolute path)
 * driver.absolutePath('/awesome/sauce');
 *
 * @param {string} path Path to make absolute.
 * @return {string} An absolute path relative to the root for a relative path,
 *  or just the path if it was already absolute.
 */
FsDriver.prototype.absolutePath = function (path) {
  if (!isString(path)) {
    return null;
  }
  if (path.charAt(0) === '/') {
    return path;
  }
  return this.root + '/' + path;
};

/**
 * Moves a file.
 *
 * @example
 * // Has the same behavior as `mv srcfile dstfile` on the command-line.
 * driver.move('srcfile', 'dstfile', function (err, result) {
 *   // err will only be present if something went wrong
 *   // result will contain information concerning the move
 * })
 *
 * @param {string} source Source file path.
 * @param {string} dest Destination file path.
 * @param {function} cb Callback to execute with the results of the file move.
 * @return {string} The move command to be executed.
 */
FsDriver.prototype.move = function (source, dest, cb) {
  var command = [
    'mv',
    this.absolutePath(source),
    this.absolutePath(dest)
  ].join(' ');
  childProcess.exec(command, cb);
  return command;
};

/**
 * Copies a file.
 *
 * @example
 * // Has the same behavior as `cp src dest` on the command-line.
 * driver.copy('src', 'dest', function (err, result) {
 *   // ...
 * });
 *
 * @param {string} source Source file path.
 * @param {string} dest Destination file path.
 * @param {function} cb Callback to execute with the results of the file copy.
 * @return {string} The copy command to be executed.
 */
FsDriver.prototype.copy = function (source, dest, cb) {
  var command = [
    'cp',
    this.absolutePath(source),
    this.absolutePath(dest)
  ].join(' ');
  childProcess.exec(command, cb);
  return command;
};

/**
 * Performs a text search in the root directory and recursively for all
 * subdirectories.
 *
 * @example
 * // Recursively search for all text file occurances of the word 'foo'
 * driver.grep('foo', function (err, result) {
 *  // ...
 * });
 *
 * @param {string} text Text to search for.
 * @param {function} cb Callback to execute after the search has been performed.
 * @return {string} The grep command that is to be executed.
 */
FsDriver.prototype.grep = function (text, cb) {
  var escaped = text
    .replace('\\', '\\\\\\\\')
    .replace('"', '\\\\"');
  var command = [
    'grep -rn',
    '"' + escaped + '"',
    this.root
  ].join(' ');
  childProcess.exec(command, cb);
  return command;
};

/**
 * Applies in-place text replacements.
 *
 * @example
 * // Replaces 'foo' with 'bar' on line 42 of '/path/to/file'
 * fsDriver('foo', 'bar', '/path/to/file', 42, function (err, result) {
 *   // ...
 * });
 *
 * @param {string} search Search text to replace.
 * @param {string} replace Text used for replacements.
 * @param {string} path Path to the file.
 * @param {Number} line Line number on which to perform the replacement.
 * @param {function} cb Callback to execute when the replacement has been made.
 * @return {string} The sed command that is to be executed.
 */
FsDriver.prototype.sed = function (search, replace, name, line, cb) {
  function escape(s) {
    return s.replace(/ /g, '\\ ')
      .replace(/\//, '\\/')
      .replace('"', '\\"');
  }

  var pattern = [
    line + 's',
    escape(search),
    escape(replace),
    'g'
  ].join('/');

  var command = [
    'sed -i.last ', pattern, ' ',
    this.absolutePath(name)
  ].join('');

  childProcess.exec(command, cb);

  return command;
};

/**
 * Determines if a path exists.
 * @param {string} path Absolute or relative path to check.
 * @return {boolean} `true` if the absolute path exists, false otherwise.
 */
FsDriver.prototype.exists = function (path) {
  return fs.existsSync(this.absolutePath(path));
};
<|endoftext|>


<commit_before>var cio = require('socket.io-client');
var nGram = require('n-gram');
var http = require('http');

//var exec = require('child_process').exec;
//function execute(command, callback) {
//    exec(command, function (error, stdout, stderr) { callback(stdout); });
//};

//execute();

function SD(values) {
    var avg = average(values);
    var squareDiffs = values.map(function (value) {
        var diff = value - avg;
        var sqrDiff = diff * diff;
        return sqrDiff;
    });
    var avgSquareDiff = average(squareDiffs);
    var stdDev = Math.sqrt(avgSquareDiff);
    return stdDev;
};
function average(data) {
    var sum = data.reduce(function (sum, value) {
        return sum + value;
    }, 0);
    var avg = sum / data.length;
    return avg;
};

var search = text.split(' ');
var searchHash = {}
for (var i = 0; i < search.length; i++) {
    searchHash[search[i]] = true;
};

var inputWords = search;
var wordFreq = {};
var item = { total: 0, totalitemcount: 0, totalitemkey: 0, totalitem: {}, totalitemkeys: [] };
for (var i = 0; i < inputWords.length; i++) {
    if (searchHash[inputWords[i]]) {
        wordFreq[inputWords[i]] = wordFreq[inputWords[i]] || 0;
        wordFreq[inputWords[i]]++;
        item.total++;
    };
};
Object.keys(wordFreq).forEach(function (key, index) {
    item.totalitem[index] = { text: key, count: wordFreq[key] };
    item.totalitemcount = index + 1;
    item.totalitemkey += wordFreq[key];
    item.totalitemkeys.push(wordFreq[key]);
    //console.log(key + ": " + wordFreq[key]);
});
console.log("Total matches: " + item.total);

var avg = item.totalitemkey / item.totalitemcount;

console.log("Total item: " + avg);
console.log("SD: " + SD(item.totalitemkeys));

//var natural = require('natural');
//var NGrams = natural.NGrams;
//var NGramsZH = natural.NGramsZH;

//var natural = require('natural'),
//    TfIdf = natural.TfIdf,
//    tfidf = new TfIdf();

//tfidf.addDocument(['document', 'about', 'node']);
//tfidf.addDocument(['document', 'about', 'ruby']);
//tfidf.addDocument(['document', 'about', 'ruby', 'node']);
//tfidf.addDocument(['document', 'about', 'node', 'node', 'examples']);

//tfidf.listTerms(0).forEach(function (item) {
//    console.log(item.term + ': ' + item.tfidf);
//});

//var text = 'Orz how are you 中文測試';

//function cheEng(acttype) {
//    if (acttype == 0) {
//        return text.replace(/[ -~]/g, "");
//    };
//    if (acttype == 1) {
//        return text.replace(/[^a-zA-Z0-9_ ]/g, "");
//    };
//};
//console.log(cheEng(1));

//console.log(NGrams.ngrams('Orz how are you 中文測試', 2));
//console.log(NGramsZH.ngrams('Orz how are you 中文測試', 2));

//var socket = cio('https://10.10.23.55/_news', { autoConnect: true, secure: true });

//var fs = require('fs');
//var dir = '../NEWS/';

//if (!fs.existsSync(dir)) {
//    fs.mkdirSync(dir);
//};
//socket.on('connect', function () {
//    console.log('Start');
//});

//socket.on('BM', function (datas) {
//    var id = datas.id;
//    var data = datas.data;

//    var option = {
//        hostname: '10.10.23.31',
//        port: 9200,
//        path: '/bigdata/_analyze?pretty=1&analyzer=ngram_analyzer',
//        method: 'POST'
//    };

//    var req = http.request(option, function (res) {
//        res.setEncoding('utf8');
//        console.log('BM Receive Start -- ' + id);

//        var dataz = '', dataq, datal = 0;

//        res.on('data', function (chunk) {
//            dataz += chunk;
//        });
//        res.on('end', function () {
//            var RightNow = new Date();
//            var uuid = RightNow.getFullYear() + "-" + RightNow.getMonth() + 1 + "-" + RightNow.getDate() + " " + RightNow.getHours() + "-" + RightNow.getMinutes() + "-" + id;
//            var file = dir + uuid + '.txt';

//            dataq = JSON.parse(dataz).tokens;
//            datal = dataq.length;
//            var ndata = '';
//            for (var i = 0; i < datal; i++) {
//                ndata += dataq[i].token + '\n';
//            };
//            var options = { encoding: "utf8" };
//            fs.writeFile(file, data + "\r\n" + "//" + "\r\n" + ndata, options, function (error) {
//                if (error) {
//                    console.log('Error');
//                } else {
//                    console.log('OK');
//                };
//            });
//        });
//    });
//    req.on('error', function (e) {
//        console.log('problem with request: ' + e.message);
//    });
//    req.write(data);
//    req.end();

//    //var ndata = '';
//    //for (var i = 2; i < 7; i++) {
//    //    ndata += '\n' + nGram(i)(data).join('\n');
//    //    if (i == 6) {
//    //        fs.writeFileSync(file, data + "\r\n" + "//" + "\r\n" + ndata, options, function (error) {
//    //            if (error) {
//    //                console.log('Error');
//    //            } else {
//    //                console.log('OK');
//    //            };
//    //        });
//    //    };
//    //};
//});
//socket.on('PM', function () {
//    console.log('PM Receive Start');

//});<commit_msg>Fix performance<commit_after>var cio = require('socket.io-client');
var http = require('http');

function SD(a) {
    var r = { mean: 0, variance: 0, deviation: 0 }, t = a.length;
    for (var m, s = 0, l = t; l--; s += a[l]);
    for (m = r.mean = s / t, l = t, s = 0; l--; s += Math.pow(a[l] - m, 2));
    return r.deviation = Math.sqrt(r.variance = s / t), r;
};

var search = text.split(' ');
var searchHash = {}
for (var i = 0; i < search.length; i++) {
    searchHash[search[i]] = true;
};

var inputWords = search;
var wordFreq = {};
var item = { total: 0, totalitemcount: 0, totalitemkey: 0, totalitem: {}, totalitemkeys: [] };
for (var i = 0; i < inputWords.length; i++) {
    if (searchHash[inputWords[i]]) {
        wordFreq[inputWords[i]] = wordFreq[inputWords[i]] || 0;
        wordFreq[inputWords[i]]++;
        item.total++;
    };
};
Object.keys(wordFreq).forEach(function (key, index) {
    item.totalitem[index] = { text: key, count: wordFreq[key] };
    item.totalitemcount = index + 1;
    item.totalitemkey += wordFreq[key];
    item.totalitemkeys.push(wordFreq[key]);
    //console.log(key + ": " + wordFreq[key]);
});
console.log("Total matches: " + item.total);

var avg = item.totalitemkey / item.totalitemcount;

console.log("Total item: " + avg);
console.log("SD: " + JSON.stringify(SD(item.totalitemkeys)));<|endoftext|>


<commit_before>var whois = require('whois');

/**
 * Copy the default response object
 * and return the clone
 * @return {Object}
 */
var createResponseObject = function (){
  var template = {
    'status': 'success'
  };

  return Object.assign({}, template);
};

/**
 * Check if specific TLD is allowed via
 * configuration file
 * @param  {Object}  $config
 * @param  {String}  $tld
 * @return {Boolean}
 */
var isTldAllowed = function (config, tld){
  if (config.tlds.indexOf(tld) === -1) {
    return false;
  } else {
    return true;
  }
};

/**
 * Whois domain name
 * @param  {String}   domain
 * @return {Callback}
 */
var whoisDomain = function (domain, callback){
  whois.lookup(domain, {"verbose": true}, function(err, data) {
    if (err) {
      return callback(false);
    }
    callback(data);
  })
};

/**
 * Check availability of a domain using the configured
 * servers.json optiona
 * @param  {String}   domain
 * @param  {Object}   whoisServers
 * @return {Callback}
 */
var checkAvailability = function (domain, whoisServers, callback){
  // Split domain
  var domainParts = domain.split('.'),
      options;

  // Create response object
  // Create new response object from template
  var responseObject = createResponseObject();

  // Load TLD specifc configuration from servers.json
  if (whoisServers[domainParts[1]]){
    options = whoisServers[domainParts[1]];
  } else {
    responseObject.status = 'error';
    responseObject.message = 'No specific TLD configuration found in servers.json';
    return callback(responseObject);
  }

  // Whois domain name
  whois.lookup(domain, options, function(err, data) {
    if (err) {
      responseObject.status = 'error';
      responseObject.message = 'Error calling whois()';
      return callback(responseObject);
    }

    // If configured "free" indicator is not found, return false
    if (data.indexOf(options.freeIndicator) === -1) {
      responseObject.data = false;
    } else {
      responseObject.data = true;
    }

    return callback(responseObject);
  })
};

module.exports = {
  createResponseObject: createResponseObject,
  isTldAllowed: isTldAllowed,
  whoisDomain: whoisDomain,
  checkAvailability: checkAvailability
};
<commit_msg>Actually use autodns WhoisProxy server<commit_after>var whois = require('whois');

/**
 * Copy the default response object
 * and return the clone
 * @return {Object}
 */
var createResponseObject = function (){
  var template = {
    'status': 'success'
  };

  return Object.assign({}, template);
};

/**
 * Check if specific TLD is allowed via
 * configuration file
 * @param  {Object}  $config
 * @param  {String}  $tld
 * @return {Boolean}
 */
var isTldAllowed = function (config, tld){
  if (config.tlds.indexOf(tld) === -1) {
    return false;
  } else {
    return true;
  }
};

/**
 * Whois domain name
 * @param  {String}   domain
 * @return {Callback}
 */
var whoisDomain = function (domain, callback){
  var options = {
    "verbose": true,
    "server": 'whois.autodns3.de'
  };

  whois.lookup(domain, options, function(err, data) {
    if (err) {
      return callback(false);
    }
    callback(data);
  })
};

/**
 * Check availability of a domain using the configured
 * servers.json optiona
 * @param  {String}   domain
 * @param  {Object}   whoisServers
 * @return {Callback}
 */
var checkAvailability = function (domain, whoisServers, callback){
  // Split domain
  var domainParts = domain.split('.'),
      options;

  // Create response object
  // Create new response object from template
  var responseObject = createResponseObject();

  // Load TLD specifc configuration from servers.json
  if (whoisServers[domainParts[1]]){
    options = whoisServers[domainParts[1]];
  } else {
    responseObject.status = 'error';
    responseObject.message = 'No specific TLD configuration found in servers.json';
    return callback(responseObject);
  }

  // Whois domain name
  whois.lookup(domain, options, function(err, data) {
    if (err) {
      responseObject.status = 'error';
      responseObject.message = 'Error calling whois()';
      return callback(responseObject);
    }

    // If configured "free" indicator is not found, return false
    if (data.indexOf(options.freeIndicator) === -1) {
      responseObject.data = false;
    } else {
      responseObject.data = true;
    }

    return callback(responseObject);
  })
};

module.exports = {
  createResponseObject: createResponseObject,
  isTldAllowed: isTldAllowed,
  whoisDomain: whoisDomain,
  checkAvailability: checkAvailability
};
<|endoftext|>


<commit_before>var _ = require('lodash');
var falafel = require('falafel');
var path = require('path');
var SourceMapGenerator = require('source-map').SourceMapGenerator;

var Generator = module.exports = function (file) {
  this.file = file;
  this.ast = file.ast;
  this.options = file.options;
  this.usedImports = [];
  this._importIDCache = [];
  this._fnIds = 0;
};

Generator.prototype = {
  _resolveScopeAndGenerateCode: function (code, attributes, includeValues, canPrecompile) {
    var localVariables = _.clone(attributes);
    var outsideVariables = false;

    var res = falafel(code, _.bind(function (node) {
      if (node.type === "Identifier") {
       if (node.parent && (
          node.parent.type === 'VariableDeclarator' || node.parent.type === 'FunctionDeclaration'
         )) {
         localVariables.push(node.name);
       } else if (localVariables.indexOf(node.name) === -1) {
         outsideVariables = true;
         if (node.parent.type !== 'MemberExpression' || node.parent.object === node) {
            variable = node.name;
            if (node.parent.type === 'MemberExpression') {
              variable += "." + node.parent.property.name;
            }
            var globalScope = this._resolveGlobalVariable(variable);
            if (includeValues) {
              node.update("((typeof values." + node.name + " !== 'undefined') ? values." + node.name + " : " + globalScope + ")");
            } else {
              node.update(globalScope)
            }
         }
       }
      }
    }, this));

    if (canPrecompile && !outsideVariables) {
      // TODO Eval is evil get rid of it by constructing functions etc
      return eval(res);
    }

    return res;
  },
  _generateDeclaration: function (declaration, includeValues) {
    switch(declaration.type) {
      case "NUMBER":
      case "STRING":
        return declaration.value;
        break;
      case "CODE":
        return this._resolveScopeAndGenerateCode(declaration.value, [], includeValues, true);
        break;
      case "FUNC_DEF":
        var code = "function fn" + (++this._fnIds) + "(" + declaration.attributes.join(', ') + ") {";
        code += declaration.value;
        code += "}";
        return this._resolveScopeAndGenerateCode(code, declaration.attributes, false, false);
      case "FUNC":
        var globalScope;
        var fn;
        var parts;
        var property;
        var name;

        if (declaration.name.indexOf('.') === -1) {
          globalScope = this._resolveGlobalVariable(declaration.name);
          fn = (includeValues ? (("typeof values." + declaration.name + " !== 'undefined' && values." +
                 declaration.name) + " || ") : '') + globalScope;
        } else {
          parts = declaration.name.split('.');
          name = parts.shift();
          globalScope = this._resolveGlobalVariable(declaration.name);
          property = parts.join('.');

          fn = (includeValues ? (("typeof values." + name + " !== 'undefined' ? values." +
                 name) + " : ") : '') + globalScope;

          fn = "(" + fn + ")." + property;
        }

        return "(" + fn + ")(" +
               _.map(declaration.attributes, function (attr) {
                 return this._generateDeclaration(attr, includeValues);
               }, this).join(', ') +
               ")";

      default:
        return '"' + declaration.value + '"';
        break;
    }
  },
  _getImportIDForFile: function (file) {
    var indx;

    if ((indx = this._importIDCache.indexOf(file.options.absolute_path)) === -1) {
      this._importIDCache.push(file.options.absolute_path);
      indx = this._importIDCache.length - 1;
    }

    return "$im_" + indx;
  },
  _resolveGlobalVariable: function (variable, stringNull) {
    var namedScope, file, namedImport;

    if (variable.indexOf('.') === -1) {
      file = this.file.variableScope[variable];

      if (!file) {
        file = this.file.functionScope[variable];
      }
    } else {
      var parts = variable.split('.');
      namedImport = parts.shift();
      var namedImportVariable = parts.join('.');

      if (!file) {
        file = this.file.namedVariableScope[namedImport];
        if (file && (file = file.scope[namedImportVariable])) {
          namedScope = true;
          // TODO trough error if not exposed by import
        }
      }

      if (!file) {
        file = this.file.namedFunctionScope[namedImport];
        if (file && (file = file.scope[namedImportVariable])) {
          namedScope = true;
          // TODO trough error if not exposed by import
        }
      }
    }
    if (file == this.file) {
      // variable is local
      return "defines." + variable;
    } else if (file) {
      if (this.usedImports.indexOf(file) === -1) {
        this.usedImports.push(file);
      }
      if (namedScope) {
        // Find a better way to handle named scope
        return this._getImportIDForFile(file) + ".d";
      } else {
        return this._getImportIDForFile(file) + ".d." + variable;
      }
    }

    return variable;
  },
  _resolveGlobalRule: function (selector) {
    var file, parts, details;

    if (selector.indexOf('.') === -1) {
      file = this.file.ruleScope[selector];
      if (file === this.file) {
        return "rules." + selector;
      } else if (file) {
        if (this.usedImports.indexOf(file) === -1) {
          this.usedImports.push(file);
        }
        return this._getImportIDForFile(file) + ".r." + selector;
      }
    } else {
      parts = selector.split('.');
      importName = parts.shift();
      selector = parts.join('.');

      details = this.file.namedRuleScope[importName];

      if (details && (file = details.scope[selector])) {
        if (this.usedImports.indexOf(file) === -1) {
          this.usedImports.push(file);
        }
        return this._getImportIDForFile(file) + ".r." + selector;
      }
    }

    // TODO better error messages
    throw new Error("Could not find rule " + selector);
  },
  _generateRule: function (rule) {
    this.lines.push(["rules." + rule.selector + " = function (values) {", rule.line]);

    if (rule.parents.length === 0) {
      this.lines.push(["var style = {};"]);
    } else {
      var parentSelector = this._resolveGlobalRule(rule.parents[0]);
      if (!parentSelector) {
        // TODO trough proper error
        throw new Error("Can't extend " + rule.parents[0] + " (Rule not found)");
      }
      this.lines.push(["var style = " + parentSelector + "(values);"]);
    }

    _.each(rule.declarations, function (declaration, key) {
      var style = "style." + key + " = " + this._generateDeclaration(declaration, true) + ";"
      this.lines = this.lines.concat(_.map(style.split('\n'), function (line, indx) {
        return [line, declaration.line + indx];
      }));
    }, this);

    this.lines.push(["return style;"]);
    this.lines.push(["};"]);
  },
  _generateRules: function () {
    var rules = this.file.ast.rules || [];

    this.lines.push(["var rules = {};"]);

    _.each(rules, this._generateRule, this);
  },
  _generateFactory: function () {
    var rules = this.file.ast.rules || {};

    var main = rules.main;
    if (!main && _.keys(rules).length > 0) {
      main = rules[_.keys(rules)[0]];
    }

    this.lines.push(["var factory = function (name, values) {"]);

    this.lines.push(["if (typeof name !== 'string') { values = name; name = null; }"]);
    this.lines.push(["values = values || {}"]);
    this.lines.push(["name = name || '" + (main ? main.selector : '') + "'"]);
    this.lines.push(["return rules[name] ? rules[name](values) : {};"]);
    this.lines.push(["};"]);

    this.lines.push(["factory.r = rules;"]);
    this.lines.push(["factory.d = defines;"]);
    this.lines.push(["module.exports = factory;"]);
  },
  _generateDefines: function () {
    var defines = _.extend({}, this.file.ast.variables, this.file.ast.functions);

    this.lines.push(["var defines = {};"]);

    _.each(defines, function (type, define) {
      var defineCode = "defines['" + define + "'] = " + this._generateDeclaration(type, false) + ";";
      this.lines = this.lines.concat(_.map(defineCode.split('\n'), function (line, indx) {
        return [line, type.line + indx];
      }));
    }, this);
  },
  _generateImports: function () {
    var dirName = path.dirname(this.options.absolute_path);
    this.lines = _.map(this.usedImports, function (file) {
      var relativePath = path.relative(dirName, file.options.absolute_path);

      // TODO make windows compitable
      if (relativePath[0] !== '.' && relativePath[0] !== '/') {
        relativePath = './' + relativePath;
      }
      // TODO resolve correct line for import
      return ["var " + this._getImportIDForFile(file) +" = require('" + relativePath + "');", 0];
    }, this).concat(this.lines);
  },
  _generateCodeFromLines: function () {
    var map;
    if (this.options.sourceMap) {
      this._map = map = new SourceMapGenerator({
        file: this.options.path
      });
    }
    return _.map(this.lines, function (line, i) {
      if (this.options.sourceMap && line.length > 1) {
        map.addMapping({
          generated: {
            line: i+1,
            column: 1
          },
          source: this.options.path,
          original: {
            line: line[1] + 1,
            column: 1
          }
        });
      }

      return line[0];
    }, this).join('\n');
  },
  _generateCode: function () {

    this.lines = [];
    this._generateDefines();
    this._generateRules();
    this._generateFactory();
    this._generateImports();

    return this._generateCodeFromLines();
  },
  getJavaScript: function () {
    this._javascript = this._javascript || this._generateCode();
    return this._javascript;
  },
  getSourceMap: function () {
    if (this.options.sourceMap && !this._map) {
      this._javascript = this._javascript || this._generateCode();
    }

    return this._map;
  }
};
<commit_msg>Simplify function processing<commit_after>var _ = require('lodash');
var falafel = require('falafel');
var path = require('path');
var SourceMapGenerator = require('source-map').SourceMapGenerator;

var Generator = module.exports = function (file) {
  this.file = file;
  this.ast = file.ast;
  this.options = file.options;
  this.usedImports = [];
  this._importIDCache = [];
  this._fnIds = 0;
};

Generator.prototype = {
  _resolveScopeAndGenerateCode: function (code, attributes, includeValues, canPrecompile) {
    var localVariables = _.clone(attributes);
    var outsideVariables = false;

    var res = falafel(code, _.bind(function (node) {
      if (node.type === "Identifier") {
       if (node.parent && (
          node.parent.type === 'VariableDeclarator' || node.parent.type === 'FunctionDeclaration'
         )) {
         localVariables.push(node.name);
       } else if (localVariables.indexOf(node.name) === -1) {
         outsideVariables = true;
         if (node.parent.type !== 'MemberExpression' || node.parent.object === node) {
            variable = node.name;
            if (node.parent.type === 'MemberExpression') {
              variable += "." + node.parent.property.name;
            }
            var globalScope = this._resolveGlobalVariable(variable);
            if (includeValues) {
              node.update("((typeof values." + node.name + " !== 'undefined') ? values." + node.name + " : " + globalScope + ")");
            } else {
              node.update(globalScope)
            }
         }
       }
      }
    }, this));

    if (canPrecompile && !outsideVariables) {
      // TODO Eval is evil get rid of it by constructing functions etc
      return eval(res);
    }

    return res;
  },
  _generateDeclaration: function (declaration, includeValues) {
    switch(declaration.type) {
      case "NUMBER":
      case "STRING":
        return declaration.value;
        break;
      case "CODE":
        return this._resolveScopeAndGenerateCode(declaration.value, [], includeValues, true);
        break;
      case "FUNC_DEF":
        var code = "function fn" + (++this._fnIds) + "(" + declaration.attributes.join(', ') + ") {";
        code += declaration.value;
        code += "}";
        return this._resolveScopeAndGenerateCode(code, declaration.attributes, false, false);
      case "FUNC":
        var code = declaration.name + "(" +
          _.map(declaration.attributes, function (attr) {
            return this._generateDeclaration(attr, includeValues);
          }, this).join(', ') +
          ")";
        return this._resolveScopeAndGenerateCode(code, [], includeValues, true);
      default:
        return '"' + declaration.value + '"';
        break;
    }
  },
  _getImportIDForFile: function (file) {
    var indx;

    if ((indx = this._importIDCache.indexOf(file.options.absolute_path)) === -1) {
      this._importIDCache.push(file.options.absolute_path);
      indx = this._importIDCache.length - 1;
    }

    return "$im_" + indx;
  },
  _resolveGlobalVariable: function (variable, stringNull) {
    var namedScope, file, namedImport;

    if (variable.indexOf('.') === -1) {
      file = this.file.variableScope[variable];

      if (!file) {
        file = this.file.functionScope[variable];
      }
    } else {
      var parts = variable.split('.');
      namedImport = parts.shift();
      var namedImportVariable = parts.join('.');

      if (!file) {
        file = this.file.namedVariableScope[namedImport];
        if (file && (file = file.scope[namedImportVariable])) {
          namedScope = true;
          // TODO trough error if not exposed by import
        }
      }

      if (!file) {
        file = this.file.namedFunctionScope[namedImport];
        if (file && (file = file.scope[namedImportVariable])) {
          namedScope = true;
          // TODO trough error if not exposed by import
        }
      }
    }
    if (file == this.file) {
      // variable is local
      return "defines." + variable;
    } else if (file) {
      if (this.usedImports.indexOf(file) === -1) {
        this.usedImports.push(file);
      }
      if (namedScope) {
        // Find a better way to handle named scope
        return this._getImportIDForFile(file) + ".d";
      } else {
        return this._getImportIDForFile(file) + ".d." + variable;
      }
    }

    return variable;
  },
  _resolveGlobalRule: function (selector) {
    var file, parts, details;

    if (selector.indexOf('.') === -1) {
      file = this.file.ruleScope[selector];
      if (file === this.file) {
        return "rules." + selector;
      } else if (file) {
        if (this.usedImports.indexOf(file) === -1) {
          this.usedImports.push(file);
        }
        return this._getImportIDForFile(file) + ".r." + selector;
      }
    } else {
      parts = selector.split('.');
      importName = parts.shift();
      selector = parts.join('.');

      details = this.file.namedRuleScope[importName];

      if (details && (file = details.scope[selector])) {
        if (this.usedImports.indexOf(file) === -1) {
          this.usedImports.push(file);
        }
        return this._getImportIDForFile(file) + ".r." + selector;
      }
    }

    // TODO better error messages
    throw new Error("Could not find rule " + selector);
  },
  _generateRule: function (rule) {
    this.lines.push(["rules." + rule.selector + " = function (values) {", rule.line]);

    if (rule.parents.length === 0) {
      this.lines.push(["var style = {};"]);
    } else {
      var parentSelector = this._resolveGlobalRule(rule.parents[0]);
      if (!parentSelector) {
        // TODO trough proper error
        throw new Error("Can't extend " + rule.parents[0] + " (Rule not found)");
      }
      this.lines.push(["var style = " + parentSelector + "(values);"]);
    }

    _.each(rule.declarations, function (declaration, key) {
      var style = "style." + key + " = " + this._generateDeclaration(declaration, true) + ";"
      this.lines = this.lines.concat(_.map(style.split('\n'), function (line, indx) {
        return [line, declaration.line + indx];
      }));
    }, this);

    this.lines.push(["return style;"]);
    this.lines.push(["};"]);
  },
  _generateRules: function () {
    var rules = this.file.ast.rules || [];

    this.lines.push(["var rules = {};"]);

    _.each(rules, this._generateRule, this);
  },
  _generateFactory: function () {
    var rules = this.file.ast.rules || {};

    var main = rules.main;
    if (!main && _.keys(rules).length > 0) {
      main = rules[_.keys(rules)[0]];
    }

    this.lines.push(["var factory = function (name, values) {"]);

    this.lines.push(["if (typeof name !== 'string') { values = name; name = null; }"]);
    this.lines.push(["values = values || {}"]);
    this.lines.push(["name = name || '" + (main ? main.selector : '') + "'"]);
    this.lines.push(["return rules[name] ? rules[name](values) : {};"]);
    this.lines.push(["};"]);

    this.lines.push(["factory.r = rules;"]);
    this.lines.push(["factory.d = defines;"]);
    this.lines.push(["module.exports = factory;"]);
  },
  _generateDefines: function () {
    var defines = _.extend({}, this.file.ast.variables, this.file.ast.functions);

    this.lines.push(["var defines = {};"]);

    _.each(defines, function (type, define) {
      var defineCode = "defines['" + define + "'] = " + this._generateDeclaration(type, false) + ";";
      this.lines = this.lines.concat(_.map(defineCode.split('\n'), function (line, indx) {
        return [line, type.line + indx];
      }));
    }, this);
  },
  _generateImports: function () {
    var dirName = path.dirname(this.options.absolute_path);
    this.lines = _.map(this.usedImports, function (file) {
      var relativePath = path.relative(dirName, file.options.absolute_path);

      // TODO make windows compitable
      if (relativePath[0] !== '.' && relativePath[0] !== '/') {
        relativePath = './' + relativePath;
      }
      // TODO resolve correct line for import
      return ["var " + this._getImportIDForFile(file) +" = require('" + relativePath + "');", 0];
    }, this).concat(this.lines);
  },
  _generateCodeFromLines: function () {
    var map;
    if (this.options.sourceMap) {
      this._map = map = new SourceMapGenerator({
        file: this.options.path
      });
    }
    return _.map(this.lines, function (line, i) {
      if (this.options.sourceMap && line.length > 1) {
        map.addMapping({
          generated: {
            line: i+1,
            column: 1
          },
          source: this.options.path,
          original: {
            line: line[1] + 1,
            column: 1
          }
        });
      }

      return line[0];
    }, this).join('\n');
  },
  _generateCode: function () {

    this.lines = [];
    this._generateDefines();
    this._generateRules();
    this._generateFactory();
    this._generateImports();

    return this._generateCodeFromLines();
  },
  getJavaScript: function () {
    this._javascript = this._javascript || this._generateCode();
    return this._javascript;
  },
  getSourceMap: function () {
    if (this.options.sourceMap && !this._map) {
      this._javascript = this._javascript || this._generateCode();
    }

    return this._map;
  }
};
<|endoftext|>