Remove welcome litcoffee file

Remove welcome litcoffee file

Welcome to the welcome file.

This script runs on the welcome page, which welcomes new users, to make it more welcoming.
If you haven't already, I welcome you to view the more important [main script](client.litcoffee).

Also, if you haven't noticed yet, I'm trying my best to use the word welcome as many times as I can just to welcome you.

First off, the buttons big, green, welcoming buttons on the bottom of the welcome page are assigned event listeners so that they can make the page show more welcoming information.

    for nextButton in document.getElementsByClassName "next"
      nextButton.addEventListener "click", advance = ->
        # The box holding the individual pages that ge scrolled
        # when pressing the "next" button is assigned to a varialbe.
        container = document.body
        # show the next page
        view = +container.getAttribute("data-view")
        window.scrollTo(0,0) # Scoll to top of the page
        history.pushState {page: view+1}, "", "?page=#{view}" # Make the broswer register a page shift
        (npage = document.querySelector("section:nth-child(#{view+2})")).style.display = "inline-block"
        npage.style.transform = npage.style.webkitTransform = npage.style.MozTransform = "translateX(#{view*100}%)"
        # increase the data-view attribute by 1. The rest is handled by the css.
        container.setAttribute "data-view", view+1
        setTimeout ->
          #After animating is done, don't display the first page
          npage.style.transform = npage.style.webkitTransform = npage.style.MozTransform = "translateX(#{view+1}00%)"
          document.querySelector("section:nth-child(#{view+1})").style.display = "none"
        , 50
        return

Additionally, the active class needs to be added when text fields are selected (for the login box) [copied from main script].

    for input in document.querySelectorAll "input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], input[type=number], input[type=search]"
      input.addEventListener "change", (evt) ->
        evt.target.parentNode.querySelector("label").classList.add "active"
      input.addEventListener "focus", (evt) ->
        evt.target.parentNode.querySelector("label").classList.add "active"
      input.addEventListener "blur", (evt) ->
        if evt.target.value.length is 0
          evt.target.parentNode.querySelector("label").classList.remove "active"

An event listener is attached to the window so that when the back button is pressed, a more welcoming page is displayed.
Most of the code is the same from next button event listener, except that the page is switched the previous one, not the next one.

    window.onpopstate = (event) ->
      container = document.body
      view = if event.state? then event.state.page else 0
      window.scrollTo(0,0) # Scoll to top of the page
      (npage = document.querySelector("section:nth-child(#{view+1})")).style.display = "inline-block"
      npage.style.transform = npage.style.webkitTransform = npage.style.MozTransform = "translateX(#{view*100}%)"
      # increase the data-view attribute by 1. The rest is handled by the css.
      container.setAttribute "data-view", view
      setTimeout ->
        #After animating is done, don't display the first page
        npage.style.transform = npage.style.webkitTransform = npage.style.MozTransform = "translateX(#{view}00%)"
        document.querySelector("section:nth-child(#{view+2})").style.display = "none"
      , 50
      return

When there is an error with parsing the data for Athena, a welcoming warning needs to be displayed (and the data needs to be saved too).

This code below is copied from the main script with two unwelcome lines commented out.

    parseAthenaData = (dat) ->
      if dat is ""
        athenaData = null
        localStorage.removeItem "athenaData"
      else
        try
          d = JSON.parse dat
          athenaData2 = {}
          allCourseDetails = {}
          for section in d.body.courses.sections
            allCourseDetails[section.course_nid] = section
          for course in d.body.courses.courses by -1
            courseDetails = allCourseDetails[course.nid]
            athenaData2[course.course_title] =
              link: "https://athena.harker.org"+courseDetails.link
              logo: courseDetails.logo.substr(0, courseDetails.logo.indexOf("\" alt=\"")).replace("
<img src=\"", "").replace("tiny", "reg")
              period: courseDetails.section_title
          athenaData = athenaData2
          localStorage["athenaData"] = JSON.stringify athenaData
          document.getElementById("athenaDataError").style.display = "none"
          #document.getElementById("athenaDataRefresh").style.display = "block"
        catch e
          document.getElementById("athenaDataError").style.display = "block"
          #document.getElementById("athenaDataRefresh").style.display = "none"
          document.getElementById("athenaDataError").innerHTML = e.message
      return

The text box also needs to execute this function when anything is typed / pasted.

    document.getElementById("athenaData").addEventListener "input", (evt) ->
      parseAthenaData evt.target.value

To avoid some unwelcoming errors, some constants are defined.

    loginURL = ""
    loginHeaders = {}
    viewData = {} # The data to send when switching PCR views

The following code is copied from the main script and slightly modified since there is no login dialog.

    send = (url, respType, headers, data, progress=false) ->
      return new Promise (resolve, reject) ->
        req = new XMLHttpRequest()
        req.open (if data? then "POST" else "GET"), url, true

        progressElement = document.getElementById "progress"
        progressInner = progressElement.querySelector "div"
        if progress
          progressElement.offsetWidth # Wait for it to render
          progressElement.classList.add "active"
          if progressInner.classList.contains "determinate"
            progressInner.classList.remove "determinate"
            progressInner.classList.add "indeterminate"

        load = localStorage["load"] or 170000

        req.onload = (evt) ->
          localStorage["load"] = evt.loaded
          progressElement.classList.remove "active" if progress
          if req.status is 200
            resolve req
          else
            reject Error req.statusText
          return

        req.onerror = ->
          progressElement.classList.remove "active" if progress
          reject Error "Network Error"
          return
        if progress
          req.onprogress = (evt) ->
            if progressInner.classList.contains "indeterminate"
              progressInner.classList.remove "indeterminate"
              progressInner.classList.add "determinate"
            progressInner.style.width = 100 * evt.loaded / (if evt.lengthComputable then evt.total else load) + "%"

        if respType?
          req.responseType = respType;
        if headers?
          for headername, header of headers
            req.setRequestHeader headername, header
        if data?
          req.send data
        else
          req.send()
        return

    getCookie = (cname) ->
      name = cname + "="
      ca = document.cookie.split(";")
      i = 0
      while i < ca.length
        c = ca[i]
        while c.charAt(0) is " "
          c = c.substring(1)
        if c.indexOf(name) isnt -1
          return c.substring(name.length, c.length)
        i++
      "" # Blank if cookie not found

    setCookie = (cname, cvalue, exdays) ->
      d = new Date
      d.setTime d.getTime() + exdays * 24 * 60 * 60 * 1000
      expires = "expires=" + d.toUTCString()
      document.cookie = cname + "=" + cvalue + "; " + expires
      return

    snackbar = (message, action, f) ->
      snack = element "div", "snackbar"
      snackInner = element "div", "snackInner", message
      snack.appendChild snackInner
      if action? and f?
        actionE = element "a", [], action
        actionE.addEventListener "click", ->
            snack.classList.remove "active"
            f()
        snackInner.appendChild actionE

      add = ->
        document.body.appendChild snack
        snack.offsetHeight
        snack.classList.add "active"
        setTimeout ->
          snack.classList.remove "active"
          setTimeout ->
            snack.remove()
          , 900
        , 5000

      existing = document.querySelector(".snackbar")
      if existing?
        existing.classList.remove "active"
        setTimeout add, 300
      else
        add()
      return

    element = (tag, cls, html, id) ->
      e = document.createElement tag
      if typeof cls is "string"
        e.classList.add cls
      else
        for c in cls
          e.classList.add c
      if html?
        e.innerHTML = html
      if id?
        e.setAttribute "id", id
      e

    dologin = (val, submitEvt=false) ->
      document.getElementById("login").classList.remove "active"
      # setTimeout ->
      #   document.getElementById("loginBackground").style.display = "none"
      # , 350
      postArray = [] # Array of data to post
      localStorage["username"] = if val? and not submitEvt then val[0] else document.getElementById("username").value
      # updateAvatar()
      for h of loginHeaders # Loop through the input elements contained in the login page. As mentioned before, they will be sent to PCR to log in.
        if h.toLowerCase().indexOf("user") isnt -1
          loginHeaders[h] = if val? and not submitEvt then val[0] else document.getElementById("username").value
        if h.toLowerCase().indexOf("pass") isnt -1
          loginHeaders[h] = if val? and not submitEvt then val[1] else document.getElementById("password").value
        postArray.push encodeURIComponent(h) + "=" + encodeURIComponent(loginHeaders[h])

      # Now send the login request to PCR
      if location.protocol is "chrome-extension:"
        console.time "Logging in"
        send loginURL, "document", { "Content-type": "application/x-www-form-urlencoded" }, postArray.join("&"), true
          .then (resp) ->
            console.timeEnd "Logging in"
            if resp.responseURL.indexOf("Login") isnt -1
              # If PCR still wants us to log in, then the username or password enterred were incorrect.
              document.getElementById("loginIncorrect").style.display = "block"
              document.getElementById("password").value = ""

              #document.getElementById("login").classList.add "active"
              #document.getElementById("loginBackground").style.display = "block"
            else
              # Otherwise, we are logged in
              if document.getElementById("remember").checked #Is the "remember me" checkbox checked?
                setCookie "userPass", window.btoa(document.getElementById("username").value + ":" + document.getElementById("password").value), 14 # Set a cookie with the username and password so we can log in automatically in the future without having to prompt for a username and password again
              # loadingBar.style.display = "none"
              t = Date.now()
              localStorage["lastUpdate"] = t
              #document.getElementById("lastUpdate").innerHTML = formatUpdate t
              try
                parse resp.response # Parse the data PCR has replied with
                display() # Added
              catch e
                console.log e
                alert "Error parsing assignments. Is PCR on list or month view?"
              return
          , (error) ->
            console.log "Could not log in to PCR. Either your network connection was lost during your visit or PCR is just not working. Here's the error:", error
      else
        console.log postArray
        send "/api/login?remember=#{document.getElementById("remember").checked}", "json", { "Content-type": "application/x-www-form-urlencoded" }, postArray.join("&"), true
          .then (resp) ->
            console.debug "Logging in:",resp.response.time
            if resp.response.login
              # If PCR still wants us to log in, then the username or password enterred were incorrect.
              document.getElementById("loginIncorrect").style.display = "block"
              document.getElementById("password").value = ""

              # document.getElementById("login").classList.add "active"
              # document.getElementById("loginBackground").style.display = "block"
            else
              t = Date.now()
              localStorage["lastUpdate"] = t
              #document.getElementById("lastUpdate").innerHTML = formatUpdate t

              window.data = resp.response.data
              display()
              localStorage["data"] = JSON.stringify(data)
            return
          , (error) ->
            console.log "Could not log in to PCR. Either your network connection was lost during your visit or PCR is just not working. Here's the error:", error
      return

    attachmentify = (element) ->
      attachments = []

      # Get all links
      as = element.getElementsByTagName('a')

      a = 0
      while a < as.length
        if as[a].id.indexOf('Attachment') isnt -1
          attachments.push [
            as[a].innerHTML
            as[a].search + as[a].hash
          ]
          as[a].remove()
          a-- # subtract because all elements have shifted down
        a++
      attachments

    urlify = (text) ->
      text.replace /// (
        https?:\/\/                  # http:// or https://
        [-A-Z0-9+&@#\/%?=~_|!:,.;]*  # Any number of url-OK characters
        [-A-Z0-9+&@#\/%=~_|]+        # At least one url-OK character except ?, !, :, ,, ., and ;
      )///ig, (str, str2, offset) -> # Function to replace matches
        if /href\s*=\s*./.test(text.substring(offset - 10, offset)) or
          /originalpath\s*=\s*./.test(text.substring(offset - 20, offset))
          then str else '' + str + ''

    findId = (element, tag, id) ->
      for e in element.getElementsByTagName(tag)
        if e.id.indexOf(id) isnt -1
          return e
      return

    parse = (doc) ->
      console.time "Handling data" # To time how long it takes to parse the assignments
      handledDataShort = [] # Array used to make sure we don"t parse the same assignment twice.
      window.data = {classes: [], assignments: [], monthView: doc.querySelector(".rsHeaderMonth").parentNode.classList.contains("rsSelected")} # Reset the array in which all of your assignments are stored in.

      for e in doc.getElementsByTagName("input")
        viewData[e.name] = e.value or ""

      # Now, the classes you take are parsed (these are the checkboxes you see up top when looking at PCR).

      classes = findId(doc, "table", "cbClasses").getElementsByTagName("label")
      for c in classes
        window.data.classes.push c.innerHTML

      assignments = doc.getElementsByClassName("rsApt rsAptSimple")

      # Now parse the assignments

      for ca in assignments
        assignment = {}

        # The starting date and ending date of the assignment are parsed first
        range = findId(ca, "span", "StartingOn").innerHTML.split(" - ")
        assignment.start = Math.floor (Date.parse range[0])/1000/3600/24
        assignment.end = if range[1]? then Math.floor (Date.parse range[1])/1000/3600/24 else assignment.start

        # Then, the name of the assignment is parsed
        t = findId(ca, "span", "lblTitle")
        title = t.innerHTML

        # The actual body of the assignment and its attachments are parsed next
        b = t.parentNode.parentNode
        divs = b.getElementsByTagName("div")
        for d in [0...2]
          divs[0].remove()
        ap = attachmentify(b) # Separates attachments from the body
        assignment.attachments = ap
        assignment.body = urlify(b.innerHTML).replace(/^(?:\s*
)*/, "").replace(/(?:\s*
)*\s*$/, "").trim() # The replaces remove leading and trailing newlines

        # Finally, we separate the class name and type (homework, classwork, or projects) from the title of the assignment
        assignment.type = title.match(/\(([^)]*\)*)\)$/)[1].toLowerCase().replace("& quizzes", "").replace("tests", "test")
        assignment.baseType = (ca.title.substring 0, ca.title.indexOf "\n").toLowerCase().replace("& quizzes", "").replace(/\s/g, "")
        for c, pos in window.data.classes
          if title.indexOf(c) isnt -1
            assignment.class = pos
            title = title.replace(c,"")
            break
        assignment.title = title.substring(title.indexOf(": ") + 2).replace(/\([^\(\)]*\)$/,"").trim()

        # To make sure there are no repeats, the title of the assignment (only letters) and its start & end date are combined to give it a unique identifier.
        assignment.id = assignment.title.replace(/[^\w]*/g, "") + (assignment.start + assignment.end)
        if handledDataShort.indexOf(assignment.id) is -1 # Make sure we haven't already parsed the assignment
          handledDataShort.push assignment.id
          window.data.assignments.push assignment

      console.timeEnd "Handling data"

      # Now allow the view to be switched
      document.body.classList.add "loaded"

      #display() # Display the data
      localStorage["data"] = JSON.stringify(data) # Store for offline use
      return

A slightly modified fetch function is then called

    do ->
      if location.protocol is "chrome-extension:"
        console.time "Fetching assignments"
        send "https://webappsca.pcrsoft.com/Clue/SC-Assignments-End-Date-Range/7536", "document"
          .then (resp) ->
            console.timeEnd "Fetching assignments"
            if resp.responseURL.indexOf("Login") isnt -1
              # We have to log in now
              loginURL = resp.responseURL
              for e in resp.response.getElementsByTagName("input")
                loginHeaders[e.name] = e.value or ""
              console.log "Need to log in"
              ### up = getCookie("userPass") # Attempts to get the cookie *userPass*, which is set if the "Remember me" checkbox is checked when logging in through CheckPCR
              if up is ""
                document.getElementById("loginBackground").style.display = "block"
                document.getElementById("login").classList.add "active"
              else
                dologin window.atob(up).split(":") # Because we were remembered, we can log in immediately without waiting for the user to log in through the login form
              ###
              # Add login button event listeners and enable the login button
              document.getElementById("login").classList.add "ready"
              document.getElementById("login").addEventListener "submit", (evt) ->
                evt.preventDefault()
                dologin(null, true)
            else
              # Logged in now
              console.log "Fetching assignments successful"
              t = Date.now()
              localStorage["lastUpdate"] = t
              # document.getElementById("lastUpdate").innerHTML = formatUpdate t
              try
                parse resp.response
              catch e
                console.log e
                alert "Error parsing assignments. Is PCR on list or month view?"
              document.getElementById("loginNext").style.display = ""
              document.getElementById("login").classList.add "done"
            return
          , (error) ->
            console.log "Could not fetch assignments; You are probably offline. Here's the error:", error
            snackbar "You must be online to set up Check PCR. Please refresh when the internet works."
            return
      else
        send "/api/start", "json"
          .then (resp) ->
            console.debug "Fetching assignments:",resp.response.time
            if resp.response.login
              loginHeaders = resp.response.loginHeaders
              # document.getElementById("loginBackground").style.display = "block"
              # document.getElementById("login").classList.add "active"
              # Add login button event listeners and enable the login button
              document.getElementById("login").classList.add "ready"
              document.getElementById("login").addEventListener "submit", (evt) ->
                evt.preventDefault()
                dologin(null, true)
            else
              console.log "Fetching assignments successful"
              t = Date.now()
              localStorage["lastUpdate"] = t
              # document.getElementById("lastUpdate").innerHTML = formatUpdate t

              window.data = resp.response.data
              localStorage["data"] = JSON.stringify(data)
              document.getElementById("loginNext").style.display = ""
              document.getElementById("login").classList.add "done"
            return
          , (error) ->
            console.log "Could not fetch assignments; You are probably offline. Here's the error:", error
            snackbar "Could not fetch your assignments", "Retry", fetch
      return

The display function is set to advance the setup proccess so there aren't any unwelcoming errors.

    display = advance


markdown fix

markdown fix

Gist of SVG elements

    Tags =
     svg: 'a altGlyph altGlyphDef altGlyphItem animate animateColor
      animateMotion animateTransform circle clipPath color-profile cursor
      defs desc ellipse feBlend feColorMatrix feComponentTransfer
      feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap
      feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
      feImage feMerge feMergeNode feMorphology feOffset fePointLight
      feSpecularLighting feSpotLight feTile feTurbulence
      filter font font-face font-face-format font-face-name font-face-src
      font-face-uri foreignObject g glyph glyphRef hkern image line
      linearGradient marker mask metadata missing-glyph mpath path pattern
      polygon polyline radialGradient rect script set stop style svg symbol
      text textPath title tref tspan use view vkern switch foreignObject'

List of HTML elements

     html: 'a abbr address article aside audio b bdi bdo blockquote body button
      canvas caption cite code colgroup datalist dd del details dfn div dl dt
      em fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header
      hgroup html i iframe ins kbd label legend li main map mark menu meter
      nav noscript object ol optgroup option output p pre progress q rp rt
      ruby s samp script section select small span strong style sub summary
      sup table tbody td textarea tfoot th thead time title tr u ul video'

     htmlVoid: 'area base br col command embed hr img input keygen link meta
      param source track wbr'

Wrapper for browser API

    Api =
     document: @document

Parse id and class string

    parseIdClass = (str) ->
     res =
      id: null
      class: []

     for c, i in str.split "."
      if c.indexOf("#") is 0
       res.id = c.substr 1
      else if c isnt ""
       if not res.class?
        res.class = [c]
       else
        res.class.push c

     return res

    getParameters = (args) ->
     params =
      idClass: null
      text: null
      attrs: null
      func: null

     for arg, i in args
      switch typeof arg
       when 'function' then params.func = arg
       when 'object' then params.attrs = arg
       when 'string'
        if args.length is 1
         params.text = arg
        else
         c = arg.charAt 0

         if i is 0 and (c is '#' or c is '.')
          params.idClass = parseIdClass arg
         else
          params.text = arg

     return params

# Weya DOM

    weyaDomCreate = ->

Weya object to be passed as `this`

     weya =
      _elem: null

## Manipulating dom objects

Set styles

     setStyles = (elem, styles) ->
      for k, v of styles
       if v?
        elem.style.setProperty k, v
       else
        elem.style.removeProperty k

Set events

     setEvents = (elem, events) ->
      for k, v of events
       elem.addEventListener k, v, false

Set data

     setData = (elem, data) ->
      for k, v of data
       elem[k] = v

Set attributes

     setAttributes = (elem, attrs) ->
      for k, v of attrs
       switch k
        when 'style' then setStyles elem, v
        when 'on' then setEvents elem, v
        when 'data' then setData elem, v
        else
         if v?
          elem.setAttribute k, v
         else
          elem.removeAttribute k

Set ID and Class with `classList`

     setIdClass = (elem, idClass) ->
      if idClass.id?
       elem.id = idClass.id

      if idClass.class?
       for c in idClass.class
        elem.classList.add c

Set ID and Class with setAttributes

     setIdClassFallback = (elem, idClass) ->
      if idClass.id?
       elem.id = idClass.id

      if idClass.class?
       className = idClass.class.join ' '
       elem.setAttribute 'class', className

Check whether classList is available and switch to fallback otherwise

     switchIdClass = ->
      elem = Api.document.createElementNS "http://www.w3.org/2000/svg", 'g'
      if not elem.classList
       setIdClass = setIdClassFallback

Switch to fall back if no classList

     switchIdClass() if Api.document?

Append a child element

     append = (ns, name, args) ->
      params = getParameters args

Keep a reference to parent element

      pElem = @_elem

Keep a reference of `elem` to return at the end of the function

      if ns?
       elem = @_elem = Api.document.createElementNS ns, name
      else
       elem = @_elem = Api.document.createElement name

      if params.idClass?
       setIdClass elem, params.idClass

      if params.attrs?
       setAttributes elem, params.attrs

      if pElem?
       pElem.appendChild elem

      if params.func?
       params.func.call this
      else if params.text?
       elem.textContent = params.text

      @_elem = pElem
      return elem

Wrap `append`

     wrapAppend = (ns, name) ->
      ->
       append.call this, ns, name, arguments

Initialize

     for name in Tags.svg.split ' '
      weya[name] = wrapAppend "http://www.w3.org/2000/svg", name

     for name in Tags.html.split ' '
      weya[name] = wrapAppend "http://www.w3.org/1999/xhtml", name

     for name in Tags.htmlVoid.split ' '
      weya[name] = wrapAppend null, name

     return weya

# Weya Markup

    weyaMarkupCreate = ->

Weya object to be passed as `this`

     weya =
      _buf: null
      _indent: 0

Render components

     setStyles = (buf, styles) ->
      buf.push " style=\""
      for k, v of styles
       buf.push "#{k}:#{v};"
      buf.push "\""

     setEvents = (buf, events) ->
      for k, v of events
       buf.push " on#{k}=\"#{v}\""

     setData = (buf, data) ->
      for k, v of data
       buf.push " data-#{k}=\"#{v}\""

     setAttributes = (buf, attrs) ->
      for k, v of attrs
       switch k
        when 'style' then setStyles buf, v
        when 'on' then setEvents buf, v
        when 'data' then setData buf, v
        else
         buf.push " #{k}=\"#{v}\""

     setIndent = (buf, indent) ->
      for i in [0...indent]
       buf.push " "

Append a child element

     append = (ns, name, isVoid, args) ->
      params = getParameters args

      buf = @_buf

      setIndent buf, @_indent

      buf.push "<#{name}"

      if params.idClass?
       if params.idClass.id?
        buf.push " id=\"#{params.idClass.id}\""
       if params.idClass.class?
        className = params.idClass.class.join ' '
        buf.push " class=\"#{className}\""

      if params.attrs?
       setAttributes buf, params.attrs

Can close void elements (element that self close) with a `/>`

      if isVoid
       buf.push "/>\n"
       if params.func? or params.text?
        throw new Error 'Void tags cannot have content'

      else
       buf.push ">\n"
       @_indent++

       if params.func?
        params.func.call this
       else if params.text?
        setIndent buf, @_indent
        buf.push params.text
        buf.push "\n"

       @_indent--
       setIndent buf, @_indent
       buf.push "</#{name}>\n"

Wrap `append`

     wrapAppend = (ns, name, isVoid) ->
      ->
       append.call this, ns, name, isVoid, arguments

Initialize

     for name in Tags.svg.split ' '
      weya[name] = wrapAppend "http://www.w3.org/2000/svg", name, false

     for name in Tags.html.split ' '
      weya[name] = wrapAppend null, name, false

     for name in Tags.htmlVoid.split ' '
      weya[name] = wrapAppend null, name, true

     return weya

# Weya API

    weyaDom = weyaDomCreate()
    weyaMarkup = weyaMarkupCreate()

Create and append to `options.elem`. If `options.context` is provied it can be
accessed via `@$`. If `options.elem` is `null`, the element is created but
not appended.

    @Weya = Weya = (options, func) ->
     weya = weyaDom
     pContext = weya.$
     weya.$ = options.context
     helpersAdded = []

     if options.helpers?
      for name, helper of options.helpers
       if not weya[name]?
        helpersAdded.push name
        weya[name] = helper.bind weya

     pElem = weya._elem
     weya._elem = options.elem
     r = func?.call weya
     weya._elem = pElem
     weya.$ = pContext

     for name in helpersAdded
      delete weya[name]

     return r

    Weya.markup = (options, func) ->
     weya = weyaMarkup
     pContext = weya.$
     weya.$ = options.context
     helpersAdded = []

     if options.helpers?
      for name, helper of options.helpers
       if not weya[name]?
        helpersAdded.push name
        weya[name] = helper.bind weya

     pBuf = weya._buf
     weya._buf = []
     r = func?.call weya
     buf = weya._buf
     weya._buf = pBuf
     weya.$ = pContext

     for name in helpersAdded
      delete weya[name]

     return buf.join ''

    Weya.setApi = (api) ->
     for k, v of api
      Api[k] = v
     weyaDom = weyaDomCreate()
     weyaMarkup = weyaMarkupCreate()

    if module?
     module.exports = Weya

Gist of SVG elements

    Tags =
     svg: 'a altGlyph altGlyphDef altGlyphItem animate animateColor
      animateMotion animateTransform circle clipPath color-profile cursor
      defs desc ellipse feBlend feColorMatrix feComponentTransfer
      feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap
      feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
      feImage feMerge feMergeNode feMorphology feOffset fePointLight
      feSpecularLighting feSpotLight feTile feTurbulence
      filter font font-face font-face-format font-face-name font-face-src
      font-face-uri foreignObject g glyph glyphRef hkern image line
      linearGradient marker mask metadata missing-glyph mpath path pattern
      polygon polyline radialGradient rect script set stop style svg symbol
      text textPath title tref tspan use view vkern switch foreignObject'

List of HTML elements

     html: 'a abbr address article aside audio b bdi bdo blockquote body button
      canvas caption cite code colgroup datalist dd del details dfn div dl dt
      em fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header
      hgroup html i iframe ins kbd label legend li main map mark menu meter
      nav noscript object ol optgroup option output p pre progress q rp rt
      ruby s samp script section select small span strong style sub summary
      sup table tbody td textarea tfoot th thead time title tr u ul video'

     htmlVoid: 'area base br col command embed hr img input keygen link meta
      param source track wbr'

Wrapper for browser API

    Api =
     document: @document

Parse id and class string

    parseIdClass = (str) ->
     res =
      id: null
      class: []

     for c, i in str.split "."
      if c.indexOf("#") is 0
       res.id = c.substr 1
      else if c isnt ""
       if not res.class?
        res.class = [c]
       else
        res.class.push c

     return res

    getParameters = (args) ->
     params =
      idClass: null
      text: null
      attrs: null
      func: null

     for arg, i in args
      switch typeof arg
       when 'function' then params.func = arg
       when 'object' then params.attrs = arg
       when 'string'
        if args.length is 1
         params.text = arg
        else
         c = arg.charAt 0

         if i is 0 and (c is '#' or c is '.')
          params.idClass = parseIdClass arg
         else
          params.text = arg

     return params

#Weya DOM

    weyaDomCreate = ->

Weya object to be passed as `this`

     weya =
      _elem: null

##Manipulating dom objects

Set styles

     setStyles = (elem, styles) ->
      for k, v of styles
       if v?
        elem.style.setProperty k, v
       else
        elem.style.removeProperty k

Set events

     setEvents = (elem, events) ->
      for k, v of events
       elem.addEventListener k, v, false

Set data

     setData = (elem, data) ->
      for k, v of data
       elem[k] = v

Set attributes

     setAttributes = (elem, attrs) ->
      for k, v of attrs
       switch k
        when 'style' then setStyles elem, v
        when 'on' then setEvents elem, v
        when 'data' then setData elem, v
        else
         if v?
          elem.setAttribute k, v
         else
          elem.removeAttribute k

Set ID and Class with `classList`

     setIdClass = (elem, idClass) ->
      if idClass.id?
       elem.id = idClass.id

      if idClass.class?
       for c in idClass.class
        elem.classList.add c

Set ID and Class with setAttributes

     setIdClassFallback = (elem, idClass) ->
      if idClass.id?
       elem.id = idClass.id

      if idClass.class?
       className = idClass.class.join ' '
       elem.setAttribute 'class', className

Check whether classList is available and switch to fallback otherwise

     switchIdClass = ->
      elem = Api.document.createElementNS "http://www.w3.org/2000/svg", 'g'
      if not elem.classList
       setIdClass = setIdClassFallback

Switch to fall back if no classList

     switchIdClass() if Api.document?

Append a child element

     append = (ns, name, args) ->
      params = getParameters args

Keep a reference to parent element

      pElem = @_elem

Keep a reference of `elem` to return at the end of the function

      if ns?
       elem = @_elem = Api.document.createElementNS ns, name
      else
       elem = @_elem = Api.document.createElement name

      if params.idClass?
       setIdClass elem, params.idClass

      if params.attrs?
       setAttributes elem, params.attrs

      if pElem?
       pElem.appendChild elem

      if params.func?
       params.func.call this
      else if params.text?
       elem.textContent = params.text

      @_elem = pElem
      return elem

Wrap `append`

     wrapAppend = (ns, name) ->
      ->
       append.call this, ns, name, arguments

Initialize

     for name in Tags.svg.split ' '
      weya[name] = wrapAppend "http://www.w3.org/2000/svg", name

     for name in Tags.html.split ' '
      weya[name] = wrapAppend "http://www.w3.org/1999/xhtml", name

     for name in Tags.htmlVoid.split ' '
      weya[name] = wrapAppend null, name

     return weya

#Weya Markup

    weyaMarkupCreate = ->

Weya object to be passed as `this`

     weya =
      _buf: null
      _indent: 0

Render components

     setStyles = (buf, styles) ->
      buf.push " style=\""
      for k, v of styles
       buf.push "#{k}:#{v};"
      buf.push "\""

     setEvents = (buf, events) ->
      for k, v of events
       buf.push " on#{k}=\"#{v}\""

     setData = (buf, data) ->
      for k, v of data
       buf.push " data-#{k}=\"#{v}\""

     setAttributes = (buf, attrs) ->
      for k, v of attrs
       switch k
        when 'style' then setStyles buf, v
        when 'on' then setEvents buf, v
        when 'data' then setData buf, v
        else
         buf.push " #{k}=\"#{v}\""

     setIndent = (buf, indent) ->
      for i in [0...indent]
       buf.push " "

Append a child element

     append = (ns, name, isVoid, args) ->
      params = getParameters args

      buf = @_buf

      setIndent buf, @_indent

      buf.push "<#{name}"

      if params.idClass?
       if params.idClass.id?
        buf.push " id=\"#{params.idClass.id}\""
       if params.idClass.class?
        className = params.idClass.class.join ' '
        buf.push " class=\"#{className}\""

      if params.attrs?
       setAttributes buf, params.attrs

Can close void elements (element that self close) with a `/>`

      if isVoid
       buf.push "/>\n"
       if params.func? or params.text?
        throw new Error 'Void tags cannot have content'

      else
       buf.push ">\n"
       @_indent++

       if params.func?
        params.func.call this
       else if params.text?
        setIndent buf, @_indent
        buf.push params.text
        buf.push "\n"

       @_indent--
       setIndent buf, @_indent
       buf.push "</#{name}>\n"

Wrap `append`

     wrapAppend = (ns, name, isVoid) ->
      ->
       append.call this, ns, name, isVoid, arguments

Initialize

     for name in Tags.svg.split ' '
      weya[name] = wrapAppend "http://www.w3.org/2000/svg", name, false

     for name in Tags.html.split ' '
      weya[name] = wrapAppend null, name, false

     for name in Tags.htmlVoid.split ' '
      weya[name] = wrapAppend null, name, true

     return weya

#Weya API

    weyaDom = weyaDomCreate()
    weyaMarkup = weyaMarkupCreate()

Create and append to `options.elem`. If `options.context` is provied it can be
accessed via `@$`. If `options.elem` is `null`, the element is created but
not appended.

    @Weya = Weya = (options, func) ->
     weya = weyaDom
     pContext = weya.$
     weya.$ = options.context
     helpersAdded = []

     if options.helpers?
      for name, helper of options.helpers
       if not weya[name]?
        helpersAdded.push name
        weya[name] = helper.bind weya

     pElem = weya._elem
     weya._elem = options.elem
     r = func?.call weya
     weya._elem = pElem
     weya.$ = pContext

     for name in helpersAdded
      delete weya[name]

     return r

    Weya.markup = (options, func) ->
     weya = weyaMarkup
     pContext = weya.$
     weya.$ = options.context
     helpersAdded = []

     if options.helpers?
      for name, helper of options.helpers
       if not weya[name]?
        helpersAdded.push name
        weya[name] = helper.bind weya

     pBuf = weya._buf
     weya._buf = []
     r = func?.call weya
     buf = weya._buf
     weya._buf = pBuf
     weya.$ = pContext

     for name in helpersAdded
      delete weya[name]

     return buf.join ''

    Weya.setApi = (api) ->
     for k, v of api
      Api[k] = v
     weyaDom = weyaDomCreate()
     weyaMarkup = weyaMarkupCreate()

    if module?
     module.exports = Weya


The description here got out of sync from the actual implementation

# Type Functions

    {curry}  = require "fairmont-core"

## type

Return the constructor function of the value, or `undefined`.

    type = (x) -> x?.constructor

## isType

    isType = curry (t, x) -> type(x) == t

## instanceOf

    instanceOf = curry (t, x) -> x instanceof t

## isNumber

    isNumber = isType Number

## isNaN

    isNaN = (n) -> Number.isNaN n

## isFinite

    isFinite = (n) -> Number.isFinite n

## isInteger

    isInteger = (n) -> Number.isInteger n

## isFloat

Adapted from [StackOverflow][isFloat].

[isFloat]:http://stackoverflow.com/questions/3885817/how-to-check-if-a-number-is-float-or-integer/3885844#3885844

    isFloat = (n) -> n == +n && n != (n|0)

## isBoolean

    isBoolean = isType Boolean

## isDate

    isDate = isType Date

## isRegExp

    isRegExp = isType RegExp

## isString

    isString = isType String

## isFunction

    isFunction = isType Function

## isObject

    isObject = isType Object

## isArray

    isArray = isType Array

## isDefined

    isDefined = (x) -> x?

## isGenerator

    GeneratorFunction = (-> yield null).constructor

    isGenerator = isType GeneratorFunction

## isPromise

    isPromise = (x) -> x?.then? && isFunction x.then

---

    module.exports = {type, isType, instanceOf,
      isBoolean, isNumber, isNaN, isFinite, isInteger, isFloat,
      isString, isFunction, isObject, isArray, isDefined,
      isRegExp, isDate, isGenerator, isPromise}

# Type Functions

    {curry}  = require "fairmont-core"

## type

Get the type of a value. Possible values are: `number`, `string`, '`boolean`, `date`, `regexp`, `function`, `array`, `object`, `null`, `undefined`.

    type = (x) -> x?.constructor

## isType

    isType = curry (t, x) -> type(x) == t

## instanceOf

    instanceOf = curry (t, x) -> x instanceof t

## isNumber

    isNumber = isType Number

## isNaN

    isNaN = (n) -> Number.isNaN n

## isFinite

    isFinite = (n) -> Number.isFinite n

## isInteger

    isInteger = (n) -> Number.isInteger n

## isFloat

Adapted from [StackOverflow][isFloat].

[isFloat]:http://stackoverflow.com/questions/3885817/how-to-check-if-a-number-is-float-or-integer/3885844#3885844

    isFloat = (n) -> n == +n && n != (n|0)

## isBoolean

    isBoolean = isType Boolean

## isDate

    isDate = isType Date

## isRegExp

    isRegExp = isType RegExp

## isString

    isString = isType String

## isFunction

    isFunction = isType Function

## isObject

    isObject = isType Object

## isArray

    isArray = isType Array

## isDefined

    isDefined = (x) -> x?

## isGenerator

    GeneratorFunction = (-> yield null).constructor

    isGenerator = isType GeneratorFunction

## isPromise

    isPromise = (x) -> x?.then? && isFunction x.then

---

    module.exports = {type, isType, instanceOf,
      isBoolean, isNumber, isNaN, isFinite, isInteger, isFloat,
      isString, isFunction, isObject, isArray, isDefined,
      isRegExp, isDate, isGenerator, isPromise}


Cleanup obsolete API calls

Cleanup obsolete API calls

# Node.js Bindings for Link Grammar

The parser is built using the awesome ffi library which allows
us to communicate with a native library under the covers.
Along with ffi, we also use ref library to reference the native
objects used by the library.

    ffi = require 'ffi'
    ref = require 'ref'
    Struct = require 'ref-struct'
    _ = require 'underscore'

Easier references to base types of data.

    pointerType = 'pointer'
    string = ref.types.CString
    int = ref.types.int

These are just references to different native structs, which are
all just pointers because we never use their actual referenced object.

    ParseOptions = pointerType
    Dictionary = pointerType
    Sentence = pointerType
    Linkage = pointerType

Here are the templates for the native functions we will use.

    apiTemplate =
        parse_options_create: [ ParseOptions, [ ] ]
        parse_options_set_islands_ok: [ ref.types.void, [ ParseOptions, int ] ]
        parse_options_set_verbosity: [ ref.types.void, [ ParseOptions, int ] ]
        parse_options_set_min_null_count: [ ref.types.void, [ ParseOptions, int ] ]
        parse_options_set_max_null_count: [ ref.types.void, [ ParseOptions, int ] ]
        dictionary_create_lang: [ Dictionary, [ string ] ]
        sentence_create: [ Sentence, [ string, Dictionary ] ]
        sentence_parse: [ int, [ Sentence, ParseOptions ] ]
        sentence_length: [ int, [ Sentence ] ]
        linkage_create: [ Linkage, [ int, Sentence, ParseOptions ] ]
        linkage_print_diagram: [ string, [ Linkage ] ]
        linkage_get_num_links: [ int, [ Linkage ] ]
        linkage_get_link_label: [ string, [ Linkage, int ] ]
        linkage_get_link_llabel: [ string, [ Linkage, int ] ]
        linkage_get_link_rlabel: [ string, [ Linkage, int ] ]
        linkage_get_word: [ string, [ Linkage, int ] ]
        linkage_get_link_lword: [ int, [ Linkage, int ] ]
        linkage_get_link_rword: [ int, [ Linkage, int ] ]

Load the library.

    libPath = __dirname + '/../../../build/link-grammar/.libs/liblink-grammar.so'
    lib = ffi.Library libPath, apiTemplate
    defaultDataPath = __dirname + '/../../../data/en/'

Default configuration for data paths.

    defaultConfig =
        lang: 'en'
        verbose: false

Main parser class which interfaces the native library to make
it very simple to get Link Grammar data from an input string.

    class LinkGrammar

A few utility methods for the parser.

        constructor: (config) ->
            @config = _.extend config or {}, defaultConfig
            @options = lib.parse_options_create()
            lib.parse_options_set_verbosity @options, (if @config.verbose then 1  else 0)
            lib.parse_options_set_min_null_count @options, 0
            lib.parse_options_set_max_null_count @options, 3
            @dictionary = lib.dictionary_create_lang @config.lang

Parse input, and return linkage, if it exists.

        parse: (input, index) ->
            sentence = lib.sentence_create input, @dictionary
            numLinkages = lib.sentence_parse sentence, @options
            if numLinkages > 0
              new Linkage lib.linkage_create index or 0, sentence, @options
            else
              throw new Error('No linkages found')

Linkage class which allows for more specific parsing of grammar.

    class Linkage

        constructor: (@linkage) ->
            @links = @getLinks()
            @words = @getWords()

Get array of grammar links based on linkage.

        getLinks: ->
            _(lib.linkage_get_num_links @linkage).times ((index) ->
                leftIndex = lib.linkage_get_link_lword @linkage, index
                rightIndex = lib.linkage_get_link_rword @linkage, index
                link =
                    label: lib.linkage_get_link_label @linkage, index
                    left:
                        label: lib.linkage_get_link_llabel @linkage, index
                        word: lib.linkage_get_word @linkage, leftIndex
                        index: leftIndex
                    right:
                        label: lib.linkage_get_link_rlabel @linkage, index
                        word: lib.linkage_get_word @linkage, rightIndex
                        index: rightIndex
                if link.left.word.indexOf '.' isnt -1
                    temp = link.left.word.split '.'
                    link.left.word = temp[0]
                    link.left.type = temp[1]
                if link.right.word.indexOf '.' isnt -1
                    temp = link.right.word.split '.'
                    link.right.word = temp[0]
                    link.right.type = temp[1]
                link
            ), @

Get parsed words with their grammar type and index

        getWords: ->
            _.chain(@links)
                .map (link) -> [link.left, link.right]
                .flatten()
                .uniq (link) -> link.word
                .value()

Get list of links by specific label type

        linksByLabel: (labelPattern) ->
            labelPattern = new RegExp labelPattern if typeof labelPattern is 'string'
            @links.filter (link) ->
                labelPattern.test link.left.label or labelPattern.test link.right.label

Get list of connector links for a specific word

        getConnectorWords: (common) ->
            words = []
            @links.forEach (link) ->
                if link.left.word is common
                    words.push
                        source: link.left
                        target: link.right
                if link.right.word is common
                    words.push
                        source: link.right
                        target: link.left
            words

    module.exports = LinkGrammar

# Node.js Bindings for Link Grammar

The parser is built using the awesome ffi library which allows
us to communicate with a native library under the covers.
Along with ffi, we also use ref library to reference the native
objects used by the library.

    ffi = require 'ffi'
    ref = require 'ref'
    Struct = require 'ref-struct'
    _ = require 'underscore'

Easier references to base types of data.

    pointerType = 'pointer'
    string = ref.types.CString
    int = ref.types.int

These are just references to different native structs, which are
all just pointers because we never use their actual referenced object.

    ParseOptions = pointerType
    Dictionary = pointerType
    Sentence = pointerType
    Linkage = pointerType

Here are the templates for the native functions we will use.

    apiTemplate =
        parse_options_create: [ ParseOptions, [ ] ]
        parse_options_set_null_block: [ ref.types.void, [ ParseOptions, int ] ]
        parse_options_set_islands_ok: [ ref.types.void, [ ParseOptions, int ] ]
        parse_options_set_verbosity: [ ref.types.void, [ ParseOptions, int ] ]
        parse_options_set_allow_null: [ ref.types.void, [ ParseOptions, int ] ]
        parse_options_set_max_null_count: [ ref.types.void, [ ParseOptions, int ] ]
        dictionary_create: [ Dictionary, [ string, string, string, string ] ]
        sentence_create: [ Sentence, [ string, Dictionary ] ]
        sentence_parse: [ int, [ Sentence, ParseOptions ] ]
        sentence_length: [ int, [ Sentence ] ]
        sentence_get_word: [ string, [ Sentence, int ] ]
        linkage_create: [ Linkage, [ int, Sentence, ParseOptions ] ]
        linkage_print_diagram: [ string, [ Linkage ] ]
        linkage_get_num_links: [ int, [ Linkage ] ]
        linkage_get_link_label: [ string, [ Linkage, int ] ]
        linkage_get_link_llabel: [ string, [ Linkage, int ] ]
        linkage_get_link_rlabel: [ string, [ Linkage, int ] ]
        linkage_get_word: [ string, [ Linkage, int ] ]
        linkage_get_link_lword: [ int, [ Linkage, int ] ]
        linkage_get_link_rword: [ int, [ Linkage, int ] ]

Load the library.

    libPath = __dirname + '/../../../build/link-grammar/.libs/liblink-grammar.so'
    lib = ffi.Library libPath, apiTemplate
    defaultDataPath = __dirname + '/../../../data/en/'

Default configuration for data paths.

    defaultConfig =
        lang: 'en'
        verbose: false

Main parser class which interfaces the native library to make
it very simple to get Link Grammar data from an input string.

    class LinkGrammar

A few utility methods for the parser.

        constructor: (config) ->
            @config = _.extend config or {}, defaultConfig
            @options = lib.parse_options_create()
            lib.parse_options_set_verbosity @options, (if @config.verbose then 1  else 0)
            lib.parse_options_set_allow_null @options, 1
            lib.parse_options_set_max_null_count @options, 3
            @dictionary = lib.dictionary_create @config.lang

Parse input, and return linkage, if it exists.

        parse: (input, index) ->
            sentence = lib.sentence_create input, @dictionary
            numLinkages = lib.sentence_parse sentence, @options
            if numLinkages > 0
              new Linkage lib.linkage_create index or 0, sentence, @options
            else
              throw new Error('No linkages found')

Linkage class which allows for more specific parsing of grammar.

    class Linkage

        constructor: (@linkage) ->
            @links = @getLinks()
            @words = @getWords()

Get array of grammar links based on linkage.

        getLinks: ->
            _(lib.linkage_get_num_links @linkage).times ((index) ->
                leftIndex = lib.linkage_get_link_lword @linkage, index
                rightIndex = lib.linkage_get_link_rword @linkage, index
                link =
                    label: lib.linkage_get_link_label @linkage, index
                    left:
                        label: lib.linkage_get_link_llabel @linkage, index
                        word: lib.linkage_get_word @linkage, leftIndex
                        index: leftIndex
                    right:
                        label: lib.linkage_get_link_rlabel @linkage, index
                        word: lib.linkage_get_word @linkage, rightIndex
                        index: rightIndex
                if link.left.word.indexOf '.' isnt -1
                    temp = link.left.word.split '.'
                    link.left.word = temp[0]
                    link.left.type = temp[1]
                if link.right.word.indexOf '.' isnt -1
                    temp = link.right.word.split '.'
                    link.right.word = temp[0]
                    link.right.type = temp[1]
                link
            ), @

Get parsed words with their grammar type and index

        getWords: ->
            _.chain(@links)
                .map (link) -> [link.left, link.right]
                .flatten()
                .uniq (link) -> link.word
                .value()

Get list of links by specific label type

        linksByLabel: (labelPattern) ->
            labelPattern = new RegExp labelPattern if typeof labelPattern is 'string'
            @links.filter (link) ->
                labelPattern.test link.left.label or labelPattern.test link.right.label

Get list of connector links for a specific word

        getConnectorWords: (common) ->
            words = []
            @links.forEach (link) ->
                if link.left.word is common
                    words.push
                        source: link.left
                        target: link.right
                if link.right.word is common
                    words.push
                        source: link.right
                        target: link.left
            words

    module.exports = LinkGrammar