packages feed

arbtt 0.8.1.4 → 0.9

raw patch · 6 files changed

+702/−139 lines, 6 filesdep ~process-extras

Dependency ranges changed: process-extras

Files

arbtt.cabal view
@@ -1,5 +1,5 @@ name:               arbtt-version:            0.8.1.4+version:            0.9 license:            GPL license-file:       LICENSE category:           Desktop@@ -22,6 +22,7 @@     you understand the consequences of a full-time logger and be careful with this     data. homepage:           http://arbtt.nomeata.de/+bug-reports:        https://bitbucket.org/nomeata/arbtt/issues  extra-source-files:     categorize.cfg, arbtt-capture.desktop, README.md,@@ -48,10 +49,10 @@         TimeLog         UpgradeLog1         LeftFold-    +     ghc-options: -rtsopts -    if os(windows) +    if os(windows)         extra-libraries: psapi         cpp-options:    -DWIN32         ghc-options:    -optl-mwindows@@ -98,7 +99,7 @@         Text.Regex.PCRE.Light.Text         TermSize     ghc-options: -rtsopts-    if os(windows) +    if os(windows)         cpp-options:    -DWIN32     else         other-modules:@@ -127,7 +128,7 @@         DumpFormat         Data.List.TakeR     ghc-options: -rtsopts-    if os(windows) +    if os(windows)         cpp-options:    -DWIN32     else         other-modules:@@ -151,7 +152,7 @@         CommonStartup         TimeLog     ghc-options: -rtsopts-    if os(windows) +    if os(windows)         cpp-options:    -DWIN32     else         other-modules:@@ -174,7 +175,7 @@         CommonStartup         TimeLog     ghc-options: -rtsopts-    if os(windows) +    if os(windows)         cpp-options:    -DWIN32     else         other-modules:@@ -195,7 +196,7 @@       , tasty-golden >= 2.2.0.2  && <= 2.3       , tasty-hunit >= 0.2  && < 0.11       , HUnit == 1.2.*-      , process-extras == 0.2.*+      , process-extras == 0.2.* || == 0.3.*       , deepseq       , binary >= 0.5       , bytestring
doc/arbtt.xml view
@@ -330,16 +330,16 @@      <figure id="grammar">         <title>The formal grammar of <filename>categorize.cfg</filename></title>-	<productionset>-	  <production id="g-rules">-	    <lhs>Rules</lhs>-	    <rhs>-	      [ <nonterminal def="#g-aliasspec"/> ]-	      <nonterminal def="#g-rule"/> ( (<quote>,</quote>-	      <nonterminal def="#g-rule"/>)* | ( <quote>;</quote>-	      <nonterminal def="#g-rule"/>)* )-	    </rhs>-	  </production>+    <productionset>+      <production id="g-rules">+        <lhs>Rules</lhs>+        <rhs>+          [ <nonterminal def="#g-aliasspec"/> ]+          <nonterminal def="#g-rule"/> ( (<quote>,</quote>+          <nonterminal def="#g-rule"/>)* | ( <quote>;</quote>+          <nonterminal def="#g-rule"/>)* )+        </rhs>+      </production>            <production id="g-aliasspec">             <lhs>AliasSpec</lhs>@@ -353,9 +353,9 @@             <rhs>Literal <quote>-&gt;</quote> Literal</rhs>           </production> -	  <production id="g-rule">-	    <lhs>Rule</lhs>-	    <rhs><quote>{</quote> <nonterminal def="#g-rules"/>+      <production id="g-rule">+        <lhs>Rule</lhs>+        <rhs><quote>{</quote> <nonterminal def="#g-rules"/>                  <quote>}</quote>             </rhs>             <rhs>@@ -368,11 +368,11 @@             <rhs>               <quote>tag</quote> <nonterminal def="#g-tag"/>             </rhs>-	  </production>+      </production> -	  <production id="g-cond">-	    <lhs>Cond</lhs>-	    <rhs><quote>(</quote> <nonterminal def="#g-cond"/>+      <production id="g-cond">+        <lhs>Cond</lhs>+        <rhs><quote>(</quote> <nonterminal def="#g-cond"/>                  <quote>)</quote>             </rhs>             <rhs><quote>!</quote> <nonterminal def="#g-cond"/> |@@ -381,67 +381,67 @@                  <nonterminal def="#g-cond"/> <quote>||</quote> <nonterminal                  def="#g-cond"/>             </rhs>-	    <rhs> <quote>$active</quote> </rhs>+        <rhs> <quote>$active</quote> </rhs>             <rhs> <nonterminal def="#g-string"/> <nonterminal def="#g-cmpop"/>-	   	 <nonterminal def="#g-string"/> </rhs>+         <nonterminal def="#g-string"/> </rhs>             <rhs> <nonterminal def="#g-string"/> <nonterminal def="#g-cmpop"/>-	   	 <quote>[</quote> <nonterminal def="#g-listofstring"/>+         <quote>[</quote> <nonterminal def="#g-listofstring"/>                  <quote>]</quote>                  </rhs>             <rhs> <nonterminal def="#g-string"/> <quote>=~</quote>-	    	 <nonterminal def="#g-regex"/></rhs>+             <nonterminal def="#g-regex"/></rhs>             <rhs> <nonterminal def="#g-string"/> <quote>=~</quote>-	   	 <quote>[</quote> <nonterminal def="#g-listofregex"/>+         <quote>[</quote> <nonterminal def="#g-listofregex"/>                  <quote>]</quote>                  </rhs>             <rhs> <nonterminal def="#g-number"/> <nonterminal def="#g-cmpop"/>-	   	 <nonterminal def="#g-number"/> </rhs>+         <nonterminal def="#g-number"/> </rhs>             <rhs> <nonterminal def="#g-timediff"/> <nonterminal def="#g-cmpop"/>-	   	 <nonterminal def="#g-timediff"/> </rhs>+         <nonterminal def="#g-timediff"/> </rhs>             <rhs> <nonterminal def="#g-date"/> <nonterminal def="#g-cmpop"/>-	   	 <nonterminal def="#g-date"/> </rhs>+         <nonterminal def="#g-date"/> </rhs>             <rhs> <quote>current window</quote> <nonterminal def="#g-cond"/> </rhs>             <rhs> <quote>any window</quote> <nonterminal def="#g-cond"/> </rhs>-	  </production>+      </production> -	  <production id="g-string">-	    <lhs>String</lhs>-	    <rhs> <quote>$title</quote> </rhs>-	    <rhs> <quote>$program</quote> </rhs>-	    <rhs> <quote>$desktop</quote> </rhs>-	    <rhs> <quote>format</quote> <nonterminal def="#g-date" /> </rhs>-	    <rhs> <quote>"</quote> string literal <quote>"</quote> </rhs>-	  </production>+      <production id="g-string">+        <lhs>String</lhs>+        <rhs> <quote>$title</quote> </rhs>+        <rhs> <quote>$program</quote> </rhs>+        <rhs> <quote>$desktop</quote> </rhs>+        <rhs> <quote>format</quote> <nonterminal def="#g-date" /> </rhs>+        <rhs> <quote>"</quote> string literal <quote>"</quote> </rhs>+      </production> -	  <production id="g-listofstring">-	    <lhs>ListOfString</lhs>-	    <rhs> <quote>"</quote> string literal <quote>"</quote> </rhs>-	    <rhs> <quote>"</quote> string literal <quote>"</quote> , <nonterminal def="#g-listofstring"/> </rhs>-	  </production>+      <production id="g-listofstring">+        <lhs>ListOfString</lhs>+        <rhs> <quote>"</quote> string literal <quote>"</quote> </rhs>+        <rhs> <quote>"</quote> string literal <quote>"</quote> , <nonterminal def="#g-listofstring"/> </rhs>+      </production> -	  <production id="g-number">-	    <lhs>Number</lhs>-	    <rhs> <quote>$idle</quote> </rhs>-	    <rhs> <quote>day of week</quote> <nonterminal def="#g-date" /> </rhs>-	    <rhs> <quote>day of month</quote> <nonterminal def="#g-date" /> </rhs>-	    <rhs> <quote>month</quote> <nonterminal def="#g-date" /> </rhs>-	    <rhs> <quote>year</quote> <nonterminal def="#g-date" /> </rhs>-	    <rhs> number literal </rhs>-	  </production>+      <production id="g-number">+        <lhs>Number</lhs>+        <rhs> <quote>$idle</quote> </rhs>+        <rhs> <quote>day of week</quote> <nonterminal def="#g-date" /> </rhs>+        <rhs> <quote>day of month</quote> <nonterminal def="#g-date" /> </rhs>+        <rhs> <quote>month</quote> <nonterminal def="#g-date" /> </rhs>+        <rhs> <quote>year</quote> <nonterminal def="#g-date" /> </rhs>+        <rhs> number literal </rhs>+      </production> -	  <production id="g-date">-	    <lhs>Date</lhs>-	    <rhs> <quote>$date</quote> </rhs>-	    <!-- <rhs> <quote>$now</quote> </rhs> -->-	  </production>+      <production id="g-date">+        <lhs>Date</lhs>+        <rhs> <quote>$date</quote> </rhs>+        <!-- <rhs> <quote>$now</quote> </rhs> -->+      </production> -	  <production id="g-timediff">-	    <lhs>TimeDiff</lhs>-	    <rhs> <quote>$time</quote> </rhs>-	    <rhs> <quote>$sampleage</quote> </rhs>-	    <!-- <rhs> <nonterminal def="#g-date"/> <quote>-</quote> <nonterminal def="#g-date"/></rhs> -->+      <production id="g-timediff">+        <lhs>TimeDiff</lhs>+        <rhs> <quote>$time</quote> </rhs>+        <rhs> <quote>$sampleage</quote> </rhs>+        <!-- <rhs> <nonterminal def="#g-date"/> <quote>-</quote> <nonterminal def="#g-date"/></rhs> -->             <rhs>( Digit )* Digit <quote>:</quote> Digit Digit</rhs>-	  </production>+      </production>            <production id="g-tag">             <lhs>Tag</lhs>@@ -457,11 +457,11 @@               character.</lineannotation> </rhs>           </production> -	  <production id="g-listofregex">-	    <lhs>ListOfRegex</lhs>-	    <rhs> <quote>"</quote> <nonterminal def="#g-regex"/> <quote>"</quote> </rhs>-	    <rhs> <quote>"</quote> <nonterminal def="#g-regex"/> <quote>"</quote> , <nonterminal def="#g-listofregex"/> </rhs>-	  </production>+      <production id="g-listofregex">+        <lhs>ListOfRegex</lhs>+        <rhs> <quote>"</quote> <nonterminal def="#g-regex"/> <quote>"</quote> </rhs>+        <rhs> <quote>"</quote> <nonterminal def="#g-regex"/> <quote>"</quote> , <nonterminal def="#g-listofregex"/> </rhs>+      </production>            <production id="g-cmpop">             <lhs>CmpOp</lhs>@@ -469,7 +469,7 @@             | <quote>&gt;</quote> | <quote>&gt;=</quote></rhs>           </production> -	</productionset>+    </productionset>       </figure>       <para>         A <literal>String</literal> refers to a double-quoted string of@@ -507,9 +507,524 @@         expressions.       </para>   </sect2>- </sect1> +<sect1 id="effective-use">+  <title>Effective Use of Arbtt</title>+  <para>+    Now that the syntax has been described and the toolbox laid out,+    how do you practically go about using and configuring arbtt?+  </para>+  <sect2>+    <title>Enabling data collection</title>+    <para>+      After installing arbtt, you need to configure it to run. There+      are many ways you can run the <literal>arbtt-capture</literal>+      daemon. One standard way is to include the command+      <programlisting>+arbtt-capture &amp;+      </programlisting>+      in your desktop environments startup script, e.g.+      <filename>~/.xinitrc</filename> or similar.+    </para>+    <para>+      Another trick is add it as a <ulink+      url="https://en.wikipedia.org/wiki/Cron"><literal>cron</literal></ulink>+      job. To do so, edit your crontab file (<literal>crontab -e</literal>) and+      add a line like this:+    </para>+    <programlisting>+DISPLAY=:0+@reboot arbtt-capture --logfile=/home/username/doc/arbtt/capture.log+</programlisting>+    <para>+      At boot, <literal>arbtt-capture</literal> will be run in the+      background and will capture a snapshot of the X metadata for+      active windows every 60 seconds (the default). If you want more+      fine-grained time data at the expense of doubling storage use,+      you could increase the sampling rate with an option like+      <literal>--sample-rate=30</literal>. To be resilient to any errors+      or segfaults, you could also wrap it in an infinite loop to restart+      the daemon should it ever crash, with a command like+    </para>+    <programlisting>+DISPLAY=:0+@reboot while true; do arbtt-capture --sample-rate=30; sleep 1m; done+</programlisting>+  </sect2>+  <sect2>+    <title>Checking data availability</title>+    <para>+      arbtt tracks <ulink url="https://en.wikipedia.org/wiki/X_Window_System_protocols_and_architecture#Attributes_and_properties"+      >X</ulink> properties like window title, class, and running+      program, and you write rules to classify those strings as+      you wish; but this assumes that the necessary data is present in+      those properties.+    </para>+    <para>+      For some programs, this is the case. For example, web browsers+      like Firefox typically set the X title to the+      HTML <literal>&lt;title&gt;</literal> element of the web page in the+      currently-focused tab, which is enough for classification.+    </para>+    <para>+      Some programs do not set titles or class, and all arbtt sees is+      empty strings like <literal>&quot;&quot;</literal>; or they may set the title/class+      to a constant like <literal>&quot;Liferea&quot;</literal>, which may be acceptable if+      that program is used for only one purpose, but if it is used for+      many purposes, then you cannot write a rule matching it without+      producing highly-misleading time analyses. (For example, a web+      browser may be used for countless purposes, ranging from work to+      research to music to writing to programming; but if the web+      browser's title/class were always just <literal>&quot;Web browser&quot;</literal>,+      how would you classify 5 hours spent using the web browser? If the+      5 hours are classified as any or all of those purposes, then the+      results will be misleading garbage - you probably did not spend 5+      hours just listening to music, but a mixture of those purposes,+      which changes from day to day.)+    </para>+    <para>+      You should check for such problematic programs upon starting using+      arbtt. It would be unfortunate if you were to log for a few+      months, go back for a detailed report for some reason, and+      discover that the necessary data was never available for+      arbtt to log!+    </para>+    <para>+      These programs can sometimes be customized internally, a bug+      report filed with the maintainers, or their titles can be+      externally set by+      <ulink url="https://en.wikipedia.org/wiki/Wmctrl"><literal>wmctrl</literal></ulink>+      or+      <ulink url="http://jonisalonen.com/2014/setting-x11-window-properties-with-xprop/"><literal>xprop</literal></ulink>.+    </para>+    <sect3>+      <title><literal>xprop</literal></title>+      <para>+        You can check the X properties of a running window by running+        the command+        <ulink url="http://www.xfree86.org/current/xprop.1.html"><literal>xprop</literal></ulink>+        and clicking on the window; <literal>xprop</literal> will print+        out all the relevant X information. For example, the output for+        Emacs might look like this+      </para>+      <programlisting>+$ xprop | tail -5+WM_CLASS(STRING) = &quot;emacs&quot;, &quot;Emacs&quot;+WM_ICON_NAME(STRING) = &quot;emacs@elan&quot;+_NET_WM_ICON_NAME(UTF8_STRING) = &quot;emacs@elan&quot;+WM_NAME(STRING) = &quot;emacs@elan&quot;+_NET_WM_NAME(UTF8_STRING) = &quot;emacs@elan&quot;+</programlisting>+      <para>+        This is not very helpful: it does not tell us the filename being+        edited, the mode being used, or anything. You could classify+        time spent in Emacs as &quot;programming&quot; or+        &quot;writing&quot;, but this would be imperfect, especially if+        you do both activities regularly. However, Emacs can be+        customized by editing <literal>~/.emacs</literal>, and after+        some searching with queries like &quot;setting Emacs window+        title&quot;, the+        <ulink url="http://www.emacswiki.org/emacs-en/FrameTitle">Emacs+        wiki</ulink> and+        <ulink url="https://www.gnu.org/software/emacs/manual/html_node/efaq/Displaying-the-current-file-name-in-the-titlebar.html">manual</ulink>+        advise us to put something like this Elisp in our+        <literal>.emacs</literal> file:+      </para>+      <programlisting>+(setq frame-title-format &quot;%f&quot;)+</programlisting>+      <para>+        Now the output looks different:+      </para>+      <programlisting>+$ xprop | tail -5+WM_CLASS(STRING) = &quot;emacs&quot;, &quot;Emacs&quot;+WM_ICON_NAME(STRING) = &quot;/home/gwern/arbtt.page&quot;+_NET_WM_ICON_NAME(UTF8_STRING) = &quot;/home/gwern/arbtt.page&quot;+WM_NAME(STRING) = &quot;/home/gwern/arbtt.page&quot;+_NET_WM_NAME(UTF8_STRING) = &quot;/home/gwern/arbtt.page&quot;+</programlisting>+      <para>+        With this, we can usefully classify all such time samples as+        being “writing”:+      </para>+<programlisting>+current window $title == &quot;/home/gwern/arbtt.page&quot; ==> tag Writing,+</programlisting>+      <para>+        Another common gap is terminals/shells: they often do not+        include information in the title like the current working+        directory or last shell command. For example, urxvt/Bash:+      </para>+      <programlisting>+WM_COMMAND(STRING) = { &quot;urxvt&quot; }+_NET_WM_ICON_NAME(UTF8_STRING) = &quot;urxvt&quot;+WM_ICON_NAME(STRING) = &quot;urxvt&quot;+_NET_WM_NAME(UTF8_STRING) = &quot;urxvt&quot;+WM_NAME(STRING) = &quot;urxvt&quot;+</programlisting>+      <para>+        Programmers may spend many hours in the shell doing a variety of+        things (like Emacs), so this is a problem. Fortunately, this is+        also solvable by customizing one's <literal>.bashrc</literal> to+        set the prompt to emit an escape code interpreted by the+        terminal (baroque, but it works). The following will include the+        working directory, a timestamp, and the last command:+      </para>+      <programlisting>+trap 'echo -ne &quot;\033]2;$(pwd); $(history 1 | sed &quot;s/^[ ]*[0-9]*[ ]*//g&quot;)\007&quot;' DEBUG+</programlisting>+      <para>+        Now the urxvt samples are useful:+      </para>+      <programlisting>+_NET_WM_NAME(UTF8_STRING) = &quot;/home/gwern/wiki; 2014-09-03 13:39:32 arbtt-stats --help&quot;+</programlisting>+      <para>+    Some distributions (e.g. Debian) already provide the relevant+    configuration for this to happen. If it does not work for you, you can try to add+    <programlisting>. /etc/profile.d/vte.sh</programlisting>+    to your <filename>~/.bashrc</filename>.+      </para>+      <para>+        A rule could classify based on the directory you are working in,+        the command one ran, or both. Other shells like zsh can be fixed+        this way too but the exact command may differ; you will need to+        research and experiment.+      </para>+      <para>+        Some programs can be tricky to set. The+        <ulink url="http://feh.finalrewind.org/">X image viewer+        feh</ulink> has a <literal>--title</literal> option but it+        cannot be set in the configuration file,+        <literal>.config/feh/themes</literal>, because it needs to be+        specified dynamically; so you need to set up a shell alias or+        script to wrap the command like+        <literal>feh --title &quot;$(pwd) / %f / %n&quot;</literal>.+      </para>+    </sect3>+    <sect3>+      <title>Raw samples</title>+      <para>+        <literal>xprop</literal> can be tedious to use on every running+        window and you may forget to check seldomly used programs. A better+        approach is to use <literal>arbtt-stats</literal>’s+        <literal>--dump-samples</literal> option: this option will print+        out the collected data for specified time periods, allowing you+        to examine the X properties en masse. This option can be used+        with the <literal>--exclude=</literal>+        option to print the samples for <emphasis>samples not matched+        by existing rules</emphasis> as well, which is indispensable for+        improving coverage and suggesting ideas for new rules. A good+        way to figure out what customizations to make is to run arbtt as+        a daemon for a day or so, and then begin examining the raw+        samples for problems.+      </para>+      <example>+      <title>An initial configuration session</title>+      <para>+        An example: suppose I create a simple category file named+        <literal>foo</literal> with just the line+      </para>+      <programlisting>+$idle &gt; 30 ==&gt; tag inactive+</programlisting>+      <para>+        I can then dump all my arbtt samples for the past day with a+        command like this:+      </para>+      <programlisting>+arbtt-stats --categorizefile=foo --m=0 --filter='$sampleage &lt;24:00' --dump-samples+</programlisting>+      <para>+        Because there are so many open windows, this produces a large+        amount (26586 lines) of hard-to-read output:+      </para>+      <programlisting>+...+( ) Navigator:      /r/Touhou's Favorite Arranges! Part 71: Retribution for the Eternal Night ~ Imperishable Night : touhou - Iceweasel+( ) Navigator:      Configuring the arbtt categorizer (arbtt-stats) - Iceweasel+( ) evince:         ATTACHMENT02+( ) evince:         2009-geisler.pdf — Heart rate variability predicts self-control in goal pursuit+( ) urxvt:          /home/gwern; arbtt-stats --categorizefile=foo --m=0 --filter='$sampleage &lt;24:00' --dump-samples+( ) mnemosyne:      Mnemosyne+( ) urxvt:          /home/gwern; 2014-09-03 13:11:45 xprop+( ) urxvt:          /home/gwern; 2014-09-03 13:42:17 history 1 | cut --delimiter=' ' --fields=5-+( ) urxvt:          /home/gwern; 2014-09-03 13:12:21 git log -p .emacs+(*) emacs:          emacs@elan+( ) urxvt:          /home/gwern; 2014-09-01 14:50:30 while true; do cd ~/ &amp;&amp; getmail_fetch --ssl pop.gmail.com gwern0 'ugaozoumbhwcijxb' ./mail/; done+( ) urxvt:          /home/gwern/blackmarket-mirrors/silkroad2-forums; 2014-08-31 23:20:10 mv /home/gwern/cookies.txt ./; http_proxy=&quot;localhost:8118&quot; wget...+( ) urxvt:          /home/gwern/blackmarket-mirrors/agora; 2014-08-31 23:15:50 mv /home/gwern/cookies.txt ./; http_proxy=&quot;localhost:8118&quot; wget --mirror ...+( ) urxvt:          /home/gwern/blackmarket-mirrors/evolution-forums; 2014-08-31 23:04:10 mv ~/cookies.txt ./; http_proxy=&quot;localhost:8118&quot; wget --mirror ...+( ) puddletag:      puddletag: /home/gwern/music+</programlisting>+      <para>+        Active windows are denoted by an asterisk, so I can focus &amp;+        simplify by adding a pipe like <literal>| fgrep '(*)'</literal>,+        producing more manageable output like+      </para>+      <programlisting>+(*) urxvt:          irssi+(*) urxvt:          irssi+(*) urxvt:          irssi+(*) Navigator:      Pyramid of Technology - NextNature.net - Iceweasel+(*) Navigator:      Search results - gwern0@gmail.com - Gmail - Iceweasel+(*) Navigator:      [New comment] The Wrong Path - gwern0@gmail.com - Gmail - Iceweasel+(*) Navigator:      Iceweasel+(*) Navigator:      Litecoin Exchange Rate - $4.83 USD - litecoinexchangerate.org - Iceweasel+(*) Navigator:      PredictionBook: LiteCoin will trade at &gt;=10 USD per ltc in 2 years, - Iceweasel+(*) urxvt:          irssi+(*) Navigator:      Bug#691547 closed by Mikhail Gusarov &lt;dottedmag@dottedmag.net&gt; (Re: s3cmd: Man page: --default-mime-type documentation incomplete...)+(*) Navigator:      Bug#691547 closed by Mikhail Gusarov &lt;dottedmag@dottedmag.net&gt; (Re: s3cmd: Man page: --default-mime-type documentation incomplete...)+(*) Navigator:      Bug#691547 closed by Mikhail Gusarov &lt;dottedmag@dottedmag.net&gt; (Re: s3cmd: Man page: --default-mime-type documentation incomplete...)+(*) urxvt:          /home/gwern; 2014-09-02 14:25:17 man s3cmd+(*) evince:         bayesiancausality.pdf+(*) evince:         bayesiancausality.pdf+(*) puddletag:      puddletag: /home/gwern/music+(*) puddletag:      puddletag: /home/gwern/music+(*) evince:         bayesiancausality.pdf+(*) Navigator:      ▶ Umineko no Naku Koro ni Music Box 4 - オルガン小曲 第2億番 ハ短調 - YouTube - Iceweasel+...+</programlisting>+      <para>+        This is better. We can see a few things: the windows all now+        produce enough information to be usefully classified (Gmail can+        be classified under email, irssi can be classified as IRC, the+        urxvt usage can clearly be classified as programming, the PDF+        being read is statistics, etc) in part because of customizations+        to bash/urxvt. The duplication still impedes focus, and we don't+        know what's most common. We can use another pipeline to sort,+        count duplicates, and sort by number of duplicates+        (<literal>| sort | uniq --count | sort --general-numeric-sort</literal>),+        yielding:+      </para>+      <programlisting>+ ...+ 14     (*) Navigator:      A Bluer Shade of White Chapter 4, a frozen fanfic | FanFiction - Iceweasel+ 14     (*) Navigator:      Iceweasel+ 15     (*) evince:         2009-geisler.pdf — Heart rate variability predicts self-control in goal pursuit+ 15     (*) Navigator:      Tool use by animals - Wikipedia, the free encyclopedia - Iceweasel+ 16     (*) Navigator:      Hacker News | Add Comment - Iceweasel+ 17     (*) evince:         bayesiancausality.pdf+ 17     (*) Navigator:      Comments - Less Wrong Discussion - Iceweasel+ 17     (*) Navigator:      Keith Gessen · Why not kill them all?: In Donetsk · LRB 11 September 2014 - Iceweasel+ 17     (*) Navigator:      Notes on the Celebrity Data Theft | Hacker News - Iceweasel+ 18     (*) Navigator:      A Bluer Shade of White Chapter 1, a frozen fanfic | FanFiction - Iceweasel+ 19     (*) gl:             mplayer2+ 19     (*) Navigator:      Neural networks and deep learning - Iceweasel+ 20     (*) Navigator:      Harry Potter and the Philosopher's Zombie, a harry potter fanfic | FanFiction - Iceweasel+ 20     (*) Navigator:      [OBNYC] Time tracking app - gwern0@gmail.com - Gmail - Iceweasel+ 25     (*) evince:         ps2007.pdf — untitled+ 35     (*) emacs:          /home/gwern/arbtt.page+ 43     (*) Navigator:      CCC comments on The Octopus, the Dolphin and Us: a Great Filter tale - Less Wrong - Iceweasel+ 62     (*) evince:         The physics of information processing superobjects - Anders Sandberg - 1999.pdf — Brains2+ 69     (*) liferea:        Liferea+ 82     (*) evince:         BMS_raftery.pdf — untitled+ 84     (*) emacs:          emacs@elan+ 87     (*) Navigator:      overview for gwern - Iceweasel+109     (*) puddletag:      puddletag: /home/gwern/music+150     (*) urxvt:          irssi+</programlisting>+      <para>+        Put this way, we can see what rules we should write to+        categorize: we could categorize the activities here into a few+        categories of &quot;recreational&quot;, &quot;statistics&quot;,+        &quot;music&quot;, &quot;email&quot;, &quot;IRC&quot;,+        &quot;research&quot;, and &quot;writing&quot;; and add to the+        <literal>categorize.cfg</literal> some rules like thus:+      </para>+      <programlisting>+$idle &gt; 30 ==&gt; tag inactive,++current window $title =~ [/.*Hacker News.*/, /.*Less Wrong.*/, /.*overview for gwern.*/, /.*[fF]an[fF]ic.*/, /.* LRB .*/]+  || current window $program == &quot;liferea&quot; ==&gt; tag Recreation,+current window $title =~ [/.*puddletag.*/, /.*mplayer2.*/] ==&gt; tag Music,+current window $title =~ [/.*[bB]ayesian.*/, /.*[nN]eural [nN]etworks.*/, /.*ps2007.pdf.*/, /.*[Rr]aftery.*/] ==&gt; tag Statistics,+current window $title =~ [/.*Wikipedia.*/, /.*Heart rate variability.*/, /.*Anders Sandberg.*/] ==&gt; tag Research,+current window $title =~ [/.*Gmail.*/] ==&gt; tag Email,+current window $title =~ [/.*arbtt.*/] ==&gt; tag Writing,+current window $title == &quot;irssi&quot; ==&gt; tag IRC,+</programlisting>+      <para>+        If we reran the command, we'd see the same output, so we need to+        leverage our new rules and <emphasis>exclude</emphasis> any+        samples matching our current tags, so now we run a command like:+      </para>+      <programlisting>+arbtt-stats --categorizefile=foo --filter='$sampleage &lt;24:00' --dump-samples --exclude=Recreation --exclude=Music --exclude=Statistics+             --exclude=Research --exclude=Email --exclude=Writing --exclude=IRC |+             fgrep '(*)' | sort | uniq --count | sort --general-numeric-sort+</programlisting>+      <para>+        Now the previous samples disappear, leaving us with a fresh+        batch of unclassified samples to work with:+      </para>+      <programlisting>+  9     (*) Navigator:      New Web Order &gt; Nik Cubrilovic - - Notes on the Celebrity Data Theft - Iceweasel+  9     ( ) urxvt:          /home/gwern; arbtt-stats --categorizefile=foo --filter='$sampleage &lt;24:00' --dump-samples | fgrep '(*)' | less+ 10     (*) evince:         ATTACHMENT02+ 10     (*) Navigator:      These Giant Copper Orbs Show Just How Much Metal Comes From a Mine | Design | WIRED - Iceweasel+ 12     (*) evince:         [Jon_Elster]_Alchemies_of_the_Mind_Rationality_an(BookFi.org).pdf — Alchemies of the mind+ 12     (*) Navigator:      Morality Quiz/Test your Morals, Values &amp; Ethics - YourMorals.Org - Iceweasel+ 33     ( ) urxvt:          /home/gwern; arbtt-stats --categorizefile=foo --filter='$sampleage &lt;24:00' --dump-samples | fgrep '(*)'...+</programlisting>+      <para>+        We can add rules categorizing these as 'Recreational',+        'Writing', 'Research', 'Recreational', 'Research', 'Writing',+        and 'Writing' respectively; and we might decide at this point+        that 'Writing' is starting to become overloaded, so we'll split+        it into two tags, 'Writing' and 'Programming'. And then after+        tossing another <literal>--exclude=Programming</literal> into+        our rules, we can repeat the process.+      </para>+      <para>+        As we refine our rules, we will quickly spot instances where the+        title/class/program are insufficient to allow accurate+        classification, and we will figure out the best collection of+        tags for our particular purposes. A few iterations is enough for+        most purposes.+      </para>+      </example>+    </sect3>+  </sect2>+  <sect2>+    <title>Categorizing advice</title>+    <para>+      When building up rules, a few rules of thumb should be kept in+      mind:+    </para>+    <sect3>+        <title>+          Categorize by purpose, not by program+        </title>+        <para>+          This leads to misleading time reports. Avoid, for example,+          lumping all web browser time into a single category named+          'Internet'; this is more misleading than helpful. Good+          categories describe an activity or goal, such as 'Work' or+          'Recreation', not a tool, like 'Emacs' or 'Vim'.+        </para>+    </sect3>+    <sect3>+        <title>+          When in doubt, write narrow rules and generalize later+        </title>+        <para>+          Regexps are tricky and it can be easy to write rules far+          broader than one intended. The <literal>--exclude</literal>+          filters mean that one will never see samples which are matched+          accidentally. If one is in doubt, it can be helpful to take a+          specific sample one wants to match and several similar strings+          and look at how well one's regexp rule works in Emacs's+          <ulink url="http://www.emacswiki.org/emacs/ReBuilder">regexp-builder</ulink>+          or online regexp-testers like+          <ulink url="http://regexpal.com/">regexpal</ulink>.+        </para>+    </sect3>+    <sect3>+        <title>+          Don't try to classify everything+        </title>+        <para>+          You will never classify 100% of samples because sometimes+          programs do not include useful X properties and cannot be+          fixed, you have samples from before you fixed them, or they+          are too transient (like popups and dialogues) to be worth+          fixing. It is not necessary to classify 100% of your time,+          since as long as the most common programs and, say,+          <ulink url="https://en.wikipedia.org/wiki/Pareto_principle">80%</ulink>+          of your time is classified, then you have most of the value.+          It is easy to waste more time tweaking arbtt than one gains+          from increased accuracy or more finely-grained tags.+        </para>+    </sect3>+    <sect3>+    <title>+      Avoid large and microscopic tags+    </title>+    <para>+      If a tag takes up more than a third or so of your time, it is+      probably too large, masks variation, and can be broken down into more+      meaningful tags. Conversely, a tag too narrow to show up regularly in+      reports (because it is below the default 1% filter) may not be+      helpful because it is usually tiny, and can be combined with the most+      similar tag to yield more compact and easily interpreted reports.+    </para>+    </sect3>+  </sect2>+  <sect2>+    <title>Long-term storage</title>+    <para>+      Each halving of the sampling rate doubles the number of samples+      taken and hence the storage requirement; sampling rates below 20s+      are probably wasteful. But even the default 60s can accumulate+      into a nontrivial amount of data over a year. A+      constantly-changing binary file can interact poorly with backup+      systems, may make arbtt analyses slower, and if one's system+      occasionally crashes or experiences other problems, cause some+      corruption of the log and be a nuisance in having to run+      <literal>arbtt-recover</literal>.+    </para>+    <para>+      Thus it may be a good idea to archive one's+      <literal>capture.log</literal> on an annual basis. If one needs to+      query the historical data, the particular log file can be+      specified as an option like+      <literal>--logfile=/home/gwern/doc/arbtt/2013-2014.log</literal>+    </para>+  </sect2>+  <sect2 id="external-processing">+    <title>External processing of arbtt statistics</title>+    <para>+      arbtt supports CSV export of time by category in various levels of+      granularity in a 'long' format (multiple rows for each day, with+      <emphasis>n</emphasis> row specifying a category's value for that+      day). These CSV exports can be imported into statistical programs+      like R or Excel and manipulated as desired.+    </para>+    <para>+      R users may prefer to have their time data in a 'wide' format+      (each row is 1 day, with <emphasis>n</emphasis> columns for each+      possible category); this can be done with the+      <literal>reshape</literal> default library. After reading in the+      CSV, the time-intervals can be converted to counts and the data to+      a wide data-frame with R code like the following:+    </para>+    <programlisting>+arbtt &lt;- read.csv(&quot;arbtt.csv&quot;)+interval &lt;- function(x) { if (!is.na(x)) { if (grepl(&quot; s&quot;,x)) as.integer(sub(&quot; s&quot;,&quot;&quot;,x))+                                          else { y &lt;- unlist(strsplit(x, &quot;:&quot;));+                                                 as.integer(y[[1]])*3600 + as.integer(y[[2]])*60 + as.integer(y[[3]]); }+                                                 }+                         else NA+                         }+arbtt$Time &lt;- sapply(as.character(arbtt$Time), interval)+library(reshape)+arbtt &lt;- reshape(arbtt, v.names=&quot;Time&quot;, timevar=&quot;Tag&quot;, idvar=&quot;Day&quot;, direction=&quot;wide&quot;)+</programlisting>+  </sect2>+</sect1>+<sect1 id="contributed">+  <title>Contributed tools</title>+  <para>+    Due to the export facilities of arbtt (as explained in <xref+    linkend="external-processing"/>), tools analyzing arbtt’s data can be+    developed independently. This section lists those that we are aware of. If+    you create a tool of your own, or find one somewhere, pleas tell us on the+    mailing list (see <xref linkend="copyright"/>).+  </para>+  <sect2>+    <title>arbtt-graph</title>+    <para>+      Jayesh Kumar Gupta created a nice d3-based visualization of your arbtt+      data, including daily pie charts and barcode graphs.+    </para>+    <para>+      You can find his tool on <ulink url="https://github.com/rejuvyesh/arbtt-graph">https://github.com/rejuvyesh/arbtt-graph</ulink>.+    </para>+  </sect2>+</sect1> <sect1 id="references">   <title>Program references</title>   <para>arbtt consists of a few command line tools, the most important one is@@ -747,9 +1262,9 @@             <term><option>--for-each</option> <replaceable>PERIOD</replaceable></term>             <listitem><para>This is not a report of its own, but causes the selected             report to be executed for each of the given PERIOD (which can be-            day, month or year) where there exist selected samples. All the reports-            will then be printed one after another or, in the case of CSV output,-            with an additional column.</para>+            minute, hour,day, month or year) where there exist selected+            samples. All the reports will then be printed one after another or,+            in the case of CSV output, with an additional column.</para>              <para>Note that if this option is enabled, samples that are filtered out             are completely ignored (to avoid empty reports for periods with@@ -913,7 +1428,7 @@         <term><option>-V</option></term>         <term><option>--version</option></term>         <listitem><simpara>shows the version number, and exists.</simpara></listitem>-	</varlistentry>+    </varlistentry>         <varlistentry>         <term><option>-f</option> <replaceable>FILE</replaceable></term>         <term><option>--logfile</option> <replaceable>FILE</replaceable></term>@@ -923,7 +1438,7 @@         <term><option>-t</option> <replaceable>FORMAT</replaceable></term>         <term><option>--format</option> <replaceable>FORMAT</replaceable></term>         <listitem><simpara>dumping format to use, where <replaceable>FORMAT</replaceable> is one of <literal>human</literal> (the default), <literal>show</literal> or <literal>JSON</literal>. Case in-sensitive.</simpara></listitem>-	</varlistentry>+    </varlistentry>         <varlistentry>         <term><option>-l</option> <replaceable>NUMBER</replaceable></term>         <term><option>--last</option> <replaceable>NUMBER</replaceable></term>@@ -1045,7 +1560,7 @@         <term><option>-V</option></term>         <term><option>--version</option></term>         <listitem><simpara>shows the version number, and exists.</simpara></listitem>-	</varlistentry>+    </varlistentry>         <varlistentry>         <term><option>-f</option> <replaceable>FILE</replaceable></term>         <term><option>--logfile</option> <replaceable>FILE</replaceable></term>@@ -1111,7 +1626,7 @@         <term><option>-V</option></term>         <term><option>--version</option></term>         <listitem><simpara>shows the version number, and exists.</simpara></listitem>-	</varlistentry>+    </varlistentry>         <varlistentry>         <term><option>-i</option></term>         <term><option>--infile</option></term>@@ -1194,14 +1709,32 @@   The version history with changes relevant for the user is documented here.   </para> +  <sect2 id="release-notes-0.9">+    <title>Version 0.9</title>+    <itemizedlist>+      <listitem>+    <para>+          The <option>--for-each</option> option of+          <command>arbtt-stats</command> now supports grouping results by+          minute or hour.+    </para>+      </listitem>+      <listitem>+    <para>+      Gwern Branwen contributed the <xref linkend="effective-use"/>.+    </para>+      </listitem>+    </itemizedlist>+  </sect2>+   <sect2 id="release-notes-0.8.1">     <title>Version 0.8.1</title>     <itemizedlist>       <listitem>-	<para>-	  The syntax now allows for time differences larger than 99:99.+    <para>+      The syntax now allows for time differences larger than 99:99.           (<ulink url="https://bitbucket.org/nomeata/arbtt/issue/14/improve-time-categorization-directives">issue #14</ulink>)-	</para>+    </para>       </listitem>     </itemizedlist>   </sect2>@@ -1210,12 +1743,12 @@     <title>Version 0.8</title>     <itemizedlist>       <listitem>-	<para>-	  <command>arbtt-dump</command> can now show the data in other formats-	  as well, as suggested by Waldir Pimenta (option-	  <option>--format</option>). This includes a human-readale output and-	  JSON.-	</para>+    <para>+      <command>arbtt-dump</command> can now show the data in other formats+      as well, as suggested by Waldir Pimenta (option+      <option>--format</option>). This includes a human-readale output and+      JSON.+    </para>       </listitem>       <listitem>         <para>New option <option>--last</option> of <command>arbtt-dump</command>.</para>@@ -1272,9 +1805,9 @@     <title>Version 0.6.4.1</title>     <itemizedlist>       <listitem>-      	<para>-      	Added missing module to the packages.-      	</para>+        <para>+        Added missing module to the packages.+        </para>       </listitem>     </itemizedlist>   </sect2>@@ -1357,7 +1890,7 @@     <title>Version 0.5 (The ZuriHac-Release)</title>     <itemizedlist>       <listitem>-	<para>New command <command>arbtt-import</command>, which imports the output from <command>arbtt-dump</command>.+    <para>New command <command>arbtt-import</command>, which imports the output from <command>arbtt-dump</command>.         </para>       </listitem>       <listitem>@@ -1376,14 +1909,14 @@       </listitem>       <listitem>         <para>-	  Unicode is handled correctly in regular expressions.-	</para>+      Unicode is handled correctly in regular expressions.+    </para>       </listitem>       <listitem>         <para>-	  Improved date-handling functions for <filename>categorize.cfg</filename>.+      Improved date-handling functions for <filename>categorize.cfg</filename>.           (<xref linkend="sergey"/>)-	</para>+    </para>       </listitem>     </itemizedlist>   </sect2>@@ -1393,7 +1926,7 @@     <itemizedlist>       <listitem>         <para>Bugfix: Added missing modules to the cabal file.-	</para>+    </para>       </listitem>     </itemizedlist>   </sect2>@@ -1403,7 +1936,7 @@     <itemizedlist>       <listitem>         <para>Implement a custom compression method greatly reduce the file size of the log file. Run <command>arbtt-capture</command> to compress the previous samples as well.-	</para>+    </para>       </listitem>     </itemizedlist>   </sect2>@@ -1438,11 +1971,11 @@         <command>arbtt-dump</command>.</para>       </listitem>       <listitem>-	<para>New command <command>arbtt-recover</command> to rescue data from-	a proken data log file.</para>+    <para>New command <command>arbtt-recover</command> to rescue data from+    a proken data log file.</para>       </listitem>       <listitem>-	<para>Actually include this documentation in the released tarball.</para>+    <para>Actually include this documentation in the released tarball.</para>       </listitem>     </itemizedlist>   </sect2>
src/Data/MyText.hs view
@@ -1,6 +1,7 @@ module Data.MyText where  import qualified Data.ByteString.UTF8 as BSU+import qualified Data.ByteString.Lazy.UTF8 as BLU import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString as BS import Data.Binary.Get@@ -27,17 +28,22 @@ -- Binary instance compatible with Binary String instance Binary Text where     put = put . unpack+    get = pack <$> get     -- The following code exploits that the Binary Char instance uses UTF8 as well     -- The downside is that it quietly suceeds for broken input+    -- Unfortunately, with binary-0.7, it is no longer possible to implement+    -- this nice and lazily, so go via String :-(+    {-     get = do         n <- get :: Get Int-        r <- remaining-        bs <- lookAhead (getByteString (min (fromIntegral r) (4*n))) -- safe approximation-        let utf8bs = BSU.take n bs-        unless (BSU.length utf8bs == n) $+        bs <- lookAhead $ getRemainingLazyByteString+        let utf8bs = BLU.take (fromIntegral n) bs+        unless (BLU.length utf8bs == n) $             fail $ "Coult not parse the expected " ++ show n ++ " utf8 characters."-        skip (BS.length utf8bs)-        return $ Text utf8bs+        let sbs = LBS.toStrict utf8bs+        skip $ BS.length sbs+        return $ Text sbs+    -}  {- Possible speedup with a version of binary that provides access to the    internals, as the Char instance is actually UTF8, but the length bit is
src/Stats.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE RecordWildCards, NamedFieldPuns, TypeOperators, TupleSections #-}+{-# LANGUAGE RecordWildCards, NamedFieldPuns, TypeOperators, TupleSections, GADTSyntax, ExistentialQuantification #-} module Stats (     Report(..),     ReportOptions(..),@@ -57,7 +57,7 @@ data ActivityFilter = ExcludeActivity ActivityMatcher | OnlyActivity ActivityMatcher         deriving (Show, Eq) -data Repeater = ByDay | ByMonth | ByYear+data Repeater = ByMinute | ByHour | ByDay | ByMonth | ByYear         deriving (Show, Eq)  -- Supported report output formats: text, comma-separated values and@@ -174,19 +174,40 @@                 let go' m act = M.insertWith' (+) act (fromInteger (tlRate tl)/1000) m                 in foldl' go' m (snd (tlData tl))) id -processRepeater :: Repeater -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults-processRepeater ByDay rep =-    filterElems (\(b :!: _) -> b) $-    pure (RepeatedReportResults "Day" . map (\(d,rr) -> (showGregorian d, rr)) . M.toList) <*>-    multiplex (utctDay . tlTime . Strict.snd) rep-processRepeater ByMonth rep =-    filterElems (\(b :!: _) -> b) $-    pure (RepeatedReportResults "Month" . map (\((y,m),rr) -> (show y ++ "-" ++ show m, rr)) . M.toList) <*>-    multiplex ((\(y,m,_) -> (y, m)). toGregorian . utctDay . tlTime . Strict.snd) rep-processRepeater ByYear rep =-    filterElems (\(b :!: _) -> b) $-    pure (RepeatedReportResults "Year" . map (\(y,rr) -> (show y, rr)) . M.toList) <*>-    multiplex ((\(y,_,_) -> y). toGregorian . utctDay . tlTime . Strict.snd) rep+processRepeater :: TimeZone -> Repeater -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults+processRepeater tz r rep = case repeaterImpl r of+    RepeaterImpl catR showR ->+        filterElems (\(b :!: _) -> b) $+        pure (RepeatedReportResults (repeaterTitle r) . map (first showR) . M.toList) <*>+        multiplex (catR . utcToLocalTime tz . tlTime . Strict.snd) rep++data RepeaterImpl where+  RepeaterImpl :: Ord r => (LocalTime -> r) -> (r -> String) -> RepeaterImpl++repeaterTitle :: Repeater -> String+repeaterTitle ByMinute = "Minute"+repeaterTitle ByHour   = "Hour"+repeaterTitle ByDay    = "Day"+repeaterTitle ByMonth  = "Month"+repeaterTitle ByYear   = "Year"++repeaterImpl :: Repeater -> RepeaterImpl+repeaterImpl ByMinute = RepeaterImpl+    -- a somewhat lazy implementations, using strings...+    (formatTime defaultTimeLocale "%F %H:%M")+    id+repeaterImpl ByHour = RepeaterImpl+    (formatTime defaultTimeLocale "%F %H:00")+    id+repeaterImpl ByDay = RepeaterImpl+    localDay+    showGregorian+repeaterImpl ByMonth = RepeaterImpl+    ((\(y,m,_) -> (y, m)) . toGregorian . localDay)+    (\(y,m) -> show y ++ "-" ++ show m)+repeaterImpl ByYear = RepeaterImpl+    ((\(y,_,_) -> y) . toGregorian . localDay)+    show  processReport :: ReportOptions -> Report -> LeftFold (Bool :!: TimeLogEntry (Ctx, ActivityData)) ReportResults processReport opts GeneralInfos =
src/TimeLog.hs view
@@ -105,13 +105,13 @@         return $ parseTimeLog content  parseTimeLog :: (NFData a, ListOfStringable a) => BS.ByteString -> TimeLog a-parseTimeLog input = +parseTimeLog input =     if startString == magic        then go Nothing rest off        else error $             "Timelog starts with unknown marker " ++             show (map (chr.fromIntegral) (BS.unpack startString))-  where                +  where     (startString, rest, off) = runGetState (getLazyByteString (BS.length magic)) input 0     go prev input off =         let (v, rest, off') = runGetState (ls_get strs) input off
src/stats-main.hs view
@@ -18,6 +18,7 @@ import System.ProgressBar import TermSize import qualified Data.MyText as T+import Data.Time.LocalTime  import TimeLog import Categorize@@ -48,7 +49,7 @@     , optLogFile = dir </> "capture.log"     , optCategorizeFile = dir </> "categorize.cfg"     }-    + versionStr, header :: String versionStr = "arbtt-stats " ++ showVersion version header = "Usage: arbtt-stats [OPTIONS...]"@@ -91,7 +92,7 @@               "only consider samples matching the condition"      , Option "m"       ["min-percentage"]               (ReqArg (\arg opt -> let ro = (optReportOptions opt) { roMinPercentage = read arg}-                                   in  return opt { optReportOptions = ro }) "COND")+                                   in  return opt { optReportOptions = ro }) "PERC")               "do not show tags with a percentage lower than PERC% (default: 1)"      , Option ""        ["output-exclude"]               (ReqArg (\arg opt -> let filters = ExcludeActivity (parseActivityMatcher arg) : roActivityFilter (optReportOptions opt)@@ -141,10 +142,12 @@ readRepeater :: String -> Repeater readRepeater arg =     case map toLower arg of-        "day"   -> ByDay-        "month" -> ByMonth-        "year"  -> ByYear-        _       -> error ("Unsupported parameter to --for-each: '" ++ arg ++ "'")+        "minute" -> ByMinute+        "hour"   -> ByHour+        "day"    -> ByDay+        "month"  -> ByMonth+        "year"   -> ByYear+        _        -> error ("Unsupported parameter to --for-each: '" ++ arg ++ "'")  readReportFormat :: String -> ReportFormat readReportFormat arg =@@ -163,6 +166,7 @@           (_,_,errs) -> do                 hPutStr stderr (concat errs ++ usageInfo header options)                 exitFailure+  tz <- getCurrentTimeZone    dir <- getAppUserDataDirectory "arbtt"   flags <- foldl (>>=) (return (defaultOptions dir)) actions@@ -175,12 +179,12 @@   categorizer <- readCategorizer (optCategorizeFile flags)    timelog <- BS.readFile (optLogFile flags)-  size <- fileSize <$> getFileStatus (optLogFile flags)   isTerm <- hIsTerminalDevice stderr    trackedTimelog <- case isTerm of     True -> do       hSetBuffering stderr NoBuffering+      size <- fileSize <$> getFileStatus (optLogFile flags)       trackProgressWithChunkSize (fromIntegral size `div` 100) (\_ b -> do         (_height, width) <- getTermSize         hPutChar stderr '\r'@@ -195,28 +199,26 @@    let captures = parseTimeLog trackedTimelog   let allTags = categorizer captures+   when (null allTags) $ do      putStrLn "Nothing recorded yet"      exitFailure-      +   let filters = (if optAlsoInactive flags then id else (defaultFilter:)) $ optFilters flags    let rep = case optReports flags of                 [] -> TotalTime                 [x] -> x                 _ -> error "Please specify exactly one report to generate"-  let repeater = foldr (.) id $ map processRepeater (optRepeater flags)+  let repeater = foldr (.) id $ map (processRepeater tz) (optRepeater flags) -  -- These are defined here, but of course only evaluated when any report-  -- refers to them. Some are needed by more than one report, which is then-  -- advantageous.   let opts = optReportOptions flags   let fold = filterPredicate filters `adjoin` repeater (processReport opts rep)   let result = runLeftFold fold allTags    -- Force the results a bit, to ensure the progress bar to be shown before the title   result `seq` return ()-  +   renderReport opts result  {-