[
  {
    "path": ".gitignore",
    "content": "/gcal\n/build\n/dist\n/vendor\n/.autoenv.zsh\n/.autoenv_leave.zsh\n/*_private.go\n/tags\n\n# vim turds\n[._]*.s[a-v][a-z]\n[._]*.sw[a-p]\n[._]s[a-v][a-z]\n[._]sw[a-p]\n*~\n/tags\n"
  },
  {
    "path": ".golangci.toml",
    "content": "[run]\ndeadline = \"5m\"\n\n[linters]\ndisable-all = true\nenable = [\n  \"deadcode\",\n  \"goconst\",\n  \"gocritic\",\n  \"gofmt\",\n  \"goimports\",\n  \"gosimple\",\n  \"ineffassign\",\n  \"scopelint\",\n  \"staticcheck\",\n  \"stylecheck\",\n  \"unconvert\",\n  \"unused\",\n  \"whitespace\",\n]\n\n[linter-settings]\n  [linter-settings.errcheck]\n  check-blank = true\n  check-type-assertions = true\n\n[issues]\n  max-same-issues = 50\n  max-issues-per-linter = 50\n  exclude = [\n    # scopelint chokes on filepath.Walk\n    \"Using the variable on range scope .* in function literal\",\n    # gocritic chokes on if t.Before(today) {\n    \"ifElseChain:\",\n  ]\n\n# vim: set ft=toml ts=4 sw=4 tw=0 et :\n"
  },
  {
    "path": "LICENCE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n<div align=\"center\">\n    <img height=\"128\" width=\"128\" src=\"https://raw.githubusercontent.com/deanishe/alfred-gcal/master/icons/icon.png\">\n</div>\n\nGoogle Calendar for Alfred\n==========================\n\nView Google Calendar events in [Alfred][alfred]. Supports multiple accounts.\n\n<!-- MarkdownTOC autolink=\"true\" bracket=\"round\" depth=\"3\" autoanchor=\"true\" -->\n\n- [Google Calendar for Alfred](#google-calendar-for-alfred)\n  - [Download & installation](#download--installation)\n  - [Usage](#usage)\n    - [Date format](#date-format)\n    - [Add event format](#add-event-format)\n  - [Configuration](#configuration)\n  - [Licensing & thanks](#licensing--thanks)\n  - [Privacy](#privacy)\n\n<!-- /MarkdownTOC -->\n\n\n<a name=\"download--installation\"></a>\nDownload & installation\n-----------------------\n\nGrab the workflow from [GitHub releases][download]. Download the `Google-Calendar-View-X.X.alfredworkflow` file and double-click it to install.\n\n\n<a name=\"usage\"></a>\nUsage\n-----\n\nWhen run, the workflow will open Google Calendar in your browser and ask for permission to access your calendars. If you do not grant permission, it won't work. The workflow requests permission to edit your calendars, as this is needed for the \"Add New Event\" feature (keyword `gnew`). It does not otherwise alter your calendars or events in any way.\n\nYou will also be prompted to activate some calendars (the workflow will show events from these calendars). You can alter the active calendars or add/remove Google accounts in the settings using keyword `gcalconf`.\n\n- `gcal` — Show upcoming events.\n    - `<query>` — Filter list of events.\n    - `↩` — Open event in browser or day in workflow.\n    - `⌘↩` — Open event in Google Maps or Apple Maps (if event has a location).\n    - `⇧` / `⌘Y` — Quicklook event details.\n- `today` / `tomorrow` / `yesterday` — Show events for the given day.\n    - `<query>` / `↩` / `⌘↩` / `⇧` / `⌘Y` — As above.\n- `gdate [<date>]` — Show one or more dates. See below for query format.\n    - `↩` — Show events for the given day.\n- `gnew [<query>]` — Add a new event in the one of active calendars. (example: Some meeting at Office at 5pm with Ian)\n    - `↩` — Create event in selected calendar.\n- `gcalconf [<query>]` — Show workflow configuration.\n    - `Active Calendars…` — Turn calendars on/off.\n        - `↩` — Toggle calendar on/off.\n    - `Add Account…` — Add a Google account.\n        - `↩` — Open Google login in browser to authorise an account.\n    - `your.email@gmail.com` — Your logged in Google account(s).\n        - `↩` — Remove account.\n    - `Open Locations in Google Maps/Apple Maps` — Choose app to open event locations.\n        - `↩` — Toggle setting between Google Maps & Apple Maps.\n    - `Workflow is up to Date` / `An Update is Available` — Whether a newer version of the workflow is available.\n        - `↩` — Check for or install update.\n    - `Open Locations in XYZ` — Open locations in Google Maps or Apple Maps.\n    - `↩` — Toggle between applications.\n    - `Open Documentation` — Open this page in your brower.\n    - `Get Help` — Visit [the thread for this workflow][forumthread] on [AlfredForum.com][alfredforum].\n    - `Report Issue` — [Open an issue][issues] on GitHub.\n    - `Clear Cached Calendars & Events` — Remove cached lists of calendars and events.\n\n\n<a name=\"date-format\"></a>\n### Date format ###\n\nWhen viewing dates/events, you can specify and jump to a particular date using the following input format:\n\n- `YYYY-MM-DD` — e.g. `2017-12-01`\n- `YYYYMMDD` — e.g. `20180101`\n- `[+|-]N[d|w]` — e.g.:\n    - `1`, `1d` or `+1d` for tomorrow\n    - `-1` or `-1d` for yesterday\n    - `3w` for 21 days from now\n    - `-4w` for 4 weeks ago\n\n\n<a name=\"add-event-format\"></a>\n### Add event format ###\n\nThe \"Add New Event\" feature (keyword `gnew`) creates an event using Google Calendar's natural language syntax. This doesn't appear to be properly documented anywhere, but it is pretty powerful. You can specify event title, location, time & duration and repetition. Some examples:\n\n- `Wash pants` — creates an event titled \"Wash pants\" starting now using your default event duration\n- `Clean pants party tomorrow` — creates an all-day event for tomorrow title \"Clean pants party\"\n- `Drink beer every day 2000-2200` — creates an event titled \"Drink beer\" starting at 8pm, finishing at 10pm, and repeating every day.\n\n\n<a name=\"configuration\"></a>\nConfiguration\n-------------\n\nThere are a couple of options in the workflow's configuration sheet (the `[x]` button in Alfred Preferences):\n\n| Setting | Description |\n|---------|-------------|\n| `CALENDAR_APP` | Name of application to open Google Calendar URLs (not map URLs) in. If blank, your default browser is used. |\n| `EVENT_CACHE_MINS` | Number of minutes to cache event lists before updating from the server. |\n| `SCHEDULE_DAYS` | The number of days' events to show with the `gcal` keyword. |\n| `APPLE_MAPS` | Set to `1` to open map links in Apple Maps instead of Google Maps. This option can be toggled from within the workflow's configuration with keyword `gcalconf`. |\n\n\n<a name=\"licensing--thanks\"></a>\nLicensing & thanks\n------------------\n\nThis workflow is released under the [MIT Licence][mit].\n\nIt is heavily based on the [Google API libraries for Go][google-libs] ([BSD 3-clause licence][google-licence]) and [AwGo][awgo] libraries ([MIT][mit]), and of course, [Google Calendar][gcal].\n\n\nThe icons are from or based on [Font Awesome][awesome] and [Weather Icons][weather] (both [SIL][sil]).\n\nSpecial thanks to [@diffmike][diffmike] for adding the \"Add New Event\" feature.\n\n\n<a name=\"privacy\"></a>\nPrivacy\n-------\n\nThe data used and accessed by this workflow are stored exclusively on your own Mac. Nothing is shared with anyone. When you authorise this workflow to access your Google Calendars, the only person you are enabling to read that data is you.\n\n[gcal]: https://calendar.google.com/calendar/\n[google-libs]: https://github.com/google/google-api-go-client\n[google-licence]: https://github.com/google/google-api-go-client/blob/master/LICENSE\n[alfred]: https://alfredapp.com/\n[alfredforum]: https://www.alfredforum.com/\n[awgo]: https://github.com/deanishe/awgo\n[forumthread]: https://www.alfredforum.com/topic/11016-google-calendar-view/\n[download]: https://github.com/deanishe/alfred-gcal/releases/latest\n[issues]: https://github.com/deanishe/alfred-gcal/issues\n[sil]: http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL\n[mit]: https://opensource.org/licenses/MIT\n[awesome]: http://fortawesome.github.io/Font-Awesome/\n[weather]: https://erikflowers.github.io/weather-icons/\n[diffmike]: https://github.com/diffmike\n"
  },
  {
    "path": "TODO.taskpaper",
    "content": "Bugs:\n\t- Clear cache after selected calendars change\n\tCurrently, workflow shows events from inactive calendars and no events from active ones.\n\nImprovements:\n\t- Change Keywords to Script Filters\n\tLets magic arguments work, too!\n\nNew Features:\n\t- Integration with Stuart's Google Maps workflow\n"
  },
  {
    "path": "account.go",
    "content": "// Copyright (c) 2019 Dean Jackson <deanishe@deanishe.net>\n// MIT Licence applies http://opensource.org/licenses/MIT\n\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\taw \"github.com/deanishe/awgo\"\n\t\"github.com/deanishe/awgo/util\"\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/net/context\"\n\t\"golang.org/x/oauth2\"\n\t\"google.golang.org/api/calendar/v3\"\n\t\"google.golang.org/api/option\"\n)\n\n// Account is a Google account. It contains user's email, avatar URL and OAuth2\n// token.\ntype Account struct {\n\tName      string // Directory account data is stored in\n\tEmail     string // User's email address\n\tAvatarURL string // URL of user's Google avatar\n\n\t// Whether account has write permissions.\n\t// Early versions of the workflow only requested \"read\"\n\t// permission from the Google Calendar API, and this flag\n\t// is used to tell users to re-authenticate with the new\n\t// read-write permissions if they try to use the\n\t// \"Add New Event\" feature with an old, read-only access token.\n\tReadWrite bool\n\n\t// Calendars contained by account\n\tCalendars []*Calendar\n\n\t// OAuth2\n\tToken *oauth2.Token\n\tauth  *Authenticator\n}\n\n// NewAccount creates a new account or loads an existing one.\nfunc NewAccount(name string) (*Account, error) {\n\tvar (\n\t\ta   = &Account{Name: name}\n\t\terr error\n\t)\n\n\tif name != \"\" {\n\t\tif err = wf.Cache.LoadJSON(a.CacheName(), a); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"load account\")\n\t\t}\n\t}\n\n\treturn a, nil\n}\n\n// LoadAccounts reads saved accounts from disk.\nfunc LoadAccounts() ([]*Account, error) {\n\tvar (\n\t\taccounts = []*Account{}\n\t\tinfos    []os.FileInfo\n\t\terr      error\n\t)\n\n\tif infos, err = ioutil.ReadDir(wf.CacheDir()); err != nil {\n\t\treturn nil, errors.Wrap(err, \"read accountsDir\")\n\t}\n\n\tfor _, fi := range infos {\n\t\tif fi.IsDir() ||\n\t\t\t!strings.HasSuffix(fi.Name(), \".json\") ||\n\t\t\t!strings.HasPrefix(fi.Name(), \"account-\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tacc := &Account{}\n\t\tif err := wf.Cache.LoadJSON(fi.Name(), acc); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"load account\")\n\t\t}\n\t\tlog.Printf(\"[account] loaded %+v\", acc)\n\n\t\taccounts = append(accounts, acc)\n\t}\n\n\treturn accounts, nil\n}\n\n// CacheName returns the name of Account's cache file.\nfunc (a *Account) CacheName() string { return \"account-\" + a.Name + \".json\" }\n\n// IconPath returns the path to the cached user avatar.\nfunc (a *Account) IconPath() string {\n\treturn filepath.Join(cacheDirIcons, a.Name+filepath.Ext(a.AvatarURL))\n}\n\n// Icon returns Account user avatar.\nfunc (a *Account) Icon() *aw.Icon {\n\tp := a.IconPath()\n\tif util.PathExists(p) {\n\t\treturn &aw.Icon{Value: p}\n\t}\n\n\treturn iconAccount\n}\n\n// Authenticator creates a new Authenticator for Account.\nfunc (a *Account) Authenticator() *Authenticator {\n\tif a.auth == nil {\n\t\ta.auth = NewAuthenticator(a, []byte(secret))\n\t}\n\n\treturn a.auth\n}\n\n// Save saves authentication token.\nfunc (a *Account) Save() error {\n\tif err := wf.Cache.StoreJSON(a.CacheName(), a); err != nil {\n\t\treturn errors.Wrap(err, \"save account\")\n\t}\n\tlog.Printf(\"[account] saved %q\", a.Name)\n\treturn nil\n}\n\n// Service returns a Calendar Service for this Account.\nfunc (a *Account) Service() (*calendar.Service, error) {\n\tvar (\n\t\tclient *http.Client\n\t\tsrv    *calendar.Service\n\t\terr    error\n\t)\n\n\tif client, err = a.Authenticator().GetClient(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"get authenticator client\")\n\t}\n\n\tif srv, err = calendar.NewService(context.Background(), option.WithHTTPClient(client)); err != nil {\n\t\treturn nil, errors.Wrap(err, \"create new calendar client\")\n\t}\n\n\treturn srv, nil\n}\n\n// FetchCalendars retrieves a list of all calendars in Account.\nfunc (a *Account) FetchCalendars() error {\n\tvar (\n\t\tsrv  *calendar.Service\n\t\tls   *calendar.CalendarList\n\t\tcals []*Calendar\n\t\terr  error\n\t)\n\n\tif srv, err = a.Service(); err != nil {\n\t\treturn errors.Wrap(err, \"create service\")\n\t}\n\n\tif ls, err = srv.CalendarList.List().Do(); err != nil {\n\t\treturn errors.Wrap(err, \"retrieve calendar list\")\n\t}\n\n\tfor _, entry := range ls.Items {\n\t\tif entry.Hidden {\n\t\t\tlog.Printf(\"[account] ignoring hidden calendar %q in %q\", entry.Summary, a.Name)\n\t\t\tcontinue\n\t\t}\n\n\t\tc := &Calendar{\n\t\t\tID:          entry.Id,\n\t\t\tTitle:       entry.Summary,\n\t\t\tDescription: entry.Description,\n\t\t\tColour:      entry.BackgroundColor,\n\t\t\tAccountName: a.Name,\n\t\t}\n\t\tif entry.SummaryOverride != \"\" {\n\t\t\tc.Title = entry.SummaryOverride\n\t\t}\n\t\tcals = append(cals, c)\n\t}\n\n\tsort.Sort(CalsByTitle(cals))\n\ta.Calendars = cals\n\treturn a.Save()\n}\n\n// FetchEvents returns events from the specified calendar.\nfunc (a *Account) FetchEvents(cal *Calendar, start time.Time) ([]*Event, error) {\n\tvar (\n\t\tend       = start.Add(opts.ScheduleDuration())\n\t\tevents    = []*Event{}\n\t\tstartTime = start.Format(time.RFC3339)\n\t\tendTime   = end.Format(time.RFC3339)\n\t\tsrv       *calendar.Service\n\t\terr       error\n\t)\n\n\tlog.Printf(\"[account] account=%q, cal=%q, start=%s, end=%s\", a.Name, cal.Title, start, end)\n\n\tif srv, err = a.Service(); err != nil {\n\t\treturn nil, a.handleAPIError(err)\n\t}\n\n\tevs, err := srv.Events.List(cal.ID).\n\t\tSingleEvents(true).\n\t\tMaxResults(2500).\n\t\tTimeMin(startTime).\n\t\tTimeMax(endTime).\n\t\tOrderBy(\"startTime\").Do()\n\n\tif err != nil {\n\t\treturn nil, a.handleAPIError(err)\n\t}\n\n\tfor _, e := range evs.Items {\n\t\tif e.Start.DateTime == \"\" { // all-day event\n\t\t\tcontinue\n\t\t}\n\n\t\tvar (\n\t\t\tstart time.Time\n\t\t\tend   time.Time\n\t\t\terr   error\n\t\t)\n\n\t\tif start, err = time.Parse(time.RFC3339, e.Start.DateTime); err != nil {\n\t\t\tlog.Printf(\"[events] ERR: parse start time (%s): %v\", e.Start.DateTime, err)\n\t\t\tcontinue\n\t\t}\n\t\tif end, err = time.Parse(time.RFC3339, e.End.DateTime); err != nil {\n\t\t\tlog.Printf(\"[events] ERR: parse end time (%s): %v\", e.End.DateTime, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tevents = append(events, &Event{\n\t\t\tID:            e.Id,\n\t\t\tIcalUID:       e.ICalUID,\n\t\t\tTitle:         e.Summary,\n\t\t\tDescription:   e.Description,\n\t\t\tURL:           e.HtmlLink,\n\t\t\tLocation:      e.Location,\n\t\t\tStart:         start,\n\t\t\tEnd:           end,\n\t\t\tColour:        cal.Colour,\n\t\t\tCalendarID:    cal.ID,\n\t\t\tCalendarTitle: cal.Title,\n\t\t})\n\t}\n\n\treturn events, nil\n}\n\n// QuickAdd creates a new event in the passed calendar from Account.\nfunc (a *Account) QuickAdd(calendarID string, quick string) error {\n\tvar (\n\t\tsrv *calendar.Service\n\t\terr error\n\t)\n\n\tif srv, err = a.Service(); err != nil {\n\t\treturn errors.Wrap(err, \"create service\")\n\t}\n\n\tif _, err = srv.Events.QuickAdd(calendarID, quick).Do(); err != nil {\n\t\treturn errors.Wrap(err, \"create new event error\")\n\t}\n\n\treturn err\n}\n\n// Check for OAuth2 error and  remove tokens if they've expired/been revoked.\nfunc (a *Account) handleAPIError(err error) error {\n\tif err2, ok := err.(*url.Error); ok {\n\t\tif err3, ok := err2.Err.(*oauth2.RetrieveError); ok {\n\t\t\tvar resp errorResponse\n\t\t\tif err4 := json.Unmarshal(err3.Body, &resp); err4 == nil {\n\t\t\t\tlog.Printf(\"[events] ERR: OAuth: %s (%s)\", resp.Name, resp.Description)\n\n\t\t\t\terr := errorAuthentication{\n\t\t\t\t\tName:        resp.Name,\n\t\t\t\t\tDescription: resp.Description,\n\t\t\t\t\tErr:         err3,\n\t\t\t\t}\n\n\t\t\t\tif err.Name == \"invalid_grant\" {\n\t\t\t\t\tlog.Printf(\"[account] clearing invalid token for %q\", a.Name)\n\n\t\t\t\t\ta.Token = nil\n\t\t\t\t\tif err := a.Save(); err != nil {\n\t\t\t\t\t\tlog.Printf(\"[account] ERR: save %q: %v\", a.Name, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn err\n}\n\n// for unmarshalling API errors.\ntype errorResponse struct {\n\tName        string `json:\"error\"`\n\tDescription string `json:\"error_description\"`\n}\n\ntype errorAuthentication struct {\n\tName        string\n\tDescription string\n\tErr         error\n}\n\n// Error implements error.\nfunc (err errorAuthentication) Error() string {\n\treturn fmt.Sprintf(\"authentication error: %s (%s)\", err.Name, err.Description)\n}\n"
  },
  {
    "path": "auth.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os/exec\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/net/context\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\t\"google.golang.org/api/calendar/v3\"\n)\n\nconst (\n\t// URL of local server that receives OAuth2 tokens\n\tauthServerURL = \"localhost:61432\"\n\t// OAuth2 scope for user's email address\n\tuserEmailScope = \"https://www.googleapis.com/auth/userinfo.email\"\n)\n\n// OAuth2 scopes used by the workflow\nvar scopes = []string{calendar.CalendarEventsScope, calendar.CalendarReadonlyScope, userEmailScope}\n\ntype response struct {\n\tcode string\n\terr  error\n}\n\n// Authenticator creates an authenticated Google API client\ntype Authenticator struct {\n\tSecret  []byte\n\tAccount *Account\n\tstate   string\n\n\tclient *http.Client\n\tmu     sync.Mutex\n\n\t// set when authentication fails so other goroutines don't\n\t// repeatedly try to log in\n\tFailed bool\n}\n\n// NewAuthenticator creates a new Authenticator\nfunc NewAuthenticator(acc *Account, secret []byte) *Authenticator {\n\treturn &Authenticator{Account: acc, Secret: secret}\n}\n\n// GetClient returns an authenticated Google API client\nfunc (a *Authenticator) GetClient() (*http.Client, error) {\n\ta.mu.Lock()\n\tdefer a.mu.Unlock()\n\n\t// bail out as previous authentication attempt has failed\n\tif a.Failed {\n\t\treturn nil, errors.New(\"authentication failed\")\n\t}\n\n\tif a.client != nil {\n\t\treturn a.client, nil\n\t}\n\n\t// generate CSRF token\n\tb := make([]byte, 32)\n\t_, err := rand.Read(b)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"couldn't read random bytes: %v\", err)\n\t}\n\ta.state = fmt.Sprintf(\"%x\", b)\n\n\tctx := context.Background()\n\tcfg, err := google.ConfigFromJSON(a.Secret, scopes...)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"load config\")\n\t}\n\n\tvar save bool\n\tif a.Account.Token == nil {\n\t\tif err = a.tokenFromWeb(cfg); err != nil {\n\t\t\ta.Failed = true\n\t\t\treturn nil, errors.Wrap(err, \"token from web\")\n\t\t}\n\t\ta.Account.ReadWrite = cfg.Scopes[0] == calendar.CalendarEventsScope\n\t\tsave = true\n\t}\n\n\ta.client = cfg.Client(ctx, a.Account.Token)\n\n\t// If Account is empty, fetch user info from Google API\n\tif a.Account.Name == \"\" {\n\t\tif err := a.getUserInfo(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsave = true\n\t}\n\n\tif save {\n\t\tif err = a.Account.Save(); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"save account\")\n\t\t}\n\t}\n\n\treturn a.client, nil\n}\n\n/*\n// tokenFromFile loads the oauth2 token from a file\nfunc (a *Authenticator) tokenFromFile() (*oauth2.Token, error) {\n\tf, err := os.Open(a.TokenFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"open token file: %v\", err)\n\t}\n\ttok := &oauth2.Token{}\n\terr = json.NewDecoder(f).Decode(tok)\n\tdefer f.Close()\n\treturn tok, err\n}\n\n// saveToken saves an oauth2 token to a file\nfunc (a *Authenticator) saveToken(tok *oauth2.Token) error {\n\tf, err := os.OpenFile(a.TokenFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"open token file: %v\", err)\n\t}\n\tdefer f.Close()\n\treturn json.NewEncoder(f).Encode(tok)\n}\n*/\n\n// tokenFromWeb initiates web-based authentication and retrieves the OAuth2 token\nfunc (a *Authenticator) tokenFromWeb(cfg *oauth2.Config) error {\n\tvar (\n\t\tcode  string\n\t\ttoken *oauth2.Token\n\t\terr   error\n\t)\n\n\tif err = a.openAuthURL(cfg); err != nil {\n\t\treturn errors.Wrap(err, \"open auth URL\")\n\t}\n\n\tif code, err = a.codeFromLocalServer(); err != nil {\n\t\treturn errors.Wrap(err, \"get token from local server\")\n\t}\n\n\tif token, err = cfg.Exchange(context.Background(), code); err != nil {\n\t\treturn errors.Wrap(err, \"token from web\")\n\t}\n\n\ta.Account.Token = token\n\n\treturn nil\n}\n\nfunc (a *Authenticator) getUserInfo() error {\n\tvar (\n\t\tresp *http.Response\n\t\tdata []byte\n\t\terr  error\n\t)\n\n\tif resp, err = a.client.Get(\"https://accounts.google.com/.well-known/openid-configuration\"); err != nil {\n\t\treturn fmt.Errorf(\"get user info: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif data, err = ioutil.ReadAll(resp.Body); err != nil {\n\t\treturn fmt.Errorf(\"read user response: %v\", err)\n\t}\n\n\ts := struct {\n\t\tEndpoint string `json:\"userinfo_endpoint\"`\n\t}{}\n\n\tif err = json.Unmarshal(data, &s); err != nil {\n\t\treturn fmt.Errorf(\"parse OpenID JSON: %v\", err)\n\t}\n\n\tlog.Printf(\"[auth] fetching user info from %s ...\", s.Endpoint)\n\n\tif resp, err = a.client.Get(s.Endpoint); err != nil {\n\t\treturn fmt.Errorf(\"read userinfo_endpoint: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif data, err = ioutil.ReadAll(resp.Body); err != nil {\n\t\treturn fmt.Errorf(\"read userinfo_endpoint response: %v\", err)\n\t}\n\n\tlog.Printf(\"[auth] response=%s\", string(data))\n\n\tst := struct {\n\t\tEmail  string `json:\"email\"`\n\t\tAvatar string `json:\"picture\"`\n\t}{}\n\n\tif err := json.Unmarshal(data, &st); err != nil {\n\t\treturn errors.Wrap(err, \"unmarshal userinfo\")\n\t}\n\n\ta.Account.Name = st.Email\n\ta.Account.Email = st.Email\n\ta.Account.AvatarURL = st.Avatar\n\n\tlog.Printf(\"[auth] fetching user avatar ...\")\n\tif err := download(a.Account.AvatarURL, a.Account.IconPath()); err != nil {\n\t\treturn errors.Wrap(err, \"fetch avatar\")\n\t}\n\n\treturn nil\n}\n\n// openAuthURL opens the Google API authentication URL in the default browser\nfunc (a *Authenticator) openAuthURL(cfg *oauth2.Config) error {\n\tauthURL := cfg.AuthCodeURL(a.state, oauth2.AccessTypeOffline, oauth2.ApprovalForce)\n\tcmd := exec.Command(\"/usr/bin/open\", authURL)\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"open auth URL: %v\", err)\n\t}\n\treturn nil\n}\n\n// codeFromLocalServer starts a local webserver to receive the oauth2 token\n// from Google\nfunc (a *Authenticator) codeFromLocalServer() (string, error) {\n\tvar (\n\t\tc   = make(chan response)\n\t\tmux = http.NewServeMux()\n\t\tsrv = &http.Server{\n\t\t\tAddr:    authServerURL,\n\t\t\tHandler: mux,\n\t\t}\n\t)\n\n\tgo func() {\n\t\tlog.Printf(\"[auth] starting local webserver on %s ...\", authServerURL)\n\t\tif err := srv.ListenAndServe(); err != nil {\n\t\t\tc <- response{err: err}\n\t\t}\n\t}()\n\n\t// automatically close server after 3 minutes\n\ttimeout := time.AfterFunc(time.Minute*3, func() {\n\t\tlog.Println(\"[auth] automatically stopping server after timeout\")\n\t\tif err := srv.Shutdown(context.Background()); err != nil && err != http.ErrServerClosed {\n\t\t\tlog.Printf(\"[error] shutdown: %v\", err)\n\t\t\tc <- response{err: err}\n\t\t\treturn\n\t\t}\n\t\tc <- response{err: errors.New(\"OAuth server timeout exceeded\")}\n\t})\n\n\tmux.HandleFunc(\"/\", func(w http.ResponseWriter, req *http.Request) {\n\t\tvars := req.URL.Query()\n\t\tcode := vars.Get(\"code\")\n\t\tstate := vars.Get(\"state\")\n\t\terrMsg := vars.Get(\"error\")\n\t\tlog.Printf(\"[auth] oauth2 state=%v\", state)\n\t\tlog.Printf(\"[auth] oauth2 code=%s\", code)\n\t\tlog.Printf(\"[auth] oauth2 error=%s\", errMsg)\n\n\t\t// Verify state to prevent CSRF\n\t\tif state != a.state {\n\t\t\tc <- response{err: fmt.Errorf(\"state mismatch: expected=%s, got=%s\", a.state, state)}\n\t\t\tif _, err := io.WriteString(w, \"bad state\\n\"); err != nil {\n\t\t\t\tlog.Printf(\"[error] write server response: %v\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// authentication failed\n\t\tif errMsg != \"\" {\n\t\t\tc <- response{err: errors.New(errMsg)}\n\t\t\tif _, err := io.WriteString(w, errMsg+\"\\n\"); err != nil {\n\t\t\t\tlog.Printf(\"[error] write server response: %v\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// user rejected\n\t\tif code == \"\" {\n\t\t\tc <- response{err: errors.New(\"user rejected access\")}\n\t\t\tif _, err := io.WriteString(w, \"access denied by user\\n\"); err != nil {\n\t\t\t\tlog.Printf(\"[error] write server response: %v\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tc <- response{code: code}\n\t\tif _, err := io.WriteString(w, \"ok\\n\"); err != nil {\n\t\t\tlog.Printf(\"[error] write server response: %v\", err)\n\t\t}\n\t})\n\n\tr := <-c\n\ttimeout.Stop()\n\n\t// log.Printf(\"srv=%+v, response=%+v\", srv, r)\n\tif err := srv.Shutdown(context.Background()); err != nil {\n\t\tlog.Printf(\"shutdown error: %v\", err)\n\t\tif err != http.ErrServerClosed {\n\t\t\treturn \"\", fmt.Errorf(\"auth webserver: %v\", err)\n\t\t}\n\t}\n\n\tlog.Printf(\"[auth] local webserver stopped\")\n\n\treturn r.code, r.err\n}\n"
  },
  {
    "path": "cmd_calendars.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\taw \"github.com/deanishe/awgo\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\terrNoActive    = errors.New(\"no active calendars\")\n\terrNoCalendars = errors.New(\"no calendars\")\n\terrNoWritable  = errors.New(\"no writeable calendars\")\n)\n\n// doListCalendars shows a list of available calendars in Alfred.\nfunc doListCalendars() error {\n\tvar (\n\t\tcals []*Calendar\n\t\terr  error\n\t)\n\n\tif cals, err = allCalendars(); err != nil {\n\t\tif err == errNoCalendars {\n\t\t\tif !wf.IsRunning(\"update-calendars\") {\n\t\t\t\tcmd := exec.Command(os.Args[0], \"update\", \"calendars\")\n\t\t\t\tif err := wf.RunInBackground(\"update-calendars\", cmd); err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"run calendar update\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twf.NewItem(\"Fetching List of Calendars…\").\n\t\t\t\tSubtitle(\"List will reload shortly\").\n\t\t\t\tValid(false).\n\t\t\t\tIcon(ReloadIcon())\n\n\t\t\twf.Rerun(0.1)\n\t\t\twf.SendFeedback()\n\n\t\t\treturn nil\n\t\t}\n\n\t\treturn err\n\t}\n\n\tif len(cals) == 0 && wf.IsRunning(\"update-calendars\") {\n\t\twf.NewItem(\"Fetching List of Calendars…\").\n\t\t\tSubtitle(\"List will reload shortly\").\n\t\t\tValid(false).\n\t\t\tIcon(ReloadIcon())\n\t\twf.Rerun(0.1)\n\t\twf.SendFeedback()\n\t\treturn nil\n\t}\n\n\tactive, err := activeCalendarIDs()\n\tif err != nil && err != errNoActive {\n\t\treturn err\n\t}\n\n\tfor _, c := range cals {\n\t\ton := active[c.ID]\n\t\ticon := iconCalOff\n\t\tif on {\n\t\t\ticon = iconCalOn\n\t\t}\n\t\tsub := c.Description + \" / \" + c.AccountName\n\t\tif c.Description == \"\" {\n\t\t\tsub = c.AccountName\n\t\t}\n\n\t\twf.NewItem(c.Title).\n\t\t\tSubtitle(sub).\n\t\t\tIcon(icon).\n\t\t\tArg(c.ID).\n\t\t\tMatch(c.Title).\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"toggle\").\n\t\t\tVar(\"calendar\", c.ID)\n\t}\n\n\twf.NewItem(\"Back\").\n\t\tSubtitle(\"Back to configuration\").\n\t\tIcon(iconPrevious).\n\t\tValid(true).\n\t\tVar(\"action\", \"config\")\n\n\tif opts.Query != \"\" {\n\t\twf.Filter(opts.Query)\n\t}\n\n\twf.WarnEmpty(\"No Calendars\", \"Did you log in with the right account?\")\n\twf.SendFeedback()\n\n\treturn nil\n}\n\n// doListWritableCalendars shows a list of active calendars in Alfred.\nfunc doListWritableCalendars() error {\n\tvar (\n\t\tcals []*Calendar\n\t\terr  error\n\t)\n\n\tif cals, err = writableCalendars(); err != nil {\n\t\tif err == errNoWritable {\n\t\t\twf.NewItem(\"No Writeable Account(s)\").\n\t\t\t\tSubtitle(\"↩ to go to config and re-authenticate account with read-write permission\").\n\t\t\t\tValid(true).\n\t\t\t\tIcon(aw.IconWarning).\n\t\t\t\tVar(\"action\", \"config\")\n\n\t\t\twf.SendFeedback()\n\n\t\t\treturn nil\n\t\t}\n\n\t\tif err == errNoActive {\n\t\t\twf.NewItem(\"No Active Calendars\").\n\t\t\t\tSubtitle(\"↩ or ⇥ to activate calendars\").\n\t\t\t\tAutocomplete(\"workflow:calendars\").\n\t\t\t\tIcon(aw.IconWarning)\n\n\t\t\t\t// TODO: reauth accounts\n\n\t\t\twf.SendFeedback()\n\n\t\t\treturn nil\n\t\t}\n\n\t\tif err == errNoCalendars {\n\t\t\tif !wf.IsRunning(\"update-calendars\") {\n\t\t\t\tcmd := exec.Command(os.Args[0], \"update\", \"calendars\")\n\t\t\t\tif err := wf.RunInBackground(\"update-calendars\", cmd); err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"run calendar update\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twf.NewItem(\"Fetching List of Calendars…\").\n\t\t\t\tSubtitle(\"List will reload shortly\").\n\t\t\t\tValid(false).\n\t\t\t\tIcon(ReloadIcon())\n\n\t\t\twf.Rerun(0.1)\n\t\t\twf.SendFeedback()\n\n\t\t\treturn nil\n\t\t}\n\n\t\treturn err\n\t}\n\n\tfor _, c := range cals {\n\t\tquery := strings.TrimSpace(opts.Query)\n\t\tsub := c.Description + \" / \" + c.AccountName\n\t\tif c.Description == \"\" {\n\t\t\tsub = c.AccountName\n\t\t}\n\t\tif query != \"\" {\n\t\t\tsub = \"Create “\" + query + \"” in \" + c.Title\n\t\t}\n\n\t\twf.NewItem(c.Title).\n\t\t\tSubtitle(sub).\n\t\t\tIcon(ColouredIcon(iconCalendar, c.Colour)).\n\t\t\tArg(c.ID).\n\t\t\tUID(c.ID).\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"create\").\n\t\t\tVar(\"quick\", opts.Query).\n\t\t\tVar(\"calendar\", c.ID)\n\t}\n\n\twf.WarnEmpty(\"No Calendars\", \"Did you log in with the right account?\")\n\twf.SendFeedback()\n\n\treturn nil\n}\n\nfunc allCalendars() ([]*Calendar, error) {\n\tvar (\n\t\tjobName = \"update-calendars\"\n\t\tcals    []*Calendar\n\t\texpired bool\n\t)\n\n\tfor _, acc := range accounts {\n\t\tif wf.Cache.Expired(acc.CacheName(), opts.MaxAgeCalendar()) {\n\t\t\texpired = true\n\t\t}\n\t\tcals = append(cals, acc.Calendars...)\n\t}\n\n\tif expired {\n\t\tif !wf.IsRunning(jobName) {\n\t\t\twf.Rerun(0.1)\n\n\t\t\tcmd := exec.Command(os.Args[0], \"update\", \"calendars\")\n\t\t\tif err := wf.RunInBackground(jobName, cmd); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tlog.Printf(\"[main] %d calendar(s) in %d account(s)\", len(cals), len(accounts))\n\n\tif len(cals) == 0 {\n\t\treturn nil, errNoCalendars\n\t}\n\n\treturn cals, nil\n}\n\nfunc activeCalendarIDs() (map[string]bool, error) {\n\tvar (\n\t\tIDs   []string\n\t\tIDMap = map[string]bool{}\n\t\tname  = \"active.json\"\n\t)\n\n\tif !wf.Cache.Exists(name) {\n\t\treturn nil, errNoActive\n\t}\n\n\tif err := wf.Cache.LoadJSON(name, &IDs); err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, id := range IDs {\n\t\tIDMap[id] = true\n\t}\n\n\tif len(IDMap) == 0 {\n\t\treturn nil, errNoActive\n\t}\n\n\treturn IDMap, nil\n}\n\nfunc activeCalendars() ([]*Calendar, error) {\n\tvar (\n\t\tcals []*Calendar\n\t\tall  []*Calendar\n\t\tIDs  map[string]bool\n\t\terr  error\n\t)\n\n\tif IDs, err = activeCalendarIDs(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif all, err = allCalendars(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(all) == 0 {\n\t\treturn nil, errNoCalendars\n\t}\n\n\tfor _, c := range all {\n\t\tif IDs[c.ID] {\n\t\t\tcals = append(cals, c)\n\t\t}\n\t}\n\n\tif len(cals) == 0 {\n\t\treturn nil, errNoActive\n\t}\n\n\treturn cals, nil\n}\n\nfunc writableCalendars() ([]*Calendar, error) {\n\tvar (\n\t\tcals      []*Calendar\n\t\tall       []*Calendar\n\t\twriteable []*Calendar\n\t\tIDs       map[string]bool\n\t\terr       error\n\t)\n\n\tif IDs, err = activeCalendarIDs(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, acc := range accounts {\n\t\tall = append(all, acc.Calendars...)\n\t\tif acc.ReadWrite {\n\t\t\twriteable = append(writeable, acc.Calendars...)\n\t\t}\n\t}\n\n\tif len(all) == 0 {\n\t\treturn nil, errNoCalendars\n\t}\n\n\tif len(writeable) == 0 {\n\t\treturn nil, errNoWritable\n\t}\n\n\tfor _, c := range writeable {\n\t\tif IDs[c.ID] {\n\t\t\tcals = append(cals, c)\n\t\t}\n\t}\n\n\tif len(cals) == 0 {\n\t\treturn nil, errNoActive\n\t}\n\n\treturn cals, nil\n}\n"
  },
  {
    "path": "cmd_config.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport (\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\taw \"github.com/deanishe/awgo\"\n\t\"github.com/pkg/errors\"\n)\n\n// doConfig shows configuration options.\nfunc doConfig() error {\n\twf.Var(\"CALENDAR_APP\", \"\") // Open links in default browser, not CALENDAR_APP\n\n\tif opts.Query == \"\" {\n\t\twf.Configure(aw.SuppressUIDs(true))\n\t}\n\n\tif len(accounts) > 0 {\n\t\twf.NewItem(\"Active Calendars…\").\n\t\t\tSubtitle(\"Turn calendars on/off\").\n\t\t\tUID(\"calendars\").\n\t\t\tIcon(iconCalendars).\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"calendars\")\n\n\t\twf.NewItem(\"Add Account…\").\n\t\t\tSubtitle(\"Action this item to add a Google account\").\n\t\t\tUID(\"add-account\").\n\t\t\tAutocomplete(\"workflow:login\").\n\t\t\tIcon(iconAccountAdd)\n\t} else {\n\t\twf.NewItem(\"No Accounts Configured\").\n\t\t\tSubtitle(\"Action this item to add a Google account\").\n\t\t\tUID(\"add-account\").\n\t\t\tAutocomplete(\"workflow:login\").\n\t\t\tIcon(aw.IconWarning)\n\t}\n\n\tfor _, acc := range accounts {\n\t\tit := wf.NewItem(acc.Name).\n\t\t\tSubtitle(\"⌥↩ to remove account / ⌘↩ to re-authenticate\").\n\t\t\tUID(acc.Name).\n\t\t\tArg(acc.Name).\n\t\t\tValid(false).\n\t\t\tIcon(acc.Icon())\n\n\t\tit.NewModifier(\"opt\").\n\t\t\tSubtitle(\"Remove account\").\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"logout\").\n\t\t\tVar(\"account\", acc.Name)\n\n\t\tit.NewModifier(\"cmd\").\n\t\t\tSubtitle(\"Re-authenticate account with read-write permission\").\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"reauth\").\n\t\t\tVar(\"account\", acc.Name)\n\t}\n\n\tvar (\n\t\tname  = \"Google Maps\"\n\t\tother = \"Apple Maps\"\n\t\ticon  = iconGoogleMaps\n\t\targ   = \"apple\"\n\t)\n\n\tif opts.UseAppleMaps {\n\t\tname, other = other, name\n\t\ticon = iconAppleMaps\n\t\targ = \"google\"\n\t}\n\n\twf.NewItem(\"Open Locations in \"+name).\n\t\tSubtitle(\"Toggle this setting to use \"+other).\n\t\tUID(\"location\").\n\t\tArg(arg).\n\t\tValid(true).\n\t\tIcon(icon).\n\t\tVar(\"action\", \"set\").\n\t\tVar(\"key\", \"maps\").\n\t\tVar(\"value\", arg)\n\n\tif wf.UpdateAvailable() {\n\t\twf.NewItem(\"An Update is Available\").\n\t\t\tSubtitle(\"A newer version of the workflow is available\").\n\t\t\tUID(\"update\").\n\t\t\tAutocomplete(\"workflow:update\").\n\t\t\tIcon(iconUpdateAvailable).\n\t\t\tValid(false)\n\t} else {\n\t\twf.NewItem(\"Workflow is up to Date\").\n\t\t\tSubtitle(\"Action to force update check\").\n\t\t\tUID(\"update\").\n\t\t\tIcon(iconUpdateOK).\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"update\")\n\t}\n\n\twf.NewItem(\"Open Documentation\").\n\t\tSubtitle(\"Open workflow README in your browser\").\n\t\tUID(\"docs\").\n\t\tArg(readmeURL).\n\t\tValid(true).\n\t\tIcon(iconDocs).\n\t\tVar(\"action\", \"open\")\n\n\twf.NewItem(\"Get Help\").\n\t\tSubtitle(\"Open alfredforum.com thread in your browser\").\n\t\tUID(\"forum\").\n\t\tArg(forumURL).\n\t\tValid(true).\n\t\tIcon(iconHelp).\n\t\tVar(\"action\", \"open\")\n\n\twf.NewItem(\"Report Issue\").\n\t\tSubtitle(\"Open GitHub issues in your browser\").\n\t\tUID(\"issues\").\n\t\tArg(helpURL).\n\t\tValid(true).\n\t\tIcon(iconIssue).\n\t\tVar(\"action\", \"open\")\n\n\twf.NewItem(\"Clear Cached Calendars & Events\").\n\t\tSubtitle(\"Remove cached list of calendars and events\").\n\t\tUID(\"clear\").\n\t\tIcon(iconDelete).\n\t\tValid(true).\n\t\tVar(\"action\", \"clear\")\n\n\tif opts.Query != \"\" {\n\t\twf.Filter(opts.Query)\n\t}\n\n\twf.WarnEmpty(\"No Matches\", \"Try a different query\")\n\twf.SendFeedback()\n\treturn nil\n}\n\n// doToggle turns a calendar on or off.\nfunc doToggle() error {\n\tIDs, err := activeCalendarIDs()\n\tif err != nil && err != errNoActive {\n\t\treturn err\n\t}\n\tif err == errNoActive {\n\t\tIDs = map[string]bool{}\n\t}\n\n\tif IDs[opts.CalendarID] {\n\t\tlog.Printf(\"deactivating calendar %s ...\", opts.CalendarID)\n\t\tdelete(IDs, opts.CalendarID)\n\t} else {\n\t\tlog.Printf(\"activating calendar %s ...\", opts.CalendarID)\n\t\tIDs[opts.CalendarID] = true\n\t}\n\n\tactive := []string{}\n\tfor ID := range IDs {\n\t\tactive = append(active, ID)\n\t}\n\n\tif err := wf.Cache.StoreJSON(\"active.json\", active); err != nil {\n\t\treturn errors.Wrap(err, \"save active calendar list\")\n\t}\n\n\t// calendars have changed, so delete cached schedules\n\treturn clearEvents()\n}\n\n// Re-authenticate specified account.\nfunc doReauth() error {\n\twf.Configure(aw.TextErrors(true))\n\tlog.Printf(\"[reauth] account=%q\", opts.Account)\n\n\tfor _, acc := range accounts {\n\t\tif acc.Name == opts.Account {\n\t\t\tacc.Token = nil\n\t\t\tif err := acc.Save(); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"reauth: save account\")\n\t\t\t}\n\n\t\t\t// retrieve calendar list to trigger authentication\n\t\t\tif err := acc.FetchCalendars(); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"reauth: fetch calendars\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// doLogout removes an account.\nfunc doLogout() error {\n\twf.Configure(aw.TextErrors(true))\n\n\tlog.Printf(\"[logout] account=%q\", opts.Account)\n\n\tdeleteMe := map[string]bool{}\n\n\tfor _, acc := range accounts {\n\t\tif acc.Name == opts.Account {\n\t\t\tfor _, cal := range acc.Calendars {\n\t\t\t\tdeleteMe[cal.ID] = true\n\t\t\t}\n\n\t\t\tif err := wf.Cache.Store(acc.CacheName(), nil); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"delete account file\")\n\t\t\t}\n\t\t\tif err := os.Remove(acc.IconPath()); err != nil && !os.IsNotExist(err) {\n\t\t\t\treturn errors.Wrap(err, \"delete account avatar\")\n\t\t\t}\n\n\t\t\tlog.Printf(\"[logout] removed account %q\", opts.Account)\n\t\t}\n\t}\n\n\tvar (\n\t\tactive []string\n\t\tIDs    map[string]bool\n\t\terr    error\n\t)\n\n\t// Remove active calendars belonging to account\n\tif IDs, err = activeCalendarIDs(); err != nil && err != errNoActive {\n\t\treturn errors.Wrap(err, \"get active calendars\")\n\t}\n\n\t// No active calendars to change\n\tif err == errNoActive || len(deleteMe) == 0 {\n\t\treturn nil\n\t}\n\n\tfor id := range IDs {\n\t\tif !deleteMe[id] {\n\t\t\tactive = append(active, id)\n\t\t}\n\t}\n\n\tif err := wf.Cache.StoreJSON(\"active.json\", active); err != nil {\n\t\treturn errors.Wrap(err, \"save active calendar list\")\n\t}\n\n\t// delete cached schedules now calendars have changed\n\treturn clearEvents()\n}\n\n// doClear removes cached calendars and events.\nfunc doClear() error {\n\tlog.Print(\"clearing cached calendars and events…\")\n\twf.Configure(aw.TextErrors(true))\n\n\tif err := clearEvents(); err != nil {\n\t\treturn errors.Wrap(err, \"clear cached data\")\n\t}\n\n\tfor _, acc := range accounts {\n\t\tacc.Calendars = []*Calendar{}\n\t\tif err := acc.Save(); err != nil {\n\t\t\treturn errors.Wrap(err, \"remove account calendars\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// delete cached events.\nfunc clearEvents() error {\n\tvar (\n\t\tinfos []os.FileInfo\n\t\terr   error\n\t)\n\n\tif infos, err = ioutil.ReadDir(wf.CacheDir()); err != nil {\n\t\treturn errors.Wrap(err, \"read cache directory\")\n\t}\n\n\tfor _, fi := range infos {\n\t\tname := fi.Name()\n\t\tif strings.HasPrefix(name, \"events-\") && strings.HasSuffix(name, \".json\") {\n\t\t\tif err = os.Remove(filepath.Join(wf.CacheDir(), name)); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"delete events cache file\")\n\t\t\t}\n\n\t\t\tlog.Printf(\"[cache] deleted %q\", name)\n\t\t}\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "cmd_dates.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\taw \"github.com/deanishe/awgo\"\n)\n\nvar (\n\toneDay     = time.Hour * 24\n\toneWeek    = oneDay * 7\n\ttoday      = midnight(time.Now())\n\ttomorrow   = midnight(today.AddDate(0, 0, 1))\n\tyesterday  = midnight(today.AddDate(0, 0, -1))\n\tparseRegex = regexp.MustCompile(`^(\\+|-)?(\\d+)(d|w)?$`)\n)\n\n// doDates shows a list of dates in Alfred.\nfunc doDates() error {\n\tif len(accounts) == 0 {\n\t\twf.NewItem(\"No Accounts Configured\").\n\t\t\tSubtitle(\"Action this item to add a Google account\").\n\t\t\tAutocomplete(\"workflow:login\").\n\t\t\tIcon(aw.IconWarning)\n\n\t\twf.SendFeedback()\n\t\treturn nil\n\t}\n\n\tvar parsed bool\n\n\tif t, ok := parseDate(opts.DateFormat); ok {\n\t\tparsed = true\n\n\t\tshort := t.Format(timeFormat)\n\t\tlong := t.Format(timeFormatLong)\n\n\t\twf.NewItem(long).\n\t\t\tSubtitle(relativeDays(t, false)).\n\t\t\tArg(short).\n\t\t\tAutocomplete(short).\n\t\t\tValid(true).\n\t\t\tIcon(iconDefault)\n\t} else {\n\t\tfor i := -3; i < 4; i++ {\n\t\t\tvar (\n\t\t\t\tt     = midnight(today.Add(oneDay * time.Duration(i)))\n\t\t\t\tlong  = t.Format(timeFormatLong)\n\t\t\t\tshort = t.Format(timeFormat)\n\t\t\t\ticon  = iconDefault\n\t\t\t)\n\n\t\t\tif t.Equal(today) {\n\t\t\t\ticon = iconCalToday\n\t\t\t}\n\n\t\t\twf.NewItem(relativeDays(t, true)).\n\t\t\t\tSubtitle(short).\n\t\t\t\tMatch(long + \" \" + t.Format(\"Monday\")).\n\t\t\t\tArg(short).\n\t\t\t\tAutocomplete(short).\n\t\t\t\tValid(true).\n\t\t\t\tIcon(icon)\n\t\t}\n\t}\n\n\tif !parsed && opts.DateFormat != \"\" {\n\t\t_ = wf.Filter(opts.DateFormat)\n\t}\n\n\twf.WarnEmpty(\"Invalid date\", \"Format is YYYY-MM-DD, YYYMMDD or [+|-]NN[d|w]\")\n\n\twf.SendFeedback()\n\treturn nil\n}\n\n// Return midnight in local timezone for given Time.\nfunc midnight(t time.Time) time.Time {\n\ts := t.Local().Format(timeFormat)\n\tm, err := time.ParseInLocation(timeFormat, s, time.Local)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn m\n}\n\n// parse string into Time. Boolean is true if parsing was successful.\nfunc parseDate(s string) (time.Time, bool) {\n\ts = strings.TrimSpace(s)\n\tif s == \"\" {\n\t\treturn time.Time{}, false\n\t}\n\n\tif t, err := time.ParseInLocation(timeFormat, s, time.Local); err == nil {\n\t\treturn t, true\n\t}\n\tif t, err := time.ParseInLocation(\"20060102\", s, time.Local); err == nil {\n\t\treturn t, true\n\t}\n\n\t// Parse custom format [+|-]NN[d|w]\n\tvar (\n\t\tadd   = true\n\t\tdelta time.Duration\n\t\tt     time.Time\n\t\tunit  = \"d\"\n\t)\n\tm := parseRegex.FindStringSubmatch(s)\n\tif m == nil {\n\t\treturn time.Time{}, false\n\t}\n\n\t// Sign\n\tif m[1] == \"-\" {\n\t\tadd = false\n\t}\n\t// Count\n\tn, err := strconv.Atoi(m[2])\n\tif err != nil {\n\t\treturn time.Time{}, false\n\t}\n\n\tif n == 0 {\n\t\treturn today, true\n\t}\n\n\t// Optional unit\n\tif m[3] != \"\" {\n\t\tunit = m[3]\n\t}\n\n\t// Calculate date\n\tif unit == \"d\" {\n\t\tdelta = oneDay * time.Duration(n)\n\t} else {\n\t\tdelta = oneWeek * time.Duration(n)\n\t}\n\n\tif add {\n\t\tt = today.Add(delta)\n\t} else {\n\t\tt = today.Add(-delta)\n\t}\n\n\treturn midnight(t), true\n}\n\n// Return Time as \"x day(s) ago\" or \"in x day(s)\"\nfunc relativeDays(t time.Time, names bool) string {\n\tvar (\n\t\td    time.Duration\n\t\tdays int\n\t)\n\tif t.Before(today) {\n\t\td = today.Sub(t)\n\t} else if t.After(today) {\n\t\td = t.Sub(today)\n\t} else {\n\t\treturn \"Today\"\n\t}\n\tdays = int(d.Hours() / 24)\n\n\t// Return day name\n\tif names {\n\t\tif days == 1 {\n\t\t\tif t.Before(today) {\n\t\t\t\treturn \"Yesterday\"\n\t\t\t}\n\t\t\treturn \"Tomorrow\"\n\t\t}\n\t\treturn t.Format(\"Monday\")\n\t}\n\n\tvar (\n\t\tformat string\n\t\tunit   = \"days\"\n\t)\n\n\t// Return in N day(s) or N day(s) ago\n\tformat = \"%d %s ago\"\n\tif t.After(today) {\n\t\tformat = \"in %d %s\"\n\t}\n\tif days == 1 {\n\t\tunit = \"day\"\n\t}\n\treturn fmt.Sprintf(format, days, unit)\n}\n\n// relativeDate returns Yesterday, Today, Tomorrow or long date.\nfunc relativeDate(t time.Time) string {\n\tt = midnight(t)\n\tif t.Equal(today) {\n\t\treturn \"Today\"\n\t}\n\tif t.Equal(yesterday) {\n\t\treturn \"Yesterday\"\n\t}\n\tif t.Equal(tomorrow) {\n\t\treturn \"Tomorrow\"\n\t}\n\treturn t.Format(\"Monday, 2 Jan 2006\")\n}\n"
  },
  {
    "path": "cmd_dates_test.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport \"testing\"\n\nvar validFormats = []string{\n\t\"2017-11-25\", // date strings\n\t\"20171125\",\n\t\"7\", // no units\n\t\"-7\",\n\t\"1d\", // days\n\t\"+1d\",\n\t\"-1d\",\n\t\"2w\", // weeks\n\t\"+2w\",\n\t\"-2w\",\n}\n\nvar invalidFormats = []string{\n\t\"1m\",\n\t\"2q\",\n\t\"l1d\",\n\t\"*2d\",\n}\n\nfunc TestParseDate(t *testing.T) {\n\ttm, ok := parseDate(\"0\")\n\tif !tm.Equal(today) || !ok {\n\t\tt.Errorf(\"zero format failed. tm=%v\", tm)\n\t}\n\n\tfor _, s := range validFormats {\n\t\ttm, ok := parseDate(s)\n\t\tif !ok {\n\t\t\tt.Errorf(\"error parsing valid format %q\", s)\n\t\t}\n\t\tif tm.IsZero() {\n\t\t\tt.Errorf(\"zero time for valid format %q\", s)\n\t\t}\n\t}\n\n\tfor _, s := range invalidFormats {\n\t\ttm, ok := parseDate(s)\n\t\tif ok {\n\t\t\tt.Errorf(\"no error parsing invalid format %q\", s)\n\t\t}\n\t\tif !tm.IsZero() {\n\t\t\tt.Errorf(\"non-zero time for invalid format %q: %v\", s, tm)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd_events.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"time\"\n\n\taw \"github.com/deanishe/awgo\"\n\t\"github.com/pkg/errors\"\n)\n\n// doEvents shows a list of events in Alfred.\nfunc doEvents() error {\n\tif len(accounts) == 0 {\n\t\twf.NewItem(\"No Accounts Configured\").\n\t\t\tSubtitle(\"Action this item to add a Google account\").\n\t\t\tAutocomplete(\"workflow:login\").\n\t\t\tIcon(aw.IconWarning)\n\n\t\twf.SendFeedback()\n\t\treturn nil\n\t}\n\n\tvar (\n\t\tcals []*Calendar\n\t\terr  error\n\t)\n\n\tif cals, err = activeCalendars(); err != nil {\n\t\tif err == errNoActive {\n\t\t\twf.NewItem(\"No Active Calendars\").\n\t\t\t\tSubtitle(\"Action this item to choose calendars\").\n\t\t\t\tAutocomplete(\"workflow:calendars\").\n\t\t\t\tIcon(aw.IconWarning)\n\n\t\t\twf.SendFeedback()\n\n\t\t\treturn nil\n\t\t}\n\n\t\tif err == errNoCalendars {\n\t\t\tif !wf.IsRunning(\"update-calendars\") {\n\t\t\t\tcmd := exec.Command(os.Args[0], \"update\", \"calendars\")\n\t\t\t\tif err := wf.RunInBackground(\"update-calendars\", cmd); err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"run calendar update\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twf.NewItem(\"Fetching List of Calendars…\").\n\t\t\t\tSubtitle(\"List will reload shortly\").\n\t\t\t\tValid(false).\n\t\t\t\tIcon(ReloadIcon())\n\n\t\t\twf.Rerun(0.1)\n\t\t\twf.SendFeedback()\n\n\t\t\treturn nil\n\t\t}\n\n\t\treturn err\n\t}\n\n\tlog.Printf(\"%d active calendar(s)\", len(cals))\n\n\tvar (\n\t\tall    []*Event\n\t\tevents []*Event\n\t\tparsed time.Time\n\t)\n\n\tif all, err = loadEvents(opts.StartTime, cals...); err != nil {\n\t\treturn errors.Wrap(err, \"load events\")\n\t}\n\n\t// Filter out events after cutoff\n\tfor _, e := range all {\n\t\tif !opts.ScheduleMode && e.Start.After(opts.EndTime) {\n\t\t\tbreak\n\t\t}\n\t\tevents = append(events, e)\n\t\tlog.Printf(\"%s\", e.Title)\n\t}\n\n\tif len(all) == 0 && wf.IsRunning(\"update-events\") {\n\t\twf.NewItem(\"Fetching Events…\").\n\t\t\tSubtitle(\"Results will refresh shortly\").\n\t\t\tIcon(ReloadIcon()).\n\t\t\tValid(false)\n\n\t\twf.Rerun(0.1)\n\t}\n\n\tlog.Printf(\"%d event(s) for %s\", len(events), opts.StartTime.Format(timeFormat))\n\n\tif t, ok := parseDate(opts.Query); ok {\n\t\tparsed = t\n\t}\n\n\tif len(events) == 0 && opts.Query == \"\" {\n\t\twf.NewItem(fmt.Sprintf(\"No Events on %s\", opts.StartTime.Format(timeFormatLong))).\n\t\t\tIcon(ColouredIcon(iconCalendar, yellow))\n\t}\n\n\tvar day time.Time\n\n\tfor _, e := range events {\n\t\t// Show day indicator if this is the first event of a given day\n\t\tif opts.ScheduleMode && midnight(e.Start).After(day) {\n\t\t\tday = midnight(e.Start)\n\n\t\t\twf.NewItem(day.Format(timeFormatLong)).\n\t\t\t\tArg(day.Format(timeFormat)).\n\t\t\t\tValid(true).\n\t\t\t\tIcon(iconDay)\n\t\t}\n\n\t\ticon := ColouredIcon(iconCalendar, e.Colour)\n\n\t\tsub := fmt.Sprintf(\"%s – %s / %s\",\n\t\t\te.Start.Local().Format(hourFormat),\n\t\t\te.End.Local().Format(hourFormat),\n\t\t\te.CalendarTitle)\n\n\t\tif e.Location != \"\" {\n\t\t\tsub = sub + \" / \" + e.Location\n\t\t}\n\n\t\tit := wf.NewItem(e.Title).\n\t\t\tSubtitle(sub).\n\t\t\tIcon(icon).\n\t\t\tArg(e.URL).\n\t\t\tQuicklook(previewURL(opts.StartTime, e.ID)).\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"open\")\n\n\t\tif e.Location != \"\" {\n\t\t\tapp := \"Google Maps\"\n\t\t\tif opts.UseAppleMaps {\n\t\t\t\tapp = \"Apple Maps\"\n\t\t\t}\n\n\t\t\ticon := ColouredIcon(iconMap, e.Colour)\n\t\t\tit.NewModifier(\"cmd\").\n\t\t\t\tSubtitle(\"Open in \"+app).\n\t\t\t\tArg(mapURL(e.Location)).\n\t\t\t\tValid(true).\n\t\t\t\tIcon(icon).\n\t\t\t\tVar(\"CALENDAR_APP\", \"\") // Don't open Maps URLs in CALENDAR_APP\n\t\t}\n\t}\n\n\tif !opts.ScheduleMode {\n\t\t// Navigation items\n\t\tprev := opts.StartTime.AddDate(0, 0, -1)\n\t\twf.NewItem(\"Previous: \"+relativeDate(prev)).\n\t\t\tIcon(iconPrevious).\n\t\t\tArg(prev.Format(timeFormat)).\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"date\")\n\n\t\tnext := opts.StartTime.AddDate(0, 0, 1)\n\t\twf.NewItem(\"Next: \"+relativeDate(next)).\n\t\t\tIcon(iconNext).\n\t\t\tArg(next.Format(timeFormat)).\n\t\t\tValid(true).\n\t\t\tVar(\"action\", \"date\")\n\t}\n\n\tif opts.Query != \"\" {\n\t\twf.Filter(opts.Query)\n\t}\n\n\tif !parsed.IsZero() {\n\t\ts := parsed.Format(timeFormat)\n\n\t\twf.NewItem(parsed.Format(timeFormatLong)).\n\t\t\tSubtitle(relativeDays(parsed, false)).\n\t\t\tArg(s).\n\t\t\tAutocomplete(s).\n\t\t\tValid(true).\n\t\t\tIcon(iconDefault)\n\t}\n\n\twf.WarnEmpty(\"No Matching Events\", \"Try a different query?\")\n\twf.SendFeedback()\n\treturn nil\n}\n\n// loadEvents loads events for given date calendar(s) from cache or server.\nfunc loadEvents(t time.Time, cal ...*Calendar) ([]*Event, error) {\n\tvar (\n\t\tevents  = []*Event{}\n\t\tdateStr = t.Format(timeFormat)\n\t\tname    = fmt.Sprintf(\"events-%s.json\", dateStr)\n\t\tjobName = \"update-events\"\n\t)\n\n\tif wf.Cache.Expired(name, opts.MaxAgeEvents()) {\n\t\twf.Rerun(0.1)\n\t\tif !wf.IsRunning(jobName) {\n\t\t\tcmd := exec.Command(os.Args[0], \"update\", \"events\", dateStr)\n\t\t\tif err := wf.RunInBackground(jobName, cmd); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tif wf.Cache.Exists(name) {\n\t\tif err := wf.Cache.LoadJSON(name, &events); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Set map URL\n\tfor _, e := range events {\n\t\te.MapURL = mapURL(e.Location)\n\t}\n\treturn events, nil\n}\n"
  },
  {
    "path": "cmd_open.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-30\n//\n\npackage main\n\nimport (\n\t\"log\"\n\t\"os/exec\"\n\n\taw \"github.com/deanishe/awgo\"\n)\n\n// Open URL in specified app or in default.\nfunc doOpen() error {\n\twf.Configure(aw.TextErrors(true))\n\targs := []string{}\n\tif opts.App != \"\" {\n\t\tlog.Printf(\"[open] opening \\\"%s\\\" in \\\"%s\\\"…\", opts.URL, opts.App)\n\t\targs = append(args, \"-a\", opts.App)\n\t} else {\n\t\tlog.Printf(\"[open] opening \\\"%s\\\" in default browser…\", opts.URL)\n\t}\n\targs = append(args, opts.URL)\n\n\tcmd := exec.Command(\"/usr/bin/open\", args...)\n\treturn cmd.Run()\n}\n"
  },
  {
    "path": "cmd_quickadd.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2019-04-03\n//\n\npackage main\n\nimport (\n\t\"log\"\n)\n\n// quickAdd check if there are configured accounts and pass data to create an event.\nfunc quickAdd() error {\n\tlog.Println(\"Creating event\", opts.Quick, opts.CalendarID)\n\n\tif err := createEvent(opts.Quick, opts.CalendarID); err != nil {\n\t\treturn err\n\t}\n\n\tif err := doUpdateEvents(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// createEvent looks for account by calendar ID and create new event in that account.\nfunc createEvent(quick string, calendarID string) error {\n\tfor _, acc := range accounts {\n\t\tfor _, c := range acc.Calendars {\n\t\t\tif c.ID == calendarID {\n\t\t\t\treturn acc.QuickAdd(calendarID, quick)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd_reload.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-30\n//\n\npackage main\n\n// Show reload bar.\nfunc doReload() error {\n\twf.Rerun(0.1)\n\n\twf.NewItem(\"Progress…\").\n\t\tIcon(ReloadIcon())\n\n\twf.SendFeedback()\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd_server.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-26\n//\n\npackage main\n\nimport (\n\t\"context\"\n\t\"html/template\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst (\n\tpreviewServerURL = \"localhost:61433\"\n\tquitAfter        = 90 * time.Second\n)\n\n// previewURL returns a preview server URL.\nfunc previewURL(t time.Time, eventID string) string {\n\tu, _ := url.Parse(\"http://\" + previewServerURL)\n\tv := u.Query()\n\tv.Set(\"date\", midnight(t).Format(timeFormat))\n\tv.Set(\"event\", eventID)\n\tu.RawQuery = v.Encode()\n\treturn u.String()\n}\n\n// doStartServer starts the preview server.\nfunc doStartServer() error {\n\tlog.Printf(\"[preview] starting preview server on %s ...\", previewServerURL)\n\tvar (\n\t\tlastRequest = time.Now()\n\t\tmu          = sync.Mutex{}\n\t\tc           = make(chan struct{})\n\t\ttemplates   = template.Must(template.ParseFiles(filepath.Join(wf.Dir(), \"preview.html\")))\n\t\tmux         = http.NewServeMux()\n\t\tsrv         = &http.Server{\n\t\t\tAddr:    previewServerURL,\n\t\t\tHandler: mux,\n\t\t}\n\t)\n\n\tgo func() {\n\t\tif err := srv.ListenAndServe(); err != nil {\n\t\t\tif err == http.ErrServerClosed {\n\t\t\t\tlog.Print(\"[preview] server stopped\")\n\t\t\t} else {\n\t\t\t\tlog.Printf(\"[preview] ERR: server failed: %v\", err)\n\t\t\t}\n\t\t}\n\t\tc <- struct{}{}\n\t}()\n\n\tgo func() {\n\t\tc := time.Tick(30 * time.Second)\n\t\tfor now := range c {\n\t\t\tmu.Lock()\n\t\t\td := now.Sub(lastRequest)\n\t\t\tmu.Unlock()\n\t\t\tlog.Printf(\"[preview] %0.0fs since last request\", d.Seconds())\n\t\t\tif d >= quitAfter {\n\t\t\t\tif err := srv.Shutdown(context.Background()); err != nil {\n\t\t\t\t\tlog.Printf(\"[preview] server shutdown error: %v\", err)\n\t\t\t\t}\n\t\t\t\t// log.Printf(\"[preview] server stopped\")\n\t\t\t}\n\t\t}\n\t}()\n\n\tmux.HandleFunc(\"/\", func(w http.ResponseWriter, req *http.Request) {\n\t\tdefer func() {\n\t\t\tmu.Lock()\n\t\t\tlastRequest = time.Now()\n\t\t\tmu.Unlock()\n\t\t}()\n\n\t\tvar (\n\t\t\tv       = req.URL.Query()\n\t\t\tdateStr = v.Get(\"date\")\n\t\t\teventID = v.Get(\"event\")\n\t\t\tevent   *Event\n\t\t)\n\t\tlog.Printf(\"[preview] date=%s, event=%s\", dateStr, eventID)\n\n\t\t// Load events\n\t\tt, err := time.Parse(timeFormat, dateStr)\n\t\tif err != nil {\n\t\t\tif _, err := io.WriteString(w, \"bad date\\n\"); err != nil {\n\t\t\t\tlog.Printf(\"[error] write server response: %v\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tcals, err := activeCalendars()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"[preview] ERR: load active calendars: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tlog.Printf(\"[preview] %d active calendar(s)\", len(cals))\n\t\tevents, err := loadEvents(t, cals...)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"[preview] ERR: load events: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tfor _, e := range events {\n\t\t\tif e.ID == eventID {\n\t\t\t\tevent = e\n\t\t\t\tevent.MapURL = mapURL(event.Location)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif event == nil {\n\t\t\tif err := templates.ExecuteTemplate(w, \"fail\", eventID); err != nil {\n\t\t\t\tlog.Printf(`[preview] ERR: execute template \"fail\": %v`, err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif err := templates.ExecuteTemplate(w, \"event\", event); err != nil {\n\t\t\tlog.Printf(`[preview] ERR: execute template \"event\": %v`, err)\n\t\t}\n\t})\n\n\t<-c\n\treturn nil\n}\n"
  },
  {
    "path": "cmd_set.go",
    "content": "// Copyright (c) 2019 Dean Jackson <deanishe@deanishe.net>\n// MIT Licence applies http://opensource.org/licenses/MIT\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\taw \"github.com/deanishe/awgo\"\n)\n\n// Change a setting.\nfunc doSet() error {\n\twf.Configure(aw.TextErrors(true))\n\n\tlog.Printf(\"[set] key=%q, value=%q\", opts.Key, opts.Value)\n\n\tswitch opts.Key {\n\tcase \"maps\":\n\t\tvalue := \"1\"\n\t\tif opts.Value == \"google\" {\n\t\t\tvalue = \"0\"\n\t\t}\n\t\treturn wf.Config.Set(\"APPLE_MAPS\", value, true).Do()\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown config key: %s\", opts.Key)\n\t}\n}\n"
  },
  {
    "path": "cmd_update.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\taw \"github.com/deanishe/awgo\"\n\t\"github.com/deanishe/awgo/util\"\n\t\"github.com/pkg/errors\"\n)\n\n// Check if a new version of the workflow is available.\nfunc doUpdateWorkflow() error {\n\twf.Configure(aw.TextErrors(true))\n\n\tlog.Print(\"[update] checking for new version of workflow…\")\n\n\treturn wf.CheckForUpdate()\n}\n\n// Fetch and cache list of calendars.\nfunc doUpdateCalendars() error {\n\tvar (\n\t\tacc *Account\n\t\terr error\n\t)\n\n\twf.Configure(aw.TextErrors(true))\n\n\tlog.Print(\"[update] reloading calendars…\")\n\n\tif len(accounts) == 0 {\n\t\tlog.Print(\"[update] no Google accounts configured\")\n\t}\n\n\tfor _, acc = range accounts {\n\t\tif err = acc.FetchCalendars(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !util.PathExists(acc.IconPath()) {\n\t\t\tif err := download(acc.AvatarURL, acc.IconPath()); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"fetch account avatar\")\n\t\t\t}\n\t\t}\n\n\t\tlog.Printf(\"[update] %d calendar(s) in account %q\", len(acc.Calendars), acc.Name)\n\t}\n\n\treturn nil\n}\n\n// Fetch events for a specified date.\nfunc doUpdateEvents() error {\n\twf.Configure(aw.TextErrors(true))\n\n\tvar (\n\t\tname   = fmt.Sprintf(\"events-%s.json\", opts.StartTime.Format(timeFormat))\n\t\tcals   []*Calendar\n\t\tevents []*Event\n\t\terr    error\n\t)\n\n\tlog.Printf(\"[update] fetching events for %s ...\", opts.StartTime.Format(timeFormat))\n\n\tif err := clearOldFiles(); err != nil {\n\t\tlog.Printf(\"[update] ERR: delete old cache files: %v\", err)\n\t}\n\n\tif cals, err = activeCalendars(); err != nil {\n\t\treturn err\n\t}\n\n\tif len(accounts) == 0 {\n\t\tlog.Print(\"[update] no Google accounts configured\")\n\t\treturn nil\n\t}\n\n\tif len(cals) == 0 {\n\t\tlog.Print(\"[update] no active calendars\")\n\t\treturn nil\n\t}\n\n\tlog.Printf(\"[update] %d active calendar(s)\", len(cals))\n\n\t// Fetch events in parallel\n\tvar (\n\t\tch     = make(chan *Event)\n\t\twg     sync.WaitGroup\n\t\twanted = make(map[string]bool, len(cals)) // IDs of calendars to update\n\t)\n\n\tfor _, c := range cals {\n\t\twanted[c.ID] = true\n\t}\n\n\twg.Add(len(cals))\n\n\tfor _, acc := range accounts {\n\t\tfor _, c := range acc.Calendars {\n\t\t\tif _, ok := wanted[c.ID]; !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tgo func(c *Calendar, acc *Account) {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\tevs, err := acc.FetchEvents(c, opts.StartTime)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"[update] ERR: fetching events for calendar %q: %v\", c.Title, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tlog.Printf(\"[update] %d event(s) in calendar %q\", len(evs), c.Title)\n\n\t\t\t\tfor _, e := range evs {\n\t\t\t\t\tch <- e\n\t\t\t\t}\n\t\t\t}(c, acc)\n\t\t}\n\t}\n\n\t// Close channel when all goroutines are done\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(ch)\n\t}()\n\n\tcolours := map[string]bool{}\n\tfor e := range ch {\n\t\tlog.Printf(\"[update] %s\", e)\n\t\tevents = append(events, e)\n\t\tcolours[e.Colour] = true\n\t}\n\n\tsort.Sort(EventsByStart(events))\n\n\tif err := wf.Cache.StoreJSON(name, events); err != nil {\n\t\treturn err\n\t}\n\n\t// Ensure icons exist in all colours\n\tfor clr := range colours {\n\t\t_ = ColouredIcon(iconCalendar, clr)\n\t\t_ = ColouredIcon(iconMap, clr)\n\t}\n\n\treturn nil\n}\n\n// Remove events-* files and icons older than two weeks.\nfunc clearOldFiles() error {\n\tvar (\n\t\tcutoff = time.Now().AddDate(0, 0, -14)\n\t\tdirs   = []string{}\n\t)\n\n\terr := filepath.Walk(wf.CacheDir(), func(path string, fi os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif fi.Name() == \"_aw\" && fi.IsDir() {\n\t\t\treturn filepath.SkipDir\n\t\t}\n\n\t\tif fi.IsDir() {\n\t\t\tdirs = append(dirs, path)\n\t\t\treturn nil\n\t\t}\n\n\t\tif fi.ModTime().After(cutoff) {\n\t\t\treturn nil\n\t\t}\n\n\t\text := filepath.Ext(path)\n\n\t\tif (strings.HasPrefix(fi.Name(), \"events-\") && ext == \".json\") || ext == \".png\" {\n\t\t\tif err := os.Remove(path); err != nil {\n\t\t\t\tlog.Printf(\"[cache] ERR: delete %q: %v\", path, err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Remove empty directories. Sort in reverse order so sub-directories are\n\t// before their parents.\n\tsort.Sort(sort.Reverse(sort.StringSlice(dirs)))\n\nOuter:\n\tfor _, dir := range dirs {\n\t\tinfos, err := ioutil.ReadDir(dir)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"[cache] ERR: open dir %s: %v\", dir, err)\n\t\t\treturn err\n\t\t}\n\n\t\t// ignore dotfiles\n\t\tfor _, fi := range infos {\n\t\t\tif strings.HasPrefix(fi.Name(), \".\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// rel, _ := filepath.Rel(wf.CacheDir(), dir)\n\t\t\t// log.Printf(\"[cache] %s -- %d item(s)\", rel, len(infos))\n\t\t\tcontinue Outer\n\t\t}\n\n\t\tif err := os.RemoveAll(dir); err != nil {\n\t\t\tlog.Printf(\"[cache] ERR: delete dir %s: %v\", dir, err)\n\t\t\treturn err\n\t\t}\n\t\tlog.Printf(\"[cache] deleted dir: %s\", util.PrettyPath(dir))\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "env.sh",
    "content": "\n# When sourced, creates an Alfred-like environment needed by modd\n# and ./bin/build (which sources the file itself)\n\n# getvar <name> | Read a value from info.plist\ngetvar() {\n    local v=\"$1\"\n    /usr/libexec/PlistBuddy -c \"Print :$v\" info.plist\n}\n\nexport alfred_workflow_bundleid=$( getvar \"bundleid\" )\nexport alfred_workflow_version=$( getvar \"version\" )\nexport alfred_workflow_name=$( getvar \"name\" )\n\nexport alfred_workflow_cache=\"${HOME}/Library/Caches/com.runningwithcrayons.Alfred/Workflow Data/${alfred_workflow_bundleid}\"\nexport alfred_workflow_data=\"${HOME}/Library/Application Support/Alfred/Workflow Data/${alfred_workflow_bundleid}\"\n\nif [[ ! -f \"$HOME/Library/Preferences/com.runningwithcrayons.Alfred.plist\" ]]; then\n    export alfred_workflow_cache=\"${HOME}/Library/Caches/com.runningwithcrayons.Alfred-3/Workflow Data/${alfred_workflow_bundleid}\"\n    export alfred_workflow_data=\"${HOME}/Library/Application Support/Alfred 3/Workflow Data/${alfred_workflow_bundleid}\"\n    export alfred_version=\"3.8.1\"\nfi\n\nexport SCHEDULE_DAYS=$( getvar \"variables:SCHEDULE_DAYS\" )\nexport EVENT_CACHE_MINS=$( getvar \"variables:EVENT_CACHE_MINS\" )\n"
  },
  {
    "path": "events.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n)\n\nconst (\n\tgMapsURL = \"https://www.google.com/maps/search/?api=1\"\n\taMapsURL = \"http://maps.apple.com/\"\n)\n\n// Calendar is a Google Calendar\ntype Calendar struct {\n\tID          string // Calendar ID\n\tTitle       string // Calendar title\n\tDescription string // Calendar description\n\tColour      string // CSS hex colour of calendar\n\n\tAccountName string // Name of account this calendar belongs to\n}\n\n// CalsByTitle sorts a slice of Calendars by title\ntype CalsByTitle []*Calendar\n\nfunc (s CalsByTitle) Len() int           { return len(s) }\nfunc (s CalsByTitle) Less(i, j int) bool { return s[i].Title < s[j].Title }\nfunc (s CalsByTitle) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }\n\n// Event is a calendar event\ntype Event struct {\n\tID            string    // Event ID\n\tIcalUID       string    // Cross-platform UID\n\tTitle         string    // Event title\n\tDescription   string    // Event summary/description\n\tURL           string    // Event URL\n\tMapURL        string    // Google Maps URL\n\tLocation      string    // Where the event takes place\n\tStart         time.Time // Time event started\n\tEnd           time.Time // Time event finished\n\tColour        string    // CSS hex colour of event\n\tCalendarID    string    // Calendar event belongs to\n\tCalendarTitle string    // Title of calendar event belongs to\n}\n\n// Duration returns the duration of the Event\nfunc (e *Event) Duration() time.Duration { return e.End.Sub(e.Start) }\n\nfunc (e *Event) String() string {\n\tdate := e.Start.Format(\"2/1 at 15:04\")\n\treturn fmt.Sprintf(\"\\\"%s\\\" on %s for %0.0fm\", e.Title, date, e.Duration().Minutes())\n}\n\n// EventsByStart sorts a slice of Events by start time.\ntype EventsByStart []*Event\n\nfunc (s EventsByStart) Len() int           { return len(s) }\nfunc (s EventsByStart) Less(i, j int) bool { return s[i].Start.Before(s[j].Start) }\nfunc (s EventsByStart) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }\n\n// URL that points to location on Google Maps or Apple Maps.\nfunc mapURL(location string) string {\n\tif location == \"\" {\n\t\treturn \"\"\n\t}\n\tif opts.UseAppleMaps {\n\t\treturn appleMapsURL(location)\n\t}\n\treturn googleMapsURL(location)\n}\n\nfunc googleMapsURL(location string) string {\n\tu, _ := url.Parse(gMapsURL)\n\tv := u.Query()\n\tv.Set(\"query\", location)\n\tu.RawQuery = v.Encode()\n\treturn u.String()\n}\n\nfunc appleMapsURL(location string) string {\n\tu, _ := url.Parse(aMapsURL)\n\tv := u.Query()\n\tv.Set(\"address\", location)\n\tu.RawQuery = v.Encode()\n\treturn u.String()\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/deanishe/alfred-gcal\n\nrequire (\n\tcloud.google.com/go v0.61.0 // indirect\n\tgithub.com/deanishe/awgo v0.25.0\n\tgithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815\n\tgithub.com/magefile/mage v1.10.0\n\tgithub.com/pkg/errors v0.9.1\n\tgolang.org/x/net v0.0.0-20200707034311-ab3426394381\n\tgolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d\n\tgolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect\n\tgoogle.golang.org/api v0.29.0\n\tgoogle.golang.org/genproto v0.0.0-20200720141249-1244ee217b7e // indirect\n)\n\nreplace github.com/golang/lint => golang.org/x/lint v0.0.0-20190409202823-959b441ac422\n\ngo 1.13\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.61.0 h1:NLQf5e1OMspfNT1RAHOB3ublr1TW3YTXO8OiWwVjK2U=\ncloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/bmatcuk/doublestar v1.3.1 h1:rT8rxDPsavp9G+4ZULzqhhUSaI/OPsTZNG88Z3i0xvY=\ngithub.com/bmatcuk/doublestar v1.3.1/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/deanishe/awgo v0.25.0 h1:r6/eJFETRB6Zex10XbjixSASGlc2a484b+FgRRAYY9g=\ngithub.com/deanishe/awgo v0.25.0/go.mod h1:reRZEwXsMuJISYxmdqkn24KrHN+WV/ZQzRBXTtKDPHY=\ngithub.com/deanishe/go-env v0.4.0 h1:tpu14o16gJGTN/w2gxntwxu2l5Eby30jSrnlgOfjzwk=\ngithub.com/deanishe/go-env v0.4.0/go.mod h1:RgEcGAqdRnt8ybQteAbv1Ys2lWIRE7TlgON/sbdjuaY=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=\ngithub.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc=\ngoogle.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200720141249-1244ee217b7e h1:KoSCpgvphmtnpVycBCtfV9hNdHbInsqdx4gqnDPQCkg=\ngoogle.golang.org/genproto v0.0.0-20200720141249-1244ee217b7e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhowett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc=\nhowett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "icons.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-26\n//\n\npackage main\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"image\"\n\t\"image/color\"\n\t\"image/draw\"\n\t\"image/png\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\taw \"github.com/deanishe/awgo\"\n\t\"github.com/deanishe/awgo/util\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\t// Static icons\n\ticonAccount         = &aw.Icon{Value: \"icons/account.png\"}\n\ticonAccountAdd      = &aw.Icon{Value: \"icons/account-add.png\"}\n\ticonDefault         = &aw.Icon{Value: \"icon.png\"} // Workflow icon\n\ticonCalendar        = &aw.Icon{Value: \"icons/calendar.png\"}\n\ticonCalendars       = &aw.Icon{Value: \"icons/calendars.png\"}\n\ticonCalOff          = &aw.Icon{Value: \"icons/calendar-off.png\"}\n\ticonCalOn           = &aw.Icon{Value: \"icons/calendar-on.png\"}\n\ticonCalToday        = &aw.Icon{Value: \"icons/calendar-today.png\"}\n\ticonDay             = &aw.Icon{Value: \"icons/day.png\"}\n\ticonDelete          = &aw.Icon{Value: \"icons/trash.png\"}\n\ticonDocs            = &aw.Icon{Value: \"icons/docs.png\"}\n\ticonIssue           = &aw.Icon{Value: \"icons/issue.png\"}\n\ticonHelp            = &aw.Icon{Value: \"icons/help.png\"}\n\ticonMap             = &aw.Icon{Value: \"icons/map.png\"}\n\ticonNext            = &aw.Icon{Value: \"icons/next.png\"}\n\ticonPrevious        = &aw.Icon{Value: \"icons/previous.png\"}\n\ticonLoading         = &aw.Icon{Value: \"icons/loading.png\"}\n\ticonUpdateOK        = &aw.Icon{Value: \"icons/update-ok.png\"}\n\ticonUpdateAvailable = &aw.Icon{Value: \"icons/update-available.png\"}\n\ticonWarning         = &aw.Icon{Value: \"icons/warning.png\"}\n\ticonAppleMaps       = &aw.Icon{Value: \"/Applications/Maps.app\", Type: aw.IconTypeFileIcon}\n\ticonGoogleMaps      = &aw.Icon{Value: \"icons/google-maps.png\"}\n)\n\nfunc init() {\n\taw.IconWarning = iconWarning\n\t// Maps.app has moved on Catalina\n\tif util.PathExists(\"/System/Applications/Maps.app\") {\n\t\ticonAppleMaps.Value = \"/System/Applications/Maps.app\"\n\t}\n}\n\n// ColouredIcon returns a version of icon in the given colour. If no colour\n// is specified or something goes wrong, icon is simply returned.\nfunc ColouredIcon(icon *aw.Icon, colour string) *aw.Icon {\n\tvar (\n\t\tc    color.RGBA\n\t\tpath string\n\t\terr  error\n\t)\n\n\tif c, err = ParseHexColour(colour); err != nil {\n\t\tlog.Printf(\"[icons] ERR: %s\", err)\n\t\treturn icon\n\t}\n\n\tpath = iconCachePath(icon, c)\n\n\tif util.PathExists(path) {\n\t\treturn &aw.Icon{Value: path}\n\t}\n\n\tif err = generateIcon(icon.Value, path, c); err != nil {\n\t\tlog.Printf(\"[icons] ERR: generate icon: %v\", err)\n\t\treturn icon\n\t}\n\n\treturn &aw.Icon{Value: path}\n}\n\nvar client = &http.Client{\n\tTransport: &http.Transport{\n\t\tDial: (&net.Dialer{\n\t\t\tTimeout:   60 * time.Second,\n\t\t\tKeepAlive: 60 * time.Second,\n\t\t}).Dial,\n\t\tTLSHandshakeTimeout:   30 * time.Second,\n\t\tResponseHeaderTimeout: 30 * time.Second,\n\t\tExpectContinueTimeout: 10 * time.Second,\n\t},\n}\n\n// Save contents of URL to path.\nfunc download(url, path string) error {\n\tr, err := client.Get(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer r.Body.Close()\n\n\tlog.Printf(\"[%d] %s\", r.StatusCode, url)\n\tif r.StatusCode > 299 {\n\t\treturn fmt.Errorf(\"bad HTTP response: [%d] %s\", r.StatusCode, url)\n\t}\n\n\tf, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\tif _, err := io.Copy(f, r.Body); err != nil {\n\t\treturn err\n\t}\n\n\tlog.Printf(\"[icons] saved %q to %q\\n\", url, path)\n\n\treturn nil\n}\n\nfunc generateIcon(src, dest string, c color.RGBA) error {\n\t// defer util.Timed(time.Now(), \"generate icon\")\n\n\tvar (\n\t\tf    *os.File\n\t\tmask image.Image\n\t\terr  error\n\t)\n\n\tif f, err = os.Open(src); err != nil {\n\t\treturn errors.Wrap(err, \"open file\")\n\t}\n\tdefer f.Close()\n\n\tif mask, _, err = image.Decode(f); err != nil {\n\t\treturn errors.Wrap(err, \"decode image\")\n\t}\n\n\timg := image.NewRGBA(mask.Bounds())\n\tdraw.DrawMask(img, img.Bounds(), &image.Uniform{c}, image.Point{}, mask, image.Point{}, draw.Src)\n\n\tif f, err = os.Create(dest); err != nil {\n\t\treturn errors.Wrap(err, \"create file\")\n\t}\n\tdefer f.Close()\n\n\tif err = png.Encode(f, img); err != nil {\n\t\treturn errors.Wrap(err, \"write PNG data\")\n\t}\n\n\trel, _ := filepath.Rel(wf.CacheDir(), dest)\n\tlog.Printf(\"[icons] new icon: %s\", rel)\n\n\treturn nil\n}\n\nfunc iconCachePath(i *aw.Icon, c color.RGBA) string {\n\tname := filepath.Base(i.Value)\n\tdir := fmt.Sprintf(\"%02X/%02X/%02X/%02X\", c.R, c.G, c.B, c.A)\n\tdir = filepath.Join(cacheDirIcons, dir)\n\n\tutil.MustExist(dir)\n\n\treturn filepath.Join(dir, name)\n}\n\n// ParseHexColour parses a CSS hex (e.g. #ffffff) to RGBA.\n//\n// Input must be with preceding # and have 6 (RGB) or 8 (RGBA) characters.\nfunc ParseHexColour(s string) (color.RGBA, error) {\n\tvar (\n\t\t// default to 100% opaque\n\t\tc   = color.RGBA{A: 0xff}\n\t\terr error\n\t)\n\n\tif s[0] == '#' {\n\t\ts = s[1:]\n\t}\n\n\tb, err := hex.DecodeString(s)\n\tif err != nil {\n\t\treturn c, fmt.Errorf(\"invalid colour: %q\", s)\n\t}\n\n\tswitch len(b) {\n\tcase 3:\n\t\tc.R = b[0]\n\t\tc.G = b[1]\n\t\tc.B = b[2]\n\tcase 4:\n\t\tc.R = b[0]\n\t\tc.G = b[1]\n\t\tc.B = b[2]\n\t\tc.A = b[3]\n\tdefault:\n\t\terr = fmt.Errorf(\"invalid colour: %q\", s)\n\t}\n\n\treturn c, err\n}\n\n// ReloadIcon returns a spinner icon. It rotates by 15 deg on every\n// subsequent call. Use with wf.Reload(0.1) to implement an animated\n// spinner.\nfunc ReloadIcon() *aw.Icon {\n\tvar (\n\t\tstep    = 15\n\t\tmax     = (45 / step) - 1\n\t\tcurrent = wf.Config.GetInt(\"RELOAD_PROGRESS\", 0)\n\t\tnext    = current + 1\n\t)\n\tif next > max {\n\t\tnext = 0\n\t}\n\n\tlog.Printf(\"progress: current=%d, next=%d\", current, next)\n\n\twf.Var(\"RELOAD_PROGRESS\", fmt.Sprintf(\"%d\", next))\n\n\tif current == 0 {\n\t\treturn iconLoading\n\t}\n\n\treturn &aw.Icon{Value: fmt.Sprintf(\"icons/loading-%d.png\", current*step)}\n}\n"
  },
  {
    "path": "info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>bundleid</key>\n\t<string>net.deanishe.alfred.gcal</string>\n\t<key>connections</key>\n\t<dict>\n\t\t<key>0553156D-6606-42C4-8BE8-18AE49A7A6D6</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>303E565E-8048-4ED5-BD89-7E58DF94BF3A</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>09A5FA54-F59A-45B7-8B66-A08BC6AB8EAC</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>CD8F2AB1-13E0-47E1-BFCA-701444818091</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>0D457EF4-A96F-4760-A4DC-07F46AE409C6</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>8C1ADFA7-4A9A-4218-9125-F0D4C2763FEC</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>11608658-A256-4625-AAD6-517E03644231</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>511603A0-C36D-4AB7-9520-0BA422CE5F05</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>14C00640-8D74-4090-AB64-5BDEA4489D4D</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>CD8F2AB1-13E0-47E1-BFCA-701444818091</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>2F7191FB-FE4C-4B0F-832C-A9BA3DE8F841</key>\n\t\t<array/>\n\t\t<key>303E565E-8048-4ED5-BD89-7E58DF94BF3A</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>9FA29DC3-2E67-45B6-B93B-F33F57EDF7E7</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>F5C23C7D-94BA-400C-8004-EC304CE8818D</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>38618982-EFBD-4CEE-AB65-D33F0BBD3C54</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>FF2770C7-3B9E-49CF-9F1D-9F2A603CB11C</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>3CCB9C03-CCBD-43F3-A51F-C95DBBD1A21C</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>862EBB67-6E29-4467-AFE1-7E6717945CB6</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>3F938397-2CD9-45EB-B0CD-FC962DC9031F</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>EAF06D56-D2F1-4FB9-B0D7-89D494AA865B</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>4C28D7FF-5BFC-4A3B-BC5C-738D3069B9E1</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>60FDD3AD-D600-4F4E-A43A-B413E83FC298</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>55D10CF4-8457-4AE5-9DC0-64A7E262D61D</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>2512097E-AB92-489E-93AF-0146592CB0D4</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>57F4280F-D680-47D8-B5CA-ED9FFDA29082</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>36D47E80-4BA8-4158-8B18-C1EDB477E6D7</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>5C03EE71-D6D9-42F5-B608-403F47234C56</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>3F938397-2CD9-45EB-B0CD-FC962DC9031F</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>62F44B78-0D8E-4215-BB14-E3950B214795</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>303E565E-8048-4ED5-BD89-7E58DF94BF3A</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>65A55A9B-1414-464D-A0FB-9A2CF705164C</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>E77CCBEC-A3BA-4614-B1B0-12B880D25580</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>6CFB2AE8-7BAF-4DE6-B949-D0FF2DB50FD7</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>CD8F2AB1-13E0-47E1-BFCA-701444818091</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>78516575-8825-4598-A589-2F3475E25DF8</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>6CFB2AE8-7BAF-4DE6-B949-D0FF2DB50FD7</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>7A027517-A35E-4028-89FF-50172EA74768</key>\n\t\t<array/>\n\t\t<key>8604FB3C-23FB-467B-803B-17F6A73073AB</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>55D10CF4-8457-4AE5-9DC0-64A7E262D61D</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>862EBB67-6E29-4467-AFE1-7E6717945CB6</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>12391721-6113-4936-B640-C307F04B697C</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>8C1ADFA7-4A9A-4218-9125-F0D4C2763FEC</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>969420B1-18E8-4F0B-AC39-06D038718311</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>92FC681E-1D4D-4E70-8010-84836DC8B371</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>532E1785-14EE-4BE7-B64A-BE7E809EDDF8</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>998D04E2-9538-4970-9233-1E57F0929ED0</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>BF14B152-A9EC-4FCE-97DA-F62C1C7846D3</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>BC6819C2-77D8-4E53-BA51-2787F2087BFD</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>14C00640-8D74-4090-AB64-5BDEA4489D4D</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>BF14B152-A9EC-4FCE-97DA-F62C1C7846D3</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>7A027517-A35E-4028-89FF-50172EA74768</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>CC4D4EE8-FD80-4612-948E-378FB259148C</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>2F7191FB-FE4C-4B0F-832C-A9BA3DE8F841</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>CD8F2AB1-13E0-47E1-BFCA-701444818091</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>BF14B152-A9EC-4FCE-97DA-F62C1C7846D3</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>CE59FD32-48E0-467C-AD19-1D9288B0DD2B</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>92FC681E-1D4D-4E70-8010-84836DC8B371</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>D55FAAFD-ABA8-4B37-940B-CB883E3BB590</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>38618982-EFBD-4CEE-AB65-D33F0BBD3C54</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>E77CCBEC-A3BA-4614-B1B0-12B880D25580</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>CD8F2AB1-13E0-47E1-BFCA-701444818091</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>EE1CC8A9-8FDF-4BC9-A5C6-A4CF2F886D97</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>5EE513CB-126C-4EC9-8D51-7205A217CA03</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>FE9B2118-827D-416D-9829-1A9DC2CAACA4</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>F5C23C7D-94BA-400C-8004-EC304CE8818D</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>6CEAE7ED-F6DF-403F-988D-4E847AF569B3</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t\t<key>FF2770C7-3B9E-49CF-9F1D-9F2A603CB11C</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>62F44B78-0D8E-4215-BB14-E3950B214795</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>D2BA16A8-E9C4-40E1-A365-9391D712D9DD</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>0553156D-6606-42C4-8BE8-18AE49A7A6D6</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>EFC34E5C-A25D-47BA-82F6-67BD4E221FE7</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>FE9B2118-827D-416D-9829-1A9DC2CAACA4</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>C4EC175E-156C-4866-8B6C-CAA71D1717B0</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>05634575-6FA7-40A9-8772-47F71A9C0DFC</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>0AFA406F-38EB-4B7A-9757-C525540B1120</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>0D457EF4-A96F-4760-A4DC-07F46AE409C6</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>770B9A97-3870-4524-BEF4-795B08C38059</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>CC4D4EE8-FD80-4612-948E-378FB259148C</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>82716642-AD94-47F6-AC7B-B1578DABAB9A</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>11608658-A256-4625-AAD6-517E03644231</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>50D356F8-9115-4738-8AD1-A5C868482186</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>EE1CC8A9-8FDF-4BC9-A5C6-A4CF2F886D97</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>1213CDA2-9D5F-40C3-91F9-42DE831983E7</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>50009EAD-DA7A-48FC-8F4F-C5EC1CAE4D97</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>379E6121-5B4C-4936-95C8-B4A0BAEF07C3</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>CE59FD32-48E0-467C-AD19-1D9288B0DD2B</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>16447B01-A94F-420F-9E78-CB34F131700C</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<dict>\n\t\t\t\t<key>destinationuid</key>\n\t\t\t\t<string>4C28D7FF-5BFC-4A3B-BC5C-738D3069B9E1</string>\n\t\t\t\t<key>modifiers</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>modifiersubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>sourceoutputuid</key>\n\t\t\t\t<string>01B754FE-3D13-4463-9FC6-D08841BBB800</string>\n\t\t\t\t<key>vitoclose</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t</array>\n\t</dict>\n\t<key>createdby</key>\n\t<string>Dean Jackson &lt;deanishe@deanishe.net&gt;</string>\n\t<key>description</key>\n\t<string>View upcoming events in Google Calendar</string>\n\t<key>disabled</key>\n\t<false/>\n\t<key>name</key>\n\t<string>Google Calendar View</string>\n\t<key>objects</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>config</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>9FA29DC3-2E67-45B6-B93B-F33F57EDF7E7</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>2</integer>\n\t\t\t\t<key>keyword</key>\n\t\t\t\t<string>gcal</string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string>Show upcoming events</string>\n\t\t\t\t<key>text</key>\n\t\t\t\t<string>Upcoming Events</string>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.keyword</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>998D04E2-9538-4970-9233-1E57F0929ED0</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal clear</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>62F44B78-0D8E-4215-BB14-E3950B214795</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.junction</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>303E565E-8048-4ED5-BD89-7E58DF94BF3A</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal update workflow</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>0553156D-6606-42C4-8BE8-18AE49A7A6D6</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>2</integer>\n\t\t\t\t<key>keyword</key>\n\t\t\t\t<string>today</string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string>Today's events from your Google Calendar(s)</string>\n\t\t\t\t<key>text</key>\n\t\t\t\t<string>Today's Events</string>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.keyword</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>BC6819C2-77D8-4E53-BA51-2787F2087BFD</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>lastpathcomponent</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>onlyshowifquerypopulated</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>removeextension</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>text</key>\n\t\t\t\t<string>{query}</string>\n\t\t\t\t<key>title</key>\n\t\t\t\t<string>ERROR</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.notification</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>F5C23C7D-94BA-400C-8004-EC304CE8818D</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>date '+%Y-%m-%d'</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>14C00640-8D74-4090-AB64-5BDEA4489D4D</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>2</integer>\n\t\t\t\t<key>keyword</key>\n\t\t\t\t<string>tomorrow</string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string>Tomorrow's events from your Google Calendar(s)</string>\n\t\t\t\t<key>text</key>\n\t\t\t\t<string>Tomorrow's Events</string>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.keyword</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>65A55A9B-1414-464D-A0FB-9A2CF705164C</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>date -v '+1d' '+%Y-%m-%d'</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>E77CCBEC-A3BA-4614-B1B0-12B880D25580</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>action</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>7A027517-A35E-4028-89FF-50172EA74768</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>test -n \"$CALENDAR_APP\" &amp;&amp; {\n  ./gcal open --app=\"$CALENDAR_APP\" \"$1\"\n} || {\n  ./gcal open \"$1\"\n}</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>5</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>FE9B2118-827D-416D-9829-1A9DC2CAACA4</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>argument</key>\n\t\t\t\t<string>{query}</string>\n\t\t\t\t<key>passthroughargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>variables</key>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>action</key>\n\t\t\t\t\t<string>date</string>\n\t\t\t\t</dict>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.argument</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>BF14B152-A9EC-4FCE-97DA-F62C1C7846D3</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.transform</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>CD8F2AB1-13E0-47E1-BFCA-701444818091</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.hidealfred</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>6CEAE7ED-F6DF-403F-988D-4E847AF569B3</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>2</integer>\n\t\t\t\t<key>keyword</key>\n\t\t\t\t<string>yesterday</string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string>Yesterday's events from your Google Calendar(s)</string>\n\t\t\t\t<key>text</key>\n\t\t\t\t<string>Yesterday's Events</string>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.keyword</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>78516575-8825-4598-A589-2F3475E25DF8</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>date -v '-1d' '+%Y-%m-%d'</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>6CFB2AE8-7BAF-4DE6-B949-D0FF2DB50FD7</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>calendars</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>05634575-6FA7-40A9-8772-47F71A9C0DFC</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>alfredfiltersresults</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>alfredfiltersresultsmatchmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttreatemptyqueryasnil</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>argumenttrimmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>keyword</key>\n\t\t\t\t<string>gdate</string>\n\t\t\t\t<key>queuedelaycustom</key>\n\t\t\t\t<integer>3</integer>\n\t\t\t\t<key>queuedelayimmediatelyinitially</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>queuedelaymode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>queuemode</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>runningsubtext</key>\n\t\t\t\t<string>Loading…</string>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal dates -- \"$1\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string>Enter a date</string>\n\t\t\t\t<key>title</key>\n\t\t\t\t<string>Events for a Specific Date</string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.scriptfilter</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>09A5FA54-F59A-45B7-8B66-A08BC6AB8EAC</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>3</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>action</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>969420B1-18E8-4F0B-AC39-06D038718311</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>alfredfiltersresults</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>alfredfiltersresultsmatchmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttreatemptyqueryasnil</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>argumenttrimmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>queuedelaycustom</key>\n\t\t\t\t<integer>3</integer>\n\t\t\t\t<key>queuedelayimmediatelyinitially</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>queuedelaymode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>queuemode</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>runningsubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>test -n \"$date\" &amp;&amp; {\n  ./gcal events --date=\"$date\" -- \"$1\"\n} || {\n  ./gcal events -- \"$1\"\n}</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>title</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>5</integer>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.scriptfilter</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>8C1ADFA7-4A9A-4218-9125-F0D4C2763FEC</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>3</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>argument</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>passthroughargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>variables</key>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>date</key>\n\t\t\t\t\t<string>{query}</string>\n\t\t\t\t</dict>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.argument</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>0D457EF4-A96F-4760-A4DC-07F46AE409C6</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>conditions</key>\n\t\t\t\t<array>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>clear</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Clear</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>D2BA16A8-E9C4-40E1-A365-9391D712D9DD</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>update</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Update</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>EFC34E5C-A25D-47BA-82F6-67BD4E221FE7</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>open</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Open</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>C4EC175E-156C-4866-8B6C-CAA71D1717B0</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>calendars</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Show Calendars</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>0AFA406F-38EB-4B7A-9757-C525540B1120</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>date</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Events for Date</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>770B9A97-3870-4524-BEF4-795B08C38059</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>set</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Save Workflow Variable</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>82716642-AD94-47F6-AC7B-B1578DABAB9A</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>logout</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Log out from Account</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>50D356F8-9115-4738-8AD1-A5C868482186</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>toggle</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Toggle Calendar On/Off</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>1213CDA2-9D5F-40C3-91F9-42DE831983E7</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>config</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Show Configuration</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>379E6121-5B4C-4936-95C8-B4A0BAEF07C3</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>create</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Create Event</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>16447B01-A94F-420F-9E78-CB34F131700C</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>inputstring</key>\n\t\t\t\t\t\t<string>{var:action}</string>\n\t\t\t\t\t\t<key>matchcasesensitive</key>\n\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t<key>matchmode</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>matchstring</key>\n\t\t\t\t\t\t<string>reauth</string>\n\t\t\t\t\t\t<key>outputlabel</key>\n\t\t\t\t\t\t<string>Re-Authenticate Account</string>\n\t\t\t\t\t\t<key>uid</key>\n\t\t\t\t\t\t<string>01B754FE-3D13-4463-9FC6-D08841BBB800</string>\n\t\t\t\t\t</dict>\n\t\t\t\t</array>\n\t\t\t\t<key>elselabel</key>\n\t\t\t\t<string>else</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.conditional</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>FF2770C7-3B9E-49CF-9F1D-9F2A603CB11C</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>config</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>2F7191FB-FE4C-4B0F-832C-A9BA3DE8F841</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>triggerid</key>\n\t\t\t\t<string>config</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.trigger.external</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>5C03EE71-D6D9-42F5-B608-403F47234C56</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>action</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>EAF06D56-D2F1-4FB9-B0D7-89D494AA865B</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>triggerid</key>\n\t\t\t\t<string>action</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.trigger.external</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>D55FAAFD-ABA8-4B37-940B-CB883E3BB590</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal set \"$key\" \"$value\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>CC4D4EE8-FD80-4612-948E-378FB259148C</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>alfredfiltersresults</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>alfredfiltersresultsmatchmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttreatemptyqueryasnil</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>argumenttrimmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>keyword</key>\n\t\t\t\t<string>gcalconf</string>\n\t\t\t\t<key>queuedelaycustom</key>\n\t\t\t\t<integer>3</integer>\n\t\t\t\t<key>queuedelayimmediatelyinitially</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>queuedelaymode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>queuemode</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>runningsubtext</key>\n\t\t\t\t<string>Loading…</string>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal config \"$1\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string>View and edit workflow settings</string>\n\t\t\t\t<key>title</key>\n\t\t\t\t<string>Google Calendar Config</string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t\t<key>inboundconfig</key>\n\t\t\t<dict>\n\t\t\t\t<key>inputmode</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.scriptfilter</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>3F938397-2CD9-45EB-B0CD-FC962DC9031F</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>3</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>argument</key>\n\t\t\t\t<string>.\n/---- ACTION IN ----\\\nquery={query}\nvariables={allvars}\n\\-------------------/</string>\n\t\t\t\t<key>cleardebuggertext</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>processoutputs</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.debug</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>38618982-EFBD-4CEE-AB65-D33F0BBD3C54</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal logout \"$account\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>11608658-A256-4625-AAD6-517E03644231</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>config</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>511603A0-C36D-4AB7-9520-0BA422CE5F05</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>alfredfiltersresults</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>alfredfiltersresultsmatchmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttreatemptyqueryasnil</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>argumenttrimmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>keyword</key>\n\t\t\t\t<string>gnew</string>\n\t\t\t\t<key>queuedelaycustom</key>\n\t\t\t\t<integer>3</integer>\n\t\t\t\t<key>queuedelayimmediatelyinitially</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>queuedelaymode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>queuemode</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>runningsubtext</key>\n\t\t\t\t<string>Loading...</string>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal active \"$1\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string>Enter event details &amp; select calendar</string>\n\t\t\t\t<key>title</key>\n\t\t\t\t<string>Add New Event</string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.scriptfilter</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>862EBB67-6E29-4467-AFE1-7E6717945CB6</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>3</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>action</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>12391721-6113-4936-B640-C307F04B697C</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>triggerid</key>\n\t\t\t\t<string>create</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.trigger.external</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>3CCB9C03-CCBD-43F3-A51F-C95DBBD1A21C</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal toggle \"$calendar\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>EE1CC8A9-8FDF-4BC9-A5C6-A4CF2F886D97</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>calendars</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>5EE513CB-126C-4EC9-8D51-7205A217CA03</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>action</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>2512097E-AB92-489E-93AF-0146592CB0D4</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>alfredfiltersresults</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>alfredfiltersresultsmatchmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttreatemptyqueryasnil</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>argumenttrimmode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>argumenttype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>queuedelaycustom</key>\n\t\t\t\t<integer>3</integer>\n\t\t\t\t<key>queuedelayimmediatelyinitially</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>queuedelaymode</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>queuemode</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>runningsubtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal calendars \"$1\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>subtext</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>title</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>withspace</key>\n\t\t\t\t<false/>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.input.scriptfilter</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>55D10CF4-8457-4AE5-9DC0-64A7E262D61D</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>3</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>triggerid</key>\n\t\t\t\t<string>calendars</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.trigger.external</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>8604FB3C-23FB-467B-803B-17F6A73073AB</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>triggerid</key>\n\t\t\t\t<string>close</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.trigger.external</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>57F4280F-D680-47D8-B5CA-ED9FFDA29082</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>config</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>50009EAD-DA7A-48FC-8F4F-C5EC1CAE4D97</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.hidealfred</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>36D47E80-4BA8-4158-8B18-C1EDB477E6D7</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>action</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>532E1785-14EE-4BE7-B64A-BE7E809EDDF8</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal create \"$quick\" \"$calendar\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>CE59FD32-48E0-467C-AD19-1D9288B0DD2B</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>argument</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>passthroughargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>variables</key>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>action</key>\n\t\t\t\t\t<string>date</string>\n\t\t\t\t</dict>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.utility.argument</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>92FC681E-1D4D-4E70-8010-84836DC8B371</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>externaltriggerid</key>\n\t\t\t\t<string>config</string>\n\t\t\t\t<key>passinputasargument</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>passvariables</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>workflowbundleid</key>\n\t\t\t\t<string>self</string>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.output.callexternaltrigger</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>60FDD3AD-D600-4F4E-A43A-B413E83FC298</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>1</integer>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>config</key>\n\t\t\t<dict>\n\t\t\t\t<key>concurrently</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>escaping</key>\n\t\t\t\t<integer>102</integer>\n\t\t\t\t<key>script</key>\n\t\t\t\t<string>./gcal reauth \"$account\"</string>\n\t\t\t\t<key>scriptargtype</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>scriptfile</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>type</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>type</key>\n\t\t\t<string>alfred.workflow.action.script</string>\n\t\t\t<key>uid</key>\n\t\t\t<string>4C28D7FF-5BFC-4A3B-BC5C-738D3069B9E1</string>\n\t\t\t<key>version</key>\n\t\t\t<integer>2</integer>\n\t\t</dict>\n\t</array>\n\t<key>readme</key>\n\t<string>Google Calendar\n===============\n\nView events from your Google calendars.\n\nConfiguration\n-------------\n\n`APPLE_MAPS`: Set to \"1\" to open location URLs in Apple Maps, not Google Maps.\n\n`CALENDAR_APP`: Set to an application name to open calendar URLs (not map URLs) in an application other than your default browser (e.g. a session-specific browser).\n\n`EVENT_CACHE_MINUTES`: How many minutes to cache events for.\n\n`SCHEDULE_DAYS`: How many days' events to show in the \"Upcoming Events\" list (keyword: \"gcal\").</string>\n\t<key>uidata</key>\n\t<dict>\n\t\t<key>0553156D-6606-42C4-8BE8-18AE49A7A6D6</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Check for new version of the workflow</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>200</integer>\n\t\t</dict>\n\t\t<key>05634575-6FA7-40A9-8772-47F71A9C0DFC</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>530</integer>\n\t\t</dict>\n\t\t<key>09A5FA54-F59A-45B7-8B66-A08BC6AB8EAC</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Show events for a given date</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>210</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>660</integer>\n\t\t</dict>\n\t\t<key>0D457EF4-A96F-4760-A4DC-07F46AE409C6</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Set $date from {query}</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1220</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>700</integer>\n\t\t</dict>\n\t\t<key>11608658-A256-4625-AAD6-517E03644231</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Remove account</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1000</integer>\n\t\t</dict>\n\t\t<key>12391721-6113-4936-B640-C307F04B697C</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>400</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1010</integer>\n\t\t</dict>\n\t\t<key>14C00640-8D74-4090-AB64-5BDEA4489D4D</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>210</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>200</integer>\n\t\t</dict>\n\t\t<key>2512097E-AB92-489E-93AF-0146592CB0D4</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>400</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1170</integer>\n\t\t</dict>\n\t\t<key>2F7191FB-FE4C-4B0F-832C-A9BA3DE8F841</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1360</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>830</integer>\n\t\t</dict>\n\t\t<key>303E565E-8048-4ED5-BD89-7E58DF94BF3A</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1400</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>150</integer>\n\t\t</dict>\n\t\t<key>36D47E80-4BA8-4158-8B18-C1EDB477E6D7</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>250</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1370</integer>\n\t\t</dict>\n\t\t<key>38618982-EFBD-4CEE-AB65-D33F0BBD3C54</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>780</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>860</integer>\n\t\t</dict>\n\t\t<key>3CCB9C03-CCBD-43F3-A51F-C95DBBD1A21C</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>40</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1010</integer>\n\t\t</dict>\n\t\t<key>3F938397-2CD9-45EB-B0CD-FC962DC9031F</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Show workflow configuration</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>210</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>830</integer>\n\t\t</dict>\n\t\t<key>4C28D7FF-5BFC-4A3B-BC5C-738D3069B9E1</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Re-authenticate account</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1660</integer>\n\t\t</dict>\n\t\t<key>50009EAD-DA7A-48FC-8F4F-C5EC1CAE4D97</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1355</integer>\n\t\t</dict>\n\t\t<key>511603A0-C36D-4AB7-9520-0BA422CE5F05</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1370</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1000</integer>\n\t\t</dict>\n\t\t<key>532E1785-14EE-4BE7-B64A-BE7E809EDDF8</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1370</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1510</integer>\n\t\t</dict>\n\t\t<key>55D10CF4-8457-4AE5-9DC0-64A7E262D61D</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Toggle calendars on/off</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>210</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1170</integer>\n\t\t</dict>\n\t\t<key>57F4280F-D680-47D8-B5CA-ED9FFDA29082</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>40</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1340</integer>\n\t\t</dict>\n\t\t<key>5C03EE71-D6D9-42F5-B608-403F47234C56</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>40</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>830</integer>\n\t\t</dict>\n\t\t<key>5EE513CB-126C-4EC9-8D51-7205A217CA03</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1370</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1170</integer>\n\t\t</dict>\n\t\t<key>60FDD3AD-D600-4F4E-A43A-B413E83FC298</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1370</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1660</integer>\n\t\t</dict>\n\t\t<key>62F44B78-0D8E-4215-BB14-E3950B214795</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Clear old cache files</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>40</integer>\n\t\t</dict>\n\t\t<key>65A55A9B-1414-464D-A0FB-9A2CF705164C</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Show tomorrow's events</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>40</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>360</integer>\n\t\t</dict>\n\t\t<key>6CEAE7ED-F6DF-403F-988D-4E847AF569B3</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1400</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>400</integer>\n\t\t</dict>\n\t\t<key>6CFB2AE8-7BAF-4DE6-B949-D0FF2DB50FD7</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>210</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>520</integer>\n\t\t</dict>\n\t\t<key>78516575-8825-4598-A589-2F3475E25DF8</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Show yesterday's events</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>40</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>520</integer>\n\t\t</dict>\n\t\t<key>7A027517-A35E-4028-89FF-50172EA74768</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>760</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>360</integer>\n\t\t</dict>\n\t\t<key>8604FB3C-23FB-467B-803B-17F6A73073AB</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>40</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1170</integer>\n\t\t</dict>\n\t\t<key>862EBB67-6E29-4467-AFE1-7E6717945CB6</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Quick-add a new event</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>210</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1010</integer>\n\t\t</dict>\n\t\t<key>8C1ADFA7-4A9A-4218-9125-F0D4C2763FEC</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Show events</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1360</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>670</integer>\n\t\t</dict>\n\t\t<key>92FC681E-1D4D-4E70-8010-84836DC8B371</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>$action to \"date\"</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1310</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1540</integer>\n\t\t</dict>\n\t\t<key>969420B1-18E8-4F0B-AC39-06D038718311</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1540</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>670</integer>\n\t\t</dict>\n\t\t<key>998D04E2-9538-4970-9233-1E57F0929ED0</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Show upcoming events</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>210</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>40</integer>\n\t\t</dict>\n\t\t<key>9FA29DC3-2E67-45B6-B93B-F33F57EDF7E7</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1540</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>40</integer>\n\t\t</dict>\n\t\t<key>BC6819C2-77D8-4E53-BA51-2787F2087BFD</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Show today's events</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>40</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>200</integer>\n\t\t</dict>\n\t\t<key>BF14B152-A9EC-4FCE-97DA-F62C1C7846D3</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Set $action to \"date\"</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>620</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>390</integer>\n\t\t</dict>\n\t\t<key>CC4D4EE8-FD80-4612-948E-378FB259148C</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Change configuration</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>830</integer>\n\t\t</dict>\n\t\t<key>CD8F2AB1-13E0-47E1-BFCA-701444818091</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Trim whitespace</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>450</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>390</integer>\n\t\t</dict>\n\t\t<key>CE59FD32-48E0-467C-AD19-1D9288B0DD2B</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1510</integer>\n\t\t</dict>\n\t\t<key>D55FAAFD-ABA8-4B37-940B-CB883E3BB590</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>590</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>830</integer>\n\t\t</dict>\n\t\t<key>E77CCBEC-A3BA-4614-B1B0-12B880D25580</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>210</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>360</integer>\n\t\t</dict>\n\t\t<key>EAF06D56-D2F1-4FB9-B0D7-89D494AA865B</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>400</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>830</integer>\n\t\t</dict>\n\t\t<key>EE1CC8A9-8FDF-4BC9-A5C6-A4CF2F886D97</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Toggle calendar</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>1170</integer>\n\t\t</dict>\n\t\t<key>F5C23C7D-94BA-400C-8004-EC304CE8818D</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1540</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>200</integer>\n\t\t</dict>\n\t\t<key>FE9B2118-827D-416D-9829-1A9DC2CAACA4</key>\n\t\t<dict>\n\t\t\t<key>note</key>\n\t\t\t<string>Open calendar URL</string>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>1180</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>370</integer>\n\t\t</dict>\n\t\t<key>FF2770C7-3B9E-49CF-9F1D-9F2A603CB11C</key>\n\t\t<dict>\n\t\t\t<key>xpos</key>\n\t\t\t<integer>880</integer>\n\t\t\t<key>ypos</key>\n\t\t\t<integer>700</integer>\n\t\t</dict>\n\t</dict>\n\t<key>variables</key>\n\t<dict>\n\t\t<key>APPLE_MAPS</key>\n\t\t<string>0</string>\n\t\t<key>CALENDAR_APP</key>\n\t\t<string></string>\n\t\t<key>EVENT_CACHE_MINS</key>\n\t\t<string>15</string>\n\t\t<key>SCHEDULE_DAYS</key>\n\t\t<string>7</string>\n\t\t<key>TIME_12H</key>\n\t\t<string>0</string>\n\t</dict>\n\t<key>version</key>\n\t<string>0.5.1</string>\n\t<key>webaddress</key>\n\t<string></string>\n</dict>\n</plist>\n"
  },
  {
    "path": "magefile.go",
    "content": "// Copyright (c) 2019 Dean Jackson <deanishe@deanishe.net>\n// MIT Licence applies http://opensource.org/licenses/MIT\n\n// +build mage\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\n\t\"github.com/deanishe/awgo/util\"\n\t\"github.com/deanishe/awgo/util/build\"\n\t\"github.com/magefile/mage/mg\"\n\t\"github.com/magefile/mage/sh\"\n)\n\n// Default target to run when none is specified\n// If not set, running mage will list available targets\n// var Default = Build\n\nvar (\n\tinfo     *build.Info\n\tbuildDir = \"./build\"\n\tdistDir  = \"./dist\"\n\tenv      map[string]string\n)\n\nfunc init() {\n\tvar err error\n\tif info, err = build.NewInfo(); err != nil {\n\t\tpanic(err)\n\t}\n\tenv = info.Env()\n\tenv[\"TAGS\"] = \"private\"\n}\n\nfunc mod(args ...string) error {\n\targv := append([]string{\"mod\"}, args...)\n\treturn sh.RunWith(info.Env(), \"go\", argv...)\n}\n\n// Aliases are mage command aliases.\nvar Aliases = map[string]interface{}{\n\t\"b\": Build,\n\t\"c\": Clean,\n\t\"d\": Dist,\n\t\"l\": Link,\n}\n\n// Build build workflow in ./build\nfunc Build() error {\n\tmg.Deps(cleanBuild)\n\t// mg.Deps(Deps)\n\tfmt.Println(\"building ...\")\n\n\tif err := sh.RunWith(env, \"go\", \"build\", \"-tags\", \"$TAGS\", \"-o\", buildDir+\"/gcal\", \".\"); err != nil {\n\t\treturn err\n\t}\n\n\tglobs := build.Globs(\n\t\t\"*.png\",\n\t\t\"info.plist\",\n\t\t\"*.html\",\n\t\t\"README.md\",\n\t\t\"LICENCE.txt\",\n\t\t\"icons/*.png\",\n\t)\n\n\treturn build.SymlinkGlobs(buildDir, globs...)\n}\n\n// run workflow\nfunc Run() error {\n\tmg.Deps(Build)\n\tfmt.Println(\"running ...\")\n\treturn sh.RunWith(info.Env(), buildDir+\"/gcal\", \"-h\")\n}\n\n// build .alfredworkflow file in ./dist\nfunc Dist() error {\n\tmg.SerialDeps(Clean, Build)\n\tp, err := build.Export(buildDir, distDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"exported %q\\n\", p)\n\treturn nil\n}\n\n// symlink ./build directory to Alfred's workflow directory\nfunc Link() error {\n\tmg.Deps(Build)\n\n\tdir := filepath.Join(info.AlfredWorkflowDir, info.BundleID)\n\tfmt.Printf(\"linking %q to %q ...\\n\", buildDir, dir)\n\tif err := sh.Rm(dir); err != nil {\n\t\treturn err\n\t}\n\treturn build.Symlink(dir, buildDir, true)\n}\n\n// clean & download dependencies\nfunc Deps() error {\n\tmg.Deps(cleanDeps)\n\tfmt.Println(\"downloading deps ...\")\n\treturn mod(\"download\")\n}\n\n// remove build files\nfunc Clean() {\n\tfmt.Println(\"cleaning ...\")\n\tmg.Deps(cleanBuild, cleanMage)\n}\n\nfunc cleanDeps() error { return mod(\"tidy\", \"-v\") }\n\nfunc cleanDir(name string) error {\n\tif !util.PathExists(name) {\n\t\treturn nil\n\t}\n\n\tinfos, err := ioutil.ReadDir(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, fi := range infos {\n\t\tif err := sh.Rm(filepath.Join(name, fi.Name())); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// remove generated icons\nfunc CleanIcons() error { return cleanDir(\"./icons\") }\n\nfunc cleanBuild() error { return cleanDir(\"./build\") }\nfunc cleanMage() error  { return sh.Run(\"mage\", \"-clean\") }\n"
  },
  {
    "path": "magic.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\npackage main\n\nimport (\n\taw \"github.com/deanishe/awgo\"\n\t\"github.com/pkg/errors\"\n)\n\n// \"magic\" action to open list of calendars\ntype calendarMagic struct{}\n\nfunc (cm *calendarMagic) Keyword() string     { return \"calendars\" }\nfunc (cm *calendarMagic) Description() string { return \"Activate/deactivate calendars\" }\nfunc (cm *calendarMagic) RunText() string     { return \"Opening calendar list…\" }\nfunc (cm *calendarMagic) Run() error          { return aw.NewAlfred().RunTrigger(\"calendars\", \"\") }\n\n// \"magic\" action to log in to a new account\ntype loginMagic struct{}\n\nfunc (lm *loginMagic) Keyword() string     { return \"login\" }\nfunc (lm *loginMagic) Description() string { return \"Add a Google account\" }\nfunc (lm *loginMagic) RunText() string     { return \"Opening Google signin page…\" }\nfunc (lm *loginMagic) Run() error {\n\twf := aw.New()\n\tif err := wf.Alfred.RunTrigger(\"close\", \"\"); err != nil {\n\t\treturn errors.Wrap(err, \"close Alfred\")\n\t}\n\n\tacc, err := NewAccount(\"\")\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"magic: new account\")\n\t}\n\n\tif err := acc.FetchCalendars(); err != nil {\n\t\treturn errors.Wrap(err, \"magic: fetch calendars\")\n\t}\n\n\t// clear cached schedules now calendars have changed\n\tif err := clearEvents(); err != nil {\n\t\treturn errors.Wrap(err, \"clear cached events\")\n\t}\n\n\t// re-open workflow configuration\n\treturn wf.Alfred.RunTrigger(\"config\", \"\")\n}\n"
  },
  {
    "path": "main.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\n// Command gcal is an Alfred 3 workflow for viewing Google Calendar events.\npackage main\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n\n\taw \"github.com/deanishe/awgo\"\n\t\"github.com/deanishe/awgo/update\"\n\t\"github.com/deanishe/awgo/util\"\n\tdocopt \"github.com/docopt/docopt-go\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\ttimeFormat     = \"2006-01-02\"\n\ttimeFormatLong = \"Monday, 2 January 2006\"\n\n\t// Workflow icon colours\n\tyellow = \"f8ac30\"\n\t// green  = \"03ae03\"\n\t// blue   = \"5484f3\"\n\t// red    = \"b00000\"\n\n\t// Workflow settings & URLs\n\trepo      = \"deanishe/alfred-gcal\"\n\thelpURL   = \"https://github.com/deanishe/alfred-gcal/issues\"\n\treadmeURL = \"https://github.com/deanishe/alfred-gcal/blob/master/README.md\"\n\tforumURL  = \"https://www.alfredforum.com/topic/11016-google-calendar-view/\"\n)\n\nconst usage = `\ngcal [<command>] [options] [<query>]\n\nUsage:\n    gcal dates [--] [<format>]\n    gcal events [--date=<date>] [--] [<query>]\n    gcal calendars [<query>]\n    gcal active [<query>]\n    gcal toggle <calID>\n    gcal set <key> <value>\n    gcal update (workflow|calendars|events) [<date>]\n    gcal config [<query>]\n    gcal logout <account>\n    gcal reauth <account>\n    gcal clear\n    gcal open [--app=<app>] <url>\n    gcal server\n    gcal reload\n    gcal create <quick> <calID>\n    gcal -h\n\nOptions:\n    -a --app <app>     Application to open URLs in.\n    -d --date <date>   Date to show events for (format YYYY-MM-DD).\n    -h --help          Show this message and exit.\n    --version          Show workflow version and exit.\n`\n\nvar (\n\twf       *aw.Workflow\n\taccounts []*Account\n\n\tcacheDirIcons string // directory generated icons are stored in\n\n\t// CLI args\n\topts *options\n\n\t// display times using 24h clock\n\thourFormat = \"15:04\"\n)\n\n// CLI flags\ntype options struct {\n\t// commands\n\tCalendars bool\n\tActive    bool\n\tClear     bool\n\tConfig    bool\n\tDates     bool\n\tEvents    bool\n\tLogout    bool\n\tReauth    bool\n\tOpen      bool\n\tReload    bool\n\tServer    bool\n\tSet       bool\n\tToggle    bool\n\tUpdate    bool\n\tCreate    bool\n\n\t// sub-commands\n\tWorkflow bool\n\n\t// flags\n\tAccount    string\n\tApp        string\n\tCalendarID string `docopt:\"<calID>\"`\n\tDate       string `docopt:\"<date>,--date\"`\n\tDateFormat string `docopt:\"<format>\"`\n\tQuery      string\n\tURL        string `docopt:\"<url>\"`\n\tKey        string\n\tValue      string\n\tQuick      string `docopt:\"<quick>\"`\n\n\t// options\n\tUseAppleMaps   bool `env:\"APPLE_MAPS\"`\n\tEventCacheMins int  `env:\"EVENT_CACHE_MINS\"`\n\tScheduleDays   int  `env:\"SCHEDULE_DAYS\"`\n\tUse12HourTime  bool `env:\"TIME_12H\"`\n\tScheduleMode   bool\n\tStartTime      time.Time\n\tEndTime        time.Time\n\n\t// needed to make '--' work\n\tEndOfOptions bool `docopt:\"--\"`\n}\n\nfunc (opts *options) MaxAgeCalendar() time.Duration { return time.Hour * 3 }\n\nfunc (opts *options) MaxAgeEvents() time.Duration {\n\td := time.Duration(opts.EventCacheMins) * time.Minute\n\tif d < time.Minute*5 {\n\t\td = time.Minute * 5\n\t}\n\n\treturn d\n}\n\nfunc (opts *options) ScheduleDuration() time.Duration {\n\treturn time.Duration(opts.ScheduleDays) * time.Hour * 24\n}\n\nfunc init() {\n\topts = &options{}\n\n\twf = aw.New(update.GitHub(repo), aw.HelpURL(helpURL))\n\twf.Configure(aw.AddMagic(&calendarMagic{}, &loginMagic{}))\n\n\tcacheDirIcons = filepath.Join(wf.CacheDir(), \"icons\")\n}\n\n// Parse command-line flags.\nfunc parseFlags() error {\n\targs, err := docopt.ParseArgs(usage, wf.Args(), wf.Version())\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"docopt parse\")\n\t}\n\n\tif err := args.Bind(opts); err != nil {\n\t\treturn errors.Wrap(err, \"bind docopt\")\n\t}\n\n\tif err := wf.Config.To(opts); err != nil {\n\t\treturn errors.Wrap(err, \"bind config\")\n\t}\n\n\t// We don't need to be fussy about the default start and end times:\n\t// The default startTime is only used in schedule mode, and it (and endTime)\n\t// will be set to midnight if user specifies a date.\n\topts.StartTime = time.Now().Local()\n\topts.ScheduleMode = true\n\n\tif opts.Date != \"\" {\n\t\topts.StartTime, err = time.ParseInLocation(timeFormat, opts.Date, time.Local)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topts.ScheduleMode = false\n\t}\n\n\tif opts.Use12HourTime {\n\t\thourFormat = \"3:04\"\n\t}\n\n\topts.EndTime = opts.StartTime.Add(time.Hour * 24)\n\n\tlog.Printf(\"[main] query=%q, startTime=%v, endTime=%v\",\n\t\topts.Query, opts.StartTime, opts.EndTime)\n\n\treturn nil\n}\n\n// Main program entry point.\nfunc run() {\n\tvar err error\n\n\tif err = parseFlags(); err != nil {\n\t\twf.FatalError(err)\n\t}\n\n\t// Ensure required directories exist\n\tutil.MustExist(cacheDirIcons)\n\n\tif accounts, err = LoadAccounts(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tif !wf.IsRunning(\"server\") {\n\t\tcmd := exec.Command(os.Args[0], \"server\")\n\t\tif err := wf.RunInBackground(\"server\", cmd); err != nil {\n\t\t\twf.FatalError(err)\n\t\t}\n\t}\n\n\tswitch {\n\t// check for Update first as Calendars and Events are also\n\t// set by the corresponding top-level commands.\n\tcase opts.Update:\n\t\tswitch {\n\t\tcase opts.Calendars:\n\t\t\terr = doUpdateCalendars()\n\t\tcase opts.Events:\n\t\t\terr = doUpdateEvents()\n\t\tcase opts.Workflow:\n\t\t\terr = doUpdateWorkflow()\n\t\t}\n\tcase opts.Calendars:\n\t\terr = doListCalendars()\n\tcase opts.Clear:\n\t\terr = doClear()\n\tcase opts.Config:\n\t\terr = doConfig()\n\tcase opts.Dates:\n\t\terr = doDates()\n\tcase opts.Events:\n\t\terr = doEvents()\n\tcase opts.Logout:\n\t\terr = doLogout()\n\tcase opts.Open:\n\t\terr = doOpen()\n\tcase opts.Set:\n\t\terr = doSet()\n\tcase opts.Server:\n\t\terr = doStartServer()\n\tcase opts.Toggle:\n\t\terr = doToggle()\n\tcase opts.Reauth:\n\t\terr = doReauth()\n\tcase opts.Reload:\n\t\terr = doReload()\n\tcase opts.Create:\n\t\terr = quickAdd()\n\tcase opts.Active:\n\t\terr = doListWritableCalendars()\n\t}\n\n\tif err != nil {\n\t\tif err == errNoActive {\n\t\t\twf.NewItem(\"No active calendars\").\n\t\t\t\tSubtitle(\"↩ or ⇥ to choose calendars\").\n\t\t\t\tAutocomplete(\"workflow:calendars\").\n\t\t\t\tValid(false).\n\t\t\t\tIcon(aw.IconWarning)\n\n\t\t\twf.SendFeedback()\n\t\t\treturn\n\t\t}\n\t\twf.FatalError(err)\n\t}\n}\n\n// Call via Workflow.Run() to rescue panics and show an error message in Alfred.\nfunc main() {\n\twf.Run(run)\n}\n"
  },
  {
    "path": "modd.conf",
    "content": "\nmagefile.go\nmagefile_*.go {\n    prep: mage -l\n}\n\nmodd.conf\n*.go\n*.html\n./bin/build\n!mage*.go\n!vendor/**\n!secret* {\n\tprep: \"\n        # run unit tests\n        go test -v @dirmods \\\n        && mage -v run\n    \"\n}\n"
  },
  {
    "path": "preview.html",
    "content": "{{ define \"event\" }}\n<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<title>{{ .Title }}</title>\n\t\t<style>\n\t\t\thtml {\n\t\t\t\tbackground-color: {{ .Colour }};\n\t\t\t\tcolor: #333;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\t\t\t\tfont-size: 16px;\n\t\t\t}\n\n\t\t\thtml, body {\n\t\t\t\theight: 100%;\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding: 0;\n\t\t\t}\n\n\t\t\t*, *:before, *:after {\n\t\t\t  box-sizing: inherit;\n\t\t\t}\n\n\t\t\tbody, h1, h2, h3, h4, h5, h6, p, ol, ul {\n\t\t\t  margin: 0;\n\t\t\t  padding: 0;\n\t\t\t  font-weight: normal;\n\t\t\t}\n\n\t\t\th1, h2, h3, h4, h5, h6 {\n\t\t\t\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\t\t\t}\n\n\t\t\ta, a:active, a:visited {\n\t\t\t\tcolor: #0000fc;\n\t\t\t\t/*font-weight: bold;*/\n\t\t\t\ttext-decoration: none;\n\t\t\t}\n\n\t\t\th1 > a, h1 > a:visited {\n\t\t\t\t/*text-decoration: none;*/\n\t\t\t\tcolor: #333;\n\t\t\t}\n\n\t\t\tbody {\n\t\t\t\tbackground-color: #fdfdfd;\n\t\t\t\tmargin-left: auto;\n\t\t\t\tmargin-right: auto;\n\t\t\t\tmax-width: 600px;\n\t\t\t\tpadding: 32px 16px;\n\t\t\t}\n\n\t\t\theader {\n\t\t\t\tmargin: 0 32px;\n\t\t\t}\n\n\t\t\ttable {\n\t\t\t\tmargin-top: 16px;\n\t\t\t}\n\n\t\t\tth, td {\n\t\t\t\tpadding: 5px;\n\t\t\t}\n\n\t\t\tth {\n\t\t\t\ttext-align: right;\n\t\t\t}\n\n\t\t\tol, ul {\n\t\t\t  list-style: none;\n\t\t\t}\n\n\t\t\timg {\n\t\t\t  max-width: 100%;\n\t\t\t  height: auto;\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<header>\n\t\t\t<h1><a href=\"{{ .URL }}\">{{ .Title }}</a></h1>\n\t\t\t<p>in {{ .CalendarTitle }}</p>\n\t\t</header>\n\t\t<table>\n\t\t\t<tr>\n\t\t\t\t<th>Date</th>\n\t\t\t\t<td>{{ .Start.Format \"Monday, 2 Jan 2006\" }}</td>\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<th>Time</th>\n\t\t\t\t<td>{{ .Start.Format \"15:04\" }} &ndash; {{ .End.Format \"15:04\" }}</td>\n\t\t\t</tr>\n\t\t\t{{ if .Location }}\n\t\t\t<tr>\n\t\t\t\t<th>Location</th>\n\t\t\t\t<td><a href=\"{{ .MapURL }}\">{{ .Location }}</a></td>\n\t\t\t</tr>\n\t\t\t{{ end }}\n\t\t\t{{ if .Description }}\n\t\t\t<tr>\n\t\t\t\t<th>Description</th>\n\t\t\t\t<td>{{ .Description }}</td>\n\t\t\t</tr>\n\t\t\t{{ end }}\n\t\t</table>\n\t</body>\n</html>\n{{ end }}\n\n{{ define \"fail\" }}\n<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<title>Event Not Found</title>\n\t</head>\n\t<body>\n\t\t<h1>Event Not Found</h1>\n\t\t<p>Couldn't find an event for ID <strong>{{ . }}</strong></p>\n\t</body>\n</html>\n{{ end }}"
  },
  {
    "path": "secret.go",
    "content": "//\n// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net>\n//\n// MIT Licence. See http://opensource.org/licenses/MIT\n//\n// Created on 2017-11-25\n//\n\n// +build !private\n\npackage main\n\n// Google doesn't allow app keys to be stored in open-source source code.\n// The built workflow includes a key.\n//\n// If you want to hack on the source code, register your own project here:\n// https://console.developers.google.com/apis/dashboard\n//\n// Add the Google Calendar API and create credentials for a web app, with\n// http://localhost:61432 as the redirect URI.\n//\n// The workflow only requires read access.\nconst secret = `\n{\n  \"web\": {\n    \"redirect_uris\": [\n      \"http://localhost:61432\"\n    ],\n    \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n    \"client_id\": \"\",\n    \"project_id\": \"\",\n    \"client_secret\": \"\",\n    \"token_uri\": \"https://accounts.google.com/o/oauth2/token\",\n    \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\"\n  }\n}\n`\n"
  }
]