codnar 0.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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