<commit_before>var _ = require('lodash')

var initializeBuffer = function() {
    buffer = []
    bufferPush = _.bind(buffer.push, buffer)
}

// TODO properly translate all binops and unops:
//   ones that GML has that JS doesn't have
//   ones with different behavior that need to be implemented differently
//   DIV, MOD, ^^, bitwise ops
//   how does GML do type coercion (42 + "hello world")?  Do I need to emulate that behavior?
var generateExpression = function(astNode) {
    switch(astNode.type) {
        case 'identifier':
            bufferPush(astNode.val)
            // TODO will this every need to be enclosed in parentheses?
            // How should I be handling this in the general case?
            break
        case 'binop':
            bufferPush('(')
            generateExpression(astNode.exp1)
            bufferPush(' ' + astNode.op + ' ')
            generateExpression(astNode.exp2)
            bufferPush(')')
            break
        case 'unop':
            bufferPush('(')
            bufferPush(astNode.op)
            generateExpression(astNode.exp1)
            bufferPush(')')
            break
        case 'number':
            bufferPush('(')
            bufferPush(astNode.val.toString())
            // TODO does toString always produce valid Javascript that will create the exact same number?
            bufferPush(')')
            break
        case 'string':
            bufferPush('(')
            bufferPush(JSON.stringify(astNode.val))
            // TODO this fails in a select few corner cases.  Use something better,
            // perhaps stolen from the Jade source code
            bufferPush(')')
            break
        default:
            throw new Error('Unknown expression type: "' + astNode.type + '"')
    }
}

var generateStatement = function(astNode) {
    switch(astNode.type) {
        case 'var':
            bufferPush('var ')
            bufferPush.apply(astNode.list.join(', '))
            bufferPush(';\n')
            break
        case 'set':
            generateExpression(astNode.lval)
            bufferPush(' = ')
            generateExpression(astNode.rval)
            bufferPush(';\n')
            break
        default:
            throw new Error('Unknown statement type: "' + astNode.type + '"')
    }
}

var generateCodeFromRootNode = function(astNode) {
    switch(astNode.type) {
        case 'statements':
            _.each(astNode.list, generateStatement)
            break
        default:
            throw new Error('Unknown root node type: "' + astNode.type + '"')

    }
}

var compile = module.exports = function(ast) {
    initializeBuffer()
    generateCodeFromRootNode(ast)
    return _.flatten(buffer).join('')
}<commit_msg>Lots more compiler features<commit_after>var _ = require('lodash')

var buffer
  , print
  , indentationLevel

var initializeCompiler = function() {
    buffer = []
    print = _.bind(buffer.push, buffer)
    indentationLevel = 0
}

var indent = function() {
    indentationLevel++
}

var outdent = function() {
    indentationLevel--
    if(indentationLevel < 0) {
        throw new Error('Tried to outdent too far.')
    }
}

var printIndent = function() {
    // TODO create customizable indentation level
    // TODO make this faster?
    _.times(indentationLevel, function() {
        print('    ')
    })
}

// TODO properly translate all binops and unops:
//   ones that GML has that JS doesn't have
//   ones with different behavior that need to be implemented differently
//   DIV, MOD, ^^, bitwise ops
//   how does GML do type coercion (42 + "hello world")?  Do I need to emulate that behavior?
var generateExpression = function(astNode) {
    switch(astNode.type) {
        case 'identifier':
            print(astNode.name)
            // TODO will this ever need to be enclosed in parentheses?
            // How should I be handling this in the general case?
            break
        case 'binop':
            print('(')
            // special-case the dot operator
            if(astNode.op === '.') {
                print('resolveObjectBeforeDot(')
                generateExpression(astNode.expr1)
                print(').')
                generateExpression(astNode.expr2)
            } else { // all other operators
                generateExpression(astNode.expr1)
                print(' ' + astNode.op + ' ')
                generateExpression(astNode.expr2)
            }
            print(')')
            break
        case 'unop':
            print('(')
            print(astNode.op)
            generateExpression(astNode.expr)
            print(')')
            break
        case 'number':
            print('(')
            print(astNode.val.toString())
            // TODO does toString always produce valid Javascript that will create the exact same number?
            print(')')
            break
        case 'string':
            print('(')
            print(JSON.stringify(astNode.val))
            // TODO this fails in a select few corner cases.  Use something better,
            // perhaps stolen from the Jade source code
            print(')')
            break
        case 'index2d':
            // TODO this needs a lot of work
            // What do we do when index values aren't numbers?  Aren't integers?
            // What about when the array isn't initialized or the target isn't an array?
            print('(')
            generateExpression(astNode.expr)
            print(')[(')
            generateExpression(astNode.indexexpr1)
            print('),(')
            generateExpression(astNode.indexexpr2)
            print(')]')
            break
        case 'funccall':
            print('(')
            generateExpression(astNode.expr)
            print(')((')
            _.each(astNode.args, function(arg, i, args) {
                generateExpression(arg)
                if(i < args.length - 1) {
                    print('), (')
                }
            })
            print('))')
            break
        default:
            throw new Error('Unknown expression type: "' + astNode.type + '"')
    }
}

var generateStatement = function(astNode, omitTerminator, omitIndentation) {
    if(arguments.length < 2) omitTerminator = false
    switch(astNode.type) {
        case 'var':
            omitIndentation || printIndent()
            print('var ')
            print(astNode.list.join(', '))
            break
        case 'assign':
            omitIndentation || printIndent()
            generateExpression(astNode.lval)
            print(' = ')
            generateExpression(astNode.rval)
            break
        case 'scriptdef':
            omitIndentation || printIndent()
            print('function ')
            print(astNode.name)
            print('(')
            print(astNode.args.join(', '))
            print(') {\n')
            indent()
            // TODO this part of the AST doesn't seem quite right, suggesting there are
            // possibilities I'm not aware of.
            // These sanity checks will reject anything unexpected.
            /*if(!(_.isObject(astNode.stmts) && _(_.keys(astNode.stmts).sort()).isEqual(['list', 'type']) && astNode.stmts.type === 'statements' && _.isArray(astNode.stmts.list))) {
                throw new Error('Failed sanity checks on stmts!')
            }
            _.each(astNode.stmts.list, generateStatement)*/
            generateStatement(astNode.stmts)
            outdent()
            omitIndentation || printIndent()
            print('}')
            break
        case 'switch':
            omitIndentation || printIndent()
            print('switch(')
            generateExpression(astNode.expr)
            print(') {\n')
            indent()
            _.each(astNode.cases, function(caseNode) {
                generateCase(caseNode)
            })
            outdent()
            omitIndentation || printIndent()
            print('}')
            break
        case 'for':
            omitIndentation || printIndent()
            print('for(')
            generateStatement(astNode.initstmt, true, true)
            print('; ')
            generateExpression(astNode.contexpr)
            print('; ')
            generateStatement(astNode.stepstmt, true, true)
            print(') {\n')
            indent()
            // TODO I bet there are some scoping issues I'm not dealing with correctly.
            generateStatement(astNode.stmt)
            outdent()
            omitIndentation || printIndent()
            print('}')
            break
        case 'cmpassign':
            // Rewrite the cmpassign into a simpler binop and assign combo.
            // E.g. a += 1 becomes a = a + 1
            // TODO this will produce somewhat uglier code.  Maybe pretty it up later.
            // TODO what if the lval contains a function call?  Will it execute twice?
            generateStatement({
                type: 'assign',
                lval: astNode.lval,
                rval: {
                    type: 'binop',
                    op: astNode.op,
                    expr1: astNode.lval,
                    expr2: astNode.rval
                }
            }, omitTerminator, omitIndentation)
            break
        case 'ifelse':
            omitIndentation || printIndent()
            print('if(')
            generateExpression(astNode.expr)
            print(') {\n')
            indent()
            generateStatement(astNode.stmt1)
            outdent()
            omitIndentation || printIndent()
            print('} else {\n')
            indent()
            generateStatement(astNode.stmt2)
            outdent()
            omitIndentation || printIndent()
            print('}')
            break
        case 'break':
            omitIndentation || printIndent()
            print('break')
            // TODO are break semantics ever different in Angl than they are in JS?
            break
        case 'statements':
            _.each(astNode.list, function(statement) {
                generateStatement(statement)
            })
            break
        case 'funccall':
            // Delegate to the expression generator
            omitIndentation || printIndent()
            generateExpression(astNode)
            break
        case 'with':
            // TODO I DONT WANNA IMPLEMENT THIS WAAAAAH
            // Also it requires some sort of runtime that can find all instances of
            // a given object type to iterate over.
            // For now, I'm emitting a comment that explains code has been omitted.
            omitIndentation || printIndent()
            print('/* with(){} block has been omitted.  Not implemented yet.*/')
            break
        case 'nop':
            // No-ops don't do anything.  I'm assuming they never trigger any behavior by
            // "seperating" adjacent statements.
            break
        default:
            throw new Error('Unknown statement type: "' + astNode.type + '"')
    }
    // Statements are terminated by a semicolon and a newline
    // except for a few exceptions.
    // Also, in certain contexts we want to omit this termination
    // (e.g., initializer statement of a for loop)
    if(!_.contains(['nop', 'statements'], astNode.type) && !omitTerminator) {
        print(';\n')
    }
}

var generateCase = function(astNode) {
    switch(astNode.type) {
        case 'case':
            printIndent()
            print('case (')
            generateExpression(astNode.expr)
            print('):\n')
            indent()
            generateStatement(astNode.stmts)
            outdent()
            break
        case 'defaultcase':
            printIndent()
            print('default:\n')
            indent()
            generateStatement(astNode.stmts)
            outdent()
            break
        default:
            throw new Error('Unknown case type: "' + astNode.type + '"')
    }
}

var generateTopNode = function(astNode) {
    switch(astNode.type) {
        case 'statements':
            // delegate to the statement generator
            generateStatement(astNode)
            break
        default:
            throw new Error('Unknown root node type: "' + astNode.type + '"')
    }
}

var compile = module.exports = function(ast) {
    initializeCompiler()
    generateTopNode(ast)
    return _.flatten(buffer).join('')
}
<|endoftext|>


<commit_before>/**
 * @author: Dennis Hernández
 * @webSite: http://djhvscf.github.io/Blog
 * @version: v3.0.0
 */

import * as UtilsFilterControl from './utils.js'
const Utils = $.fn.bootstrapTable.utils

$.extend($.fn.bootstrapTable.defaults, {
  filterControl: false,
  filterControlVisible: true,
  // eslint-disable-next-line no-unused-vars
  onColumnSearch (field, text) {
    return false
  },
  onCreatedControls () {
    return false
  },
  alignmentSelectControlOptions: undefined,
  filterTemplate: {
    input (that, field, placeholder, value) {
      return Utils.sprintf(
        '<input type="search" class="form-control bootstrap-table-filter-control-%s search-input" style="width: 100%;" placeholder="%s" value="%s">',
        field,
        'undefined' === typeof placeholder ? '' : placeholder,
        'undefined' === typeof value ? '' : value
      )
    },

    select ({ options }, field) {
      return Utils.sprintf(
        '<select class="form-control bootstrap-table-filter-control-%s" style="width: 100%;" dir="%s"></select>',
        field,
        UtilsFilterControl.getDirectionOfSelectOptions(
          options.alignmentSelectControlOptions
        )
      )
    },
    datepicker (that, field, value) {
      return Utils.sprintf(
        '<input type="text" class="form-control date-filter-control bootstrap-table-filter-control-%s" style="width: 100%;" value="%s">',
        field,
        'undefined' === typeof value ? '' : value
      )
    }
  },
  disableControlWhenSearch: false,
  searchOnEnterKey: false,
  showFilterControlSwitch: false,
  // internal variables
  valuesFilterControl: [],
  initialized: false
})

$.extend($.fn.bootstrapTable.columnDefaults, {
  filterControl: undefined, // input, select, datepicker
  filterDataCollector: undefined,
  filterData: undefined,
  filterDatepickerOptions: {},
  filterStrictSearch: false,
  filterStartsWithSearch: false,
  filterControlPlaceholder: '',
  filterDefault: '',
  filterOrderBy: 'asc' // asc || desc
})

$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
  'column-search.bs.table': 'onColumnSearch',
  'created-controls.bs.table': 'onCreatedControls'
})

$.extend($.fn.bootstrapTable.defaults.icons, {
  clear: {
    bootstrap3: 'glyphicon-trash icon-clear'
  }[$.fn.bootstrapTable.theme] || 'fa-trash',
  filterControlSwitchHide: {
    bootstrap3: 'glyphicon-zoom-out icon-zoom-out',
    materialize: 'zoom_out'
  }[$.fn.bootstrapTable.theme] || 'fa-search-minus',
  filterControlSwitchShow: {
    bootstrap3: 'glyphicon-zoom-in icon-zoom-in',
    materialize: 'zoom_in'
  }[$.fn.bootstrapTable.theme] || 'fa-search-plus'
})

$.extend($.fn.bootstrapTable.locales, {
  formatFilterControlSwitch () {
    return 'Hide/Show controls'
  },
  formatFilterControlSwitchHide () {
    return 'Hide controls'
  },
  formatFilterControlSwitchShow () {
    return 'Show controls'
  }
})
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)

$.extend($.fn.bootstrapTable.defaults, {
  formatClearSearch () {
    return 'Clear filters'
  }
})

$.fn.bootstrapTable.methods.push('triggerSearch')
$.fn.bootstrapTable.methods.push('clearFilterControl')
$.fn.bootstrapTable.methods.push('toggleFilterControl')

$.BootstrapTable = class extends $.BootstrapTable {
  init () {
    // Make sure that the filterControl option is set
    if (this.options.filterControl) {
      // Make sure that the internal variables are set correctly
      this.options.valuesFilterControl = []
      this.options.initialized = false

      this.$el
        .on('reset-view.bs.table', () => {
          setTimeout(() => {
            UtilsFilterControl.initFilterSelectControls(this)
            UtilsFilterControl.setValues(this)
          }, 2)
        })
        .on('toggle.bs.table', (_, cardView) => {
          this.options.initialized = false
          if (!cardView) {
            UtilsFilterControl.initFilterSelectControls(this)
            UtilsFilterControl.setValues(this)
            this.options.initialized = true
          }
        })
        .on('post-header.bs.table', () => {
          setTimeout(() => {
            UtilsFilterControl.initFilterSelectControls(this)
            UtilsFilterControl.setValues(this)
          }, 2)
        })
        .on('column-switch.bs.table', () => {
          UtilsFilterControl.setValues(this)
          if (this.options.height) {
            this.fitHeader()
          }
        })
        .on('post-body.bs.table', () => {
          if (this.options.height && !this.options.filterControlContainer && this.options.filterControlVisible) {
            UtilsFilterControl.fixHeaderCSS(this)
          }
          this.$tableLoading.css('top', this.$header.outerHeight() + 1)
        })
        .on('all.bs.table', (event, name) => {
          console.log('**********')
          console.log(`EVENT:: ${name}`)
          console.log('**********')
          UtilsFilterControl.syncHeaders(this)
        })
    }

    super.init()
  }

  initBody () {
    super.initBody()
    setTimeout(() => {
      UtilsFilterControl.initFilterSelectControls(this)
      UtilsFilterControl.setValues(this)
    }, 3)
  }

  initHeader () {
    super.initHeader()
    if (!this.options.filterControl) {
      return
    }

    UtilsFilterControl.createControls(this, UtilsFilterControl.getControlContainer(this))
    this.options.initialized = true
  }

  initSearch () {
    const that = this
    const fp = $.isEmptyObject(that.filterColumnsPartial) ? null : that.filterColumnsPartial

    super.initSearch()

    if (this.options.sidePagination === 'server' || fp === null) {
      return
    }

    // Check partial column filter
    that.data = fp ?
      that.data.filter((item, i) => {
        const itemIsExpected = []
        const keys1 = Object.keys(item)
        const keys2 = Object.keys(fp)
        const keys = keys1.concat(keys2.filter(item => !keys1.includes(item)))

        keys.forEach(key => {
          const thisColumn = that.columns[that.fieldsColumnsIndex[key]]
          const fval = (fp[key] || '').toLowerCase()
          let value = Utils.unescapeHTML(Utils.getItemField(item, key, false))
          let tmpItemIsExpected

          if (fval === '') {
            tmpItemIsExpected = true
          } else {
            // Fix #142: search use formatted data
            if (thisColumn && thisColumn.searchFormatter) {
              value = $.fn.bootstrapTable.utils.calculateObjectValue(
                that.header,
                that.header.formatters[$.inArray(key, that.header.fields)],
                [value, item, i],
                value
              )
            }

            if ($.inArray(key, that.header.fields) !== -1) {
              if (value === undefined || value === null) {
                tmpItemIsExpected = false
              } else if (typeof value === 'object') {
                value.forEach(objectValue => {
                  if (tmpItemIsExpected) {
                    return
                  }

                  if (this.options.searchAccentNeutralise) {
                    objectValue = Utils.normalizeAccent(objectValue)
                  }

                  tmpItemIsExpected = that.isValueExpected(fval, objectValue, thisColumn, key)
                })
              } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
                if (this.options.searchAccentNeutralise) {
                  value = Utils.normalizeAccent(value)
                }
                tmpItemIsExpected = that.isValueExpected(fval, value, thisColumn, key)
              }
            }
          }

          itemIsExpected.push(tmpItemIsExpected)
        })

        return !itemIsExpected.includes(false)
      }) :
      that.data

    that.unsortedData = [...that.data]
  }

  isValueExpected (searchValue, value, column, key) {
    let tmpItemIsExpected = false

    if (column.filterStrictSearch) {
      tmpItemIsExpected = value.toString().toLowerCase() === searchValue.toString().toLowerCase()
    } else if (column.filterStartsWithSearch) {
      tmpItemIsExpected = (`${value}`).toLowerCase().indexOf(searchValue) === 0
    } else {
      tmpItemIsExpected = (`${value}`).toLowerCase().includes(searchValue)
    }

    const largerSmallerEqualsRegex = /(?:(<=|=>|=<|>=|>|<)(?:\s+)?(\d+)?|(\d+)?(\s+)?(<=|=>|=<|>=|>|<))/gm
    const matches = largerSmallerEqualsRegex.exec(searchValue)

    if (matches) {
      const operator = matches[1] || `${matches[5]}l`
      const comparisonValue = matches[2] || matches[3]
      const int = parseInt(value, 10)
      const comparisonInt = parseInt(comparisonValue, 10)

      switch (operator) {
        case '>':
        case '<l':
          tmpItemIsExpected = int > comparisonInt
          break
        case '<':
        case '>l':
          tmpItemIsExpected = int < comparisonInt
          break
        case '<=':
        case '=<':
        case '>=l':
        case '=>l':
          tmpItemIsExpected = int <= comparisonInt
          break
        case '>=':
        case '=>':
        case '<=l':
        case '=<l':
          tmpItemIsExpected = int >= comparisonInt
          break
        default:
          break
      }
    }

    if (column.filterCustomSearch) {
      const customSearchResult = Utils.calculateObjectValue(this, column.filterCustomSearch, [searchValue, value, key, this.options.data], true)

      if (customSearchResult !== null) {
        tmpItemIsExpected = customSearchResult
      }
    }

    return tmpItemIsExpected
  }

  initColumnSearch (filterColumnsDefaults) {
    UtilsFilterControl.cacheValues(this)

    if (filterColumnsDefaults) {
      this.filterColumnsPartial = filterColumnsDefaults
      this.updatePagination()

      // eslint-disable-next-line guard-for-in
      for (const filter in filterColumnsDefaults) {
        this.trigger('column-search', filter, filterColumnsDefaults[filter])
      }
    }
  }

  initToolbar () {
    this.showToolbar = this.showToolbar || this.options.showFilterControlSwitch
    this.showSearchClearButton = this.options.filterControl && this.options.showSearchClearButton

    if (this.options.showFilterControlSwitch) {
      this.buttons = Object.assign(this.buttons, {
        filterControlSwitch: {
          text: this.options.filterControlVisible ? this.options.formatFilterControlSwitchHide() : this.options.formatFilterControlSwitchShow(),
          icon: this.options.filterControlVisible ? this.options.icons.filterControlSwitchHide : this.options.icons.filterControlSwitchShow,
          event: this.toggleFilterControl,
          attributes: {
            'aria-label': this.options.formatFilterControlSwitch(),
            title: this.options.formatFilterControlSwitch()
          }
        }
      })
    }

    super.initToolbar()
  }

  resetSearch (text) {
    if (this.options.filterControl && this.options.showSearchClearButton) {
      this.clearFilterControl()
    }
    super.resetSearch(text)
  }

  clearFilterControl () {
    if (!this.options.filterControl) {
      return
    }

    const that = this
    const cookies = UtilsFilterControl.collectBootstrapCookies()
    const table = this.$el.closest('table')
    const controls = UtilsFilterControl.getSearchControls(that)
    const search = Utils.getSearchInput(this)
    let hasValues = false
    let timeoutId = 0

    $.each(that.options.valuesFilterControl, (i, item) => {
      hasValues = hasValues ? true : item.value !== ''
      item.value = ''
    })

    $.each(that.options.filterControls, (i, item) => {
      item.text = ''
    })

    UtilsFilterControl.setValues(that)

    // clear cookies once the filters are clean
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => {
      if (cookies && cookies.length > 0) {
        $.each(cookies, (i, item) => {
          if (that.deleteCookie !== undefined) {
            that.deleteCookie(item)
          }
        })
      }
    }, that.options.searchTimeOut)

    // If there is not any value in the controls exit this method
    if (!hasValues) {
      return
    }

    // Clear each type of filter if it exists.
    // Requires the body to reload each time a type of filter is found because we never know
    // which ones are going to be present.
    if (controls.length > 0) {
      this.filterColumnsPartial = {}
      $(controls[0]).trigger(
        controls[0].tagName === 'INPUT' ? 'keyup' : 'change', { keyCode: 13 }
      )
    } else {
      return
    }

    if (search.length > 0) {
      that.resetSearch()
    }

    // use the default sort order if it exists. do nothing if it does not
    if (that.options.sortName !== table.data('sortName') || that.options.sortOrder !== table.data('sortOrder')) {
      const sorter = this.$header.find(Utils.sprintf('[data-field="%s"]', $(controls[0]).closest('table').data('sortName')))

      if (sorter.length > 0) {
        that.onSort({ type: 'keypress', currentTarget: sorter })
        $(sorter).find('.sortable').trigger('click')
      }
    }
  }

  // EVENTS
  onColumnSearch ({ currentTarget, keyCode }) {
    if (UtilsFilterControl.isKeyAllowed(keyCode)) {
      return
    }
    UtilsFilterControl.cacheValues(this)

    const $currentTarget = $(currentTarget)
    const currentTargetValue = $currentTarget.val()

    const text = currentTargetValue ? currentTargetValue.trim() : ''
    const $field = $currentTarget.closest('[data-field]').data('field')

    this.trigger('column-search', $field, text)

    if ($.isEmptyObject(this.filterColumnsPartial)) {
      this.filterColumnsPartial = {}
    }

    if (text) {
      this.filterColumnsPartial[$field] = text
    } else {
      delete this.filterColumnsPartial[$field]
    }

    this.options.pageNumber = 1
    this.onSearch({ currentTarget }, false)
  }

  toggleFilterControl () {
    this.options.filterControlVisible = !this.options.filterControlVisible
    // Controls in original header or container.
    const $filterControls = UtilsFilterControl.getControlContainer(this).find('.filter-control, .no-filter-control')

    if (this.options.filterControlVisible) {
      $filterControls.show()
    } else {
      $filterControls.hide()
      this.clearFilterControl()
    }

    // Controls in fixed header
    if (this.options.height) {
      const $fixedControls = $('.fixed-table-header table thead').find('.filter-control, .no-filter-control')

      if (this.options.filterControlVisible) {
        $fixedControls.show()
      } else {
        $fixedControls.hide()
      }

      UtilsFilterControl.fixHeaderCSS(this, '49px')
    }

    const icon = this.options.showButtonIcons ? this.options.filterControlVisible ? this.options.icons.filterControlSwitchHide : this.options.icons.filterControlSwitchShow : ''
    const text = this.options.showButtonText ? this.options.filterControlVisible ? this.options.formatFilterControlSwitchHide() : this.options.formatFilterControlSwitchShow() : ''

    this.$toolbar.find('>.columns').find('.filter-control-switch')
      .html(`${Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, icon) } ${ text}`)
  }

  triggerSearch () {
    const searchControls = UtilsFilterControl.getSearchControls(this)

    searchControls.each(function () {
      const $element = $(this)

      if ($element.is('select')) {
        $element.trigger('change')
      } else {
        $element.trigger('keyup')
      }
    })
  }

  _toggleColumn (index, checked, needUpdate) {
    this.options.initialized = false
    super._toggleColumn(index, checked, needUpdate)
    UtilsFilterControl.syncHeaders(this)
  }
}
<commit_msg>Fix showFilterControlSwitch<commit_after>/**
 * @author: Dennis Hernández
 * @webSite: http://djhvscf.github.io/Blog
 * @version: v3.0.0
 */

import * as UtilsFilterControl from './utils.js'
const Utils = $.fn.bootstrapTable.utils

$.extend($.fn.bootstrapTable.defaults, {
  filterControl: false,
  filterControlVisible: true,
  // eslint-disable-next-line no-unused-vars
  onColumnSearch (field, text) {
    return false
  },
  onCreatedControls () {
    return false
  },
  alignmentSelectControlOptions: undefined,
  filterTemplate: {
    input (that, field, placeholder, value) {
      return Utils.sprintf(
        '<input type="search" class="form-control bootstrap-table-filter-control-%s search-input" style="width: 100%;" placeholder="%s" value="%s">',
        field,
        'undefined' === typeof placeholder ? '' : placeholder,
        'undefined' === typeof value ? '' : value
      )
    },

    select ({ options }, field) {
      return Utils.sprintf(
        '<select class="form-control bootstrap-table-filter-control-%s" style="width: 100%;" dir="%s"></select>',
        field,
        UtilsFilterControl.getDirectionOfSelectOptions(
          options.alignmentSelectControlOptions
        )
      )
    },
    datepicker (that, field, value) {
      return Utils.sprintf(
        '<input type="text" class="form-control date-filter-control bootstrap-table-filter-control-%s" style="width: 100%;" value="%s">',
        field,
        'undefined' === typeof value ? '' : value
      )
    }
  },
  disableControlWhenSearch: false,
  searchOnEnterKey: false,
  showFilterControlSwitch: false,
  // internal variables
  valuesFilterControl: [],
  initialized: false
})

$.extend($.fn.bootstrapTable.columnDefaults, {
  filterControl: undefined, // input, select, datepicker
  filterDataCollector: undefined,
  filterData: undefined,
  filterDatepickerOptions: {},
  filterStrictSearch: false,
  filterStartsWithSearch: false,
  filterControlPlaceholder: '',
  filterDefault: '',
  filterOrderBy: 'asc' // asc || desc
})

$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
  'column-search.bs.table': 'onColumnSearch',
  'created-controls.bs.table': 'onCreatedControls'
})

$.extend($.fn.bootstrapTable.defaults.icons, {
  clear: {
    bootstrap3: 'glyphicon-trash icon-clear'
  }[$.fn.bootstrapTable.theme] || 'fa-trash',
  filterControlSwitchHide: {
    bootstrap3: 'glyphicon-zoom-out icon-zoom-out',
    materialize: 'zoom_out'
  }[$.fn.bootstrapTable.theme] || 'fa-search-minus',
  filterControlSwitchShow: {
    bootstrap3: 'glyphicon-zoom-in icon-zoom-in',
    materialize: 'zoom_in'
  }[$.fn.bootstrapTable.theme] || 'fa-search-plus'
})

$.extend($.fn.bootstrapTable.locales, {
  formatFilterControlSwitch () {
    return 'Hide/Show controls'
  },
  formatFilterControlSwitchHide () {
    return 'Hide controls'
  },
  formatFilterControlSwitchShow () {
    return 'Show controls'
  }
})
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)

$.extend($.fn.bootstrapTable.defaults, {
  formatClearSearch () {
    return 'Clear filters'
  }
})

$.fn.bootstrapTable.methods.push('triggerSearch')
$.fn.bootstrapTable.methods.push('clearFilterControl')
$.fn.bootstrapTable.methods.push('toggleFilterControl')

$.BootstrapTable = class extends $.BootstrapTable {
  init () {
    // Make sure that the filterControl option is set
    if (this.options.filterControl) {
      // Make sure that the internal variables are set correctly
      this.options.valuesFilterControl = []
      this.options.initialized = false

      this.$el
        .on('reset-view.bs.table', () => {
          setTimeout(() => {
            UtilsFilterControl.initFilterSelectControls(this)
            UtilsFilterControl.setValues(this)
          }, 2)
        })
        .on('toggle.bs.table', (_, cardView) => {
          this.options.initialized = false
          if (!cardView) {
            UtilsFilterControl.initFilterSelectControls(this)
            UtilsFilterControl.setValues(this)
            this.options.initialized = true
          }
        })
        .on('post-header.bs.table', () => {
          setTimeout(() => {
            UtilsFilterControl.initFilterSelectControls(this)
            UtilsFilterControl.setValues(this)
          }, 2)
        })
        .on('column-switch.bs.table', () => {
          UtilsFilterControl.setValues(this)
          if (this.options.height) {
            this.fitHeader()
          }
        })
        .on('post-body.bs.table', () => {
          if (this.options.height && !this.options.filterControlContainer && this.options.filterControlVisible) {
            UtilsFilterControl.fixHeaderCSS(this)
          }
          this.$tableLoading.css('top', this.$header.outerHeight() + 1)
        })
        .on('all.bs.table', (event, name) => {
          console.log('**********')
          console.log(`EVENT:: ${name}`)
          console.log('**********')
          UtilsFilterControl.syncHeaders(this)
        })
    }

    super.init()
  }

  initBody () {
    super.initBody()
    setTimeout(() => {
      UtilsFilterControl.initFilterSelectControls(this)
      UtilsFilterControl.setValues(this)
    }, 3)
  }

  initHeader () {
    super.initHeader()
    if (!this.options.filterControl) {
      return
    }

    UtilsFilterControl.createControls(this, UtilsFilterControl.getControlContainer(this))
    this.options.initialized = true
  }

  initSearch () {
    const that = this
    const fp = $.isEmptyObject(that.filterColumnsPartial) ? null : that.filterColumnsPartial

    super.initSearch()

    if (this.options.sidePagination === 'server' || fp === null) {
      return
    }

    // Check partial column filter
    that.data = fp ?
      that.data.filter((item, i) => {
        const itemIsExpected = []
        const keys1 = Object.keys(item)
        const keys2 = Object.keys(fp)
        const keys = keys1.concat(keys2.filter(item => !keys1.includes(item)))

        keys.forEach(key => {
          const thisColumn = that.columns[that.fieldsColumnsIndex[key]]
          const fval = (fp[key] || '').toLowerCase()
          let value = Utils.unescapeHTML(Utils.getItemField(item, key, false))
          let tmpItemIsExpected

          if (fval === '') {
            tmpItemIsExpected = true
          } else {
            // Fix #142: search use formatted data
            if (thisColumn && thisColumn.searchFormatter) {
              value = $.fn.bootstrapTable.utils.calculateObjectValue(
                that.header,
                that.header.formatters[$.inArray(key, that.header.fields)],
                [value, item, i],
                value
              )
            }

            if ($.inArray(key, that.header.fields) !== -1) {
              if (value === undefined || value === null) {
                tmpItemIsExpected = false
              } else if (typeof value === 'object') {
                value.forEach(objectValue => {
                  if (tmpItemIsExpected) {
                    return
                  }

                  if (this.options.searchAccentNeutralise) {
                    objectValue = Utils.normalizeAccent(objectValue)
                  }

                  tmpItemIsExpected = that.isValueExpected(fval, objectValue, thisColumn, key)
                })
              } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
                if (this.options.searchAccentNeutralise) {
                  value = Utils.normalizeAccent(value)
                }
                tmpItemIsExpected = that.isValueExpected(fval, value, thisColumn, key)
              }
            }
          }

          itemIsExpected.push(tmpItemIsExpected)
        })

        return !itemIsExpected.includes(false)
      }) :
      that.data

    that.unsortedData = [...that.data]
  }

  isValueExpected (searchValue, value, column, key) {
    let tmpItemIsExpected = false

    if (column.filterStrictSearch) {
      tmpItemIsExpected = value.toString().toLowerCase() === searchValue.toString().toLowerCase()
    } else if (column.filterStartsWithSearch) {
      tmpItemIsExpected = (`${value}`).toLowerCase().indexOf(searchValue) === 0
    } else {
      tmpItemIsExpected = (`${value}`).toLowerCase().includes(searchValue)
    }

    const largerSmallerEqualsRegex = /(?:(<=|=>|=<|>=|>|<)(?:\s+)?(\d+)?|(\d+)?(\s+)?(<=|=>|=<|>=|>|<))/gm
    const matches = largerSmallerEqualsRegex.exec(searchValue)

    if (matches) {
      const operator = matches[1] || `${matches[5]}l`
      const comparisonValue = matches[2] || matches[3]
      const int = parseInt(value, 10)
      const comparisonInt = parseInt(comparisonValue, 10)

      switch (operator) {
        case '>':
        case '<l':
          tmpItemIsExpected = int > comparisonInt
          break
        case '<':
        case '>l':
          tmpItemIsExpected = int < comparisonInt
          break
        case '<=':
        case '=<':
        case '>=l':
        case '=>l':
          tmpItemIsExpected = int <= comparisonInt
          break
        case '>=':
        case '=>':
        case '<=l':
        case '=<l':
          tmpItemIsExpected = int >= comparisonInt
          break
        default:
          break
      }
    }

    if (column.filterCustomSearch) {
      const customSearchResult = Utils.calculateObjectValue(this, column.filterCustomSearch, [searchValue, value, key, this.options.data], true)

      if (customSearchResult !== null) {
        tmpItemIsExpected = customSearchResult
      }
    }

    return tmpItemIsExpected
  }

  initColumnSearch (filterColumnsDefaults) {
    UtilsFilterControl.cacheValues(this)

    if (filterColumnsDefaults) {
      this.filterColumnsPartial = filterColumnsDefaults
      this.updatePagination()

      // eslint-disable-next-line guard-for-in
      for (const filter in filterColumnsDefaults) {
        this.trigger('column-search', filter, filterColumnsDefaults[filter])
      }
    }
  }

  initToolbar () {
    this.showToolbar = this.showToolbar || this.options.showFilterControlSwitch
    this.showSearchClearButton = this.options.filterControl && this.options.showSearchClearButton

    if (this.options.showFilterControlSwitch) {
      this.buttons = Object.assign(this.buttons, {
        filterControlSwitch: {
          text: this.options.filterControlVisible ? this.options.formatFilterControlSwitchHide() : this.options.formatFilterControlSwitchShow(),
          icon: this.options.filterControlVisible ? this.options.icons.filterControlSwitchHide : this.options.icons.filterControlSwitchShow,
          event: this.toggleFilterControl,
          attributes: {
            'aria-label': this.options.formatFilterControlSwitch(),
            title: this.options.formatFilterControlSwitch()
          }
        }
      })
    }

    super.initToolbar()
  }

  resetSearch (text) {
    if (this.options.filterControl && this.options.showSearchClearButton) {
      this.clearFilterControl()
    }
    super.resetSearch(text)
  }

  clearFilterControl () {
    if (!this.options.filterControl) {
      return
    }

    const that = this
    const cookies = UtilsFilterControl.collectBootstrapCookies()
    const table = this.$el.closest('table')
    const controls = UtilsFilterControl.getSearchControls(that)
    const search = Utils.getSearchInput(this)
    let hasValues = false
    let timeoutId = 0

    $.each(that.options.valuesFilterControl, (i, item) => {
      hasValues = hasValues ? true : item.value !== ''
      item.value = ''
    })

    $.each(that.options.filterControls, (i, item) => {
      item.text = ''
    })

    UtilsFilterControl.setValues(that)

    // clear cookies once the filters are clean
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => {
      if (cookies && cookies.length > 0) {
        $.each(cookies, (i, item) => {
          if (that.deleteCookie !== undefined) {
            that.deleteCookie(item)
          }
        })
      }
    }, that.options.searchTimeOut)

    // If there is not any value in the controls exit this method
    if (!hasValues) {
      return
    }

    // Clear each type of filter if it exists.
    // Requires the body to reload each time a type of filter is found because we never know
    // which ones are going to be present.
    if (controls.length > 0) {
      this.filterColumnsPartial = {}
      $(controls[0]).trigger(
        controls[0].tagName === 'INPUT' ? 'keyup' : 'change', { keyCode: 13 }
      )
    } else {
      return
    }

    if (search.length > 0) {
      that.resetSearch()
    }

    // use the default sort order if it exists. do nothing if it does not
    if (that.options.sortName !== table.data('sortName') || that.options.sortOrder !== table.data('sortOrder')) {
      const sorter = this.$header.find(Utils.sprintf('[data-field="%s"]', $(controls[0]).closest('table').data('sortName')))

      if (sorter.length > 0) {
        that.onSort({ type: 'keypress', currentTarget: sorter })
        $(sorter).find('.sortable').trigger('click')
      }
    }
  }

  // EVENTS
  onColumnSearch ({ currentTarget, keyCode }) {
    if (UtilsFilterControl.isKeyAllowed(keyCode)) {
      return
    }
    UtilsFilterControl.cacheValues(this)

    const $currentTarget = $(currentTarget)
    const currentTargetValue = $currentTarget.val()

    const text = currentTargetValue ? currentTargetValue.trim() : ''
    const $field = $currentTarget.closest('[data-field]').data('field')

    this.trigger('column-search', $field, text)

    if ($.isEmptyObject(this.filterColumnsPartial)) {
      this.filterColumnsPartial = {}
    }

    if (text) {
      this.filterColumnsPartial[$field] = text
    } else {
      delete this.filterColumnsPartial[$field]
    }

    this.options.pageNumber = 1
    this.onSearch({ currentTarget }, false)
  }

  toggleFilterControl () {
    this.options.filterControlVisible = !this.options.filterControlVisible
    // Controls in original header or container.
    const $filterControls = UtilsFilterControl.getControlContainer(this).find('.filter-control, .no-filter-control')

    if (this.options.filterControlVisible) {
      $filterControls.show()
    } else {
      $filterControls.hide()
      this.clearFilterControl()
    }

    // Controls in fixed header
    if (this.options.height) {
      const $fixedControls = $('.fixed-table-header table thead').find('.filter-control, .no-filter-control')

      if (this.options.filterControlVisible) {
        $fixedControls.show()
        UtilsFilterControl.fixHeaderCSS(this)
      } else {
        $fixedControls.hide()
        UtilsFilterControl.fixHeaderCSS(this, '49px')
      }
    }

    const icon = this.options.showButtonIcons ? this.options.filterControlVisible ? this.options.icons.filterControlSwitchHide : this.options.icons.filterControlSwitchShow : ''
    const text = this.options.showButtonText ? this.options.filterControlVisible ? this.options.formatFilterControlSwitchHide() : this.options.formatFilterControlSwitchShow() : ''

    this.$toolbar.find('>.columns').find('.filter-control-switch')
      .html(`${Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, icon) } ${ text}`)
  }

  triggerSearch () {
    const searchControls = UtilsFilterControl.getSearchControls(this)

    searchControls.each(function () {
      const $element = $(this)

      if ($element.is('select')) {
        $element.trigger('change')
      } else {
        $element.trigger('keyup')
      }
    })
  }

  _toggleColumn (index, checked, needUpdate) {
    this.options.initialized = false
    super._toggleColumn(index, checked, needUpdate)
    UtilsFilterControl.syncHeaders(this)
  }
}
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react'
import buildInfluxQLQuery from 'utils/influxql'

import DatabaseList from './DatabaseList'
import MeasurementList from './MeasurementList'
import FieldList from './FieldList'
import TagList from './TagList'
import RawQueryEditor from './RawQueryEditor'

const {
  string,
  shape,
  func,
} = PropTypes

const QueryEditor = React.createClass({
  propTypes: {
    query: shape({
      id: string,
    }).isRequired,
    timeRange: shape({
      upper: string,
      lower: string,
    }).isRequired,
    actions: shape({
      chooseNamespace: func.isRequired,
      chooseMeasurement: func.isRequired,
      applyFuncsToField: func.isRequired,
      chooseTag: func.isRequired,
      groupByTag: func.isRequired,
      toggleField: func.isRequired,
      groupByTime: func.isRequired,
      toggleTagAcceptance: func.isRequired,
      editRawText: func.isRequired,
    }).isRequired,
  },

  getInitialState() {
    return {
      database: null,
      measurement: null,
    }
  },

  handleChooseNamespace(namespace) {
    this.props.actions.chooseNamespace(this.props.query.id, namespace)
  },

  handleChooseMeasurement(measurement) {
    this.props.actions.chooseMeasurement(this.props.query.id, measurement)
  },

  handleToggleField(field) {
    this.props.actions.toggleField(this.props.query.id, field)
  },

  handleGroupByTime(time) {
    this.props.actions.groupByTime(this.props.query.id, time)
  },

  handleApplyFuncsToField(fieldFunc) {
    this.props.actions.applyFuncsToField(this.props.query.id, fieldFunc)
  },

  handleChooseTag(tag) {
    this.props.actions.chooseTag(this.props.query.id, tag)
  },

  handleToggleTagAcceptance() {
    this.props.actions.toggleTagAcceptance(this.props.query.id)
  },

  handleGroupByTag(tagKey) {
    this.props.actions.groupByTag(this.props.query.id, tagKey)
  },

  handleEditRawText(text) {
    this.props.actions.editRawText(this.props.query.id, text)
  },

  render() {
    return (

          {this.renderQuery()}
          {this.renderLists()}

    )
  },

  renderQuery() {
    const {query, timeRange} = this.props
    const statement = query.rawText || buildInfluxQLQuery(timeRange, query) || 'Select a database, measurement, and field below.'

    if (typeof query.rawText !== 'string') {
      return (

{statement}

      )
    }

    return <RawQueryEditor query={query} onUpdate={this.handleEditRawText} />
  },

  renderLists() {
    const {query} = this.props

    return (

        <DatabaseList
          query={query}
          onChooseNamespace={this.handleChooseNamespace}
        />
        <MeasurementList
          query={query}
          onChooseMeasurement={this.handleChooseMeasurement}
        />
        <FieldList
          query={query}
          onToggleField={this.handleToggleField}
          onGroupByTime={this.handleGroupByTime}
          applyFuncsToField={this.handleApplyFuncsToField}
        />
        <TagList
          query={query}
          onChooseTag={this.handleChooseTag}
          onGroupByTag={this.handleGroupByTag}
          onToggleTagAcceptance={this.handleToggleTagAcceptance}
        />

    )
  },
})

export default QueryEditor
<commit_msg>Remove unused state<commit_after>import React, {PropTypes} from 'react'
import buildInfluxQLQuery from 'utils/influxql'

import DatabaseList from './DatabaseList'
import MeasurementList from './MeasurementList'
import FieldList from './FieldList'
import TagList from './TagList'
import RawQueryEditor from './RawQueryEditor'

const {
  string,
  shape,
  func,
} = PropTypes

const QueryEditor = React.createClass({
  propTypes: {
    query: shape({
      id: string,
    }).isRequired,
    timeRange: shape({
      upper: string,
      lower: string,
    }).isRequired,
    actions: shape({
      chooseNamespace: func.isRequired,
      chooseMeasurement: func.isRequired,
      applyFuncsToField: func.isRequired,
      chooseTag: func.isRequired,
      groupByTag: func.isRequired,
      toggleField: func.isRequired,
      groupByTime: func.isRequired,
      toggleTagAcceptance: func.isRequired,
      editRawText: func.isRequired,
    }).isRequired,
  },

  handleChooseNamespace(namespace) {
    this.props.actions.chooseNamespace(this.props.query.id, namespace)
  },

  handleChooseMeasurement(measurement) {
    this.props.actions.chooseMeasurement(this.props.query.id, measurement)
  },

  handleToggleField(field) {
    this.props.actions.toggleField(this.props.query.id, field)
  },

  handleGroupByTime(time) {
    this.props.actions.groupByTime(this.props.query.id, time)
  },

  handleApplyFuncsToField(fieldFunc) {
    this.props.actions.applyFuncsToField(this.props.query.id, fieldFunc)
  },

  handleChooseTag(tag) {
    this.props.actions.chooseTag(this.props.query.id, tag)
  },

  handleToggleTagAcceptance() {
    this.props.actions.toggleTagAcceptance(this.props.query.id)
  },

  handleGroupByTag(tagKey) {
    this.props.actions.groupByTag(this.props.query.id, tagKey)
  },

  handleEditRawText(text) {
    this.props.actions.editRawText(this.props.query.id, text)
  },

  render() {
    return (

          {this.renderQuery()}
          {this.renderLists()}

    )
  },

  renderQuery() {
    const {query, timeRange} = this.props
    const statement = query.rawText || buildInfluxQLQuery(timeRange, query) || 'Select a database, measurement, and field below.'

    if (typeof query.rawText !== 'string') {
      return (

{statement}

      )
    }

    return <RawQueryEditor query={query} onUpdate={this.handleEditRawText} />
  },

  renderLists() {
    const {query} = this.props

    return (

        <DatabaseList
          query={query}
          onChooseNamespace={this.handleChooseNamespace}
        />
        <MeasurementList
          query={query}
          onChooseMeasurement={this.handleChooseMeasurement}
        />
        <FieldList
          query={query}
          onToggleField={this.handleToggleField}
          onGroupByTime={this.handleGroupByTime}
          applyFuncsToField={this.handleApplyFuncsToField}
        />
        <TagList
          query={query}
          onChooseTag={this.handleChooseTag}
          onGroupByTag={this.handleGroupByTag}
          onToggleTagAcceptance={this.handleToggleTagAcceptance}
        />

    )
  },
})

export default QueryEditor
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require("path");
var ejs      = require('ejs'),
    markdown = require('markdown').markdown;
var heads                   = require('../heads'),
    RoboHydraHead           = heads.RoboHydraHead,
    RoboHydraHeadFilesystem = heads.RoboHydraHeadFilesystem,
    RoboHydraHeadProxy      = heads.RoboHydraHeadProxy;
var extendObject = require("../utils").extendObject;

var robohydraAdminBaseUrlPath = '/robohydra-admin';
var mainLayoutTemplateString =
    fs.readFileSync(__dirname + "/templates/layout-main.ejs", "utf-8");
var startTimestamp = new Date();

function templateData(templateName) {
    return fs.readFileSync(path.join(__dirname,
                                     "templates",
                                     templateName + ".ejs"),
                           "utf-8");
}

function renderPartial(partialName, stash) {
    var templateString = templateData("_" + partialName);

    return ejs.render(templateString, extendObject({
        baseUrl: robohydraAdminBaseUrlPath,
        renderPartial: renderPartial
    }, stash));
}

/* There are two special stash variables used in the layout templates:
 * - title (title of the page, used eg. in the <title> element)
 * - currentSection (URL for the current section, eg. '/' or '/scenarios')
 */
function renderPage(res, templateName, config, userStash) {
    var pageStash = extendObject({
        baseUrl: robohydraAdminBaseUrlPath,
        renderPartial: renderPartial
    }, userStash);
    var templateString = templateData(templateName);
    var mainContent = ejs.render(templateString, pageStash);

    var layoutStash = extendObject({
        baseUrl: robohydraAdminBaseUrlPath,
        title: "RoboHydra Admin",
        currentSection: "/",
        hydraName: config.robohydra.name,
        startTime: startTimestamp,
        renderPartial: renderPartial,
        content: mainContent
    }, userStash);

    res.headers['content-type'] = 'text/html';
    res.send(ejs.render(mainLayoutTemplateString, layoutStash));
}

exports.name = '*admin*';
exports.getBodyParts = function(config) {
    "use strict";

    var showAdminPlugin = !!config.adminui_show_admin_plugin;

    return [
        // Static files
        new RoboHydraHeadFilesystem({
            name: 'static',
            mountPath: robohydraAdminBaseUrlPath + '/static',
            documentRoot: __dirname + '/static'
        }),

        // "Test" to "Scenario" compatibility heads (deprecated)
        new RoboHydraHead({
            name: 'testResultsCompat',
            path: robohydraAdminBaseUrlPath + '/tests/results.json',
            handler: function(req, res, next) {
                req.url = robohydraAdminBaseUrlPath + '/rest/test-results';
                next(req, res);
            }
        }),
        new RoboHydraHead({
            name: 'testToScenarioCompat',
            path: robohydraAdminBaseUrlPath + '/tests/',
            handler: function(req, res, next) {
                req.url = req.url.replace(new RegExp('/tests'), '/scenarios');
                next(req, res);
            }
        }),
        new RoboHydraHead({
            name: 'testToScenarioToggleCompat',
            path: robohydraAdminBaseUrlPath + '/tests/:plugin/:testName',
            handler: function(req, res, next) {
                req.url = robohydraAdminBaseUrlPath +
                    '/scenarios/' + req.params.plugin + '/' +
                    req.params.testName;

                var cScenario = config.robohydra.currentScenario;
                var plugin = req.params.plugin,
                    scenarioName = req.params.scenarioName;
                var stop = (cScenario.plugin === plugin &&
                            cScenario.scenario === scenarioName);
                req.bodyParams.active = stop ? 'false' : 'true';

                next(req, res);
            }
        }),

        // Scenario start/stop
        new RoboHydraHead({
            name: 'scenarioActivation',
            path: robohydraAdminBaseUrlPath + '/scenarios/:plugin/:scenarioName',
            handler: function(req, res) {
                var cScenario = config.robohydra.currentScenario;
                var plugin = req.params.plugin, scenarioName = req.params.scenarioName;
                var startScenario = false;
                if (cScenario.plugin === plugin && cScenario.scenario === scenarioName) {
                    config.robohydra.stopScenario();
                } else {
                    config.robohydra.startScenario(plugin, scenarioName);
                    startScenario = true;
                }

                var p = config.robohydra.currentScenario.plugin,
                    t = config.robohydra.currentScenario.scenario;
                var currentScenarioInstructions;
                if (startScenario &&
                    (currentScenarioInstructions =
                     config.robohydra.getPlugin(p).scenarios[t].instructions)) {
                    renderPage(res, 'scenario-instructions', config, {
                        currentSection: '/scenarios',
                        instructions: currentScenarioInstructions,
                        currentScenario: config.robohydra.currentScenario,
                        markdown: markdown.toHTML
                    });
                } else {
                    res.headers.Location = robohydraAdminBaseUrlPath + '/scenarios';
                    res.statusCode = 302;
                    res.end();
                }
            }
        }),

        // Scenario index
        new RoboHydraHead({
            name: 'scenarioIndex',
            path: robohydraAdminBaseUrlPath + '/scenarios',
            handler: function(req, res) {
                renderPage(res, 'scenarios', config, {
                    currentSection: '/scenarios',
                    plugins: config.robohydra.getPlugins(),
                    currentTest: config.robohydra.currentTest,
                    testResults: config.robohydra.testResults
                });
            }
        }),

        // Create dynamic RoboHydra heads
        new RoboHydraHead({
            name: 'createHead',
            path: robohydraAdminBaseUrlPath + '/head/create',
            handler: function(req, res) {
                function createStaticHead(params) {
                    var content = req.bodyParams.newStaticHeadContent;
                    var defaultContentType = 'application/octet-stream';
                    try {
                        JSON.parse(content);
                        defaultContentType = 'application/json';
                    } catch (e) {
                        // It's ok if it's not JSON
                    }

                    var fullUrlPath = params.newStaticHeadPath;
                    // Javascript's split method is retarded, have to
                    // do this shit
                    var baseUrlPath = fullUrlPath, queryParamString = '';
                    var questionMarkIndex = fullUrlPath.indexOf('?');
                    if (questionMarkIndex !== -1) {
                        baseUrlPath = fullUrlPath.slice(0, questionMarkIndex);
                        queryParamString =
                            fullUrlPath.slice(questionMarkIndex + 1);
                    }
                    var queryParams = {};
                    queryParamString.split("&").forEach(function(keyValue) {
                        var pair = keyValue.split("=");
                        queryParams[pair[0]] = pair[1];
                    });

                    return new RoboHydraHead({
                        path:    baseUrlPath,
                        handler: function(req, res, next) {
                            // Check if we match all given query parameters
                            var matches = true;
                            for (var p in queryParams) {
                                if (req.queryParams[p] !== queryParams[p]) {
                                    matches = false;
                                }
                            }

                            if (matches) {
                                res.headers['content-type'] =
                                    params.newStaticHeadContentType ||
                                    defaultContentType;
                                res.send(content);
                            } else {
                                next(req, res);
                            }
                        }
                    });
                }

                function createFilesystemHead(params) {
                    return new RoboHydraHeadFilesystem({
                        mountPath:    params.newFilesystemHeadMountPath,
                        documentRoot: params.newFilesystemHeadDocumentRoot
                    });
                }

                function createProxyHead(params) {
                    return new RoboHydraHeadProxy({
                        mountPath:     params.newProxyHeadMountPath,
                        proxyTo:       params.newProxyHeadProxyTo,
                        setHostHeader: !!params.newProxyHeadSetHostHeader
                    });
                }

                var headCreationFunctionMap = {
                    'static': createStaticHead,
                    'filesystem': createFilesystemHead,
                    'proxy': createProxyHead
                };
                var headCreationFunction =
                        headCreationFunctionMap[req.bodyParams.newHeadType];
                if (typeof headCreationFunction !== 'function') {
                    res.statusCode = 400;
                    res.headers['content-type'] = 'text/html';
                    res.send("Unknown head type '" +
                             req.bodyParams.newHeadType + "'");
                    return;
                }

                var head = headCreationFunction(req.bodyParams);
                config.robohydra.registerDynamicHead(head);
                res.headers.Location = robohydraAdminBaseUrlPath;
                res.statusCode = 302;
                res.end();
            }
        }),

        // Toggle RoboHydra head attachment.
        // This (DEPRECATED) URL and interface is pretty retarded, but
        // we'll keep it around for a while for compatibility purposes.
        // Once we don't want compatibility, the URL can be changed,
        // but the functionality has to stay for the web interface to
        // work
        new RoboHydraHead({
            name: 'toggleHeadAttachment',
            path: robohydraAdminBaseUrlPath + '/head/toggle-attachment',
            handler: function(req, res) {
                var pluginName = req.bodyParams.pluginName;
                var headName   = req.bodyParams.headName;
                var head = config.robohydra.findHead(pluginName, headName);
                if (head.attached()) {
                    head.detach();
                } else {
                    head.attach();
                }
                res.headers.Location = robohydraAdminBaseUrlPath;
                res.statusCode = 302;
                res.end();
            }
        }),

        // REST API =======================================================
        // Head information/attaching
        new RoboHydraHead({
            name: 'restHead',
            path: robohydraAdminBaseUrlPath + '/rest/plugins/:plugin/heads/:head',
            handler: function(req, res) {
                var pluginName = req.params.plugin;
                var headName   = req.params.head;
                var head = config.robohydra.findHead(pluginName, headName);

                if (req.method === 'POST') {
                    var newAttachedState = req.bodyParams.attached;
                    if (head.attached() && newAttachedState === 'false') {
                        head.detach();
                    }
                    if (! head.attached() && newAttachedState === 'true') {
                        head.attach();
                    }
                }

                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify({
                    plugin: pluginName,
                    name: headName,
                    attached: head.attached()
                }));
            }
        }),
        // Scenario information/activation
        new RoboHydraHead({
            name: 'restScenario',
            path: robohydraAdminBaseUrlPath + '/rest/plugins/:plugin/scenarios/:scenario',
            handler: function(req, res) {
                var rh = config.robohydra;
                var pluginName   = req.params.plugin;
                var scenarioName = req.params.scenario;
                var plugin = rh.getPlugin(pluginName);
                var scenario = plugin.scenarios[scenarioName];

                if (req.method === 'POST') {
                    var newActiveState = req.bodyParams.active;
                    var scenarioActive =
                            (rh.currentScenario.plugin === pluginName &&
                             rh.currentScenario.scenario === scenarioName);
                    if (scenarioActive && newActiveState === 'false') {
                        rh.stopScenario();
                    }
                    if (! scenarioActive && newActiveState === 'true') {
                        rh.startScenario(pluginName, scenarioName);
                    }
                }

                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify({
                    plugin: pluginName,
                    name: scenarioName,
                    active: (rh.currentScenario.plugin === pluginName &&
                             rh.currentScenario.scenario === scenarioName)
                }));
            }
        }),
        // Plugin information
        new RoboHydraHead({
            name: 'restPluginList',
            path: robohydraAdminBaseUrlPath + '/rest/plugins',
            handler: function(req, res) {
                var rh = config.robohydra;

                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify({
                    plugins: rh.getPluginNames().filter(function(pluginName) {
                        return pluginName[0] !== '*';
                    })
                }));
            }
        }),
        new RoboHydraHead({
            name: 'restPlugin',
            path: robohydraAdminBaseUrlPath + '/rest/plugins/:plugin',
            handler: function(req, res) {
                var pluginName = req.params.plugin;
                var plugin = config.robohydra.getPlugin(pluginName);
                var currentScenario = config.robohydra.currentScenario;

                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify({
                    name: pluginName,
                    heads: plugin.heads.map(function(h) {
                        return {
                            plugin: pluginName,
                            name: h.name,
                            attached: h.attached()
                        };
                    }),
                    scenarios: Object.keys(plugin.scenarios).map(function(s) {
                        return {
                            plugin: pluginName,
                            name: s,
                            active: (pluginName === currentScenario.plugin &&
                                     s === currentScenario.scenario)
                        };
                    })
                }));
            }
        }),
        new RoboHydraHead({
            name: 'restTestResults',
            path: robohydraAdminBaseUrlPath + '/rest/test-results',
            handler: function(req, res) {
                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify(config.robohydra.testResults));
            }
        }),

        // Admin UI index
        new RoboHydraHead({
            name: 'index',
            path: robohydraAdminBaseUrlPath,
            handler: function(req, res) {
                var highlightPath = req.queryParams.highlightPath || '';
                var stash = {plugins: config.robohydra.getPlugins(),
                             matchingHeads: [],
                             highlightPath: highlightPath};
                if (! showAdminPlugin) {
                    stash.plugins = stash.plugins.filter(function(plugin) {
                        return plugin.name !== '*admin*';
                    });
                }
                if (highlightPath) {
                    var matchingPair ;
                    while (1) {
                        matchingPair =
                                config.robohydra.headForPath(highlightPath,
                                                             matchingPair);
                        if (! matchingPair) {
                            break;
                        }

                        stash.matchingHeads.push({
                            plugin: matchingPair.plugin.name,
                            head:   matchingPair.head.name
                        });
                    }
                }

                renderPage(res, 'index', config, stash);
            }})
    ];
};
<commit_msg>Clearly mark deprecated admin UI URLs<commit_after>var fs = require('fs');
var path = require("path");
var ejs      = require('ejs'),
    markdown = require('markdown').markdown;
var heads                   = require('../heads'),
    RoboHydraHead           = heads.RoboHydraHead,
    RoboHydraHeadFilesystem = heads.RoboHydraHeadFilesystem,
    RoboHydraHeadProxy      = heads.RoboHydraHeadProxy;
var extendObject = require("../utils").extendObject;

var robohydraAdminBaseUrlPath = '/robohydra-admin';
var mainLayoutTemplateString =
    fs.readFileSync(__dirname + "/templates/layout-main.ejs", "utf-8");
var startTimestamp = new Date();

function templateData(templateName) {
    return fs.readFileSync(path.join(__dirname,
                                     "templates",
                                     templateName + ".ejs"),
                           "utf-8");
}

function renderPartial(partialName, stash) {
    var templateString = templateData("_" + partialName);

    return ejs.render(templateString, extendObject({
        baseUrl: robohydraAdminBaseUrlPath,
        renderPartial: renderPartial
    }, stash));
}

/* There are two special stash variables used in the layout templates:
 * - title (title of the page, used eg. in the <title> element)
 * - currentSection (URL for the current section, eg. '/' or '/scenarios')
 */
function renderPage(res, templateName, config, userStash) {
    var pageStash = extendObject({
        baseUrl: robohydraAdminBaseUrlPath,
        renderPartial: renderPartial
    }, userStash);
    var templateString = templateData(templateName);
    var mainContent = ejs.render(templateString, pageStash);

    var layoutStash = extendObject({
        baseUrl: robohydraAdminBaseUrlPath,
        title: "RoboHydra Admin",
        currentSection: "/",
        hydraName: config.robohydra.name,
        startTime: startTimestamp,
        renderPartial: renderPartial,
        content: mainContent
    }, userStash);

    res.headers['content-type'] = 'text/html';
    res.send(ejs.render(mainLayoutTemplateString, layoutStash));
}

exports.name = '*admin*';
exports.getBodyParts = function(config) {
    "use strict";

    var showAdminPlugin = !!config.adminui_show_admin_plugin;

    return [
        // Static files
        new RoboHydraHeadFilesystem({
            name: 'static',
            mountPath: robohydraAdminBaseUrlPath + '/static',
            documentRoot: __dirname + '/static'
        }),

        // "Test" to "Scenario" compatibility heads (deprecated)
        new RoboHydraHead({
            name: 'testResultsCompat',
            path: robohydraAdminBaseUrlPath + '/tests/results.json',
            handler: function(req, res, next) {
                console.warn("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
                console.warn("XXX DEPRECATED: use /rest/test-results instead XXX");
                console.warn("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
                req.url = robohydraAdminBaseUrlPath + '/rest/test-results';
                next(req, res);
            }
        }),
        new RoboHydraHead({
            name: 'testToScenarioCompat',
            path: robohydraAdminBaseUrlPath + '/tests/',
            handler: function(req, res, next) {
                console.warn("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
                console.warn("XXX DEPRECATED URL: use /scenarios instead XXX");
                console.warn("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
                req.url = req.url.replace(new RegExp('/tests'), '/scenarios');
                next(req, res);
            }
        }),
        new RoboHydraHead({
            name: 'testToScenarioToggleCompat',
            path: robohydraAdminBaseUrlPath + '/tests/:plugin/:testName',
            handler: function(req, res, next) {
                console.warn("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
                console.warn("XXX DEPRECATED: use /scenarios/<PLUGIN>/<SCENARIONAME> instead XXX");
                console.warn("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
                req.url = robohydraAdminBaseUrlPath +
                    '/scenarios/' + req.params.plugin + '/' +
                    req.params.testName;

                var cScenario = config.robohydra.currentScenario;
                var plugin = req.params.plugin,
                    scenarioName = req.params.scenarioName;
                var stop = (cScenario.plugin === plugin &&
                            cScenario.scenario === scenarioName);
                req.bodyParams.active = stop ? 'false' : 'true';

                next(req, res);
            }
        }),

        // Scenario start/stop
        new RoboHydraHead({
            name: 'scenarioActivation',
            path: robohydraAdminBaseUrlPath + '/scenarios/:plugin/:scenarioName',
            handler: function(req, res) {
                var cScenario = config.robohydra.currentScenario;
                var plugin = req.params.plugin, scenarioName = req.params.scenarioName;
                var startScenario = false;
                if (cScenario.plugin === plugin && cScenario.scenario === scenarioName) {
                    config.robohydra.stopScenario();
                } else {
                    config.robohydra.startScenario(plugin, scenarioName);
                    startScenario = true;
                }

                var p = config.robohydra.currentScenario.plugin,
                    t = config.robohydra.currentScenario.scenario;
                var currentScenarioInstructions;
                if (startScenario &&
                    (currentScenarioInstructions =
                     config.robohydra.getPlugin(p).scenarios[t].instructions)) {
                    renderPage(res, 'scenario-instructions', config, {
                        currentSection: '/scenarios',
                        instructions: currentScenarioInstructions,
                        currentScenario: config.robohydra.currentScenario,
                        markdown: markdown.toHTML
                    });
                } else {
                    res.headers.Location = robohydraAdminBaseUrlPath + '/scenarios';
                    res.statusCode = 302;
                    res.end();
                }
            }
        }),

        // Scenario index
        new RoboHydraHead({
            name: 'scenarioIndex',
            path: robohydraAdminBaseUrlPath + '/scenarios',
            handler: function(req, res) {
                renderPage(res, 'scenarios', config, {
                    currentSection: '/scenarios',
                    plugins: config.robohydra.getPlugins(),
                    currentTest: config.robohydra.currentTest,
                    testResults: config.robohydra.testResults
                });
            }
        }),

        // Create dynamic RoboHydra heads
        new RoboHydraHead({
            name: 'createHead',
            path: robohydraAdminBaseUrlPath + '/head/create',
            handler: function(req, res) {
                function createStaticHead(params) {
                    var content = req.bodyParams.newStaticHeadContent;
                    var defaultContentType = 'application/octet-stream';
                    try {
                        JSON.parse(content);
                        defaultContentType = 'application/json';
                    } catch (e) {
                        // It's ok if it's not JSON
                    }

                    var fullUrlPath = params.newStaticHeadPath;
                    // Javascript's split method is retarded, have to
                    // do this shit
                    var baseUrlPath = fullUrlPath, queryParamString = '';
                    var questionMarkIndex = fullUrlPath.indexOf('?');
                    if (questionMarkIndex !== -1) {
                        baseUrlPath = fullUrlPath.slice(0, questionMarkIndex);
                        queryParamString =
                            fullUrlPath.slice(questionMarkIndex + 1);
                    }
                    var queryParams = {};
                    queryParamString.split("&").forEach(function(keyValue) {
                        var pair = keyValue.split("=");
                        queryParams[pair[0]] = pair[1];
                    });

                    return new RoboHydraHead({
                        path:    baseUrlPath,
                        handler: function(req, res, next) {
                            // Check if we match all given query parameters
                            var matches = true;
                            for (var p in queryParams) {
                                if (req.queryParams[p] !== queryParams[p]) {
                                    matches = false;
                                }
                            }

                            if (matches) {
                                res.headers['content-type'] =
                                    params.newStaticHeadContentType ||
                                    defaultContentType;
                                res.send(content);
                            } else {
                                next(req, res);
                            }
                        }
                    });
                }

                function createFilesystemHead(params) {
                    return new RoboHydraHeadFilesystem({
                        mountPath:    params.newFilesystemHeadMountPath,
                        documentRoot: params.newFilesystemHeadDocumentRoot
                    });
                }

                function createProxyHead(params) {
                    return new RoboHydraHeadProxy({
                        mountPath:     params.newProxyHeadMountPath,
                        proxyTo:       params.newProxyHeadProxyTo,
                        setHostHeader: !!params.newProxyHeadSetHostHeader
                    });
                }

                var headCreationFunctionMap = {
                    'static': createStaticHead,
                    'filesystem': createFilesystemHead,
                    'proxy': createProxyHead
                };
                var headCreationFunction =
                        headCreationFunctionMap[req.bodyParams.newHeadType];
                if (typeof headCreationFunction !== 'function') {
                    res.statusCode = 400;
                    res.headers['content-type'] = 'text/html';
                    res.send("Unknown head type '" +
                             req.bodyParams.newHeadType + "'");
                    return;
                }

                var head = headCreationFunction(req.bodyParams);
                config.robohydra.registerDynamicHead(head);
                res.headers.Location = robohydraAdminBaseUrlPath;
                res.statusCode = 302;
                res.end();
            }
        }),

        // Toggle RoboHydra head attachment.
        // This (DEPRECATED) URL and interface is pretty retarded, but
        // we'll keep it around for a while for compatibility purposes.
        // Once we don't want compatibility, the URL can be changed,
        // but the functionality has to stay for the web interface to
        // work
        new RoboHydraHead({
            name: 'toggleHeadAttachment',
            path: robohydraAdminBaseUrlPath + '/head/toggle-attachment',
            handler: function(req, res) {
                var pluginName = req.bodyParams.pluginName;
                var headName   = req.bodyParams.headName;
                var head = config.robohydra.findHead(pluginName, headName);
                if (head.attached()) {
                    head.detach();
                } else {
                    head.attach();
                }
                res.headers.Location = robohydraAdminBaseUrlPath;
                res.statusCode = 302;
                res.end();
            }
        }),

        // REST API =======================================================
        // Head information/attaching
        new RoboHydraHead({
            name: 'restHead',
            path: robohydraAdminBaseUrlPath + '/rest/plugins/:plugin/heads/:head',
            handler: function(req, res) {
                var pluginName = req.params.plugin;
                var headName   = req.params.head;
                var head = config.robohydra.findHead(pluginName, headName);

                if (req.method === 'POST') {
                    var newAttachedState = req.bodyParams.attached;
                    if (head.attached() && newAttachedState === 'false') {
                        head.detach();
                    }
                    if (! head.attached() && newAttachedState === 'true') {
                        head.attach();
                    }
                }

                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify({
                    plugin: pluginName,
                    name: headName,
                    attached: head.attached()
                }));
            }
        }),
        // Scenario information/activation
        new RoboHydraHead({
            name: 'restScenario',
            path: robohydraAdminBaseUrlPath + '/rest/plugins/:plugin/scenarios/:scenario',
            handler: function(req, res) {
                var rh = config.robohydra;
                var pluginName   = req.params.plugin;
                var scenarioName = req.params.scenario;
                var plugin = rh.getPlugin(pluginName);
                var scenario = plugin.scenarios[scenarioName];

                if (req.method === 'POST') {
                    var newActiveState = req.bodyParams.active;
                    var scenarioActive =
                            (rh.currentScenario.plugin === pluginName &&
                             rh.currentScenario.scenario === scenarioName);
                    if (scenarioActive && newActiveState === 'false') {
                        rh.stopScenario();
                    }
                    if (! scenarioActive && newActiveState === 'true') {
                        rh.startScenario(pluginName, scenarioName);
                    }
                }

                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify({
                    plugin: pluginName,
                    name: scenarioName,
                    active: (rh.currentScenario.plugin === pluginName &&
                             rh.currentScenario.scenario === scenarioName)
                }));
            }
        }),
        // Plugin information
        new RoboHydraHead({
            name: 'restPluginList',
            path: robohydraAdminBaseUrlPath + '/rest/plugins',
            handler: function(req, res) {
                var rh = config.robohydra;

                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify({
                    plugins: rh.getPluginNames().filter(function(pluginName) {
                        return pluginName[0] !== '*';
                    })
                }));
            }
        }),
        new RoboHydraHead({
            name: 'restPlugin',
            path: robohydraAdminBaseUrlPath + '/rest/plugins/:plugin',
            handler: function(req, res) {
                var pluginName = req.params.plugin;
                var plugin = config.robohydra.getPlugin(pluginName);
                var currentScenario = config.robohydra.currentScenario;

                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify({
                    name: pluginName,
                    heads: plugin.heads.map(function(h) {
                        return {
                            plugin: pluginName,
                            name: h.name,
                            attached: h.attached()
                        };
                    }),
                    scenarios: Object.keys(plugin.scenarios).map(function(s) {
                        return {
                            plugin: pluginName,
                            name: s,
                            active: (pluginName === currentScenario.plugin &&
                                     s === currentScenario.scenario)
                        };
                    })
                }));
            }
        }),
        new RoboHydraHead({
            name: 'restTestResults',
            path: robohydraAdminBaseUrlPath + '/rest/test-results',
            handler: function(req, res) {
                res.headers['content-type'] = 'application/json; charset=utf-8';
                res.send(JSON.stringify(config.robohydra.testResults));
            }
        }),

        // Admin UI index
        new RoboHydraHead({
            name: 'index',
            path: robohydraAdminBaseUrlPath,
            handler: function(req, res) {
                var highlightPath = req.queryParams.highlightPath || '';
                var stash = {plugins: config.robohydra.getPlugins(),
                             matchingHeads: [],
                             highlightPath: highlightPath};
                if (! showAdminPlugin) {
                    stash.plugins = stash.plugins.filter(function(plugin) {
                        return plugin.name !== '*admin*';
                    });
                }
                if (highlightPath) {
                    var matchingPair ;
                    while (1) {
                        matchingPair =
                                config.robohydra.headForPath(highlightPath,
                                                             matchingPair);
                        if (! matchingPair) {
                            break;
                        }

                        stash.matchingHeads.push({
                            plugin: matchingPair.plugin.name,
                            head:   matchingPair.head.name
                        });
                    }
                }

                renderPage(res, 'index', config, stash);
            }})
    ];
};
<|endoftext|>


<commit_before>/** @babel */

import {CompositeDisposable} from 'atom'
import FileList from './file-list'
import FileListViewModel from './file-list-view-model'
import DiffViewModel from './diff-view-model'
import Common from './common'

let FileListComponent = null
let DiffPaneItemComponent = null

module.exports = GitExperiment = {
  subscriptions: null,
  state: null,
  changesPanel: null,

  activate: (state) => {
    this.state = state

    atom.commands.add('atom-workspace', 'git:view-and-commit-changes', () => {
      GitExperiment.openChangesPanel()
    })

    atom.commands.add('atom-workspace', 'git:close-commit-panel', () => {
      GitExperiment.closeChangesPanel()
      let workspaceElement = atom.views.getView(atom.workspace)
      workspaceElement.focus()
    })

    atom.commands.add('atom-workspace', 'git:open-file-diff', () => {
      let editor = atom.workspace.getActiveTextEditor()
      let filePath = atom.project.relativizePath(editor.getPath())[1]
      atom.workspace.open(Common.DiffURI + filePath)
    })

    atom.commands.add('atom-workspace', 'git:refresh-status', () => {
      this.fileList.loadFromGitUtils()
    })

    this.subscriptions = new CompositeDisposable

    this.fileList = new FileList()
    this.fileList.loadFromGitUtils()

    process.nextTick(() => {
      this.subscriptions.add(atom.workspace.addOpener((filePath) => {
        if (!filePath.startsWith(Common.DiffURI)) return
        return createDiffPaneItem({
          uri: filePath,
          pathName: filePath.replace(Common.DiffURI, ''),
          fileList: this.fileList
        })
      }))
    })
  },

  serialize: () => {
    return {}
  },

  deactivate: () => {
    if (this.subscriptions)
      this.subscriptions.dispose()
  },

  closeChangesPanel: () => {
    if (this.changesPanel)
      this.changesPanel.hide()
  },

  openChangesPanel: () => {
    if (this.changesPanel) {
      this.changesPanel.show()
    }
    else {
      let fileListViewModel = new FileListViewModel(this.fileList)
      this.changesPanel = atom.workspace.addRightPanel({item: fileListViewModel})
    }
    let fileList = this.changesPanel.getItem()
    return atom.views.getView(fileList).focus()
  },
}

let createDiffPaneItem = ({uri, pathName, fileList}) => {
  let fileDiff = fileList.getFileDiffFromPathName(pathName)
  return new DiffViewModel({uri: uri, fileList: new FileList([fileDiff])})
}

atom.views.addViewProvider(DiffViewModel, (diffViewModel) => {
  if (!DiffPaneItemComponent)
    DiffPaneItemComponent = require('./diff-pane-item-component')
  let component = new DiffPaneItemComponent({diffViewModel})
  return component.element
})

atom.views.addViewProvider(FileListViewModel, (fileListViewModel) => {
  if (!FileListComponent)
    FileListComponent = require('./file-list-component')
  let component = new FileListComponent({fileListViewModel})
  return component.element
})

// Maybe add this later?
// atom.deserializers.add
//   name: 'GitHistoryView'
//   deserialize: (state) => createHistoryView(state)
<commit_msg>Serialize the diff pane item<commit_after>/** @babel */

import {CompositeDisposable} from 'atom'
import FileList from './file-list'
import FileListViewModel from './file-list-view-model'
import DiffViewModel from './diff-view-model'
import Common from './common'

let FileListComponent = null
let DiffPaneItemComponent = null
let fileList = null

module.exports = GitExperiment = {
  subscriptions: null,
  state: null,
  changesPanel: null,

  activate: (state) => {
    this.state = state

    atom.commands.add('atom-workspace', 'git:view-and-commit-changes', () => {
      GitExperiment.openChangesPanel()
    })

    atom.commands.add('atom-workspace', 'git:close-commit-panel', () => {
      GitExperiment.closeChangesPanel()
      let workspaceElement = atom.views.getView(atom.workspace)
      workspaceElement.focus()
    })

    atom.commands.add('atom-workspace', 'git:open-file-diff', () => {
      let editor = atom.workspace.getActiveTextEditor()
      let filePath = atom.project.relativizePath(editor.getPath())[1]
      atom.workspace.open(Common.DiffURI + filePath)
    })

    atom.commands.add('atom-workspace', 'git:refresh-status', () => {
      getFileListInstance().loadFromGitUtils()
    })

    this.subscriptions = new CompositeDisposable

    getFileListInstance().loadFromGitUtils()

    process.nextTick(() => {
      this.subscriptions.add(atom.workspace.addOpener((uri) => {
        if (uri.startsWith(Common.DiffURI))
          return createDiffPaneItem({ uri })
      }))
    })
  },

  serialize: () => {
    return {}
  },

  deactivate: () => {
    if (this.subscriptions)
      this.subscriptions.dispose()
  },

  closeChangesPanel: () => {
    if (this.changesPanel)
      this.changesPanel.hide()
  },

  openChangesPanel: () => {
    if (this.changesPanel) {
      this.changesPanel.show()
    }
    else {
      let fileListViewModel = new FileListViewModel(getFileListInstance())
      this.changesPanel = atom.workspace.addRightPanel({item: fileListViewModel})
    }
    let fileList = this.changesPanel.getItem()
    return atom.views.getView(fileList).focus()
  },
}

let getFileListInstance = () => {
  if (!fileList)
    fileList = new FileList()
  return fileList
}

let createDiffPaneItem = ({uri}) => {
  let pathName = uri.replace(Common.DiffURI, '')
  let fileDiff = getFileListInstance().getOrCreateFileFromPathName(pathName)
  return new DiffViewModel({
    uri: uri,
    deserializer: 'GitDiffPaneItem',
    fileList: new FileList([fileDiff])
  })
}

atom.deserializers.add({
  name: 'GitDiffPaneItem',
  deserialize: (state) => {
    return createDiffPaneItem(state)
  }
})

atom.views.addViewProvider(DiffViewModel, (diffViewModel) => {
  if (!DiffPaneItemComponent)
    DiffPaneItemComponent = require('./diff-pane-item-component')
  let component = new DiffPaneItemComponent({diffViewModel})
  return component.element
})

atom.views.addViewProvider(FileListViewModel, (fileListViewModel) => {
  if (!FileListComponent)
    FileListComponent = require('./file-list-component')
  let component = new FileListComponent({fileListViewModel})
  return component.element
})
<|endoftext|>