codnar 0.1.64

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/ChangeLog +165 -0
  2. data/LICENSE +19 -0
  3. data/README.rdoc +32 -0
  4. data/Rakefile +66 -0
  5. data/bin/codnar-split +5 -0
  6. data/bin/codnar-weave +5 -0
  7. data/codnar.html +10945 -0
  8. data/doc/logo.png +0 -0
  9. data/doc/root.html +22 -0
  10. data/doc/story.markdown +180 -0
  11. data/doc/system.markdown +671 -0
  12. data/lib/codnar.rb +41 -0
  13. data/lib/codnar/application.rb +92 -0
  14. data/lib/codnar/cache.rb +61 -0
  15. data/lib/codnar/data/contents.js +113 -0
  16. data/lib/codnar/data/control_chunks.js +44 -0
  17. data/lib/codnar/data/style.css +95 -0
  18. data/lib/codnar/data/sunlight/README.txt +4 -0
  19. data/lib/codnar/data/sunlight/css-min.js +1 -0
  20. data/lib/codnar/data/sunlight/default.css +236 -0
  21. data/lib/codnar/data/sunlight/javascript-min.js +1 -0
  22. data/lib/codnar/data/sunlight/min.js +1 -0
  23. data/lib/codnar/data/sunlight/ruby-min.js +1 -0
  24. data/lib/codnar/data/yui/README.txt +3 -0
  25. data/lib/codnar/data/yui/base.css +132 -0
  26. data/lib/codnar/data/yui/reset.css +142 -0
  27. data/lib/codnar/formatter.rb +180 -0
  28. data/lib/codnar/grouper.rb +28 -0
  29. data/lib/codnar/gvim.rb +132 -0
  30. data/lib/codnar/hash_extensions.rb +41 -0
  31. data/lib/codnar/markdown.rb +47 -0
  32. data/lib/codnar/merger.rb +138 -0
  33. data/lib/codnar/rake.rb +41 -0
  34. data/lib/codnar/rake/split_task.rb +71 -0
  35. data/lib/codnar/rake/weave_task.rb +59 -0
  36. data/lib/codnar/rdoc.rb +9 -0
  37. data/lib/codnar/reader.rb +121 -0
  38. data/lib/codnar/scanner.rb +216 -0
  39. data/lib/codnar/split.rb +58 -0
  40. data/lib/codnar/split_configurations.rb +367 -0
  41. data/lib/codnar/splitter.rb +32 -0
  42. data/lib/codnar/string_extensions.rb +25 -0
  43. data/lib/codnar/sunlight.rb +17 -0
  44. data/lib/codnar/version.rb +8 -0
  45. data/lib/codnar/weave.rb +58 -0
  46. data/lib/codnar/weave_configurations.rb +48 -0
  47. data/lib/codnar/weaver.rb +105 -0
  48. data/lib/codnar/writer.rb +38 -0
  49. data/test/cache_computations.rb +41 -0
  50. data/test/deep_merge.rb +29 -0
  51. data/test/embed_images.rb +12 -0
  52. data/test/expand_markdown.rb +27 -0
  53. data/test/expand_rdoc.rb +20 -0
  54. data/test/format_code_gvim_configurations.rb +55 -0
  55. data/test/format_code_sunlight_configurations.rb +37 -0
  56. data/test/format_comment_configurations.rb +86 -0
  57. data/test/format_lines.rb +72 -0
  58. data/test/group_lines.rb +31 -0
  59. data/test/gvim_highlight_syntax.rb +49 -0
  60. data/test/identify_chunks.rb +32 -0
  61. data/test/lib/test_with_configurations.rb +15 -0
  62. data/test/merge_lines.rb +133 -0
  63. data/test/rake_tasks.rb +38 -0
  64. data/test/read_chunks.rb +110 -0
  65. data/test/run_application.rb +56 -0
  66. data/test/run_split.rb +38 -0
  67. data/test/run_weave.rb +75 -0
  68. data/test/scan_lines.rb +78 -0
  69. data/test/split_chunk_configurations.rb +55 -0
  70. data/test/split_code.rb +109 -0
  71. data/test/split_code_configurations.rb +73 -0
  72. data/test/split_combined_configurations.rb +114 -0
  73. data/test/split_complex_comment_configurations.rb +73 -0
  74. data/test/split_documentation.rb +92 -0
  75. data/test/split_documentation_configurations.rb +97 -0
  76. data/test/split_simple_comment_configurations.rb +50 -0
  77. data/test/sunlight_highlight_syntax.rb +25 -0
  78. data/test/weave_configurations.rb +144 -0
  79. data/test/write_chunks.rb +28 -0
  80. metadata +363 -0
@@ -0,0 +1,25 @@
1
+ # Extend the core String class.
2
+ class String
3
+
4
+ # Convert this String to an identifier. This is a stable operation, so
5
+ # anything that accept a name will also accept an identifier as well.
6
+ def to_id
7
+ return self.strip.gsub(/[^a-zA-Z0-9]+/, "-").downcase
8
+ end
9
+
10
+ # {{{ Clean HTML
11
+
12
+ # Clean HTML generated by markup formatters. Such HTML tends to have extra
13
+ # empty lines for no apparent reason. Cleaning it up seems to be safe enough,
14
+ # and eliminates the ugly additional vertical space in the final HTML.
15
+ def clean_markup_html
16
+ return gsub(/\n*<p>\n*/, "\n<p>\n") \
17
+ .gsub(/\n*<\/p>\n*/, "\n</p>\n") \
18
+ .gsub(/\n*<pre>\n*/, "\n<pre>\n") \
19
+ .gsub(/\n*<\/pre>\n*/, "\n</pre>\n") \
20
+ .sub(/^\n*/, "")
21
+ end
22
+
23
+ # }}}
24
+
25
+ end
@@ -0,0 +1,17 @@
1
+ module Codnar
2
+
3
+ # Syntax highlight using Sunlight.
4
+ class Sunlight
5
+
6
+ # Convert a sequence of classified code lines to HTML using Sunlight syntax
7
+ # highlighting. All we need to do is wrap the lines in an HTML +pre+
8
+ # element with the correct class (<tt>sunlight-highlight-_syntax_</tt>).
9
+ # The actual highlighting is done in the HTML DOM using Javascript.
10
+ # Embedding this Javascript into the final HTML should be done separately.
11
+ def self.lines_to_html(lines, syntax)
12
+ return Formatter.lines_to_pre_html(lines, :class => "sunlight-highlight-#{syntax}")
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,8 @@
1
+ # This module contains all the code narrator code.
2
+ module Codnar
3
+
4
+ # This version number. The third number is automatically updated to track the
5
+ # number of Git commits by running <tt>rake version</tt>.
6
+ VERSION = "0.1.64"
7
+
8
+ end
@@ -0,0 +1,58 @@
1
+ module Codnar
2
+
3
+ # Weave application.
4
+ class Weave < Application
5
+
6
+ # Run the weaving Codnar application, returning its status.
7
+ def run
8
+ super { weave }
9
+ end
10
+
11
+ protected
12
+
13
+ # Weave all the chunks together to a single HTML.
14
+ def weave
15
+ @configuration = Codnar::Configuration::WEAVE_INCLUDE if @configuration == {}
16
+ weaver = Weaver.new(@errors, ARGV, @configuration)
17
+ puts(weaver.weave("include"))
18
+ weaver.collect_unused_chunk_errors
19
+ end
20
+
21
+ # Parse remaining command-line file arguments.
22
+ def parse_arguments
23
+ return if ARGV.size > 0
24
+ $stderr.puts("#{$0}: No chunk files to weave")
25
+ exit(1)
26
+ end
27
+
28
+ # Return the banner line of the help message.
29
+ def banner
30
+ return "codnar-weave - Weave documentation chunks to a single HTML."
31
+ end
32
+
33
+ # Return the name and description of any final command-line file arguments.
34
+ def arguments
35
+ return "MAIN-CHUNK ADDITIONAL-CHUNKS", "Chunk files to weave together."
36
+ end
37
+
38
+ # Return a short description of the program.
39
+ def description
40
+ print(<<-EOF.unindent)
41
+ Weave chunks in all chunk files (from codnar-split) to a single HTML that is
42
+ printed to the output. The first file is the main documentation file that is
43
+ expected to include all the rest of the chunks via directives of the format:
44
+
45
+ <embed src="chunk-name" type="x-codnar/template-name"></embed>
46
+
47
+ Where the template-name is a key in the configuration, whose value is an ERB
48
+ template for embedding the named chunk into the documentation.
49
+
50
+ If no configuration is specified, the WEAVE_INCLUDE configuration is assumed.
51
+ This configuration contains a single template named "include", which simply
52
+ includes the named chunk into the generated HTML.
53
+ EOF
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,48 @@
1
+ module Codnar
2
+
3
+ module Configuration
4
+
5
+ # Weave configuration providing a single simple +include+ template.
6
+ WEAVE_INCLUDE = { "include" => "<%= chunk.expanded_html %>\n" }
7
+
8
+ # Weave chunks in the plainest possible way.
9
+ WEAVE_PLAIN_CHUNK = {
10
+ "plain_chunk" => <<-EOF.unindent, #! ((( html
11
+ <div class="plain chunk">
12
+ <a name="<%= chunk.name.to_id %>"/>
13
+ <%= chunk.expanded_html %>
14
+ </div>
15
+ EOF
16
+ } #! ))) html
17
+
18
+ # Weave chunks with their name and the list of container chunks.
19
+ WEAVE_NAMED_CHUNK_WITH_CONTAINERS = {
20
+ "named_chunk_with_containers" => <<-EOF.unindent, #! ((( html
21
+ <div class="named_with_containers chunk">
22
+ <div class="chunk name">
23
+ <a name="<%= chunk.name.to_id %>">
24
+ <span><%= CGI.escapeHTML(chunk.name) %></span>
25
+ </a>
26
+ </div>
27
+ <div class="chunk html">
28
+ <%= chunk.expanded_html %>
29
+ </div>
30
+ % if chunk.containers != []
31
+ <div class="chunk containers">
32
+ <span class="chunk containers header">Contained in:</span>
33
+ <ul class="chunk containers">
34
+ % chunk.containers.each do |container|
35
+ <li class="chunk container">
36
+ <a class="chunk container" href="#<%= container.to_id %>"><%= CGI.escapeHTML(container) %></a>
37
+ </li>
38
+ % end
39
+ </ul>
40
+ </div>
41
+ % end
42
+ </div>
43
+ EOF
44
+ } #! ))) html
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,105 @@
1
+ module Codnar
2
+
3
+ # Weave all chunks to a unified HTML.
4
+ class Weaver < Reader
5
+
6
+ # Load all chunks from the specified disk files to memory for weaving using
7
+ # the specified templates.
8
+ def initialize(errors, paths, templates)
9
+ super(errors, paths)
10
+ @templates = templates
11
+ end
12
+
13
+ # How to process each magical file template.
14
+ FILE_TEMPLATE_PROCESSORS = {
15
+ "file" => lambda { |name, data| data },
16
+ "image" => lambda { |name, data| Weaver.embedded_base64_img_tag(name, data) },
17
+ }
18
+
19
+ # Weave the HTML for a named chunk.
20
+ def weave(template, chunk_name = @root_chunk)
21
+ return process_file_template(template, chunk_name) if FILE_TEMPLATE_PROCESSORS.include?(template)
22
+ @last_chunk = chunk = self[chunk_name.to_id]
23
+ expand_chunk_html(chunk)
24
+ return process_template(chunk, template)
25
+ end
26
+
27
+ protected
28
+
29
+ # Due to http://github.com/relevance/rcov/issues/#issue/43 the following regular expressions must be on a single line.
30
+
31
+ # Detect embedded chunks (+type+ before +src+).
32
+ TYPE_SRC_CHUNK = / [ ]* <embed \s+ type = ['\"] x-codnar\/ (.*?) ['\"] \s+ src = ['\"] \#* (.*?) ['\"] \s* (?: \/> | > \s* <\/embed> ) [ ]* /x
33
+
34
+ # Detect embedded chunks (+src+ before +type+).
35
+ SRC_TYPE_CHUNK = / [ ]* <embed \s+ src = ['\"] \#* (.*?) ['\"] \s+ type = ['\"] x-codnar\/ (.*?) ['\"] \s* (?: \/> | > \s* <\/embed> ) [ ]* /x
36
+
37
+ # Recursively expand all embedded chunks inside a container chunk.
38
+ def expand_chunk_html(chunk)
39
+ html = chunk.html
40
+ @errors.push("No HTML in chunk: #{chunk.name} #{Weaver.locations_message(chunk)}") unless html
41
+ #! TRICKY: All "container" chunks are assumed to be whole-file chunks with
42
+ #! a single location. Which makes sense as these are documentation and not
43
+ #! code chunks. TODO: It would be nice to know the exact line number of
44
+ #! the chunk embedding directive for better pinpointing of any error.
45
+ @errors.in_path(chunk.locations[0].file) do
46
+ chunk.expanded_html ||= expand_embedded_chunks(html || "").chomp
47
+ end
48
+ end
49
+
50
+ # Recursively expand_embedded_chunks all embedded chunk inside an HTML.
51
+ def expand_embedded_chunks(html)
52
+ return html.gsub(TYPE_SRC_CHUNK) { |match| weave($1, $2).chomp } \
53
+ .gsub(SRC_TYPE_CHUNK) { |match| weave($2, $1).chomp }
54
+ end
55
+
56
+ # Process the chunk using an ERB template prior to inclusion in container
57
+ # chunk.
58
+ def process_template(chunk, template_name)
59
+ template_text = @templates[template_name] ||= (
60
+ @errors << "Missing ERB template: #{template_name}"
61
+ "<%= chunk.expanded_html %>\n"
62
+ )
63
+ return (
64
+ (
65
+ chunk.erb ||= {}
66
+ )[template_name] ||= ERB.new(template_text, nil, "%")
67
+ ).result(binding)
68
+ end
69
+
70
+ # {{{ Processing the file template
71
+
72
+ # Process one of the magical file templates. The content of the file,
73
+ # optionally processed, is directly embedded into the generated
74
+ # documentation. If the file's path begins with ".", it is taken to be
75
+ # relative to the current working directory. Otherwise, it is searched for
76
+ # in Ruby's load path, allowing easy access to files packaged inside gems.
77
+ def process_file_template(template, path)
78
+ begin
79
+ path = Olag::DataFiles.expand_path(path) unless path[0,1] == "."
80
+ return FILE_TEMPLATE_PROCESSORS[template].call(path, File.read(path))
81
+ rescue Exception => exception
82
+ @errors.push("#{$0}: Reading file: #{path} exception: #{exception} #{Reader.locations_message(@last_chunk)}") \
83
+ if @last_chunk
84
+ return "FILE: #{path} EXCEPTION: #{exception}"
85
+ end
86
+ end
87
+
88
+ # }}}
89
+
90
+ # {{{ Processing Base64 embedded data images
91
+
92
+ # Create an +img+ tag with an embedded data URL. Different browsers have
93
+ # different constraints about the size of the resulting URL, so YMMV.
94
+ def self.embedded_base64_img_tag(name, data)
95
+ extension = File.extname(name).sub(".", "/")
96
+ return "<img src='data:image#{extension};base64," \
97
+ + Base64.encode64(data) \
98
+ + "'/>"
99
+ end
100
+
101
+ # }}}
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,38 @@
1
+ module Codnar
2
+
3
+ # Write chunks into a disk file.
4
+ class Writer
5
+
6
+ # Write one chunk or an array of chunks to a disk file.
7
+ def self.write(path, data)
8
+ self.new(path) do |writer|
9
+ writer << data
10
+ end
11
+ end
12
+
13
+ # Add one chunk or an array of chunks to the disk file.
14
+ def <<(data)
15
+ case data
16
+ when Array
17
+ @chunks += data
18
+ when Hash
19
+ @chunks << data
20
+ else
21
+ raise "Invalid data class: #{data.class}"
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ # Write chunks into the specified disk file.
28
+ def initialize(path, &block)
29
+ @chunks = []
30
+ File.open(path, "w") do |file|
31
+ block.call(self)
32
+ file.print(@chunks.to_yaml)
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,41 @@
1
+ require "codnar"
2
+ require "olag/test"
3
+ require "test/spec"
4
+
5
+ # Test caching long computations.
6
+ class TestCacheComputations < Test::Unit::TestCase
7
+
8
+ include Test::WithTempfile
9
+
10
+ def test_cached_computation
11
+ cache = make_addition_cache(directory = create_tempdir(".."))
12
+ cache[1].should == 2
13
+ File.open(Dir.glob(directory + "/*")[0], "w") { |file| file.puts("3") }
14
+ cache[1].should == 3
15
+ cache.force_recompute = true
16
+ cache[1].should == 2
17
+ end
18
+
19
+ def test_uncached_computation
20
+ stderr = capture_stderr { make_addition_cache("no-such-directory")[1].should == 2 }
21
+ stderr.should.include?("no-such-directory")
22
+ end
23
+
24
+ protected
25
+
26
+ # Run a block and capture its standard error (without using FakeFS).
27
+ def capture_stderr
28
+ stderr_path = write_tempfile("stderr", "")
29
+ Olag::Globals.without_changes do
30
+ $stderr = File.open(stderr_path, "w")
31
+ yield
32
+ end
33
+ return File.read(stderr_path)
34
+ end
35
+
36
+ # Create a cache for the "+ 1" operation.
37
+ def make_addition_cache(directory)
38
+ return Codnar::Cache.new(directory) { |value| value + 1 }
39
+ end
40
+
41
+ end
@@ -0,0 +1,29 @@
1
+ require "codnar"
2
+ require "test/spec"
3
+
4
+ # Test deep-merging complex structures.
5
+ class TestDeepMerge < Test::Unit::TestCase
6
+
7
+ def test_deep_merge
8
+ default = {
9
+ "only_default" => "default_value",
10
+ "overriden" => "default_value",
11
+ "overriden_array" => [ "default_value" ],
12
+ "merged_array" => [ "default_value" ],
13
+ }
14
+ override = {
15
+ "only_override" => "overriden_value",
16
+ "overriden" => "overriden_value",
17
+ "overriden_array" => [ "overriden_value" ],
18
+ "merged_array" => [ "overriden_value", [] ],
19
+ }
20
+ default.deep_merge(override).should == {
21
+ "only_default" => "default_value",
22
+ "only_override" => "overriden_value",
23
+ "overriden" => "overriden_value",
24
+ "overriden_array" => [ "overriden_value" ],
25
+ "merged_array" => [ "overriden_value", "default_value" ],
26
+ }
27
+ end
28
+
29
+ end
@@ -0,0 +1,12 @@
1
+ require "codnar"
2
+ require "test/spec"
3
+
4
+ # Test computing embedded image HTML tags.
5
+ class TestEmbedImages < Test::Unit::TestCase
6
+
7
+ def test_embed_image
8
+ Codnar::Weaver.embedded_base64_img_tag('fake file.png', 'fake file content').should \
9
+ == "<img src='data:image/png;base64,ZmFrZSBmaWxlIGNvbnRlbnQ=\n'/>"
10
+ end
11
+
12
+ end
@@ -0,0 +1,27 @@
1
+ require "codnar"
2
+ require "test/spec"
3
+
4
+ # Test expanding Markdown text.
5
+ class TestExpandMarkdown < Test::Unit::TestCase
6
+
7
+ def test_emphasis_text
8
+ Markdown.to_html("*text*").should == "<p>\n<em>text</em>\n</p>\n"
9
+ end
10
+
11
+ def test_strong_text
12
+ Markdown.to_html("**text**").should == "<p>\n<strong>text</strong>\n</p>\n"
13
+ end
14
+
15
+ def test_embed_chunk
16
+ Markdown.to_html("[[Chunk|template]]").should == "<p>\n<embed src='chunk' type='x-codnar/template'/>\n</p>\n"
17
+ end
18
+
19
+ def test_embed_anchor
20
+ Markdown.to_html("[[#Name]]").should == "<p>\n<a id='name'/>\n</p>\n"
21
+ end
22
+
23
+ def test_embed_link
24
+ Markdown.to_html("[Label](#Name)").should == "<p>\n<a href=\"#name\">Label</a>\n</p>\n"
25
+ end
26
+
27
+ end
@@ -0,0 +1,20 @@
1
+ require "codnar"
2
+ require "test/spec"
3
+
4
+ # Test expanding RDoc text.
5
+ class TestExpandRDoc < Test::Unit::TestCase
6
+
7
+ def test_emphasis_text
8
+ RDoc.to_html("_text_").should == "<p>\n<em>text</em>\n</p>\n"
9
+ end
10
+
11
+ def test_strong_text
12
+ RDoc.to_html("*text*").should == "<p>\n<b>text</b>\n</p>\n"
13
+ end
14
+
15
+ def test_indented_pre
16
+ RDoc.to_html("base\n indented\n more\nback\n").should \
17
+ == "<p>\nbase\n</p>\n<pre>\nindented\n more\n</pre>\n<p>\nback\n</p>\n"
18
+ end
19
+
20
+ end
@@ -0,0 +1,55 @@
1
+ require "codnar"
2
+ require "olag/test"
3
+ require "test/spec"
4
+ require "test_with_configurations"
5
+
6
+ # Test built-in split code formatting configurations using GVim.
7
+ class TestGVimFormatCodeConfigurations < Test::Unit::TestCase
8
+
9
+ include Test::WithConfigurations
10
+ include Test::WithErrors
11
+ include Test::WithTempfile
12
+
13
+ CODE_TEXT = <<-EOF.unindent
14
+ int x;
15
+ EOF
16
+
17
+ GVIM_HTML = <<-EOF.unindent.chomp
18
+ <div class='c code syntax' bgcolor=\"#ffffff\" text=\"#000000\">
19
+ <font face=\"monospace\">
20
+ <font color=\"#00ff00\">int</font>&nbsp;x;<br />
21
+ </font>
22
+ </div>
23
+ EOF
24
+
25
+ def test_html_code
26
+ check_any_code(GVIM_HTML, Codnar::Configuration::FORMAT_CODE_GVIM_HTML.call("c"))
27
+ end
28
+
29
+ GVIM_CSS = <<-EOF.unindent.chomp
30
+ <pre class='c code syntax'>
31
+ <span class=\"Type\">int</span> x;
32
+ </pre>
33
+ EOF
34
+
35
+ def test_css_code
36
+ check_any_code(GVIM_CSS, Codnar::Configuration::FORMAT_CODE_GVIM_CSS.call("c"))
37
+ end
38
+
39
+ protected
40
+
41
+ def check_any_code(html, configuration)
42
+ check_split_file(CODE_TEXT,
43
+ Codnar::Configuration::CLASSIFY_SOURCE_CODE.call("c"),
44
+ configuration) do |path|
45
+ [ {
46
+ "name" => path,
47
+ "locations" => [ { "file" => path, "line" => 1 } ],
48
+ "containers" => [],
49
+ "contained" => [],
50
+ "html" => html,
51
+ } ]
52
+ end
53
+ end
54
+
55
+ end