├── 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 | ![Progress and Speed Plots](https://github.com/smarr/WritingStats/raw/master/example-book.jpg "Progress and Speed Plots") 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 |

Overview

214 | '.$overview_images.' 215 | 216 | 217 | '; 218 | 219 | 220 | 221 | file_put_contents($folder.'/status.html', $full_html); 222 | 223 | shell_exec("cp $pdffile status/$pdffile"); 224 | echo $result."\n"; 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /status/docx2html.xsl: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 | 45 |

46 |
47 | 48 | 49 |

50 |
51 | 52 | 53 |

54 |
55 | 56 | 57 |

58 |
59 | 60 | 61 |

62 |
63 | 64 | 65 |

66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 | <ul> 74 |
  • 75 | </ul> 76 |
    77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | false() 88 | 89 | 90 | 91 | 92 | 93 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 129 | 323 | 324 |
    --------------------------------------------------------------------------------