どのような時間配分でプログラミングや仕事をしているのだろう
僕はソフトウェア開発と書籍の執筆、翻訳、監訳を生業としている。そのため日常的にプログラミングをするし、コンピュータ上の文章を書いたり触れたりする時間は長い。作業効率を考える上で、具体的に作業のどの部分に時間をかけているかを知る必要がある。そこで今回は作業時間を計測し、計測した結果を分析できるように工夫してみる事にする。
org-clockとwakatime
ソフトウェアの開発というと、代表的な作業はプログラミングだろうか。当然、僕も日常的にプログラミングをしている。プログラミングに使うツールは、開発対象や環境、またはプログラマーの好みによって変わる。僕はGNU Emacsというテキストエディタを愛用していて、プログラミングに関わる多くの作業でそれを使っている。プログラミング以外にも、仕様書の作成、作図など様々な作業でそれを使う。それと同様に書籍に関連する多くの作業にもEmacsを使う。
Emacsにはorg-modeというものがある。これはマークアップ言語であり、一連の機能を備えたドキュメントシステムだ。文章の作成、表計算、タスク管理など多くの機能を含んでいる。僕はタスク管理にこのorg-modeを使っている。org-modeのタスク管理には、時間計測の機能である org-clock
も備わっている。そのため各タスクにかかった作業時間を計測できるようになっている。しかし org-clock
だけでは、どの種類のファイルの編集にどの程度の時間を使っているか等については計測できない。
どの種類のファイルの編集にどの程度時間を使っているのかを記録するには一工夫必要になるが、ちょうどその機能を提供しているWebサービスがある。それが wakatime
だ。
Emacsでwakatimeを使う
wakatime-mode
wakatime
3は作業時間を計測し集計可視化するWebサービスであり、様々な環境向けの拡張を提供している。その中にはEmacs向けの拡張として wakatime-mode
を提供しており、特別な理由がなければそれを使う事で、作業時間を自動的に収集し、wakatime上で可視化してくれる4。 wakatime-mode
自体は MELPA
からインストールできる。
M-x package-install RET wakatime-mode
wakatime-mode
は、Pythonで実装されたCLIコマンド wakatime
を利用してデータを送信する実装となってい5。そのため pip
などを利用して wakatime
パッケージをインストールする必要がある。
pip install wakatime
wakatime
の設定は .wakatime.cfg
に記述する。 `api_key` にwakatimeのサイトで発行したapi_keyを設定する。
そして global-wakatime-mode
を有効にする事で、データの記録を行う事ができる。
(global-wakatime-mode t)
この状態で Git
リポジトリ内のファイルを開くと、リポジトリをプロジェクトと見立てて作業時間を計測してくれる。
ファイルではないバッファに対して時間の計測を行う1
wakatime-mode
は開いているファイルに対して、プロジェクトでの作業時間の計測をするが、ファイルではないバッファに対しては集計してくれない。ファイルじゃないバッファの場合も、プロジェクトで作業していると考えるなら、作業時間として計測したい。そこでバッファ名をプロジェクトとしてみたてるようにカスタマイズする6。
wakatime-client-command
は wakatime
コマンドを生成する。これを上書きしてファイル名がなかったら、バッファ名を用いるように変更している。
(defun wakatime-client-command (savep)
"Return client command executable and arguments.
Set SAVEP to non-nil for write action."
(format "%s%s--file \'%s\' %s --plugin \"%s/%s\" --time %.2f%s%s"
(if (s-blank wakatime-python-bin) "" (format "%s " wakatime-python-bin))
(if (s-blank wakatime-cli-path) "wakatime " (format "%s " wakatime-cli-path))
(or (buffer-file-name (current-buffer)) (buffer-name))
(if (buffer-file-name (current-buffer)) "" (format " --entity-type app --project \'%s\' " mode-name))
wakatime-user-agent
wakatime-version
(float-time)
(if savep " --write" "")
(if (s-blank wakatime-api-key) "" (format " --key %s" wakatime-api-key))))
(defun wakatime-save ()
"Send save notice to WakaTime."
(wakatime-call t))
(defun wakatime-bind-hooks ()
"Watch for activity in buffers."
(add-hook 'after-save-hook 'wakatime-save nil t)
(add-hook 'auto-save-hook 'wakatime-save nil t)
(add-hook 'first-change-hook 'wakatime-ping nil t))
(defun wakatime-unbind-hooks ()
"Stop watching for activity in buffers."
(remove-hook 'after-save-hook 'wakatime-save t)
(remove-hook 'auto-save-hook 'wakatime-save t)
(remove-hook 'first-change-hook 'wakatime-ping t))
プロジェクトやカテゴリーの値を設定する
この wakatime
コマンドは、ファイルがあるディレクトリからリポジトリなどの情報を用いてプロジェクトごとの時間を計測している。しかし、それでは都合が悪い事もある。例えばマルチレポ戦略を取っている開発チームの場合、同一プロジェクトでも複数のGitリポジトリを持っている。この複数のGitリポジトリを別々として集計したいのであれば、問題はないけれど同一プロジェクトとして集計したいなら工夫が必要になる。プロジェクトやカテゴリーの値は org-clock
の提供するコマンド org-clock-in
で集計を開始した org-todo
のタスクの属性から取得したい。
wakatimeにデータを送信するために独自の拡張を書く
Python
製の wakatime
コマンドと、公式のEmacs拡張である wakatime-mode
を調整し、それを実現する事もできるかもしれない。ただ、それよりも自前で実装しEmacsから直接制御したほうが、見通しがよくなると考えた。また実装の見通しを良くするために、データをローカルにキャッシュする部分であある wakatime-record.el
と、キャッシュしたデータをwakatimeに送信する部分である wakatime-transport.el
に分けて実装した。またorg-modeとの橋渡しとして org-wakatime.el
も実装した。
wakatimeへ送信するデータをローカルにキャッシュする
wakatime-record.el
は計測した結果をローカルのファイルにキャッシュする。 wakatime
へのデータの送信は行わない。 (wakatime-record-tunrn-on)
することで有効になる。
(require 'wakatime-record)
(wakatime-record-tunrn-on)
wakatime-record.el
;;; wakatime-record.el --- Yet Another Wakatime plugin for Emacs.
;; Copyright (C) 2021 TakesxiSximada <[email protected]>
;; Author: TakesxiSximada <[email protected]>
;; Maintainer: TakesxiSximada <[email protected]>
;; Website: https://github.com/TakesxiSximada/emacs.d/master/wakatime/
;; Keywords: calendar, comm
;; Package-Version: 20210730.240
;; Package-Commit: 5e6deddda7a70f4b79832e9e8b6636418812e162
;; Version: 1.0.0
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; wakatime-record.el is an unofficial wakatime plugin for Emacs.
;; It was implemented based on a different design philosophy.
;; Heartbeats are recorded as Line-delimited JSON in a temporary journal.
;;; Code:
(require 'cl-lib)
(require 'json)
(require 'org-clock)
(defvar wakatime-record-buffer-name "*WAKATIME RECORD*")
(defvar wakatime-record-file-path (expand-file-name "~/.wakatime.heartbeat.json"))
(defvar wakatime-record-timer nil)
(setq wakatime-record-language-alist
'(
(Info-mode . "Emacs")
(c-mode . "C")
(compilation-mode . "Completion")
(completion-list-mode . "Completion")
(conf-toml-mode . "TOML")
(css-mode . "CSS")
(dired-mode . "Dired")
(dockerfile-mode . "Docker")
(editor-mode . "Org")
(emacs-lisp-mode . "Emacs Lisp")
(foreman-mode . "Foreman")
(fundamental-mode . "Emacs Lisp")
(go-mode . "Go")
(html-mode . "HTML")
(js-mode . "JavaScript")
(js2-mode . "JavaScript")
(json-mode . "JSON")
(lisp-interaction-mode . "Emacs Lisp")
(magit-refs-mode . "Git")
(magit-status-mode . "Git")
(messages-buffer-mode . "Emacs")
(mhtml-mode . "HTML")
(org-agenda-mode . "Org")
(org-mode . "Org")
(python-mode . "Python")
(restclient-mode . "HTTP")
(rustic-mode . "Rust")
(scss-mode . "CSS")
(shell-script-mode . "Sehll")
(sql-interactive-mode . "SQL")
(sql-mode . "SQL")
(typescript-mode . "TypeScript")
(vterm-mode . "Shell")
(vue-html-mode . "Vue")
(vue-mode . "Vue")
(xwidget-webkit-mode . "HTML")
(yaml-mode . "YAML")
))
(setq wakatime-record-category-alist
'(
(Info-mode . "coding")
(c-mode . "coding")
(compilation-mode . "building")
(compilation-mode . "coding")
(completion-list-mode . "coding")
(conf-toml-mode . "coding")
(css-mode . "coding")
(dired-mode . "planning")
(dockerfile-mode . "coding")
(editor-mode . "writing docs")
(emacs-lisp-mode . "coding")
(eww-mode . "eww-mode")
(fundamental-mode . "coding")
(go-mode . "coding")
(html-mode . "coding")
(js-mode . "coding")
(js2-mode . "coding")
(json-mode . "coding")
(lisp-interaction-mode . "coding")
(magit-refs-mode . "coding")
(magit-status-mode . "coding")
(messages-buffer-mode . "planning")
(mhtml-mode . "coding")
(org-agenda-mode . "planning")
(org-mode . "writing docs")
(python-mode . "coding")
(restclient-mode . "coding")
(rustic-mode . "coding")
(scss-mode . "coding")
(shell-script-mode . "coding")
(sql-interactive-mode . "coding")
(sql-mode . "coding")
(typescript-mode . "coding")
(vterm-mode . "coding")
(vue-html-mode . "coding")
(vue-mode . "coding")
(xwidget-webkit-mode . "coding")
(yaml-mode . "coding")
(nil . "code reviewing")
(nil . "debugging")
(nil . "designing")
(nil . "indexing")
(nil . "learning")
(nil . "manual testing")
(nil . "meeting")
(nil . "researching")
(nil . "running tests")
(nil . "writing tests")))
(cl-defstruct wakatime-record-heartbeat
time
user_agent
entity
type
category
is_write
project
;; branch
language
;; dependencies
;; lines
;; lineno
;; cursorpos
)
(defun wakatime-record-get-category-by-major-mode ()
(or
(cdr (assoc major-mode wakatime-record-category-alist))
"planning"))
(defalias 'wakatime-record-get-category 'wakatime-record-get-category-by-major-mode)
(defun make-wakatime-record-current-heartbeat ()
(make-wakatime-record-heartbeat
:time (float-time)
:type "file"
:user_agent "emacs"
:entity (buffer-name)
:language (or (cdr (assoc major-mode wakatime-record-language-alist)) major-mode)
:project (if-let ((current-task-buffer (org-clock-is-active)))
(with-current-buffer current-task-buffer
(org-get-category))
"GLOBAL")
:is_write t
:category (wakatime-record-get-category)
))
(defun wakatime-record-serialize (heatbeat)
(concat
(json-encode-alist
(let ((typ (type-of heatbeat)))
(mapcar (lambda (key) `(,key . ,(cl-struct-slot-value typ key heatbeat)))
(mapcar 'car (cdr (cl-struct-slot-info typ))))))
"\n"))
(defun wakatime-record-save-heatbeat ()
(interactive)
(let ((serialized-heatbeat (wakatime-record-serialize
(make-wakatime-record-current-heartbeat))))
(with-current-buffer (get-buffer-create wakatime-record-buffer-name)
(insert serialized-heatbeat)
(write-region (point-min) (point-max)
wakatime-record-file-path
t)
(kill-buffer))))
(defun wakatime-record-tunrn-off ()
(interactive)
(when (timerp wakatime-record-timer)
(cancel-timer wakatime-record-timer)))
(defun wakatime-record-tunrn-on ()
(interactive)
(wakatime-record-tunrn-off)
(setq wakatime-record-timer
(run-with-idle-timer 20 t #'wakatime-record-save-heatbeat)))
(provide 'wakatime-record)
;;; wakatime-record.el ends here
wakatimeへデータを送信する
wakatime-transport.el
は wakatime-record.el
がキャッシュしたデータをwakatimeへ送信します。 (wakatime-transport-turn-on))
することで有効になります。
(require 'wakatime-transport)
(wakatime-transport-turn-on)
wakatime-transport.el
;;; wakatime-transport.el --- Yet Another Wakatime plugin for Emacs.
;; Copyright (C) 2021 TakesxiSximada <[email protected]>
;; Author: TakesxiSximada <[email protected]>
;; Maintainer: TakesxiSximada <[email protected]>
;; Website: https://github.com/TakesxiSximada/emacs.d/master/wakatime/
;; Keywords: calendar, comm
;; Package-Version: 20210730.240
;; Package-Commit: 5e6deddda7a70f4b79832e9e8b6636418812e162
;; Version: 1.0.0
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; wakatime-transport.el is Unofficial wakatime plugin for Emacs.
;; It was implemented based on a different design philosophy.
;; wakatime-transport.el is an unofficial wakatime plugin for
;; Emacs. Send the Heartbeat Journal to https://wakatime.com. This
;; package does not save the heatbeat journal, wakatime-record.el does
;; it instead.
;;; Code:
(require 'json)
(require 'seq)
(require 'timer)
(require 'restclient)
(defvar wakatime-transport-buffer-name "*WAKATIME TRANSPORT*")
(defvar wakatime-transport-file-path (expand-file-name "~/.wakatime.heartbeat.json"))
(defvar wakatime-transport-response-buffer nil)
(defvar wakatime-transport-restclient-file (expand-file-name
"~/.emacs.d/wakatime.heartbeat.bulk.http"))
(defvar wakatime-transport-timer nil)
(defun wakatime-transport-get-heartbeeats-cache ()
(json-encode-array
(mapcar #'json-parse-string
(seq-filter
(lambda (elt) (> (length elt) 0))
(split-string
(with-current-buffer (get-buffer-create wakatime-transport-buffer-name)
(buffer-substring-no-properties (point-min) (point-max)))
"\n")))))
(defun wakatime-transport-send-request ()
(when (buffer-live-p wakatime-transport-response-buffer)
(let ((kill-buffer-query-functions nil))
(kill-buffer wakatime-transport-response-buffer)))
(setq wakatime-transport-response-buffer
(with-current-buffer (find-file-noselect wakatime-transport-restclient-file)
(restclient-http-send-current-stay-in-window))))
(defun wakatime-transport-load-heatbeats ()
(let ((buf (get-buffer-create wakatime-transport-buffer-name)))
(with-current-buffer buf (erase-buffer))
(make-process
:name "WAKATIME TRANSPORT"
:buffer buf
:command `("/usr/local/bin/gsed" "-i"
"-e" "1,10 w /dev/stdout"
"-e" "1,10d"
,wakatime-transport-file-path))))
(defun wakatime-transport-heartbeats ()
(let ((proc (wakatime-transport-load-heatbeats)))
(set-process-sentinel
proc (lambda (proc sig)
(wakatime-transport-send-request)))))
(defun wakatime-transport-turn-off ()
(interactive)
(when (timerp wakatime-transport-timer)
(cancel-timer wakatime-transport-timer)))
(defun wakatime-transport-turn-on ()
(interactive)
(wakatime-transport-turn-off)
(setq wakatime-transport-timer
(run-with-idle-timer 60 t #'wakatime-transport-heartbeats)))
(provide 'wakatime-transport)
;;; wakatime-transport.el ends here
org-modeとの橋渡し
org-mode
のプロパティに wakatime
のカテゴリーを登録するための関数を実装する。 wakatime-record-get-category
関数を上書きすることで org-mode
のプロパティからカテゴリーの取得を試みる。設定されていなければ、メジャーモードからカテゴリーを推測する。
(require 'org-wakatime)
(defun wakatime-record-get-category ()
(interactive)
(or (org-wakatime-get-category)
(wakatime-record-get-category-by-major-mode)))
org-wakatime.el
;;; org-wakatime.el --- Wakatime Org Mode Support.
;; Copyright (C) 2021 TakesxiSximada <[email protected]>
;; Author: TakesxiSximada <[email protected]>
;; Maintainer: TakesxiSximada <[email protected]>
;; Website: https://github.com/TakesxiSximada/emacs.d/master/wakatime/
;; Keywords: calendar, comm
;; Package-Version: 20210730.240
;; Package-Commit: 5e6deddda7a70f4b79832e9e8b6636418812e162
;; Version: 1.0.0
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; org-wakatime.el is an unofficial package related to wakatime. It save
;; category for wakatimed in the org property. wakatime-record.el can
;; detect the current working category from there.
;;; Code:
(defvar org-wakatime-category-property-name "WAKATIME_CATEGORY")
(defun org-wakatime-set-category (wakatime-category)
(interactive (list (completing-read
"WAKATIME_CATEGORY: "
(delete-dups (mapcar #'cdr wakatime-record-category-alist)))))
(org-set-property org-wakatime-category-property-name
wakatime-category))
(defun org-wakatime-get-category ()
(interactive)
(if-let ((current-task-buffer (org-clock-is-active)))
(with-current-buffer current-task-buffer
(save-excursion
(goto-char (marker-position org-clock-marker))
(cdr (assoc org-wakatime-category-property-name (org-entry-properties)))))))
(provide 'org-wakatime)
;;; org-wakatime.el ends here
wakatimeのハートビートに設定するprojectをorg-clockのカレントタスクから取得する
取得する関数を定義する7。
(defun waka-get-project ()
(interactive)
(when org-clock-marker
(with-current-buffer (marker-buffer org-clock-marker)
(org-get-category))))
その関数をrestclientで利用する。
先程定義したwaka-get-projectで取得した値を
restclient内で :PROJECT
に設定している。
# -*- restclient -*-
:ORIGIN := "https://wakatime.com"
:API_KEY := (base64-encode-string (getenv+ "WAKATIME_API_KEY"))
:TYPE := "file"
:CATEGORY := "coding"
:TIMESTAMP := (float-time)
:LANGUAGE := "org-mode"
:ENTITY := (buffer-name)
:PROJECT := (waka-get-project)
POST :ORIGIN/api/v1/users/current/heartbeats
Authorization: Basic :API_KEY
Content-Type: application/json
{
"entity": ":ENTITY",
"project": ":PROJECT",
"category": ":CATEGORY",
"language": ":LANGUAGE",
"time": :TIMESTAMP,
"is_write": true
}
以下の結果が返される。
#+BEGIN_SRC js { "data": { "branch": null, "category": "coding", "created_at": "2021-07-28T20:24:00Z", "cursorpos": null, "dependencies": [], "entity": "*temp*-271500", "id": "33ca1dcd-1a81-44fd-baea-7575bd3dc1ab", "is_write": true, "language": "org-mode", "lineno": null, "lines": null, "machine_name_id": null, "project": "symdon", "time": 1627503839.625936, "type": "file", "user_agent_id": null, "user_id": "71580bdd-fd95-41a2-babd-182b9a930d8b" } } // POST https://wakatime.com/api/v1/users/current/heartbeats // HTTP/1.1 201 CREATED // Server: nginx // Date: Wed, 28 Jul 2021 20:24:00 GMT // Content-Type: application/json // Content-Length: 401 // Connection: keep-alive // Access-Control-Allow-Methods: GET, PUT, OPTIONS, PATCH, HEAD, POST // Access-Control-Max-Age: 21600 // Access-Control-Allow-Origin: * // X-Content-Type-Options: nosniff // Strict-Transport-Security: max-age=31536000; includeSubDomains; preload // X-XSS-Protection: 1; mode=block // Expect-CT: enforce, max-age=1209600, report-uri='https://3bca3d665311907a9472c2b8373ab3f6.report-uri.com/r/d/ct/enforce' // Feature-Policy: accelerometer 'none';autoplay 'self';camera 'none';document-domain 'none';fullscreen 'self';geolocation 'none';gyroscope 'none';magnetometer 'none';microphone 'none';midi 'none';payment 'self';picture-in-picture 'none';sync-xhr 'self';usb 'none'; // Referrer-Policy: strict-origin-when-cross-origin // X-Frame-Options: SAMEORIGIN // Content-Security-Policy: default-src 'self'; frame-ancestors 'self'; script-src 'self' 'unsafe-eval' https://js.stripe.com https://*.braintreegateway.com https://api.github.com https://www.google.com/ https://www.gstatic.com/ https://www.googletagmanager.com https://www.google-analytics.com https://heapanalytics.com https://*.heapanalytics.com; img-src 'self' data: https://cdn.loom.com/ https://pbs.twimg.com https://checkout.paypal.com https://*.braintreegateway.com heapanalytics.com; style-src 'self' 'unsafe-inline'; media-src 'self' https://*.amazonaws.com; frame-src 'self' https://www.google.com/ https://js.stripe.com/ https://hooks.stripe.com/ https://www.youtube.com/ https://www.loom.com/ player.vimeo.com checkout.paypal.com; object-src 'self'; connect-src 'self' api.github.com https://www.google.com/ www.google-analytics.com heapanalytics.com https://avatar-cdn.atlassian.com https://api.stripe.com; // Request duration: 0.678229s #+END_SRC
wakatimeのハートビートを手動で送信する
restclientを使ってwakatimeのハートビートの送信方法を確認確認した。 認証方法はBasic認証でAPIキーを用いる簡易な方法を用いた。 APIキーの作成は https://wakatime.com/settings/api-key から行える。
:ORIGIN := "https://wakatime.com"
:API_KEY := (base64-encode-string (getenv "WAKATIME_API_KEY"))
:TYPE := "file" ;; app file domain
:CATEGORY := "learning" ;;
:TIMESTAMP := (float-time)
POST :ORIGIN/api/v1/users/current/heartbeats
Authorization: Basic :API_KEY
Content-Type: application/json
{
"entity": "foo",
"project": "symdon",
"category": ":CATEGORY",
"language": "org-mode",
"time": :TIMESTAMP,
"is_write": true
}
{
"data": {
"branch": null,
"category": "learning",
"created_at": "2021-07-25T23:43:13Z",
"cursorpos": null,
"dependencies": [],
"entity": "foo",
"id": "0bff3974-cbf2-4246-84de-14849ad3d357",
"is_write": true,
"language": "org-mode",
"lineno": null,
"lines": null,
"machine_name_id": null,
"project": "symdon",
"time": 1627256593.444148,
"type": "file",
"user_agent_id": null,
"user_id": "71580bdd-fd95-41a2-babd-182b9a930d8b"
}
}
// POST https://wakatime.com/api/v1/users/current/heartbeats
// HTTP/1.1 201 CREATED
// Server: nginx
// Date: Sun, 25 Jul 2021 23:43:13 GMT
// Content-Type: application/json
// Content-Length: 393
// Connection: keep-alive
// Access-Control-Allow-Methods: GET, PUT, OPTIONS, PATCH, HEAD, POST
// Access-Control-Max-Age: 21600
// Access-Control-Allow-Origin: *
// X-Content-Type-Options: nosniff
// Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
// X-XSS-Protection: 1; mode=block
// Expect-CT: enforce, max-age=1209600, report-uri='https://3bca3d665311907a9472c2b8373ab3f6.report-uri.com/r/d/ct/enforce'
// Feature-Policy: accelerometer 'none';autoplay 'self';camera 'none';document-domain 'none';fullscreen 'self';geolocation 'none';gyroscope 'none';magnetometer 'none';microphone 'none';midi 'none';payment 'self';picture-in-picture 'none';sync-xhr 'self';usb 'none';
// Referrer-Policy: strict-origin-when-cross-origin
// X-Frame-Options: SAMEORIGIN
// Content-Security-Policy: default-src 'self'; frame-ancestors 'self'; script-src 'self' 'unsafe-eval' https://js.stripe.com https://*.braintreegateway.com https://api.github.com https://www.google.com/ https://www.gstatic.com/ https://www.googletagmanager.com https://www.google-analytics.com https://heapanalytics.com https://*.heapanalytics.com; img-src 'self' data: https://cdn.loom.com/ https://pbs.twimg.com https://checkout.paypal.com https://*.braintreegateway.com heapanalytics.com; style-src 'self' 'unsafe-inline'; media-src 'self' https://*.amazonaws.com; frame-src 'self' https://www.google.com/ https://js.stripe.com/ https://hooks.stripe.com/ https://www.youtube.com/ https://www.loom.com/ player.vimeo.com checkout.paypal.com; object-src 'self'; connect-src 'self' api.github.com https://www.google.com/ www.google-analytics.com heapanalytics.com https://avatar-cdn.atlassian.com https://api.stripe.com;
// Request duration: 0.156332s
各フィールド2
Field | Type | Must | Description |
---|---|---|---|
entity | string | must | エンティティのハートビートは、絶対ファイルパスやドメインなどに対して時間を記録しています |
type | string | optional | エンティティのタイプ。ファイル、アプリ、またはドメインにすることができます |
category | string | optional | このアクティビティのカテゴリ。通常、これは型から自動的に推測されます。コーディング、ビルド、インデックス作成、デバッグ、ブラウジング、テストの実行、テストの作成、手動テスト、ドキュメントの作成、コードレビュー、調査、学習、または設計が可能です。 |
time | float | must | UNIXエポックタイムスタンプ。小数点以下の数値は秒の端数です>、 |
project | string | optional | プロジェクト名 |
branch | string | optional | ブランチ名 |
language | string | optional | プログラミング言語など |
dependencies | string | optional | エンティティファイルから検出された依存関係のコンマ区切りリスト |
lines | integer | when entity type is file | エンティティ内の行の総数 |
lineno | integer | optional | カーソルの現在の行行番号 |
cursorpos | integer | optional | 現在のカーソル列の位置 |
is_write | boolean | optional | このハートビートがファイルへの書き込みからトリガーされたかどうか |
この記事はEmacs Advent Calendar 2017及びしむどん Advent Calendar 2017の20日目の記事です。(作成: 2017-12-20T07:50:09+0900)
(作成: 2021-07-29T05:25:11+0900)