inversion 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data.tar.gz.sig +0 -0
  2. data/Manifest.txt +45 -0
  3. data/lib/inversion.rb +2 -2
  4. data/lib/inversion/template/begintag.rb +122 -0
  5. data/lib/inversion/template/defaulttag.rb +135 -0
  6. data/lib/inversion/template/rescuetag.rb +92 -0
  7. data/lib/inversion/template/timedeltatag.rb +93 -0
  8. data/manual/layouts/default.erb +87 -0
  9. data/manual/lib/api-filter.rb +96 -0
  10. data/manual/lib/editorial-filter.rb +59 -0
  11. data/manual/lib/examples-filter.rb +238 -0
  12. data/manual/lib/links-filter.rb +111 -0
  13. data/manual/resources/css/manual.css +764 -0
  14. data/manual/resources/fonts/GraublauWeb.otf +0 -0
  15. data/manual/resources/fonts/GraublauWebBold.otf +0 -0
  16. data/manual/resources/fonts/Inconsolata.otf +0 -0
  17. data/manual/resources/images/arrow_225_small.png +0 -0
  18. data/manual/resources/images/arrow_315_small.png +0 -0
  19. data/manual/resources/images/arrow_skip.png +0 -0
  20. data/manual/resources/images/cc-by.png +0 -0
  21. data/manual/resources/images/dialog-error.png +0 -0
  22. data/manual/resources/images/dialog-information.png +0 -0
  23. data/manual/resources/images/dialog-warning.png +0 -0
  24. data/manual/resources/images/emblem-important.png +0 -0
  25. data/manual/resources/images/help.png +0 -0
  26. data/manual/resources/images/information.png +0 -0
  27. data/manual/resources/images/magnifier.png +0 -0
  28. data/manual/resources/images/magnifier_left.png +0 -0
  29. data/manual/resources/images/page_white_code.png +0 -0
  30. data/manual/resources/images/page_white_copy.png +0 -0
  31. data/manual/resources/images/printer.png +0 -0
  32. data/manual/resources/images/question.png +0 -0
  33. data/manual/resources/images/scripts_code.png +0 -0
  34. data/manual/resources/images/wrap.png +0 -0
  35. data/manual/resources/images/wrapping.png +0 -0
  36. data/manual/resources/js/jquery-1.4.4.min.js +167 -0
  37. data/manual/resources/js/manual.js +30 -0
  38. data/manual/resources/js/sh.js +580 -0
  39. data/manual/resources/swf/clipboard.swf +0 -0
  40. data/manual/src/examples.page +154 -0
  41. data/manual/src/gettingstarted.page +64 -0
  42. data/manual/src/index.page +97 -0
  43. data/manual/src/tags.page +501 -0
  44. data/manual/src/templates.page +74 -0
  45. data/spec/inversion/template/begintag_spec.rb +217 -0
  46. data/spec/inversion/template/defaulttag_spec.rb +60 -0
  47. data/spec/inversion/template/rescuetag_spec.rb +109 -0
  48. data/spec/inversion/template/timedeltatag_spec.rb +215 -0
  49. metadata +72 -27
  50. metadata.gz.sig +0 -0
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'uri'
5
+ require 'time'
6
+ require 'date'
7
+ require 'inversion/template/attrtag'
8
+
9
+ # Inversion time delta tag.
10
+ #
11
+ # This tag is a derivative of the 'attr' tag that transforms the results of its method call
12
+ # to a Time object (if it isn't already), and then generates an English description of
13
+ # the different between it and the current time.
14
+ #
15
+ # == Syntax
16
+ #
17
+ # Updated <?timedelta entry.update_date ?>.
18
+ #
19
+ class Inversion::Template::TimeDeltaTag < Inversion::Template::AttrTag
20
+
21
+ # Approximate Time Constants (in seconds)
22
+ MINUTES = 60
23
+ HOURS = 60 * MINUTES
24
+ DAYS = 24 * HOURS
25
+ WEEKS = 7 * DAYS
26
+ MONTHS = 30 * DAYS
27
+ YEARS = 365.25 * DAYS
28
+
29
+
30
+ ######
31
+ public
32
+ ######
33
+
34
+ ### Render the tag.
35
+ def render( renderstate )
36
+ val = super( renderstate )
37
+ time = nil
38
+
39
+ if val.respond_to?( :to_time )
40
+ time = val.to_time
41
+ elsif val.is_a?( Numeric )
42
+ time = Time.at( val )
43
+ else
44
+ time = Time.parse( val.to_s )
45
+ end
46
+
47
+ now = Time.now
48
+ if now > time
49
+ seconds = now - time
50
+ return "%s ago" % [ timeperiod(seconds) ]
51
+ else
52
+ seconds = time - now
53
+ return "%s from now" % [ timeperiod(seconds) ]
54
+ end
55
+ end
56
+
57
+
58
+ #######
59
+ private
60
+ #######
61
+
62
+ ### Return a string describing +seconds+ as an approximate interval of time.
63
+ def timeperiod( seconds )
64
+ return case
65
+ when seconds < MINUTES - 5
66
+ 'less than a minute'
67
+ when seconds < 50 * MINUTES
68
+ if seconds <= 89
69
+ "a minute"
70
+ else
71
+ "%d minutes" % [ (seconds.to_f / MINUTES).ceil ]
72
+ end
73
+ when seconds < 90 * MINUTES
74
+ 'about an hour'
75
+ when seconds < 18 * HOURS
76
+ "%d hours" % [ (seconds.to_f / HOURS).ceil ]
77
+ when seconds < 30 * HOURS
78
+ 'about a day'
79
+ when seconds < WEEKS
80
+ "%d days" % [ (seconds.to_f / DAYS).ceil ]
81
+ when seconds < 2 * WEEKS
82
+ 'about a week'
83
+ when seconds < 3 * MONTHS
84
+ "%d weeks" % [ (seconds.to_f / WEEKS).ceil ]
85
+ when seconds < 18 * MONTHS
86
+ "%d months" % [ (seconds.to_f / MONTHS).ceil ]
87
+ else
88
+ "%d years" % [ (seconds.to_f / YEARS).ceil ]
89
+ end
90
+ end
91
+
92
+ end # class Inversion::Template::TimeDeltaTag
93
+
@@ -0,0 +1,87 @@
1
+ <!DOCTYPE html>
2
+ <!--
3
+
4
+ Main Manual Layout
5
+ $Id: default.erb,v 8d85088f0601 2011/08/15 16:36:48 mahlon $
6
+
7
+ Author: Michael Granger <ged@FaerieMUD.org>
8
+ Two column fluid layout adapted from Matthew James Taylor's "Perfect 'Left Menu'
9
+ 2 Column Liquid Layout":
10
+ http://matthewjamestaylor.com/blog/perfect-2-column-left-menu.htm
11
+
12
+ -->
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="utf-8">
16
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
17
+
18
+ <title>inversion Manual — <%= page.config['title'] || "untitled" %></title>
19
+
20
+ <link rel="stylesheet" href="css/manual.css">
21
+
22
+ <script src="js/jquery-1.4.4.min.js"></script>
23
+ <script src="js/sh.js"></script>
24
+ <script src="js/manual.js"></script>
25
+
26
+ </head>
27
+ <body>
28
+ <div id="page">
29
+
30
+ <header>
31
+ <hgroup>
32
+ <a href="/">
33
+ <h1>inversion</h1>
34
+ </a>
35
+ <% if metadata.version %><h2 class="version"><%= metadata.version %></h2><% end %>
36
+ <% if page.config['tagline'] %><h2 class="tagline"><%= page.config['tagline'] %></h2><% end %>
37
+ </hgroup>
38
+ </header>
39
+
40
+ <div class="colmask leftmenu">
41
+ <div class="colleft">
42
+
43
+ <section id="content" class="col1">
44
+ <!-- Generated content -->
45
+ <%= content %>
46
+ <!-- end of generated content -->
47
+ </section>
48
+
49
+ <aside id="sidebar" class="col2">
50
+ <nav>
51
+ <%
52
+ @catalog.traverse_page_hierarchy( self ) do |type, title, path|
53
+ case type
54
+ when :section
55
+ %>
56
+ <div class="section">
57
+ <h3><a href="<%= self.basepath + path %>/"><%= title %></a></h3>
58
+ <ul class="index-section">
59
+ <% when :current_section %>
60
+ <div class="section current-section">
61
+ <h3><a href="<%= self.basepath + path %>/"><%= title %></a></h3>
62
+ <ul class="index-section current-index-section">
63
+ <% when :section_end, :current_section_end %>
64
+ </ul>
65
+ </div>
66
+ <% when :entry %>
67
+ <li><a href="<%= self.basepath + path %>.html"><%= title %></a></li>
68
+ <% when :current_entry %>
69
+ <li class="current-entry"><%= title %></li>
70
+ <% end
71
+ end
72
+ %>
73
+ </nav>
74
+ </aside>
75
+
76
+ </div>
77
+ </div>
78
+
79
+ <footer>
80
+ <div class="copyright">Copyright &copy; 2008-<%= Time.now.year %> Michael Granger, Mahlon E. Smith.</div>
81
+ <div class="vcsrev">Rev: $Revision: 8d85088f0601 $</div>
82
+ <div class="timestamp">Built: <%= Time.now.strftime("%Y%m%d %H:%M:%S %Z") %></div>
83
+ </footer>
84
+
85
+ </body>
86
+ </html>
87
+
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # A manual filter to generate links from the Darkfish API.
4
+ #
5
+ # Authors:
6
+ # * Michael Granger <ged@FaerieMUD.org>
7
+ # * Mahlon E. Smith <mahlon@martini.nu>
8
+ #
9
+
10
+
11
+ ### A filter for generating links from the generated API documentation. This allows you to refer
12
+ ### to class documentation by simply referencing a class name.
13
+ ###
14
+ ### Links are XML processing instructions. Pages can be referenced as such:
15
+ ###
16
+ ### <?api Class::Name ?>
17
+ ### <?api Class::Name#instance_method ?>
18
+ ### <?api Class::Name.class_method ?>
19
+ ###
20
+ ### Link text can be overridden, too:
21
+ ###
22
+ ### <?api "click here":Class::Name ?>
23
+ ### <?api "click here":Class::Name#instance_method ?>
24
+ ###
25
+ class Hoe::ManualGen::APIFilter < Hoe::ManualGen::PageFilter
26
+
27
+ # PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
28
+ ApiPI = %r{
29
+ <\?
30
+ api # Instruction Target
31
+ \s+
32
+ (?:"
33
+ (.*?) # Optional link text [$1]
34
+ ":)?
35
+ (.*?) # Class name [$2]
36
+ ([\.#].*?)? # Method anchor [$3]
37
+ \s+
38
+ \?>
39
+ }x
40
+
41
+
42
+ ######
43
+ public
44
+ ######
45
+
46
+ ### Process the given +source+ for <?api ... ?> processing-instructions, calling out
47
+ def process( source, page, metadata )
48
+
49
+ apipath = metadata.api_dir or
50
+ raise "The API output directory is not defined in the manual task."
51
+
52
+ return source.gsub( ApiPI ) do |match|
53
+ # Grab the tag values
54
+ link_text = $1
55
+ classname = $2
56
+ methodname = $3
57
+
58
+ self.generate_link( page, apipath, classname, methodname, link_text )
59
+ end
60
+ end
61
+
62
+
63
+ ### Create an HTML link fragment from the parsed ApiPI.
64
+ def generate_link( current_page, apipath, classname, methodname=nil, link_text=nil )
65
+
66
+ classpath = "%s.html" % [ classname.gsub('::', '/') ]
67
+ classfile = apipath + classpath
68
+ classuri = current_page.basepath + 'api' + classpath
69
+
70
+ if classfile.exist?
71
+ return %{<a href="%s%s">%s</a>} % [
72
+ classuri,
73
+ make_anchor( methodname ),
74
+ link_text || (classname + methodname || '')
75
+ ]
76
+ else
77
+ link_text ||= classname
78
+ error_message = "Could not find a link for class '%s'" % [ classname ]
79
+ $stderr.puts( error_message )
80
+ return %{<a href="#" title="#{error_message}" class="broken-link">#{link_text}</a>}
81
+ end
82
+ end
83
+
84
+
85
+ ### Attach a method anchor.
86
+ def make_anchor( methodname )
87
+ return '' unless methodname
88
+
89
+ method_type = methodname[ 0, 1 ]
90
+ return "#method-%s-%s" % [
91
+ ( method_type == '#' ? 'i' : 'c' ),
92
+ methodname[ 1..-1 ]
93
+ ]
94
+ end
95
+ end
96
+
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # A manual filter to highlight content that needs editorial help.
4
+ #
5
+ # Authors:
6
+ # * Michael Granger <ged@FaerieMUD.org>
7
+ #
8
+ #
9
+
10
+
11
+
12
+ ### A filter for making editorial marks in manual content.
13
+ ###
14
+ ### Editorial marks are XML processing instructions. There are several available types of
15
+ ### marks:
16
+ ###
17
+ ### <?ed "This is an editor's note." ?>
18
+ ### <?ed verify:"this content needs checking or verification" ?>
19
+ ###
20
+ class Hoe::ManualGen::EditorialFilter < Hoe::ManualGen::PageFilter
21
+
22
+ # PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
23
+ LinkPI = %r{
24
+ <\?
25
+ ed # Instruction Target
26
+ \s+
27
+ (\w+?) # type of editorial mark [$1]
28
+ :? # optional colon
29
+ "
30
+ (.*?) # content that should be edited [$2]
31
+ "
32
+ \s*
33
+ \?>
34
+ }x
35
+
36
+
37
+ ######
38
+ public
39
+ ######
40
+
41
+ ### Process the given +source+ for <?ed ... ?> processing-instructions
42
+ def process( source, page, metadata )
43
+ return source.gsub( LinkPI ) do |match|
44
+ # Grab the tag values
45
+ mark_type = $1
46
+ content = $2
47
+
48
+ self.generate_mark( page, mark_type, content )
49
+ end
50
+ end
51
+
52
+
53
+ ### Create an HTML fragment from the parsed LinkPI.
54
+ def generate_mark( current_page, mark_type, content )
55
+ return "%%(editorial %s-mark)%s%%" % [ mark_type, content ]
56
+ end
57
+
58
+
59
+ end
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # A collection of standard filters for the manual generation tasklib.
4
+ #
5
+ # Authors:
6
+ # Michael Granger <ged@FaerieMUD.org>
7
+ #
8
+ #
9
+
10
+ # Dependencies deferred until #initialize
11
+
12
+
13
+
14
+ ### A filter for inline example code or command-line sessions -- does
15
+ ### syntax-checking for some languages and captioning.
16
+ ###
17
+ ### Examples are enclosed in XML processing instructions like so:
18
+ ###
19
+ ### <?example {language: ruby, testable: true, caption: "A fine example"} ?>
20
+ ### a = 1
21
+ ### puts a
22
+ ### <?end example ?>
23
+ ###
24
+ ### This will be pulled out into a preformatted section in the HTML,
25
+ ### highlighted as Ruby source, checked for valid syntax, and annotated with
26
+ ### the specified caption. Valid keys in the example PI are:
27
+ ###
28
+ ### language::
29
+ ### Specifies which (machine) language the example is in.
30
+ ### testable::
31
+ ### If set and there is a testing function for the given language, run it and append
32
+ ### any errors to the output.
33
+ ### caption::
34
+ ### A small blurb to put below the pulled-out example in the HTML.
35
+ class Hoe::ManualGen::ExamplesFilter < Hoe::ManualGen::PageFilter
36
+
37
+ DEFAULTS = {
38
+ :language => :ruby,
39
+ :line_numbers => :inline,
40
+ :tab_width => 4,
41
+ :hint => :debug,
42
+ :testable => false,
43
+ }
44
+
45
+ # PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
46
+ ExamplePI = %r{
47
+ <\?
48
+ example # Instruction Target
49
+ (?: # Optional instruction body
50
+ \s+
51
+ ((?: # [$1]
52
+ [^?]* # Run of anything but a question mark
53
+ | # -or-
54
+ \?(?!>) # question mark not followed by a closing angle bracket
55
+ )*)
56
+ )?
57
+ \?>
58
+ }x
59
+
60
+ EndPI = %r{ <\? end (?: \s+ example )? \s* \?> }x
61
+
62
+
63
+ ### Defer loading of dependenies until the filter is loaded
64
+ def initialize( *args )
65
+ begin
66
+ require 'pathname'
67
+ require 'strscan'
68
+ require 'yaml'
69
+ require 'rcodetools/xmpfilter'
70
+ require 'digest/md5'
71
+ require 'tmpdir'
72
+ require 'erb'
73
+ rescue LoadError => err
74
+ unless Object.const_defined?( :Gem )
75
+ require 'rubygems'
76
+ retry
77
+ end
78
+
79
+ raise
80
+ end
81
+ end
82
+
83
+
84
+ ######
85
+ public
86
+ ######
87
+
88
+ ### Process the given +source+ for <?example ... ?> processing-instructions, calling out
89
+ def process( source, page, metadata )
90
+ scanner = StringScanner.new( source )
91
+
92
+ buffer = ''
93
+ until scanner.eos?
94
+ startpos = scanner.pos
95
+
96
+ # If we find an example
97
+ if scanner.skip_until( ExamplePI )
98
+ contents = ''
99
+
100
+ # Append the interstitial content to the buffer
101
+ if ( scanner.pos - startpos > scanner.matched.length )
102
+ offset = scanner.pos - scanner.matched.length - 1
103
+ buffer << scanner.string[ startpos..offset ]
104
+ end
105
+
106
+ # Append everything up to it to the buffer and save the contents of
107
+ # the tag
108
+ params = scanner[1]
109
+
110
+ # Now find the end of the example or complain
111
+ contentpos = scanner.pos
112
+ scanner.skip_until( EndPI ) or
113
+ raise "Unterminated example at line %d" %
114
+ [ scanner.string[0..scanner.pos].count("\n") ]
115
+
116
+ # Now build the example and append to the buffer
117
+ if ( scanner.pos - contentpos > scanner.matched.length )
118
+ offset = scanner.pos - scanner.matched.length - 1
119
+ contents = scanner.string[ contentpos..offset ]
120
+ end
121
+
122
+ trace "Processing with params: %p, contents: %p" % [ params, contents ]
123
+ buffer << self.process_example( params, contents, page )
124
+ else
125
+ break
126
+ end
127
+
128
+ end
129
+ buffer << scanner.rest
130
+ scanner.terminate
131
+
132
+ return buffer
133
+ end
134
+
135
+
136
+ ### Filter out 'example' macros, doing syntax highlighting, and running
137
+ ### 'testable' examples through a validation process appropriate to the
138
+ ### language the example is in.
139
+ def process_example( params, body, page )
140
+ options = self.parse_options( params )
141
+ caption = options.delete( :caption )
142
+ content = ''
143
+ lang = options.delete( :language ).to_s
144
+
145
+ # Test it if it's testable
146
+ if options[:testable]
147
+ content = test_content( body, lang, page )
148
+ else
149
+ content = body
150
+ end
151
+
152
+ # Strip trailing blank lines and syntax-highlight
153
+ content = highlight( content.strip, options, lang )
154
+ caption = %{<div class="caption">} + caption.to_s + %{</div>} if caption
155
+
156
+ return %{<notextile><div class="example #{lang}-example">%s%s</div></notextile>} %
157
+ [content, caption || '']
158
+ end
159
+
160
+
161
+ ### Parse an options hash for filtering from the given +args+, which can either
162
+ ### be a plain String, in which case it is assumed to be the name of the language the example
163
+ ### is in, or a Hash of configuration options.
164
+ def parse_options( args )
165
+ args = "{ #{args} }" unless args.strip[0] == ?{
166
+ args = YAML.load( args )
167
+
168
+ # Convert to Symbol keys and value
169
+ args.keys.each do |k|
170
+ newval = args.delete( k )
171
+ next if newval.nil? || (newval.respond_to?(:size) && newval.size == 0)
172
+ args[ k.to_sym ] = newval.respond_to?( :to_sym ) ? newval.to_sym : newval
173
+ end
174
+ return DEFAULTS.merge( args )
175
+ end
176
+
177
+
178
+ ### Test the given +content+ with a rule specific to the given +language+.
179
+ def test_content( body, language, page )
180
+ case language.to_sym
181
+ when :ruby
182
+ return self.test_ruby_content( body, page )
183
+
184
+ when :yaml
185
+ return self.test_yaml_content( body, page )
186
+
187
+ else
188
+ return body
189
+ end
190
+ end
191
+
192
+
193
+ ### Test the specified Ruby content for valid syntax
194
+ def test_ruby_content( source, page )
195
+ # $stderr.puts "Testing ruby content..."
196
+ libdir = Pathname.new( __FILE__ ).dirname.parent.parent.parent + 'lib'
197
+ extdir = Pathname.new( __FILE__ ).dirname.parent.parent.parent + 'ext'
198
+
199
+ options = Rcodetools::XMPFilter::INITIALIZE_OPTS.dup
200
+ options[:include_paths] |= [ libdir.to_s, extdir.to_s ]
201
+ options[:width] = 60
202
+
203
+ if page.config['example_prelude']
204
+ prelude = page.config['example_prelude']
205
+ trace " prepending prelude:\n#{prelude}"
206
+ source = prelude.strip + "\n" + source.strip
207
+ else
208
+ trace " no prelude; page config is: %p" % [ page.config ]
209
+ end
210
+
211
+ rval = Rcodetools::XMPFilter.run( source, options )
212
+
213
+ trace "test output: ", rval
214
+ return rval.join
215
+ rescue Exception => err
216
+ return "%s while testing: %s\n %s" %
217
+ [ err.class.name, err.message, err.backtrace.join("\n ") ]
218
+ end
219
+
220
+
221
+ ### Test the specified YAML content for valid syntax
222
+ def test_yaml_content( source, metadata )
223
+ YAML.load( source )
224
+ rescue YAML::Error => err
225
+ return "# Invalid YAML: " + err.message + "\n" + source
226
+ else
227
+ return source
228
+ end
229
+
230
+
231
+ ### Highlights the given +content+ in language +lang+.
232
+ def highlight( content, options, lang )
233
+ source = ERB::Util.html_escape( content )
234
+ return %Q{\n\n<pre class="brush:#{lang}">#{source}</pre>\n\n}
235
+ end
236
+
237
+ end
238
+