grunt-gss
Version: 
Save your Google Spreadsheets as CSV or JSON.
143 lines (119 loc) • 4.33 kB
text/coffeescript
module.exports = (grunt) ->
  csv2json = require './lib/csv2json'
  done = undefined
  extend = require 'deep-extend'
  google = require 'googleapis'
  http = require 'http'
  open = require 'open'
  request = require 'request'
  toType = (o) -> ({}).toString.call(o).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
  getCsv = (id, secret, fileId, gid, callback) ->
    getAuth id, secret, 'http://localhost:4477/', 'offline',
    'https://www.googleapis.com/auth/drive.readonly', (auth) ->
      getFile auth, fileId, (file) ->
        getSheet auth, file, gid, callback
  _sheets = {}
  getSheet = (auth, file, gid, callback) ->
    grunt.verbose.write 'export...'
    uri = file['exportLinks']['text/csv'] + '&gid=' + gid
    if not _sheets[uri]
      headers = Authorization: "Bearer #{auth.credentials.access_token}"
      request {uri, headers}, (err, sheet) ->
        if err and err.message
          grunt.log.error err.message
          done false
        else callback _sheets[uri] = sheet.body
    else callback _sheets[uri]
  _files = {}
  getFile = (auth, fileId, callback) ->
    grunt.verbose.write 'drive...'
    if not _files[fileId]
      drive = google.drive {version: 'v2', auth}
      drive.files.get {fileId}, (err, file) ->
        if err and err.message
          grunt.log.error err.message
          done false
        else callback _files[fileId] = file
    else callback _files[fileId]
  _auths = {}
  getAuth = (id, secret, redirect, access_type, scope, callback) ->
    grunt.verbose.write 'auth...'
    if not _auths[id]
      client = _auths[id] = new google.auth.OAuth2 id, secret, redirect
      getAccessToken client.generateAuthUrl({access_type, scope}), (code) ->
        client.getToken code, (err, tokens) ->
          if err and err.message
            grunt.log.error err.message
            done false
          else
            client.setCredentials tokens
            callback _auths[id] = client
    else callback _auths[id]
  getAccessToken = (url, callback) ->
    grunt.verbose.write 'token...'
    open url
    server = http.createServer (req, rep) ->
      rep.end()
      req.connection.destroy()
      server.close()
      callback req.url.substr 7
    server.maxConnections = 1
    server.listen 4477 # ggss
  rxFileIdAndGid = /^.*[\/\=](\w{44}).*gid=(\d+).*$/i
  rxTrue = /^true$/i
  grunt.registerMultiTask 'gss', ->
    done = @async()
    data = @data
    (next = (file, files) ->
      # extend file
      matches = file.src[0].match rxFileIdAndGid
      fileId = matches[1]
      gid = matches[2]
      opts = extend {}, data.options, file.options or {}
      grunt.log.write "Processing #{file.dest}..."
      getCsv opts.clientId, opts.clientSecret, fileId, gid, (out) ->
        # json
        if opts.json
          grunt.log.write 'parse...'
          out = JSON.parse csv2json out
          # mapping
          if opts.mapping
            grunt.log.write 'map...'
            cols = []
            types = []
            for col, type of opts.mapping
              cols.push col
              types.push type
            for row in out
              for type, i in types
                col = cols[i]
                val = row[col]
                # convert
                if toType(type) is 'function'
                  row[col] = type val, row
                else if toType(val) isnt type
                  if type is 'array' then row[col] =
                    if not val then []
                    else if val.indexOf(',') isnt -1 then val.split ','
                    else [val]
                  else if type is 'boolean' then row[col] = rxTrue.test val
                  else if type is 'number' then row[col] = parseFloat val or 0
                  else if type is 'undefined' then delete row[col]
          # prettify
          if opts.json and opts.prettify
            grunt.log.write 'prettify...'
            out = JSON.stringify out, null, 2
        # wrap
        if toType(opts.wrap) is 'function'
          grunt.log.write 'wrap...'
          out = opts.wrap out
        # write
        grunt.log.write 'write...'
        grunt.file.write file.dest, out
        grunt.log.ok()
        # loop
        if not files.length then done true
        else next files.shift().orig, files
    ) @files.shift().orig, @files
    null
  null