docbook_status 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,7 +1,9 @@
1
1
  source "http://rubygems.org"
2
2
  gem "libxml-ruby", "~> 2.2.2", :require => 'xml'
3
3
  gem "directory_watcher"
4
+ gem "json"
5
+ gem "term-ansicolor"
4
6
 
5
7
  group :development do
6
8
  gem "bones"
7
- end
9
+ end
data/Gemfile.lock CHANGED
@@ -1,16 +1,24 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ bones (3.7.1)
5
+ little-plugger (>= 1.1.2)
6
+ loquacious (>= 1.8.1)
7
+ rake (>= 0.8.7)
4
8
  directory_watcher (1.4.1)
5
- hoe (2.12.3)
6
- rake (~> 0.8)
9
+ json (1.6.1)
7
10
  libxml-ruby (2.2.2)
11
+ little-plugger (1.1.2)
12
+ loquacious (1.8.1)
8
13
  rake (0.9.2)
14
+ term-ansicolor (1.0.6)
9
15
 
10
16
  PLATFORMS
11
17
  ruby
12
18
 
13
19
  DEPENDENCIES
20
+ bones
14
21
  directory_watcher
15
- hoe
22
+ json
16
23
  libxml-ruby (~> 2.2.2)
24
+ term-ansicolor
data/History.txt CHANGED
@@ -1,3 +1,22 @@
1
+ == 0.3.0 / 2011-09-28
2
+
3
+ * Writing goals (total and daily) can be defined and tracked
4
+ * Added YAML and JSON output formats, available with option --outputformat
5
+ * Minor fixes
6
+ ** Removed daemon state persistence file dw_state.yml
7
+ ** Colorized screen output
8
+ ** Reorganized library
9
+
10
+ == 0.2.1 / 2011-09-18
11
+
12
+ * Minor fixes for Gem packaging
13
+
14
+ == 0.2.0 / 2011-09-18
15
+
16
+ * Added remarks listing.
17
+ * Minor fixes
18
+ ** removed _formalpara_ from the list of countable text elements because they contain _para_ elements
19
+
1
20
  == 0.1.1 / 2011-09-07
2
21
 
3
22
  * Documentation corrections
@@ -5,4 +24,3 @@
5
24
  == 0.1.0 / 2011-09-07
6
25
 
7
26
  * Initial version
8
-
data/README.rdoc ADDED
@@ -0,0 +1,142 @@
1
+ = docbook_status
2
+
3
+ A utility for DocBook authors/publishers showing the document structure (sections) and word count of a DocBook project. It is intended to provide an overview of a DocBook project's structure and volume while you are writing or editing it.
4
+
5
+ == Features
6
+
7
+ * lists the section hierarchy (set, book, ... section, simplesect) of a DocBook file
8
+ * calculates a word count for each section (words in paras, simparas and formalparas)
9
+ * works with included sections (XInclude)
10
+ * finds and lists remarks (remarks are not included in the word count)
11
+ * output in YAML and JSON format (optional)
12
+ * tracks writing progress
13
+
14
+ == Synopsis
15
+
16
+ docbook_status is mainly a comandline application, bin/docbook_status, that helps with writing and editing DocBook 5 documents. The application provides information about the content of a DocBook project. That project can consist of a single file or of several files that are included in the master file via XInclude.
17
+
18
+ To run docbook_status manually:
19
+
20
+ docbook_status myproject.xml
21
+
22
+ This will show the section hierarchy and the word count of the individual sections of myproject.xml.
23
+
24
+ If the file contains editing remarks, you could list them, too:
25
+
26
+ docbook_status --remarks myproject.xml
27
+
28
+ Here docbook_status looks for all _remark_ elements. The application uses the same convention as many source code tools: if the content of the remark element starts with a all-uppercase word, like FIXME or TODO, this will be listed as the remark type, otherwise just REMARK will be used.
29
+
30
+ If there are many remarks and you just want to concentrate on the most important ones, let's say the FIXMEs, you could restrict the listing with:
31
+
32
+ docbook_status --remarks=FIXME myproject.xml
33
+
34
+ If you need to preprocess the XML before feeding it to docbook_status, there is an option for that:
35
+
36
+ docbook_status --pre "asciddoc -bdocbook50 myproject.txt" myproject.xml
37
+
38
+ --pre takes shell commands as arguments and executes them before starting the analysis on the XML file.
39
+
40
+ Finally, if you are tired to run docbook_status manually each time you changed your file, you could run the application in demon mode, continually:
41
+
42
+ docbook_status --demon --glob "*.xml" --dir "." myproject.xml
43
+
44
+ In demon-mode the application checks the files matched by the _glob_ pattern in the directory specified by _dir_ for changes, and redisplays the document analysis whenever a change occures. The demon can be terminated by simply pressing RETURN.
45
+
46
+ == Tracking writing progress
47
+
48
+ As an experiment docbook_status provides features to define and track writing goals/schedules. Currently there are the following options:
49
+
50
+ * an end date, or deadline, with option --end=[YYYY-MM-DD]
51
+ * a total word count goal, with option --total=[number]
52
+ * a daily word count goal, with option --daily=[number]
53
+
54
+ These features are currently not related, you can use any combination of them. When an end date is defined the application will simply remind you on every run of how many days are left.
55
+
56
+ If one of this options is used, a file called 'dbs_work.yml' is created in the working directory. This file is used to store the goals and the tracking information. If you want to get rid of all tracking, simply delete this file. To disable a specific kind of tracking, just call the options mentioned above with no arguments. That would delete the defined value. An example:
57
+
58
+ docbook_status --end=2099-01-01 --total=1000000 endofworld.xml
59
+
60
+ This call would define the goals: scheduled delivery date of 2099-01-01, and a total document size of one million words. To disable the time tracking call it again with no argument:
61
+
62
+ docbook_status --end endofworld.xml
63
+
64
+ This would delete the defined delivery date but not the total. Once defined the goal options must not be repeated, since they are stored in the 'dbs_work.yml' file.
65
+
66
+
67
+ == Integration, Customization
68
+
69
+ In the unlikely case that you don't like the standard screen output and would prefer to replace it, there are other output formats available, JSON and YAML, which make that possible. Normally all output is formatted for output on a terminal screen, but the option _--outputformat_ alllows to specify a different output format, that is printed to STDOUT. Using that you could create your own frontend or integrate the application with other tools (like editors).
70
+
71
+ Use
72
+
73
+ docbook_status --outputformat=yaml
74
+ or
75
+ docbook_status --outputformat=json
76
+
77
+ to get YAML or JSON structures back. The structure returned is equivalent to the normal terminal output:
78
+
79
+ * file - path to XML file
80
+ * modified - the modification time of the XML file
81
+
82
+ * sections - an array of section entries, each with
83
+ * title - section name
84
+ * words - word count
85
+ * level - section level in the document hierarchy
86
+ * tag - the section's DocBook tag
87
+
88
+ * remarks - (optional) an array of remark entries, each with
89
+ * keyword - uppercase keyword, e.g. REMARK, FIXME
90
+ * text - remark text
91
+ * file - file name, location of the remark
92
+ * line - line number, location of the remark
93
+
94
+ * goals - (optional) information about the defined writing goals
95
+ * start - start date of the tracking
96
+ * end - scheduled end date or nil
97
+ * goal_total - planned total word count or 0
98
+ * goal_daily - planned daily word count or 0
99
+
100
+ * today - (optional) document size information
101
+ * min - minimum no. of words
102
+ * max - maximum no. of words
103
+ * start - first word count
104
+ * end - last (current) word count
105
+ * ctr - number of runs
106
+
107
+ == Download
108
+
109
+ https://rubygems.org/gems/docbook_status
110
+
111
+ == Requirements
112
+
113
+ * libxml2
114
+
115
+ == Install
116
+
117
+ * gem install docbook_status
118
+
119
+ == License
120
+
121
+ The MIT License
122
+
123
+ Copyright (c) 2011 Rainer Volz
124
+
125
+ Permission is hereby granted, free of charge, to any person obtaining
126
+ a copy of this software and associated documentation files (the
127
+ 'Software'), to deal in the Software without restriction, including
128
+ without limitation the rights to use, copy, modify, merge, publish,
129
+ distribute, sublicense, and/or sell copies of the Software, and to
130
+ permit persons to whom the Software is furnished to do so, subject to
131
+ the following conditions:
132
+
133
+ The above copyright notice and this permission notice shall be
134
+ included in all copies or substantial portions of the Software.
135
+
136
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
137
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
138
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
139
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
140
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
141
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
142
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
-
1
+ #-*- encoding: utf-8 ; mode:ruby -*-
2
2
  begin
3
3
  require 'bones'
4
4
  rescue LoadError
@@ -13,5 +13,8 @@ Bones {
13
13
  authors 'Rainer Volz'
14
14
  email 'dev@textmulch.de'
15
15
  url 'http://projekte.textmulch.de/docbook_status/'
16
+ depend_on 'directory_watcher'
17
+ depend_on 'libxml-ruby'
18
+ depend_on 'json'
19
+ depend_on 'term-ansicolor'
16
20
  }
17
-
data/bin/docbook_status CHANGED
@@ -1,68 +1,233 @@
1
1
  #!/usr/bin/env ruby
2
2
  # -*-encoding:utf-8 ; mode:ruby-*-
3
3
  ##
4
- # docbook_status is the commandline application for the library.
5
- # Its main purpose is to display the structure and word counts for DocBook 5 documents.
6
- # docbook_status can be used in single-run mode or demon-mode. In demon_mode it stays active
7
- # and looks for changes in the filesystem.
4
+ # docbook_status is the commandline application for the library. Its
5
+ # main purpose is to display the structure and word counts for DocBook
6
+ # 5 documents. docbook_status can be used in single-run mode or
7
+ # demon-mode. In demon-mode it stays active and looks for changes in
8
+ # the filesystem.
8
9
  #
9
10
 
10
11
  require 'rubygems'
11
12
  require "bundler/setup"
12
13
  require 'optparse'
13
14
  require 'directory_watcher'
15
+ require 'yaml'
16
+ require 'json'
17
+ require 'term/ansicolor'
18
+
19
+ class String
20
+ include Term::ANSIColor
21
+ end
14
22
 
15
23
  require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib docbook_status]))
16
24
 
25
+ NOVAL = -1
26
+
17
27
  @demon = false
18
28
  @glob = '*.xml'
19
29
  @dir = '.'
20
30
  @pre = []
31
+ @remarks = false
32
+ @remarks_filter = []
33
+ @output_format = :screen
34
+ @total_words = NOVAL
35
+ @daily_words = NOVAL
36
+ @end_date = NOVAL
37
+
21
38
  banner = <<EOB
22
39
  docbook_status, Version #{DocbookStatus.version}
23
40
 
24
41
  Display DocBook 5 document structure and word counts.
25
42
 
26
- Usage: docbook_status [--demon] [--glob <PATTERN>] [--dir <DIR>] [--pre COMMAND] <DOCBOOK-FILE>
43
+ Usage: docbook_status [options] <DOCBOOK-FILE>
27
44
  EOB
28
45
 
29
46
  opts = OptionParser.new
30
47
  opts.banner = banner
31
48
  opts.on('--demon', 'Keep running, act when files change') {|val| @demon = true}
32
49
  opts.on('--glob PATTERN', String, 'File mask for demon mode, default = "*.xml"') {|val| @glob = val}
33
- opts.on('--dir DIR', String, 'Source directory for demon mode, default = "." ') {|val| @dir = val}
50
+ opts.on('--dir DIR', String, 'Source directory for demon mode, default = "." ') {|val| @dir = File.expand_path(val)}
34
51
  opts.on('--pre COMMAND', String, 'A shell command that should be executed before') {|val| @pre << val}
52
+ opts.on('--remarks[=FILTER]', String, 'Show the remarks, comments. Use a keyword FILTER to restrict the listing') {|val|
53
+ @remarks = true
54
+ unless (val.nil? || val.empty?)
55
+ @remarks_filter << val
56
+ end
57
+ }
58
+ opts.on('--end[=DATE]', String, 'Goal: planned delivery date, YYYY-MM-DD') {|val|
59
+ if val.nil?
60
+ @end_date = nil
61
+ else
62
+ @end_date = Date.parse(val)
63
+ end
64
+ }
65
+ opts.on('--total[=WORDS]',Integer, 'Goal: total number of words') {|val|
66
+ if val.nil?
67
+ @total_words = 0
68
+ else
69
+ @total_words = val.abs
70
+ end}
71
+ opts.on('--daily[=WORDS]',Integer, 'Goal: daily number of words') {|val|
72
+ if val.nil?
73
+ @daily_words = 0
74
+ else
75
+ @daily_words = val.abs
76
+ end}
77
+ opts.on('--outputformat=yaml|json',['json','yaml'],'Return the result in YAML or JSON format instead of printing it') {|format|
78
+ case
79
+ when format == 'yaml'
80
+ @output_format = :yaml
81
+ when format == 'json'
82
+ @output_format = :json
83
+ else
84
+ STDERR.puts "Unknown output format #{format}. Using screen output."
85
+ @output = :screen
86
+ end
87
+ }
35
88
  rest = opts.parse(ARGV)
36
89
 
37
- def run(file)
38
- @pre.each {|cmd| system(cmd)}
39
- doc = XML::Document.file(file)
40
- dbs = DocbookStatus.new
41
- if !dbs.is_docbook?(doc)
42
- puts "Error: #{file} is apparently not DocBook 5."
43
- return
90
+ # See, if there is need to track the writing progress.
91
+ def history_on?()
92
+ (DocbookStatus::History.exists? || @end_date != NOVAL || @total_words != NOVAL || @daily_words != NOVAL)
93
+ end
94
+
95
+
96
+ # Color the actuals according to the schema
97
+ #
98
+ # * bold red if <= 30%
99
+ # * red if > 30% and <= 70%
100
+ # * magenta if >70 and < 100%
101
+ # * green if >= 100%
102
+ #
103
+ def meter(actual,goal)
104
+ ad = actual.to_f / goal.to_f
105
+ percs = ""
106
+ case
107
+ when ad <= 0.3
108
+ fct = lambda {|s| s.red.bold}
109
+ when ad > 0.3 && ad <= 0.7
110
+ fct = lambda {|s| s.red}
111
+ when ad > 0.7 && ad < 1.0
112
+ fct = lambda {|s| s.magenta}
113
+ else
114
+ fct = lambda {|s| s.green}
115
+ end
116
+ [actual.to_s,"%3.0f\%" % (ad*100)].map {|s| fct.call(s)}
117
+ end
118
+
119
+ # Format the output for the screen and print it
120
+ #
121
+ def output_terminal(doc_info)
122
+
123
+ # Header
124
+ puts
125
+ puts "File: #{doc_info[:file]}"
126
+ puts "Modified: #{doc_info[:modified]}"
127
+
128
+ # Goal section
129
+ unless doc_info[:goals].nil?
130
+ gdend = doc_info[:goals][:end]
131
+ gwtotal = doc_info[:goals][:goal_total]
132
+ gwdaily = doc_info[:goals][:goal_daily]
133
+ unless gwtotal == 0
134
+ tactual = doc_info[:sections][0][:words]
135
+ (tactuals,tactperc) = meter(tactual,gwtotal)
136
+ end
137
+ unless gwdaily == 0
138
+ # Coloring the actuals
139
+ dactual = doc_info[:today][:end] - doc_info[:today][:start]
140
+ (dactuals,dactperc) = meter(dactual,gwdaily)
141
+ end
142
+ unless gdend.nil?
143
+ dstart = doc_info[:goals][:start]
144
+ ddur = (gdend - dstart).round
145
+ ddiff = (gdend - Date.today).round
146
+ dsleft = case
147
+ when ddiff == 0 then 'today'
148
+ when ddiff > 0 then "#{ddiff} day(s) left"
149
+ else "#{ddiff} day(s) behind schedule"
150
+ end
151
+ end
152
+ unless (gdend.nil? && gwtotal == 0 && gwdaily == 0)
153
+ puts "Goals".bold
154
+ puts "Delivery: #{dsleft}, #{gdend}" unless gdend.nil?
155
+ puts "Words, total: #{tactperc} #{tactuals}/#{gwtotal}" unless gwtotal == 0
156
+ puts "Words, daily: #{dactperc} #{dactuals}/#{gwdaily}" unless gwdaily == 0
157
+ end
44
158
  end
45
- doc.xinclude if dbs.has_xinclude?(doc)
46
- sections = dbs.analyze_document(doc)
159
+ # Structure
47
160
  puts
48
- puts "File: #{file}"
49
- puts "Modified: #{File.ctime(file)}"
50
- puts "Document structure:"
51
- puts "%-50.50s %-16s %5s" % ['Title','Tag','Words']
52
- puts "-"*73
53
- sections.each do |s|
54
- puts "%-50.50s %-16s %5d" % [(' ' * s[2])+s[0], s[3], s[1]]
161
+ puts "Document structure".bold
162
+ puts "%-57.57s %-16s %5s" % ['Title','Tag','Words']
163
+ puts "-"*80
164
+ doc_info[:sections].each do |s|
165
+ puts "%-57.57s %-16s %5d" % [(' ' * s[:level])+s[:title], s[:tag], s[:words]]
166
+ end
167
+ #Remarks
168
+ if doc_info[:remarks]
169
+ puts
170
+ puts "Remarks".bold
171
+ puts "%-10s %10s %5s %-52s" % %w[Type File Line Content]
172
+ puts "-"*80
173
+ doc_info[:remarks].each do |r|
174
+ puts "%-10.10s %10s %5d %-52.52s" % ["#{r[:keyword]}",r[:file],r[:line],r[:text]]
175
+ end
55
176
  end
56
177
  end
57
178
 
179
+ # Processes the DocBook document once. Runs first all defined
180
+ # preprocessing (--pre) commands. Checks then for a DocBook namespace
181
+ # declaration, which would imply DocBook 5. If the namespaces include
182
+ # the XInclude-NS, XInclude-processing is started. At last the
183
+ # resulting document is analyzed.
184
+ #
185
+ # If history processing is on the writing progress is recorded.
186
+ #
187
+ def run(file)
188
+ # OPTIMIZE Detailed output for --pre commands with popen4?
189
+ @pre.each { |cmd|
190
+ ret = system(cmd)
191
+ unless ret
192
+ STDERR.puts "Error: This preprocessing command signalled failure (#{$?}), please check --> #{cmd}"
193
+ return
194
+ end
195
+ }
196
+ dbs = DocbookStatus::Status.new(file)
197
+ doc_info = dbs.analyze_file
198
+ doc_info[:remarks] = dbs.find_remarks(@remarks_filter) if @remarks
199
+ if history_on?()
200
+ dbs_h = DocbookStatus::History.new(file)
201
+ dbs_h.planned_end(@end_date) if @end_date != NOVAL
202
+ dbs_h.total_words(@total_words) if @total_words != NOVAL
203
+ dbs_h.daily_words(@daily_words) if @daily_words != NOVAL
204
+ dbs_h.add(DateTime.now,doc_info[:sections][0][:words])
205
+ dbs_h.save
206
+ doc_info[:goals] = dbs_h.goals
207
+ doc_info[:today] = dbs_h.today
208
+ end
209
+ case
210
+ when @output_format == :yaml
211
+ YAML.dump(doc_info,STDOUT)
212
+ when @output_format == :json
213
+ STDOUT.puts doc_info.to_json
214
+ else
215
+ output_terminal(doc_info)
216
+ end
217
+ end
218
+
219
+
220
+ # Demon routine for continuous processing of a DocBook document.
221
+ # This routine calls _run_ whenever the filesystem signals changes. To
222
+ # reduce load it waits for a defined time (interval * stable) before
223
+ # starting the actual processing.
224
+ #
58
225
  def demon(file)
59
226
  dw = DirectoryWatcher.new @dir, :glob => @glob, :pre_load => true
60
227
  dw.interval = 5.0
61
228
  dw.stable = 2
62
- dw.persist = "dw_state.yml"
63
229
  dw.add_observer {|*args|
64
230
  args.each {|event|
65
- #puts event
66
231
  if event.type == :stable
67
232
  run(file)
68
233
  end
@@ -79,7 +244,13 @@ if rest.length < 1
79
244
  exit 1
80
245
  end
81
246
 
82
- puts("docbook_status, Version #{DocbookStatus.version}")
247
+ unless File.exists?(rest[0])
248
+ STDERR.puts "Error: File #{rest[0]} not found."
249
+ exit 1
250
+ end
251
+
252
+ # The main routine
253
+ puts("docbook_status, Version #{DocbookStatus.version}") if @output_format == :screen
83
254
  run(rest[0])
84
255
  if (@demon)
85
256
  demon(rest[0])