Add script to generate user guide HTML
This commit is contained in:
parent
dbc5560aec
commit
f5756e9523
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,7 @@
|
|||||||
/.yardoc
|
/.yardoc
|
||||||
/_yardoc/
|
/_yardoc/
|
||||||
/coverage/
|
/coverage/
|
||||||
/doc/
|
/gen/
|
||||||
/pkg/
|
/pkg/
|
||||||
/spec/reports/
|
/spec/reports/
|
||||||
/tmp/
|
/tmp/
|
||||||
|
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
- Initial release
|
3
Gemfile
3
Gemfile
@ -2,3 +2,6 @@ source "https://rubygems.org"
|
|||||||
|
|
||||||
gem "rake"
|
gem "rake"
|
||||||
gem "rspec"
|
gem "rspec"
|
||||||
|
gem "rdoc"
|
||||||
|
gem "redcarpet"
|
||||||
|
gem "syntax"
|
||||||
|
12
Gemfile.lock
12
Gemfile.lock
@ -2,7 +2,12 @@ GEM
|
|||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
diff-lcs (1.5.0)
|
diff-lcs (1.5.0)
|
||||||
|
psych (5.0.1)
|
||||||
|
stringio
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
|
rdoc (6.5.0)
|
||||||
|
psych (>= 4.0.0)
|
||||||
|
redcarpet (3.5.1)
|
||||||
rspec (3.11.0)
|
rspec (3.11.0)
|
||||||
rspec-core (~> 3.11.0)
|
rspec-core (~> 3.11.0)
|
||||||
rspec-expectations (~> 3.11.0)
|
rspec-expectations (~> 3.11.0)
|
||||||
@ -16,13 +21,18 @@ GEM
|
|||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.11.0)
|
rspec-support (~> 3.11.0)
|
||||||
rspec-support (3.11.0)
|
rspec-support (3.11.0)
|
||||||
|
stringio (3.0.4)
|
||||||
|
syntax (1.2.2)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
rake
|
rake
|
||||||
|
rdoc
|
||||||
|
redcarpet
|
||||||
rspec
|
rspec
|
||||||
|
syntax
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.4.0.dev
|
2.3.7
|
||||||
|
5
Rakefile
5
Rakefile
@ -7,3 +7,8 @@ RSpec::Core::RakeTask.new(:spec, :example_pattern) do |task, args|
|
|||||||
end
|
end
|
||||||
|
|
||||||
task :default => :spec
|
task :default => :spec
|
||||||
|
|
||||||
|
desc "Build user guide"
|
||||||
|
task :user_guide do
|
||||||
|
system("ruby", "-Ilib", "rb/gen_user_guide.rb")
|
||||||
|
end
|
||||||
|
17
doc/user_guide.md
Normal file
17
doc/user_guide.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#> Overview
|
||||||
|
|
||||||
|
Propane is an LR Parser Generator (LPG) which:
|
||||||
|
|
||||||
|
* accepts LR(0), SLR, and LALR grammars
|
||||||
|
* generates a built-in lexer to tokenize input
|
||||||
|
* supports UTF-8 lexer inputs
|
||||||
|
* generates a table-driven parser to parse input in linear time
|
||||||
|
* is MIT-licensed
|
||||||
|
* is distributable as a standalone Ruby script
|
||||||
|
|
||||||
|
${remove}
|
||||||
|
WARNING: This user guide is meant to be preprocessed and rendered by a custom
|
||||||
|
script.
|
||||||
|
The markdown source file is not intended to be viewed directly and will not
|
||||||
|
include all intended content.
|
||||||
|
${/remove}
|
68
rb/assets/user_guide.html.erb
Normal file
68
rb/assets/user_guide.html.erb
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Propane User Guide<%= subpage_title %> - Version <%= Propane::VERSION %></title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #CCC;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
#body-content {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
background-color: #FFF;
|
||||||
|
width: 120ex;
|
||||||
|
padding: 2ex;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.separator {
|
||||||
|
height: 2em;
|
||||||
|
}
|
||||||
|
.img_block_center {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.page_nav {
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
.page_nav_toc {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.page_nav_next {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.code {
|
||||||
|
padding-left: 2ex;
|
||||||
|
width: 116ex;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.ruby_code .normal {}
|
||||||
|
.ruby_code .comment { color: #005; font-style: italic; }
|
||||||
|
.ruby_code .keyword { color: #A00; font-weight: bold; }
|
||||||
|
.ruby_code .method { color: #077; }
|
||||||
|
.ruby_code .class { color: #074; }
|
||||||
|
.ruby_code .module { color: #050; }
|
||||||
|
.ruby_code .punct { color: #447; font-weight: bold; }
|
||||||
|
.ruby_code .symbol { color: #099; }
|
||||||
|
.ruby_code .string { color: #090; }
|
||||||
|
.ruby_code .char { color: #F07; }
|
||||||
|
.ruby_code .ident { color: #004; }
|
||||||
|
.ruby_code .constant { color: #07F; }
|
||||||
|
.ruby_code .regex { color: #B66; }
|
||||||
|
.ruby_code .number { color: #D55; }
|
||||||
|
.ruby_code .attribute { color: #377; }
|
||||||
|
.ruby_code .global { color: #3B7; }
|
||||||
|
.ruby_code .expr { color: #227; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="body-content"><%= content %></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
227
rb/gen_user_guide.rb
Normal file
227
rb/gen_user_guide.rb
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require "erb"
|
||||||
|
require "fileutils"
|
||||||
|
require "redcarpet"
|
||||||
|
require "syntax"
|
||||||
|
require "syntax/convertors/html"
|
||||||
|
require "propane/version"
|
||||||
|
|
||||||
|
def load_file(file_name)
|
||||||
|
contents = File.read(file_name)
|
||||||
|
contents.gsub(/\$\{include (.*?)\}/) do |match|
|
||||||
|
include_file_name = $1
|
||||||
|
File.read(include_file_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Generator
|
||||||
|
class Section
|
||||||
|
attr_reader :number
|
||||||
|
attr_reader :title
|
||||||
|
attr_reader :page
|
||||||
|
attr_reader :contents
|
||||||
|
attr_reader :anchor
|
||||||
|
def initialize(number, title, page, anchor)
|
||||||
|
@number = number
|
||||||
|
@title = title.gsub(/[`]/, "")
|
||||||
|
@page = page
|
||||||
|
@anchor = anchor
|
||||||
|
@contents = ""
|
||||||
|
end
|
||||||
|
def append(contents)
|
||||||
|
@contents += contents
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Page
|
||||||
|
attr_reader :name
|
||||||
|
attr_reader :title
|
||||||
|
attr_accessor :contents
|
||||||
|
def initialize(name, title, contents = "")
|
||||||
|
@name = name
|
||||||
|
@title = title
|
||||||
|
@contents = contents
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(input, output_file, multi_page)
|
||||||
|
current_page =
|
||||||
|
if multi_page
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
File.basename(output_file).sub(/\.html$/, "")
|
||||||
|
end
|
||||||
|
@sections = []
|
||||||
|
@current_section_number = [0]
|
||||||
|
@lines = input.lines
|
||||||
|
while @lines.size > 0
|
||||||
|
line = @lines.slice!(0)
|
||||||
|
if line =~ /^```(.*)$/
|
||||||
|
@sections.last.append(gather_code_section($1))
|
||||||
|
elsif line.chomp == "${remove}"
|
||||||
|
remove_section
|
||||||
|
elsif line =~ /^(#+)(>)?\s*(.*)$/
|
||||||
|
level_text, new_page_text, title_text = $1, $2, $3
|
||||||
|
level = $1.size
|
||||||
|
new_page = !new_page_text.nil?
|
||||||
|
section_number = get_next_section_number(level)
|
||||||
|
anchor = make_anchor(section_number, title_text)
|
||||||
|
if multi_page and (new_page or current_page.nil?)
|
||||||
|
current_page = anchor
|
||||||
|
end
|
||||||
|
@sections << Section.new(section_number, title_text, current_page, anchor)
|
||||||
|
@sections.last.append("#{level_text} #{section_number} #{title_text}")
|
||||||
|
elsif @sections.size > 0
|
||||||
|
@sections.last.append(line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
renderer = Redcarpet::Render::HTML.new
|
||||||
|
@markdown_renderer = Redcarpet::Markdown.new(renderer)
|
||||||
|
changelog = @markdown_renderer.render(File.read("CHANGELOG.md"))
|
||||||
|
|
||||||
|
@pages = [Page.new("toc", "Table of Contents", render_toc)]
|
||||||
|
@sections.each do |section|
|
||||||
|
unless @pages.last.name == section.page
|
||||||
|
@pages << Page.new(section.page, "#{section.number} #{section.title}")
|
||||||
|
end
|
||||||
|
@pages.last.contents += render_section(section)
|
||||||
|
end
|
||||||
|
|
||||||
|
@pages.each do |page|
|
||||||
|
page.contents.gsub!("${changelog}", changelog)
|
||||||
|
page.contents.gsub!(%r{\$\{#(.+?)\}}) do |match|
|
||||||
|
section_name = $1
|
||||||
|
href = get_link_to_section(section_name)
|
||||||
|
%[<a href="#{href}">#{section_name}</a>]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
template = File.read("rb/assets/user_guide.html.erb")
|
||||||
|
erb = ERB.new(template, trim_mode: "<>")
|
||||||
|
|
||||||
|
if multi_page
|
||||||
|
@pages.each_with_index do |page, page_index|
|
||||||
|
subpage_title = " - #{page.title}"
|
||||||
|
page_nav_bar = render_page_nav_bar(page_index)
|
||||||
|
content = page_nav_bar + separator + page.contents + separator + page_nav_bar
|
||||||
|
html_result = erb.result(binding.clone)
|
||||||
|
File.open(File.join(output_file, "#{page.name}.html"), "w") do |fh|
|
||||||
|
fh.write(html_result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
subpage_title = ""
|
||||||
|
content = @pages.reduce("") do |result, page|
|
||||||
|
result + page.contents
|
||||||
|
end
|
||||||
|
html_result = erb.result(binding.clone)
|
||||||
|
File.open(output_file, "w") do |fh|
|
||||||
|
fh.write(html_result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def separator
|
||||||
|
%[<div class="separator"></div>]
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_page_nav_bar(page_index)
|
||||||
|
page_nav_prev =
|
||||||
|
if page_index > 1
|
||||||
|
%[<a href="#{@pages[page_index - 1].name}.html">« Prev<br/>#{@pages[page_index - 1].title}</a>]
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
page_nav_toc =
|
||||||
|
if page_index > 0
|
||||||
|
%[<a href="toc.html">Table of Contents</a>]
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
page_nav_next =
|
||||||
|
if page_index < @pages.size - 1
|
||||||
|
%[<a href="#{@pages[page_index + 1].name}.html">Next »<br/>#{@pages[page_index + 1].title}</a>]
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
%[<table class="page_nav"><tr>] + \
|
||||||
|
%[<td class="page_nav_prev">#{page_nav_prev}</td>] + \
|
||||||
|
%[<td class="page_nav_toc">#{page_nav_toc}</td>] + \
|
||||||
|
%[<td class="page_nav_next">#{page_nav_next}</td>] + \
|
||||||
|
%[</tr></table>]
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_toc
|
||||||
|
toc_content = %[<h1>Table of Contents</h1>\n]
|
||||||
|
@sections.each do |section|
|
||||||
|
indent = section.number.split(".").size - 1
|
||||||
|
toc_content += %[<span style="padding-left: #{4 * indent}ex;">]
|
||||||
|
toc_content += %[<a href="#{section.page}.html##{section.anchor}">#{section.number} #{section.title}</a><br/>\n]
|
||||||
|
toc_content += %[</span>]
|
||||||
|
end
|
||||||
|
toc_content
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_section(section)
|
||||||
|
%[<a name="#{section.anchor}" />] + \
|
||||||
|
@markdown_renderer.render(section.contents)
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_anchor(section_number, section_title)
|
||||||
|
"s" + ("#{section_number} #{section_title}").gsub(/[^a-zA-Z0-9]/, "_")
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_link_to_section(section_name)
|
||||||
|
section = @sections.find do |section|
|
||||||
|
section.title == section_name
|
||||||
|
end
|
||||||
|
raise "Could not find section #{section_name}" unless section
|
||||||
|
"#{section.page}.html##{section.anchor}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def gather_code_section(syntax)
|
||||||
|
code = ""
|
||||||
|
loop do
|
||||||
|
line = @lines.slice!(0)
|
||||||
|
if line =~ /^```/
|
||||||
|
break
|
||||||
|
end
|
||||||
|
code += line
|
||||||
|
end
|
||||||
|
if syntax != ""
|
||||||
|
convertor = Syntax::Convertors::HTML.for_syntax(syntax)
|
||||||
|
%[<div class="code #{syntax}_code">\n#{convertor.convert(code)}\n</div>\n]
|
||||||
|
else
|
||||||
|
%[<div class="code">\n<pre>#{code}</pre>\n</div>\n]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_section
|
||||||
|
loop do
|
||||||
|
line = @lines.slice!(0)
|
||||||
|
if line.chomp == "${/remove}"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_next_section_number(level)
|
||||||
|
if @current_section_number.size == level - 1
|
||||||
|
@current_section_number << 1
|
||||||
|
elsif @current_section_number.size >= level
|
||||||
|
@current_section_number[level - 1] += 1
|
||||||
|
@current_section_number.slice!(level, @current_section_number.size)
|
||||||
|
else
|
||||||
|
raise "Section level change from #{@current_section_number.size} to #{level}"
|
||||||
|
end
|
||||||
|
@current_section_number.join(".")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
input = load_file("doc/user_guide.md")
|
||||||
|
FileUtils.rm_rf("gen/user_guide")
|
||||||
|
FileUtils.mkdir_p("gen/user_guide")
|
||||||
|
Generator.new(input, "gen/user_guide/user_guide.html", false)
|
||||||
|
Generator.new(input, "gen/user_guide", true)
|
Loading…
x
Reference in New Issue
Block a user