├── example-book.jpg
├── status
├── post-receive
├── status.rss.php
├── png.php
├── graph.php
├── status.php
└── docx2html.xsl
├── .gitignore
├── Makefile
├── example-book.tex
├── status.conf
└── README.md
/example-book.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smarr/WritingStats/HEAD/example-book.jpg
--------------------------------------------------------------------------------
/status/post-receive:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # update directory
3 | cur=`hostname`
4 | soft="soft.vub.ac.be"
5 |
6 | if [ "$cur" = "$soft" ]
7 | then
8 | GIT_WORK_TREE=checkout git reset --hard
9 | cd checkout
10 | make pdf
11 | status/status.php
12 | fi
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # LaTeX specific
2 | *.aux
3 | *.idx
4 | *.log
5 | *.synctex.gz
6 | *.pdf
7 | *.toc
8 | *.idx.old
9 | *.ilg
10 | *.ind
11 | *.ind.old
12 |
13 | # Statistic Data
14 | status/status.data
15 | status/status.html
16 | status/status.items
17 | status/status.txt
18 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # This Makefile relies on LaTeX-Mk
2 | # See: http://latex-mk.sourceforge.net/
3 |
4 | NAME=example-book
5 | TEXSRCS=example-book.tex
6 | USE_PDFLATEX=true
7 |
8 | CLEAN_FILES+=$(wildcard *.synctex.gz)
9 |
10 | # We are going to try a couple of standard locations to find LaTeX-Mk:
11 | -include /opt/local/share/latex-mk/latex.gmk
12 | -include /usr/local/share/latex-mk/latex.gmk
13 | -include ~/.local/share/latex-mk/latex.gmk
14 |
--------------------------------------------------------------------------------
/example-book.tex:
--------------------------------------------------------------------------------
1 | % Very simple example tex file to demonstrate the setup of WritingStats
2 | \documentclass[12pt]{book}
3 |
4 | \makeindex
5 |
6 | \title{A Simple \LaTeX \ Book Example}
7 |
8 | \author{Jane Doe}
9 | \date{21st of December 2012}
10 |
11 | \begin{document}
12 |
13 | \maketitle
14 | \tableofcontents
15 |
16 | \chapter{Introduction}
17 |
18 | Here goes the introduction.
19 |
20 |
21 | \chapter{Main Part}
22 |
23 | Here perhaps the main content.
24 |
25 |
26 | \chapter{Conclusion}
27 |
28 | And finally, the conclusion.
29 |
30 |
31 | \end{document}
32 |
--------------------------------------------------------------------------------
/status/status.rss.php:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | Thesis Status
14 | http://www.stefan-marr.de/
15 | Current Metrics of the Thesis Text
16 | en-en
17 | '.date('r', filemtime($items)).'
18 | '.date('r', filemtime($items)).'
19 | http://stefan-marr.de/phd/status.rss.php
20 | 120
21 |
22 | '.file_get_contents($items).'
23 |
24 |
25 | ';
--------------------------------------------------------------------------------
/status.conf:
--------------------------------------------------------------------------------
1 | 1,3
11 | * 4. ratio < 1,78
12 | *
13 | * area / height = width
14 | * area / width = height
15 | *
16 | * width = ratio * height
17 | * height = width / ratio
18 | *
19 | * area / height = ratio * height
20 | * area = ratio * height * height
21 | *
22 | * height = sqrt(area / ratio)
23 | *
24 | * area / width = width / ratio
25 | * area = width * width / ratio
26 | *
27 | * width = sqrt(area * ratio)
28 | */
29 | function determine_rect($pages) {
30 | $ratio = ((1 + sqrt(5)) / 2) /*Goldener Schnitt*/ * sqrt(2) /* A4 ratio, because pages aren't square */;
31 | $height = (int)sqrt($pages / $ratio);
32 | $width = (int)sqrt($pages * $ratio);
33 | return array($width, $height);
34 | }
35 |
36 | function determine_adjusted_rect($pages) {
37 | list($width, $height) = determine_rect($pages);
38 |
39 | if ($pages > $width * $height) $width += 1;
40 | if ($pages > $width * $height) $height += 1;
41 | return array($width, $height);
42 | }
43 |
44 | list($width, $height) = determine_adjusted_rect($total_pages);
45 |
46 | if ($pages > $width * $height) {
47 | list($width, $height) = determine_adjusted_rect($pages);
48 | }
49 |
50 | mkdir("tmp");
51 | shell_exec("convert $pdffile tmp/$pdffile-%04d.png");
52 |
53 | $cmd = "montage -background \#ffffff -geometry +0+0 -tile {$width}x{$height} \"tmp/{$pdffile}-*.png\" status/overview-{$version_count}.png";
54 | shell_exec($cmd);
55 | shell_exec("rm -rf tmp");
56 | shell_exec("chmod a+r status/overview-*.png");
--------------------------------------------------------------------------------
/status/graph.php:
--------------------------------------------------------------------------------
1 | $value) {
52 | $xAxis[] = sprintf("%.2f", diff_in_days($start_timestamp, $date));
53 | $wordAxis[] = $value[0];
54 | $pagesAxis[] = $value[1];
55 |
56 | $curDay = (int)floor(diff_in_days($start_timestamp, $date));
57 | $commits[$curDay]++;
58 |
59 | $words[$curDay] = $value[0];
60 | $pages[$curDay] = $value[1];
61 | while ($last_day < $curDay) {
62 | if ($words[$last_day] === 0 && $last_day > 0) {
63 | $words[$last_day] = $words[$last_day - 1];
64 | $pages[$last_day] = $pages[$last_day - 1];
65 | }
66 | $last_day++;
67 | }
68 | }
69 |
70 | // generate diffs to previous day
71 | $wordDiff[0] = 0;
72 | $pageDiff[0] = 0;
73 | for ($i = 1; $i <= $last_day; $i++) {
74 | $wordDiff[$i] = $words[$i] - $words[$i - 1];
75 | $pageDiff[$i] = $pages[$i] - $pages[$i - 1];
76 | }
77 |
78 |
79 | $progressData = "
80 | [
81 | ['x', 'Pages', 'Words', 'Ideal'],";
82 |
83 | while (count($xAxis) > 0) {
84 | $x = array_shift($xAxis);
85 | $page = array_shift($pagesAxis);
86 | $word = array_shift($wordAxis);
87 |
88 | $progressData .= "[$x, $page, $word, $target_page_number_body/$duration*$x],\n";
89 | }
90 |
91 | // Add the ideal line
92 | $progressData .= "[$duration, null, null, $target_page_number_body],\n";
93 | $progressData .= " ]";
94 |
95 |
96 | $speedData = "
97 | [
98 | ['x', 'Pages', 'Words', 'Commits'],";
99 |
100 |
101 | for ($i = 0; $i <= $last_day; $i++) {
102 | $speedData .= "[$i, ${pageDiff[$i]}, ${wordDiff[$i]}, ${commits[$i]}],\n";
103 | }
104 | $speedData .= " ]";
105 |
106 | return array('progress' => $progressData,
107 | 'speed' => $speedData,
108 | 'totalWords' => $target_word_count,
109 | 'targetPages' => $target_page_number_body,
110 | 'duration' => $duration);
111 | }
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WritingStats: Tracking and Visualizing Writing Progress
2 | =======================================================
3 |
4 | WritingStats is a collection of scripts to automatically create statistics
5 | from LaTeX documents and visualize them with an overall progress plots and a
6 | plot of the daily speed of change.
7 |
8 | Currently, this project provides a setup for a standard LaTeX environment in
9 | combination with git version tracking.
10 |
11 | Features
12 | --------
13 |
14 | - determine page and word count, generate statistics based on them
15 | - generate RSS feed with the latest data
16 | - generate graphs to visualize progress
17 |
18 | Example
19 | -------
20 |
21 | Goal
22 | ----
23 |
24 | Words: 52500
25 | Pages: 150
26 | Words/Page: ca. 350
27 | Draft Deadline: 1 September 2012
28 |
29 | Current Status
30 | --------------
31 |
32 | Words: 29367
33 | Pages: 119
34 | Words to be written: 23133
35 | Pages to be written: ca. 49
36 | Progress: 55.94% finished
37 | Remaining Days: 49.21
38 | Remaining Workdays: 35.15
39 | Remaining Words/Workday: 658
40 | Pages/Day: 1.88
41 | Current Date: Friday, 13. July 2012 18:50:43
42 |
43 | 
44 |
45 | Setup
46 | -----
47 |
48 | WritingStats depends on the following software:
49 |
50 | - PHP, command-line interpreter (any recent version should do)
51 |
52 | - LaTeX-Mk, for the Makefile used in the git post-receive hook
53 | http://latex-mk.sourceforge.net/
54 |
55 | - TeXcount, for the word count, tested with version 3.0
56 | http://app.uio.no/ifi/texcount/
57 |
58 | - Google Chart Tools (no installation required, uses online copy)
59 | https://developers.google.com/chart/
60 |
61 | To use WritingStats, clone this repository as template for your new LaTeX
62 | project.
63 |
64 | git clone https://github.com/smarr/WritingStats.git
65 |
66 | Alternatively, check out the repository and copy the following files to your
67 | existing LaTeX project:
68 |
69 | cp Makefile $YOURFOLDER/
70 | cp status.conf $YOURFOLDER/
71 | cp -R status $YOURFOLDER/
72 |
73 | ### Customize Settings
74 |
75 | As you have seen in the example above, WritingStats uses a defined goal to
76 | estimate your progress. The goal and other settings are defined in
77 | `status.conf`. You will have to set the file name for your main tex file, and
78 | the name of the log-file LaTeX produces. The log file has typically the same
79 | name but uses the .log-extension.
80 |
81 | To achieve accurate results, you will need to estimate the number of words per
82 | page, and how many additional pages the final PDF will contain. Often, you
83 | will want to ignore for instance the table of content, lists of figures and
84 | tables, appendices, etc. Since, we currently do not have a way to detect them
85 | automatically, they will need to be configured instead.
86 |
87 | The settings for start date and deadline are necessary to calculate the length
88 | of the project, the number of days left, and to size the charts properly.
89 |
90 | Note that the RSS feed needs to be modified separately (cf. below).
91 |
92 | ### Server Repository
93 |
94 | After customizing `status.conf`, we suggest to set up a shared repository on a
95 | server to automatically generate the statistics on every push to the server.
96 | Note, this will require that the server is able to generate the PDF file using
97 | make and LateX-Mk. Furthermore, it will require a installation or local copy
98 | of TeXcount.
99 |
100 | To make sure that the post-receive script only executes the code on the
101 | desired server, we do check the hostname of the machine. Please adapt it for
102 | your deployment!
103 |
104 | To set up a shared git repository with a post-receive hook, execute the
105 | following steps:
106 |
107 | 1. Create bare repository on remote server:
108 | `git init --bare`
109 |
110 | 2. Push to the newly create remote repository.
111 |
112 | 3. Checkout a working copy on the remote server:
113 | `mkdir checkout`
114 | `GIT_WORK_TREE=checkout git reset --hard`
115 |
116 | 4. Create a symlink to the post-receive hook:
117 | ``ln -s `pwd`/checkout/status/post-receive hooks/post-receive``
118 |
119 | #### Branches
120 |
121 | In case you want to use a specific branch, or there is not the normal
122 | master branch, do the following right after creating the `checkout` directory:
123 |
124 | `GIT_WORK_TREE=checkout git checkout `
125 |
126 | ### Graphs and RSS Feed
127 |
128 | Both, the graphs and RSS feed are now automatically generated by the
129 | post-receive hook. Another side product is the latest PDF. To make them
130 | available on the web, I used symlinks reference `checkout/status/status.html`
131 | with statistics and graphs, `checkout/status/status.rss.php` with the
132 | statistics feed, and `checkout/example-book.pdf` as the latest PDF available
133 | from a public web folder.
134 |
135 | The RSS feed is published using the `checkout/status/status.rss.php` file.
136 | However, it needs to be adapted to reflect your own project properly. The RSS
137 | feed has a title, description, language, and so on. Furthermore, it contains
138 | the URLs it corresponds to. These settings are not imported from the
139 | `status.conf` file. Instead, they are directly hard code in `status.rss.php`.
140 | Note that the script accesses the `status/status.items` file, which is
141 | automatically generated as part of the post-receive hook.
142 |
143 | History
144 | -------
145 |
146 | The foundation of this project was already laid out when I wrote my master
147 | thesis. To track my own progress, I wrote the initial version of these
148 | scripts. However, back then, I was happily using Word and it's XML-based docx
149 | file format. To be able to count words automatically, I used the
150 | `status/docx2html.xsl` XSLT transformation, which also generated reasonably
151 | good HTML from the Word files. The scripts are currently not adapted to use
152 | it, and thus, Word is not supported anymore. However, pull requests to
153 | reintegrate it are of course welcome.
154 |
155 | License
156 | -------
157 |
158 | Copyright (c) 2012 Stefan Marr
159 |
160 | Permission is hereby granted, free of charge, to any person obtaining a copy
161 | of this software and associated documentation files (the "Software"), to deal
162 | in the Software without restriction, including without limitation the rights
163 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
164 | copies of the Software, and to permit persons to whom the Software is
165 | furnished to do so, subject to the following conditions:
166 |
167 | The above copyright notice and this permission notice shall be included in all
168 | copies or substantial portions of the Software.
169 |
170 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
171 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
172 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
173 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
174 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
175 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
176 | SOFTWARE.
177 |
178 |
--------------------------------------------------------------------------------
/status/status.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | \n";
97 |
98 | file_put_contents($folder.'/status.txt', $result);
99 |
100 |
101 | for ($i = 1; $i <= $version_count; $i++) {
102 | $overview_images .= "\n";
103 | }
104 | $overview_images .= "\n";
105 |
106 | $rss = "
107 |
108 | Current Status: $progress%, $timeLeftUseableDays days left
109 | ".$html_result."
110 | ".date('r').'
111 | http://soft.vub.ac.be/~smarr/phd/status.rss.php#'.uniqid().'
112 | ';
113 |
114 | $rss .= "\n".file_get_contents($folder.'/status.items');
115 |
116 | file_put_contents($folder.'/status.items', $rss);
117 |
118 |
119 | // Generate the HTML status page
120 | $full_html = 'Latest Stats on PhD Writing Progress
121 |
122 |
207 |
208 |
209 | '.$html_result.'
210 |
211 |
212 |
213 |