glyph 0.3.0 → 0.4.0

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 (162) hide show
  1. data/.gitignore +7 -0
  2. data/AUTHORS.textile +8 -7
  3. data/CHANGELOG.textile +89 -8
  4. data/LICENSE.textile +1 -2
  5. data/README.textile +89 -61
  6. data/Rakefile +12 -10
  7. data/VERSION +1 -1
  8. data/benchmark.rb +1 -1
  9. data/book/config.yml +18 -4
  10. data/book/document.glyph +269 -45
  11. data/book/images/glyph/commands_tasks.png +0 -0
  12. data/book/images/{document_generation.png → glyph/document_generation.png} +0 -0
  13. data/book/images/glyph/glyph.eps +123 -0
  14. data/book/images/glyph/glyph.png +0 -0
  15. data/book/images/glyph/glyph.svg +29 -0
  16. data/book/lib/commands/commands.rb +11 -0
  17. data/book/lib/layouts/bookindex.glyph +127 -0
  18. data/book/lib/layouts/bookpage.glyph +129 -0
  19. data/book/lib/layouts/project.glyph +26 -0
  20. data/book/lib/macros/reference.rb +27 -7
  21. data/book/lib/tasks/tasks.rake +52 -0
  22. data/book/snippets.yml +1 -1
  23. data/book/text/{acknowledgement.glyph → acknowledgements.glyph} +4 -2
  24. data/book/text/changelog.glyph +29 -3
  25. data/book/text/compiling/compiling.glyph +44 -20
  26. data/book/text/compiling/lite_mode.glyph +0 -4
  27. data/book/text/compiling/programmatic_usage.glyph +1 -5
  28. data/book/text/config/document.glyph +35 -0
  29. data/book/text/config/filters.glyph +28 -0
  30. data/book/text/config/options.glyph +25 -0
  31. data/book/text/config/output.glyph +83 -0
  32. data/book/text/extending/bookmarks_headers.glyph +0 -5
  33. data/book/text/extending/command.glyph +56 -0
  34. data/book/text/extending/commands_tasks.glyph +39 -0
  35. data/book/text/extending/further_reading.glyph +0 -3
  36. data/book/text/extending/internals.glyph +3 -5
  37. data/book/text/extending/interpreting.glyph +0 -4
  38. data/book/text/extending/layouts.glyph +68 -0
  39. data/book/text/extending/macro_def.glyph +0 -5
  40. data/book/text/extending/output_format.glyph +78 -0
  41. data/book/text/extending/params_attrs.glyph +0 -3
  42. data/book/text/extending/placeholders.glyph +0 -4
  43. data/book/text/extending/task.glyph +46 -0
  44. data/book/text/extending/validators.glyph +5 -6
  45. data/book/text/getting_started/configuration.glyph +1 -5
  46. data/book/text/getting_started/create_project.glyph +1 -5
  47. data/book/text/getting_started/structure.glyph +0 -4
  48. data/book/text/introduction.glyph +100 -75
  49. data/book/text/license.glyph +1 -2
  50. data/book/text/macros/macros_block.glyph +8 -4
  51. data/book/text/macros/macros_core.glyph +0 -3
  52. data/book/text/macros/macros_filters.glyph +2 -7
  53. data/book/text/macros/macros_inline.glyph +0 -4
  54. data/book/text/macros/macros_structure.glyph +0 -4
  55. data/book/text/ref_commands.glyph +29 -7
  56. data/book/text/stats/bookmarks.glyph +49 -0
  57. data/book/text/stats/links.glyph +90 -0
  58. data/book/text/stats/macros.glyph +73 -0
  59. data/book/text/stats/snippets.glyph +50 -0
  60. data/book/text/stats/stats.glyph +79 -0
  61. data/book/text/text_editing/attribute_intro.glyph +22 -0
  62. data/book/text/text_editing/code.glyph +0 -5
  63. data/book/text/text_editing/conditionals.glyph +0 -4
  64. data/book/text/text_editing/esc_quot.glyph +64 -0
  65. data/book/text/text_editing/evaluation.glyph +0 -3
  66. data/book/text/text_editing/glyph_files.glyph +0 -3
  67. data/book/text/text_editing/images.glyph +0 -5
  68. data/book/text/text_editing/inclusions.glyph +0 -4
  69. data/book/text/text_editing/links.glyph +2 -7
  70. data/book/text/text_editing/macro_intro.glyph +1 -98
  71. data/book/text/text_editing/raw_html.glyph +0 -87
  72. data/book/text/text_editing/section_aliases.glyph +28 -0
  73. data/book/text/text_editing/sections.glyph +1 -32
  74. data/book/text/text_editing/stylesheets.glyph +3 -5
  75. data/book/text/text_editing/topics.glyph +33 -0
  76. data/book/text/text_editing/xml_fallback.glyph +73 -0
  77. data/book/text/troubleshooting/errors_command.glyph +0 -3
  78. data/book/text/troubleshooting/errors_generic.glyph +21 -6
  79. data/book/text/troubleshooting/errors_macro.glyph +11 -8
  80. data/book/text/troubleshooting/errors_parser.glyph +0 -3
  81. data/config.yml +60 -25
  82. data/glyph.gemspec +90 -36
  83. data/layouts/web/index.glyph +16 -0
  84. data/layouts/web/topic.glyph +15 -0
  85. data/layouts/web5/index.glyph +16 -0
  86. data/layouts/web5/topic.glyph +17 -0
  87. data/lib/glyph.rb +36 -49
  88. data/lib/glyph/analyzer.rb +253 -0
  89. data/lib/glyph/bookmark.rb +92 -0
  90. data/lib/glyph/commands.rb +9 -221
  91. data/lib/glyph/commands/add.rb +8 -0
  92. data/lib/glyph/commands/compile.rb +93 -0
  93. data/lib/glyph/commands/config.rb +38 -0
  94. data/lib/glyph/commands/init.rb +6 -0
  95. data/lib/glyph/commands/outline.rb +45 -0
  96. data/lib/glyph/commands/stats.rb +48 -0
  97. data/lib/glyph/commands/todo.rb +29 -0
  98. data/lib/glyph/config.rb +2 -0
  99. data/lib/glyph/document.rb +61 -30
  100. data/lib/glyph/interpreter.rb +2 -2
  101. data/lib/glyph/macro.rb +14 -5
  102. data/lib/glyph/macro_helpers.rb +280 -0
  103. data/lib/glyph/macro_validators.rb +37 -2
  104. data/lib/glyph/reporter.rb +182 -0
  105. data/lib/glyph/syntax_node.rb +37 -10
  106. data/lib/glyph/system_extensions.rb +8 -45
  107. data/lib/glyph/utils.rb +148 -0
  108. data/macros/core.rb +10 -15
  109. data/macros/filters.rb +4 -5
  110. data/macros/html/block.rb +46 -30
  111. data/macros/html/inline.rb +9 -35
  112. data/macros/html/structure.rb +59 -72
  113. data/macros/html5/block.rb +69 -0
  114. data/macros/html5/inline.rb +24 -0
  115. data/macros/html5/structure.rb +139 -0
  116. data/macros/xml.rb +1 -1
  117. data/spec/files/custom_command.rb +6 -0
  118. data/spec/files/custom_tasks.rake +6 -0
  119. data/spec/files/document_for_stats.glyph +12 -0
  120. data/spec/files/references.glyph +4 -0
  121. data/spec/files/web1.glyph +11 -0
  122. data/spec/files/web2.glyph +10 -0
  123. data/spec/files/web_doc.glyph +23 -0
  124. data/spec/lib/analyzer_spec.rb +137 -0
  125. data/spec/lib/bookmark_spec.rb +64 -0
  126. data/spec/lib/commands_spec.rb +30 -5
  127. data/spec/lib/document_spec.rb +49 -9
  128. data/spec/lib/glyph_spec.rb +21 -1
  129. data/spec/lib/macro_spec.rb +6 -6
  130. data/spec/lib/macro_validators_spec.rb +24 -0
  131. data/spec/lib/reporter_spec.rb +132 -0
  132. data/spec/macros/core_spec.rb +2 -3
  133. data/spec/macros/filters_spec.rb +2 -2
  134. data/spec/macros/html5_spec.rb +101 -0
  135. data/spec/macros/macros_spec.rb +16 -6
  136. data/spec/macros/web5_spec.rb +32 -0
  137. data/spec/macros/web_spec.rb +59 -0
  138. data/spec/macros/xml_spec.rb +1 -1
  139. data/spec/spec_helper.rb +24 -4
  140. data/spec/tasks/generate_spec.rb +54 -0
  141. data/spec/tasks/load_spec.rb +29 -3
  142. data/spec/tasks/project_spec.rb +21 -3
  143. data/styles/default.css +40 -4
  144. data/styles/pagination.css +59 -41
  145. data/tasks/generate.rake +110 -31
  146. data/tasks/load.rake +39 -7
  147. data/tasks/project.rake +9 -7
  148. metadata +115 -34
  149. data/book/images/glyph.png +0 -0
  150. data/book/images/glyph.svg +0 -351
  151. data/book/output/html/glyph.html +0 -4482
  152. data/book/output/html/images/document_generation.png +0 -0
  153. data/book/output/html/images/glyph.png +0 -0
  154. data/book/output/html/images/glyph.svg +0 -351
  155. data/book/output/pdf/glyph.pdf +4 -10254
  156. data/book/script/authors +0 -1
  157. data/book/script/changelog +0 -1
  158. data/book/script/license +0 -1
  159. data/book/script/readme +0 -1
  160. data/book/text/ref_config.glyph +0 -100
  161. data/book/text/ref_macros.glyph +0 -6
  162. data/book/text/troubleshooting/errors_intro.glyph +0 -3
@@ -0,0 +1,6 @@
1
+ GLI.desc 'Create a new Glyph project'
2
+ command :init do |c|
3
+ c.action do |global_options,options,args|
4
+ Glyph.run 'project:create', Dir.pwd
5
+ end
6
+ end
@@ -0,0 +1,45 @@
1
+ GLI.desc 'Display the document outline'
2
+ command :outline do |c|
3
+ c.desc "Limit to level N"
4
+ c.flag :l, :level
5
+ c.desc "Show file names"
6
+ c.switch :f, :files
7
+ c.desc "Show titles"
8
+ c.switch :t, :titles
9
+ c.desc "Show IDs"
10
+ c.switch :i, :ids
11
+ c.action do |global_options, options, args|
12
+ levels = options[:l]
13
+ ids = options[:i]
14
+ files = options[:f]
15
+ titles = options[:t]
16
+ titles = true if !ids && !levels && !files || levels && !ids
17
+ Glyph['system.quiet'] = true
18
+ Glyph.run "generate:document"
19
+ Glyph['system.quiet'] = false
20
+ puts "====================================="
21
+ puts "#{Glyph['document.title']} - Outline"
22
+ puts "====================================="
23
+ Glyph.document.structure.descend do |n, level|
24
+ if n.is_a?(Glyph::MacroNode) then
25
+ case
26
+ when n[:name].in?(Glyph['system.structure.headers']) then
27
+ header = Glyph.document.header?(n[:header].code) rescue nil
28
+ next if !header || levels && header.level-1 > levels.to_i
29
+ last_level = header.level
30
+ h_id = ids ? "[##{header.code}]" : ""
31
+ h_title = titles ? "#{header.title} " : ""
32
+ text = (" "*(header.level-1))+"- "+h_title+h_id
33
+ puts text unless text.blank?
34
+ when n[:name] == :include then
35
+ if files && n.find_parent{|p| p[:name] == :document && p.is_a?(Glyph::MacroNode)} then
36
+ # When using the book or article macros, includes appear twice:
37
+ # * in the macro parameters
38
+ # * as children of the document macro
39
+ puts "=== #{n.param(0)}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ GLI.desc 'Display statistics'
2
+ command :stats do |c|
3
+ c.desc "Display stats about macros"
4
+ c.switch [:m, :macros]
5
+ c.desc "Display stats about snippets"
6
+ c.switch [:s, :snippets]
7
+ c.desc "Display stats about bookmarks"
8
+ c.switch [:b, :bookmarks]
9
+ c.desc "Display stats about links"
10
+ c.switch [:l, :links]
11
+ c.desc "Display stats about project files"
12
+ c.switch [:f, :files]
13
+ c.desc "Display stats about a single macro"
14
+ c.flag :macro
15
+ c.desc "Display stats about a single bookmark"
16
+ c.flag :bookmark
17
+ c.desc "Display stats about links matching a regular expression"
18
+ c.flag :link
19
+ c.desc "Display stats about a single snippet"
20
+ c.flag :snippet
21
+ c.action do |global_options, options, args|
22
+ Glyph.info "Collecting stats..."
23
+ Glyph.run 'generate:document'
24
+ analyzer = Glyph::Analyzer.new
25
+ no_switches = true
26
+ [[:m, :macros], [:s, :snippets], [:b, :bookmarks], [:l, :links], [:f, :files]].each do |s|
27
+ if options[s[0]] then
28
+ analyzer.stats_for s[1]
29
+ no_switches = false
30
+ end
31
+ end
32
+ no_flags = true
33
+ [:macro, :bookmark, :link, :snippet].each do |f|
34
+ if options[f] then
35
+ analyzer.stats_for f, options[f]
36
+ no_flags = false
37
+ end
38
+ end
39
+ analyzer.stats_for :global if no_switches && no_flags
40
+ puts "====================================="
41
+ puts "#{Glyph['document.title']} - Statistics"
42
+ puts "====================================="
43
+ puts
44
+ reporter = Glyph::Reporter.new(analyzer.stats)
45
+ reporter.detailed = false if no_switches && no_flags
46
+ reporter.display
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ GLI.desc 'Display all project TODO items'
2
+ command :todo do |c|
3
+ c.action do |global_options, options, args|
4
+ Glyph['system.quiet'] = true
5
+ Glyph.run "generate:document"
6
+ Glyph['system.quiet'] = false
7
+ unless Glyph.document.todos.blank?
8
+ puts "====================================="
9
+ puts "#{Glyph['document.title']} - TODOs"
10
+ puts "====================================="
11
+ # Group items
12
+ if Glyph.document.todos.respond_to? :group_by then
13
+ Glyph.document.todos.group_by{|e| e[:source]}.each_pair do |k, v|
14
+ puts
15
+ puts "=== #{k} "
16
+ v.each do |i|
17
+ puts " * #{i[:text]}"
18
+ end
19
+ end
20
+ else
21
+ Glyph.document.todos.each do |t|
22
+ Glyph.info t
23
+ end
24
+ end
25
+ else
26
+ Glyph.info "Nothing left to do."
27
+ end
28
+ end
29
+ end
data/lib/glyph/config.rb CHANGED
@@ -4,6 +4,8 @@ module Glyph
4
4
  # and provides some useful methods to access keys and subkeys.
5
5
  class Config
6
6
 
7
+ include Glyph::Utils
8
+
7
9
  # Initializes the configuration with a hash of options:
8
10
  # * :file (default: nil) - A YAML file to read data from
9
11
  # * :data (default: {})- The initial contents
@@ -18,24 +18,28 @@ module Glyph
18
18
  ['\\|', '|']
19
19
  ]
20
20
 
21
- attr_reader :bookmarks, :placeholders, :headers, :context, :errors, :todos
21
+ attr_reader :bookmarks, :placeholders, :headers, :styles, :context, :errors, :todos, :topics, :links, :toc
22
22
 
23
23
  # Creates a new document
24
24
  # @param [GlyphSyntaxNode] tree the syntax tree to be evaluate
25
25
  # @param [Glyph::Node] context the context associated with the tree
26
26
  # @raise [RuntimeError] unless tree responds to :evaluate
27
- def initialize(tree, context)
27
+ def initialize(tree, context={})
28
28
  @tree = tree
29
29
  @context = context
30
- @bookmarks = {}
30
+ @context[:source] ||= {:file => nil, :name => '--', :topic => nil}
31
31
  @placeholders = {}
32
- @headers = []
32
+ @bookmarks = {}
33
+ @headers = {}
34
+ @styles = []
33
35
  @errors = []
34
36
  @todos = []
37
+ @topics = []
38
+ @links = []
39
+ @toc = {}
35
40
  @state = :new
36
41
  end
37
42
 
38
-
39
43
  # Returns a tree of Glyph::Node objects corresponding to the analyzed document
40
44
  # @raise [RuntimeError] unless the document has been analized
41
45
  def structure
@@ -43,13 +47,18 @@ module Glyph
43
47
  @tree
44
48
  end
45
49
 
46
- # Copies bookmarks, headers, todos and placeholders from another Glyph::Document
50
+ # Copies bookmarks, headers, todos, styles and placeholders from another Glyph::Document
47
51
  # @param [Glyph::Document] document a valid Glyph::Document
48
52
  def inherit_from(document)
49
53
  @bookmarks = document.bookmarks
50
54
  @headers = document.headers
51
55
  @todos = document.todos
56
+ @styles = document.styles
57
+ @topics = document.topics
52
58
  @placeholders = document.placeholders
59
+ @toc = document.toc
60
+ @links = document.links
61
+ self
53
62
  end
54
63
 
55
64
  # Defines a placeholder block that will be evaluated after the whole document has been analyzed
@@ -63,34 +72,51 @@ module Glyph
63
72
 
64
73
  # Returns a stored bookmark or nil
65
74
  # @param [#to_sym] key the bookmark identifier
66
- # @return [Hash, nil] the bookmark hash or nil if no bookmark is found
75
+ # @return [Glyph::Bookmark, nil] the bookmark or nil if no bookmark is found
67
76
  def bookmark?(key)
68
77
  @bookmarks[key.to_sym]
69
78
  end
70
79
 
71
80
  # Stores a new bookmark
72
- # @param [Hash] hash the bookmark hash: {:id => "Bookmark ID", :title => "Bookmark Title"}
73
- # @return [Hash] the stored bookmark (:id is converted to a symbol)
81
+ # @param [Hash] hash the bookmark hash: {:id => "BookmarkID", :title => "Bookmark Title", :file => "dir/preface.glyph"}
82
+ # @return [Glyph::Bookmark] the stored bookmark
83
+ # @raise [RuntimeError] if the bookmark is already defined.
74
84
  def bookmark(hash)
75
- ident = hash[:id].to_sym
76
- hash[:id] = ident
77
- @bookmarks[ident] = hash
78
- hash
85
+ b = Glyph::Bookmark.new(hash)
86
+ raise RuntimeError, "Bookmark '#{b.code}' already exists" if @bookmarks.has_key? b.code
87
+ @bookmarks[b.code] = b
88
+ b
79
89
  end
80
90
 
81
91
  # Stores a new header
82
- # @param [Hash] hash the header hash: {:id => "Bookmark ID", :title => "Bookmark Title", :level => 3}
83
- # @return [Hash] the stored header
92
+ # @param [Hash] hash the header hash: {:id => "Bookmark_ID", :title => "Bookmark Title", :level => 3}
93
+ # @return [Glyph::Header] the stored header
94
+ # @raise [RuntimeError] if the bookmark is already defined.
84
95
  def header(hash)
85
- @headers << hash
86
- hash
96
+ b = Glyph::Header.new(hash)
97
+ raise RuntimeError, "Bookmark '#{b.code}' already exists" if @bookmarks.has_key? b.code
98
+ @bookmarks[b.code] = b
99
+ @headers[b.code] = b
100
+ b
87
101
  end
88
102
 
89
103
  # Returns a stored header or nil
90
- # @param [String] key the header identifier
91
- # @return [Hash, nil] the header hash or nil if no header is found
104
+ # @param [String, Symbol] key the header identifier
105
+ # @return [Glyph::Header, nil] the header or nil if no header is found
92
106
  def header?(key)
93
- @headers.select{|h| h[:id] == key}[0] rescue nil
107
+ @headers[key.to_sym]
108
+ end
109
+
110
+ # @since 0.4.0
111
+ # Stores a stylesheet
112
+ # @param [String] file the stylesheet file
113
+ # @raises [RuntimeError] if the stylesheet is already specified for the document (unless the output has more than one file)
114
+ def style(file)
115
+ f = Pathname.new file
116
+ if @styles.include?(f) && !Glyph.multiple_output_files? then
117
+ raise RuntimeError, "Stylesheet '#{f}' already specified for the current document"
118
+ end
119
+ @styles << f
94
120
  end
95
121
 
96
122
  # Analyzes the document by evaluating its @tree
@@ -114,18 +140,23 @@ module Glyph
114
140
  raise RuntimeError, "Document cannot be finalized due to previous errors" unless @context[:document].errors.blank?
115
141
  # Substitute placeholders
116
142
  ESCAPES.each{|e| @output.gsub! e[0], e[1]}
117
- begin
118
- @placeholders.each_pair do |key, value|
119
- begin
120
- @output.gsub! key.to_s, value.call(self).to_s
121
- rescue Glyph::MacroError => e
122
- e.macro.macro_warning e.message, e
123
- rescue Exception => e
124
- puts e.class
125
- Glyph.warning e.message
143
+ @placeholders.each_pair do |key, value|
144
+ begin
145
+ key_s = key.to_s
146
+ value_s = value.call(self).to_s
147
+ toc[:contents].gsub! key_s, value_s if toc[:contents].to_s.match key_s
148
+ if Glyph.multiple_output_files? then
149
+ @topics.each do |t|
150
+ ESCAPES.each{|e| t[:contents].gsub! e[0], e[1]}
151
+ t[:contents].gsub! key_s, value_s
152
+ end
126
153
  end
154
+ @output.gsub! key_s, value_s
155
+ rescue Glyph::MacroError => e
156
+ e.macro.macro_warning e.message, e
157
+ rescue Exception => e
158
+ Glyph.warning e.message
127
159
  end
128
- rescue Exception => e
129
160
  end
130
161
  @state = :finalized
131
162
  end
@@ -11,7 +11,7 @@ module Glyph
11
11
  # @param [Hash] context the context to pass along when expanding macros
12
12
  def initialize(text, context={})
13
13
  @context = context
14
- @context[:source] ||= {:name => "--"}
14
+ @context[:source] ||= {:name => "--", :file => nil, :topic => nil}
15
15
  @text = text
16
16
  @parser = Glyph::Parser.new text, @context[:source][:name]
17
17
  end
@@ -47,7 +47,7 @@ module Glyph
47
47
  @document.inherit_from @context[:document] if @context[:document]
48
48
  @tree
49
49
  end
50
-
50
+
51
51
  end
52
52
  end
53
53
 
data/lib/glyph/macro.rb CHANGED
@@ -8,8 +8,10 @@ module Glyph
8
8
  class Macro
9
9
 
10
10
  include Validators
11
+ include Helpers
12
+ include Utils
11
13
 
12
- attr_reader :node, :source
14
+ attr_reader :node, :source_name, :source_file, :source_topic
13
15
 
14
16
  # Creates a new macro instance from a Node
15
17
  # @param [Node] node a node populated with macro data
@@ -17,14 +19,20 @@ module Glyph
17
19
  @node = node
18
20
  @name = @node[:name]
19
21
  @updated_source = nil
20
- @source = @node[:source][:name] rescue "--"
22
+ @source_name = @node[:source][:name] || nil rescue "--"
23
+ @source_topic = @node[:source][:topic] || nil rescue "--"
24
+ @source_file = @node[:source][:file] rescue nil
21
25
  end
22
26
 
23
27
  # Resets the name of the updated source (call before calling
24
28
  # Macro#interpret)
25
29
  # @param [String] name the source name
26
- def update_source(name)
27
- @updated_source = {:node => @node, :name => name}
30
+ # @param [String] file the source file
31
+ # @param [String] topic the topic file
32
+ # @since 0.3.0
33
+ def update_source(name, file=nil, topic=nil)
34
+ file ||= @node[:source][:file] rescue nil
35
+ @updated_source = {:name => name, :file => file, :topic => topic}
28
36
  end
29
37
 
30
38
  # Returns a Glyph code representation of the specified parameter
@@ -180,7 +188,7 @@ module Glyph
180
188
  if e.is_a?(Glyph::MacroError) then
181
189
  e.display
182
190
  else
183
- message = "#{msg}\n source: #{@source}\n path: #{path}"
191
+ message = "#{msg}\n source: #{@source_name}\n path: #{path}"
184
192
  if Glyph.debug? then
185
193
  message << %{\n#{"-"*54}\n#{@node.to_s.gsub(/\t/, ' ')}\n#{"-"*54}}
186
194
  if e then
@@ -206,6 +214,7 @@ module Glyph
206
214
  context[:document] = @node[:document]
207
215
  interpreter = Glyph::Interpreter.new string, context
208
216
  subtree = interpreter.parse
217
+ subtree[:source] = context[:source]
209
218
  @node << subtree
210
219
  result = interpreter.document.output
211
220
  end
@@ -0,0 +1,280 @@
1
+ module Glyph
2
+ class Macro
3
+
4
+ # This module includes some output-agnostic methods used by the most common Glyph macros.
5
+ # @since 0.4.0
6
+ module Helpers
7
+
8
+ # Renders a link
9
+ # @param [String] target the target of the link
10
+ # @param[String] title the title of the link
11
+ # @yield [link_path, link_title] the block to call to render the link
12
+ # @yieldparam [String] link_path the path to the link target
13
+ # @yieldparam [String] link_title the title of the link
14
+ def link_element_for(target, title, &block)
15
+ if target.match /^#/ then
16
+ @node[:document].links << target
17
+ anchor = target.gsub /^#/, ''
18
+ bmk = bookmark? anchor
19
+ if !bmk then
20
+ placeholder do |document|
21
+ bmk = document.bookmark?(anchor)
22
+ macro_error "Bookmark '#{anchor}' does not exist" unless bmk
23
+ bmk_title = title
24
+ bmk_title = bmk.title if bmk_title.blank?
25
+ block.call bmk.link(@source_file), bmk_title
26
+ end
27
+ else
28
+ bmk_title = title
29
+ bmk_title = bmk.title if bmk_title.blank?
30
+ block.call bmk.link(@source_file), bmk_title
31
+ end
32
+ else
33
+ if Glyph['options.url_validation'] && !@node[:document].links.include?(target) then
34
+ begin
35
+ url = URI.parse(target.gsub(/\\\./, ''))
36
+ rescue Exception => e
37
+ macro_warning "Invalid URL: #{url||target}", e
38
+ end
39
+ response = Net::HTTP.get_response(url)
40
+ debug "Checking link URL: #{url} (#{response.code})"
41
+ if response.code.to_i > 302 then
42
+ macro_warning "Linked URL '#{url}' returned status #{response.code} (#{response.message})"
43
+ end
44
+ end
45
+ @node[:document].links << target
46
+ title ||= target
47
+ block.call target, title
48
+ end
49
+ end
50
+
51
+ # Renders a For More Information note
52
+ # @param [String] topic the topic of the note
53
+ # @param [String] href the reference to link to
54
+ # @yield [topic, link] the block used to render the FMI note
55
+ def fmi_element_for(topic, href, &block)
56
+ link = placeholder do |document|
57
+ interpret "link[#{href}]"
58
+ end
59
+ block.call topic, link
60
+ end
61
+
62
+ # Renders a draft comment element
63
+ # @yield [value] the block used to render the comment
64
+ # @yieldparam [String] value the comment text
65
+ def draftcomment_element(&block)
66
+ if Glyph['document.draft'] then
67
+ block.call value
68
+ else
69
+ ""
70
+ end
71
+ end
72
+
73
+ # Renders a todo element
74
+ # @yield [value] the block used to render the todo element
75
+ # @yieldparam [String] value the todo text
76
+ def todo_element(&block)
77
+ todo = {:source => @source_name, :text => value}
78
+ @node[:document].todos << todo unless @node[:document].todos.include? todo
79
+ if Glyph['document.draft'] then
80
+ block.call value
81
+ else
82
+ ""
83
+ end
84
+ end
85
+
86
+ # Renders an image element
87
+ # @param [String] image the image to render
88
+ # @param [String] alt the value of the image's ALT tag
89
+ # @yield [alt, dest_file] the block used to render the image
90
+ def image_element_for(image, alt, &block)
91
+ src_file = Glyph.lite? ? image : Glyph::PROJECT/"images/#{image}"
92
+ dest_file = Glyph.lite? ? image : "images/#{image}"
93
+ warning "Image '#{image}' not found" unless Pathname.new(src_file).exist?
94
+ block.call alt, dest_file
95
+ end
96
+
97
+ # Renders a figure element
98
+ # @param [String] image the image to render
99
+ # @param [String] alt the value of the image's ALT tag
100
+ # @param [String] caption
101
+ # @yield alt, dest_file, caption] the block used to render the figure
102
+ def figure_element_for(image, alt, caption, &block)
103
+ src_file = Glyph.lite? ? image : Glyph::PROJECT/"images/#{image}"
104
+ dest_file = Glyph.lite? ? image : "images/#{image}"
105
+ warning "Figure '#{image}' not found" unless Pathname.new(src_file).exist?
106
+ block.call alt, dest_file, caption
107
+ end
108
+
109
+ # Renders a title element
110
+ def title_element(&block)
111
+ unless Glyph["document.title"].blank? then
112
+ block.call
113
+ else
114
+ ""
115
+ end
116
+ end
117
+
118
+ # Renders a subtitle element
119
+ def subtitle_element(&block)
120
+ unless Glyph["document.subtitle"].blank? then
121
+ block.call
122
+ else
123
+ ""
124
+ end
125
+ end
126
+
127
+ # Renders an author element
128
+ def author_element(&block)
129
+ unless Glyph['document.author'].blank? then
130
+ block.call
131
+ else
132
+ ""
133
+ end
134
+ end
135
+
136
+ # Renders a revision element
137
+ def revision_element(&block)
138
+ unless Glyph["document.revision"].blank? then
139
+ block.call
140
+ else
141
+ ""
142
+ end
143
+ end
144
+
145
+ # Renders a Table of Contents
146
+ # @param [Integer] depth the maximum header level
147
+ # @param [String] title the title of the TOC
148
+ # @param [Hash] procs the Proc objects used to render the TOC
149
+ # @option procs [Proc] :link used to render TOC header links (parameters: Glyph::Header).
150
+ # @option procs [Proc] :toc_list used to render the TOC list (parameters: a Proc used to traverse the document tree, the Glyph::Bookmark used for the TOC header, a Glyph::Document)
151
+ # @option procs [Proc] :toc_item used to render a TOC item (parameters: an Array of header classes, a String used for the header link)
152
+ # @option procs [Proc] :toc_sublist used to render a TOC sublist (parameters: a String containing the contents of the list)
153
+ def toc_element_for(depth, title, procs={})
154
+ return @node[:document].toc[:contents] if @node[:document].toc[:contents]
155
+ link_header = procs[:link]
156
+ toc = placeholder do |document|
157
+ descend_section = lambda do |n1, added_headers|
158
+ list = ""
159
+ added_headers ||= []
160
+ n1.descend do |n2, level|
161
+ if n2.is_a?(Glyph::MacroNode) && Glyph['system.structure.headers'].include?(n2[:name]) then
162
+ if Glyph.multiple_output_files? then
163
+ # Only consider topics/booklets when building TOC for web/web5
164
+ next if !n2.attribute(:src) && n2.child_macros.select{|child| child.attribute(:src)}.blank?
165
+ end
166
+ next if n2.find_parent{|node| Glyph['system.structure.special'].include? node[:name] }
167
+ header_hash = n2[:header]
168
+ next if depth && header_hash && (header_hash.level-1 > depth.to_i) || header_hash && !header_hash.toc?
169
+ next if added_headers.include? header_hash
170
+ added_headers << header_hash
171
+ # Check if part of frontmatter, bodymatter or backmatter
172
+ container = n2.find_parent do |node|
173
+ node.is_a?(Glyph::MacroNode) &&
174
+ node[:name].in?([:frontmatter, :bodymatter, :appendix, :backmatter])
175
+ end[:name] rescue nil
176
+ list << procs[:toc_item].call([container, n2[:name]], link_header.call(header_hash)) if header_hash
177
+ child_list = ""
178
+ n2.children.each do |c|
179
+ child_list << descend_section.call(c, added_headers)
180
+ end
181
+ list << procs[:toc_sublist].call(child_list) unless child_list.blank?
182
+ end
183
+ end
184
+ list
185
+ end
186
+ title ||= "Table of Contents"
187
+ bmk = @node[:document].bookmark?(:toc) || bookmark(:id => :toc, :file => @source_file, :title => title)
188
+ procs[:toc_list].call descend_section, bmk, document
189
+ end
190
+ @node[:document].toc[:contents] = toc.to_s
191
+ toc
192
+ end
193
+
194
+ # Renders a section element
195
+ # @param [Hash] procs the Proc objects used to render the section
196
+ # @option procs [Proc] :title used to render the section header (parameters: the header level, the section ID, the section title)
197
+ # @option procs [Proc] :body used to render the section body (parameters: the section title, the section body)
198
+ def section_element_for(procs={})
199
+ h = ""
200
+ if attr(:title) then
201
+ level = 1
202
+ @node.ascend do |n|
203
+ break if n.respond_to?(:attribute) && n.attribute(:class) && n.attribute(:class).children.join.strip == "topic"
204
+ if n.is_a?(Glyph::MacroNode) && Glyph["system.structure.headers"].include?(n[:name]) then
205
+ level+=1
206
+ end
207
+ end
208
+ ident = (attr(:id) || "h_#{@node[:document].headers.length+1}").to_sym
209
+ # The bookmark is added when the section is first processed; therefore it will exist already when a topic layout is processed
210
+ bmk = @node[:document].bookmark?(ident)
211
+ bmk ||= header :title => attr(:title),
212
+ :level => level,
213
+ :id => ident,
214
+ :toc => !attr(:notoc),
215
+ :definition => @source_file,
216
+ :file => (attr(:src) || @source_file)
217
+ @node[:header] = bmk
218
+ h = procs[:title].call level, bmk, attr(:title)
219
+ end
220
+ if attr(:src) then
221
+ # Create topic
222
+ if Glyph.multiple_output_files?
223
+ topic_id = (attr(:id) || "t_#{@node[:document].topics.length}").to_sym
224
+ layout = attr(:layout) || Glyph["output.#{Glyph['document.output']}.layouts.topic"] || :topic
225
+ layout_name = "layout:#{layout}".to_sym
226
+ macro_error "Layout '#{layout}' not found" unless Glyph::MACROS[layout_name]
227
+ result = interpret %{#{layout_name}[
228
+ @title[#{attr(:title)}]
229
+ @id[#{topic_id}]
230
+ @contents[include[@topic[true]#{attr(:src)}]]
231
+ ]}
232
+ bmk = @node[:document].bookmark? topic_id
233
+ if bmk then
234
+ # Fix file for topic bookmark
235
+ @node[:document].bookmark?(topic_id).file = attr(:src)
236
+ else
237
+ bookmark :title => attr(:title), :id => topic_id, :file => attr(:src), :definition => @source_file
238
+ end
239
+ topic_src = attr(:src)
240
+ topic_src += ".glyph" unless topic_src.match /\..+$/
241
+ @node[:document].topics << {:src => topic_src, :title => attr(:title), :id => topic_id, :contents => result}
242
+ # Process section contents
243
+ procs[:body].call h, value
244
+ # Return nothing
245
+ nil
246
+ else
247
+ v = raw_value
248
+ @node.children.delete_if{|c| !c.is_a?(Glyph::AttributeNode)}
249
+ body = interpret "include[#{attr(:src)}]#{v}"
250
+ procs[:body].call h, body
251
+ end
252
+ else
253
+ procs[:body].call h, value
254
+ end
255
+ end
256
+
257
+ # Renders a navigation element
258
+ # @param [String] topic_id the ID of the current topic
259
+ # @param [Hash] procs the Proc objects used to render the navigation element
260
+ # @option procs [Proc] :previous the link to the previous topic
261
+ # @option procs [Proc] :next the link to the next topic
262
+ # @option procs [Proc] :contents the link to the document contents
263
+ def navigation_element_for(topic_id, procs={})
264
+ # Get the previous topic
265
+ previous_topic = @node[:document].topics.last
266
+ previous_link = procs[:previous].call previous_topic
267
+ # The next topic is not going to be available yet, use a placeholder
268
+ next_link = placeholder do |document|
269
+ current_topic = document.topics.select{|t| t[:id] == topic_id}[0] rescue nil
270
+ next_topic = document.topics[document.topics.index(current_topic)+1] rescue nil
271
+ procs[:next].call next_topic
272
+ end
273
+ contents_link = procs[:contents].call
274
+ procs[:navigation].call contents_link, previous_link, next_link
275
+ end
276
+
277
+
278
+ end
279
+ end
280
+ end