smartgen 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog.md CHANGED
@@ -1,3 +1,17 @@
1
+ 0.2.0
2
+ -----
3
+
4
+ * Fixed README to explain that only directories may be specified in assets config
5
+ * Added support for a table of contents of every generated page
6
+ * ObjectHash will output a warning instead of raising error when a missing key accessor method is called.
7
+ * Added documentation for classes and some improvements in README
8
+
9
+ 0.1.3
10
+ -----
11
+
12
+ * Bug Fix: Error when declareted file not exists
13
+ * Bug Fix: Error when metadata not exists
14
+
1
15
  0.1.2
2
16
  -----
3
17
 
data/Gemfile.lock CHANGED
@@ -1,11 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- smartgen (0.1.1)
4
+ smartgen (0.1.2)
5
5
  RedCloth (>= 4.2.3)
6
6
  activesupport (>= 2.3.5)
7
7
  bluecloth (>= 2.0.9)
8
8
  i18n (>= 0.5.0)
9
+ nokogiri (>= 1.4.4)
9
10
  thor (>= 0.14.6)
10
11
 
11
12
  GEM
@@ -21,6 +22,7 @@ GEM
21
22
  linecache (0.43)
22
23
  linecache19 (0.5.11)
23
24
  ruby_core_source (>= 0.1.4)
25
+ nokogiri (1.4.4)
24
26
  rake (0.8.7)
25
27
  rspec (2.3.0)
26
28
  rspec-core (~> 2.3.0)
@@ -55,6 +57,7 @@ DEPENDENCIES
55
57
  activesupport (>= 2.3.5)
56
58
  bluecloth (>= 2.0.9)
57
59
  i18n (>= 0.5.0)
60
+ nokogiri (>= 1.4.4)
58
61
  rake (= 0.8.7)
59
62
  rspec (>= 2.3.0)
60
63
  ruby-debug
data/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  Smartgen
2
2
  ========
3
3
 
4
- Smartgen generates static HTML files from markup files, using textile or markdown, and ERB to create layout templates.
4
+ Smartgen generates static HTML files from markup files, using textile or markdown and ERB to create layout templates.
5
5
 
6
6
  ## Basic Usage
7
7
 
8
- To use you must first add the gem to your Gemfile:
8
+ To use it, you must first add the gem to your Gemfile:
9
9
 
10
10
  gem 'smartgen'
11
11
 
12
- Then you can create a resource configuration:
12
+ Then, create a resource configuration:
13
13
 
14
14
  Smartgen[:my_doc].configure do |config|
15
15
  config.src_files = ['doc/**/*']
@@ -22,7 +22,7 @@ Finally you generate it:
22
22
 
23
23
  ## Adding a layout
24
24
 
25
- By default Smartgen will only parse your textile (*.textile) or markdown (*.md or *.markdown) files, based on their extension and output it. You can give a layout file that will be used when rendering each file:
25
+ By default Smartgen will only parse your textile (*.textile) or markdown (*.md or *.markdown) files, based on their extension and output it. You can setup a layout file that will be used when rendering each file:
26
26
 
27
27
  Smartgen[:my_doc].configure do |config|
28
28
  config.src_files = ['doc/**/*']
@@ -63,7 +63,7 @@ This would be `doc/metadata.yml` contents
63
63
  description: Description for some page
64
64
  menu: [*index, *some_page]
65
65
 
66
- And this could be a layout that makes use of these metadata:
66
+ And this could be a layout that makes use of the metadata above:
67
67
 
68
68
  <html>
69
69
  <head>
@@ -88,19 +88,19 @@ As you can see, there is a special metadata method called `current_page`. This i
88
88
 
89
89
  ## Copying assets
90
90
 
91
- When you generate documentation with Smartgen you'll often need to also copy stylesheets, javascript files and images to the output folder. You can easily do so by specifying an array of assets when configuring:
91
+ When you generate documentation with Smartgen you'll often need to also copy stylesheets, javascript files and images to the output folder. You can easily do so by specifying an array of dirs to copy when configuring:
92
92
 
93
93
  Smartgen[:my_doc].configure do |config|
94
94
  config.src_files = ['doc/**/*']
95
95
  config.output_folder = 'public/docs'
96
- config.assets = ['doc/stylesheets/*.css', 'doc/javascripts/*.js', 'doc/images/*.{png|jpg|gif}']
96
+ config.assets = ['doc/stylesheets', 'doc/javascripts', 'doc/images']
97
97
  end
98
98
 
99
- As you can see you can pass an array of `Dir.glob` to either assets or src_files configuration.
99
+ Only directories may be specified. Smartgen will copy the contents of each directory to the output folder, preserving the directory itself. So `doc/stylesheets` will be copied to `public/docs/stylesheets` for example.
100
100
 
101
101
  ## Using different configurations
102
102
 
103
- You may create as many resource configurations as you want. You create a configuration the first time you call `Smartgen[:some_config_name]`, and then, whenever you use it again it will return that configuration. You can change the configuration by calling `configure`:
103
+ You may create as many resource configurations as you want. When you call `Smartgen[:some_config_name]`, Smartgen creates a configuration with name `:some_config_name` and then, whenever you use it again, Smartgen returns that configuration. You can even change the configuration by calling `configure`:
104
104
 
105
105
  Smartgen[:doc].configure do |config|
106
106
  config.src_files = ['doc/**/*']
@@ -129,4 +129,15 @@ You may also use a rake task:
129
129
  config.output_folder = 'public/docs'
130
130
  end
131
131
 
132
- The yielded config is exactly the same config yielded by Smartgen::Resource#configure method, so you can use any of the above configs.
132
+ The yielded config is exactly the same config yielded by Smartgen::Resource#configure method, so you can use any of the above configs.
133
+
134
+ ## Contributors
135
+
136
+ * [Vicente Mundim](http://github.com/vicentemundim) _author_
137
+ * [Emerson Leite](http://github.com/emerleite)
138
+ * [Rodrigo Lopes](http://github.com/rodvlopes)
139
+ * [Marcos Silva Pereira](http://github.com/marcospereira)
140
+
141
+ ## Copyright
142
+
143
+ Copyright (c) 2011 Vicente Mundim. See LICENSE for details.
data/lib/smartgen.rb CHANGED
@@ -2,13 +2,16 @@
2
2
  require File.expand_path(File.join('smartgen', 'object_hash'), File.dirname(__FILE__))
3
3
  require File.expand_path(File.join('smartgen', 'resource'), File.dirname(__FILE__))
4
4
  require File.expand_path(File.join('smartgen', 'configuration'), File.dirname(__FILE__))
5
+ require File.expand_path(File.join('smartgen', 'indexer'), File.dirname(__FILE__))
5
6
  require File.expand_path(File.join('smartgen', 'markup_file'), File.dirname(__FILE__))
6
7
  require File.expand_path(File.join('smartgen', 'renderers'), File.dirname(__FILE__))
7
8
  require File.expand_path(File.join('smartgen', 'engines'), File.dirname(__FILE__))
8
9
  require File.expand_path(File.join('smartgen', 'generator'), File.dirname(__FILE__))
9
10
 
11
+ # Smartgen generates HTML files from markup files, possibly using layouts.
10
12
  module Smartgen
11
13
  class << self
14
+ # Returns a resouces with the given name, creating it if necessary.
12
15
  def [](name)
13
16
  resources[name] ||= Smartgen::Resource.new
14
17
  end
@@ -1,17 +1,69 @@
1
1
  module Smartgen
2
+
3
+ # Holds configurations for each smartgen resource.
2
4
  class Configuration
5
+
6
+ # An array with all the source files that should be generated.
7
+ #
8
+ # Each entry may be a glob, such as 'doc/**/*'. Only files with extensions
9
+ # supported by the registered engines will be used. Files without extensions
10
+ # will be used as well, but they will use the highest priority engine.
11
+ #
12
+ # default:
13
+ # []
3
14
  attr_accessor :src_files
15
+
16
+ # The output folder, where all generated files will be located.
17
+ #
18
+ # default:
19
+ # nil
4
20
  attr_accessor :output_folder
21
+
22
+ # An optional layout file to be used when rendering each page.
23
+ #
24
+ # default:
25
+ # nil
5
26
  attr_accessor :layout
27
+
28
+ # An array of dirs to be copied to output folder.
29
+ #
30
+ # It will copy the contents of each directory to the output folder,
31
+ # preserving the directory itself. So, if output folder is '/public/doc',
32
+ # then 'doc/stylesheets' will be copied to 'public/docs/stylesheets'.
33
+ #
34
+ # default:
35
+ # []
6
36
  attr_accessor :assets
37
+
38
+ # A YAML metadata file used to specify metadata used in all pages, or even
39
+ # specific page metadata.
40
+ #
41
+ # default:
42
+ # nil
7
43
  attr_accessor :metadata_file
8
44
 
45
+ # Whether indexer should be used or not.
46
+ #
47
+ # default:
48
+ # false
49
+ attr_accessor :use_indexer
50
+
51
+ # Whether indexer should add numbered indexes on header tags.
52
+ #
53
+ # Only makes sense if #use_indexer is true
54
+ #
55
+ # default:
56
+ # false
57
+ attr_accessor :numbered_index
58
+
9
59
  def initialize
10
60
  @src_files = []
11
61
  @output_folder = nil
12
62
  @layout = nil
13
63
  @assets = []
14
64
  @metadata_file = nil
65
+ @use_indexer = false
66
+ @numbered_index = false
15
67
  end
16
68
  end
17
69
  end
@@ -2,22 +2,29 @@ require 'active_support/core_ext/class/inheritable_attributes'
2
2
 
3
3
  module Smartgen
4
4
  module Engine
5
+ # Base class for engines.
6
+ #
7
+ # An engine process markup files, converting them to HTML.
5
8
  class Base
9
+ # An array of pre processors that will process files before conversion.
6
10
  class_inheritable_accessor :pre_processors
7
11
 
8
12
  def initialize
9
13
  self.pre_processors ||= []
10
14
  end
11
15
 
16
+ # Process a file, calling each pre processor if any.
12
17
  def process(body)
13
18
  parse(pre_process(body))
14
19
  end
15
20
 
21
+ # Returns true if the given extension is supported by this engine.
16
22
  def supported?(extension)
17
23
  extensions.include?(extension)
18
24
  end
19
25
 
20
26
  class << self
27
+ # Registers a pre processor for this engine.
21
28
  def register(processor)
22
29
  self.pre_processors ||= []
23
30
  self.pre_processors << processor
@@ -2,6 +2,7 @@ require 'bluecloth'
2
2
 
3
3
  module Smartgen
4
4
  module Engine
5
+ # Processes markdown files, supporting both '.md' and '.markdown' extensions.
5
6
  class Markdown < Base
6
7
  protected
7
8
  def parse(body)
@@ -2,6 +2,7 @@ require 'RedCloth'
2
2
 
3
3
  module Smartgen
4
4
  module Engine
5
+ # Processes textile files, supporting '.textile' extension.
5
6
  class Textile < Base
6
7
  protected
7
8
  def parse(body)
@@ -1,17 +1,30 @@
1
1
  require 'thor/group'
2
2
 
3
3
  module Smartgen
4
+ # Generates files, possibly using layout and copying assets.
4
5
  class Generator < Thor::Group
5
6
  include Thor::Actions
6
7
 
8
+ desc "An array with all the source files that should be generated. See Configuration#src_files"
7
9
  argument :src_files, :type => :array
10
+
11
+ desc "The output folder, where all generated files will be located. See Configuration#output_folder"
8
12
  argument :output_folder, :type => :string
9
13
 
14
+ desc "An optional layout file to be used when rendering each page. See Configuration#layout"
10
15
  class_option :layout, :type => :string
16
+
17
+ desc "An array of dirs to be copied to output folder. See Configuration#assets"
11
18
  class_option :assets, :type => :array, :default => []
19
+
20
+ desc "A YAML metadata file used to specify metadata used in all pages, or even specific page metadata. See Configuration#metadata_file"
12
21
  class_option :metadata_file, :type => :string
13
22
 
14
- attr_reader :loaded_files
23
+ desc "Whether indexer should be used or not. See Configuration#use_indexer"
24
+ class_option :use_indexer, :type => :boolean, :default => false
25
+
26
+ desc "Whether indexer should add numbered indexes on header tags. See Configuration#numbered_index"
27
+ class_option :numbered_index, :type => :boolean, :default => false
15
28
 
16
29
  def create_output_folder
17
30
  destination_root = output_folder
@@ -32,10 +45,12 @@ module Smartgen
32
45
  end
33
46
 
34
47
  class << self
48
+ # Returns the current renderer.
35
49
  def renderer
36
50
  @renderer ||= Smartgen::Renderer::ERB.new
37
51
  end
38
52
 
53
+ # Sets the renderer used when generating files.
39
54
  def renderer=(value)
40
55
  @renderer = value
41
56
  end
@@ -89,7 +104,15 @@ module Smartgen
89
104
 
90
105
  def markup_files
91
106
  Dir[*src_files].select { |f| supported?(File.extname(f)) }.map do |markup_filename|
92
- MarkupFile.new markup_filename
107
+ MarkupFile.new markup_filename, markup_file_options
108
+ end
109
+ end
110
+
111
+ def markup_file_options
112
+ if options[:numbered_index]
113
+ { :indexer => { :numbered_index => true } }
114
+ else
115
+ { :indexer => options[:use_indexer] }
93
116
  end
94
117
  end
95
118
 
@@ -0,0 +1,162 @@
1
+ require 'nokogiri'
2
+
3
+ module Smartgen
4
+ # Parses the given HTML contents and generates a structure of indexes based
5
+ # on h1, h2, h3, h4, h5, h6 tags.
6
+ #
7
+ # It will also add IDs to each hx tag, based on their content and store this
8
+ # modified content on #result.
9
+ #
10
+ # On #index it will store a hierarchical structure for the h tags it
11
+ # encounters, with title, id, level, numbered_index and chidren.
12
+ #
13
+ # For example, this HTML:
14
+ #
15
+ # <h2>My header</h2>
16
+ # <h3>Some minor header</h3>
17
+ # <h3>Another minor header</h3>
18
+ # <h2>My other header</h2>
19
+ # <h3>Some other minor header</h3>
20
+ #
21
+ # Will generate this HTML using #result:
22
+ #
23
+ # <h2 id="my-header">My header</h2>
24
+ # <h3 id="some-minor-header">Some minor header</h3>
25
+ # <h3 id="another-minor-header">Another minor header</h3>
26
+ # <h2 id="my-other-header">My other header</h2>
27
+ # <h3 id="some-other-minor-header">Some other minor header</h3>
28
+ #
29
+ # And this structure in #index:
30
+ #
31
+ # [{
32
+ # :level=>2,
33
+ # :text=>"My header",
34
+ # :id=>"my-header",
35
+ # :children=>
36
+ # [{:level=>3,
37
+ # :text=>"Some minor header",
38
+ # :children=>[],
39
+ # :id=>"some-minor-header"},
40
+ # {:level=>3,
41
+ # :text=>"Another minor header",
42
+ # :children=>[],
43
+ # :id=>"another-minor-header"}],
44
+ # },
45
+ # {
46
+ # :level=>2,
47
+ # :text=>"My other header",
48
+ # :id=>"my-other-header"
49
+ # :children=> [{:level=>3, :text=>"Some other minor header", :children=>[], :id=>"some-other-minor-header"}],
50
+ # }]
51
+ #
52
+ # If can pass <tt>:numbered_index => true</tt> in the options, and it will add
53
+ # a numbered index on each title and add a +:numbered_index+ key with its value
54
+ # on each node in the hash generated by #index.
55
+ class Indexer
56
+ attr_accessor :index, :result
57
+
58
+ def initialize(contents, options={})
59
+ @contents = contents
60
+ @result = @contents.dup
61
+ @options = options.is_a?(Hash) ? options : {}
62
+ @index = []
63
+
64
+ parse
65
+ end
66
+
67
+ private
68
+ def parse
69
+ current_level = 0
70
+ parent = nil
71
+
72
+ Nokogiri::HTML(@result).xpath('//h1|//h2|//h3|//h4|//h5|//h6').each do |h_tag|
73
+ tag_level = tag_level_for(h_tag)
74
+
75
+ if tag_level < current_level
76
+ parent = parent_with_level(tag_level, parent)
77
+ elsif tag_level == current_level
78
+ parent = parent[:parent]
79
+ end
80
+
81
+ title = h_tag.inner_html
82
+
83
+ update_h_tag(h_tag, parent)
84
+ parent = add_to(parent, title, tag_level)
85
+
86
+ current_level = tag_level
87
+ end
88
+
89
+ remove_parents_from(index)
90
+ end
91
+
92
+ def tag_level_for(h_tag)
93
+ h_tag.name.sub('h', '').to_i
94
+ end
95
+
96
+ def add_to(parent, title, tag_level)
97
+ index_hash(parent, title, tag_level).tap do |child|
98
+ if parent.nil? # add to index
99
+ index << child
100
+ else
101
+ parent[:children] << child
102
+ end
103
+ end
104
+ end
105
+
106
+ def index_hash(parent, title, tag_level)
107
+ return {
108
+ :text => title,
109
+ :id => title_to_idx(title),
110
+ :level => tag_level,
111
+ :parent => parent,
112
+ :children => []
113
+ }.merge(numbered_index_data(parent))
114
+ end
115
+
116
+ def numbered_index_data(parent)
117
+ if numbered_index?
118
+ { :numbered_index => numbered_index_for(parent) }
119
+ else
120
+ {}
121
+ end
122
+ end
123
+
124
+ def numbered_index_for(parent)
125
+ if parent.nil?
126
+ "#{index.size + 1}"
127
+ else
128
+ "#{parent[:numbered_index]}.#{parent[:children].size + 1}"
129
+ end
130
+ end
131
+
132
+ def numbered_index?
133
+ @options[:numbered_index].present?
134
+ end
135
+
136
+ def parent_with_level(level, current_parent)
137
+ while current_parent.present? && current_parent[:level] >= level
138
+ current_parent = current_parent[:parent]
139
+ end
140
+
141
+ current_parent
142
+ end
143
+
144
+ def remove_parents_from(items)
145
+ items.each do |item|
146
+ item.delete(:parent)
147
+ remove_parents_from(item[:children])
148
+ end
149
+ end
150
+
151
+ def update_h_tag(h_tag, parent)
152
+ snippet = h_tag.to_html
153
+ h_tag['id'] ||= title_to_idx(h_tag.inner_html)
154
+ h_tag.inner_html = "#{numbered_index_for(parent)} #{h_tag.inner_html}" if numbered_index?
155
+ @result.sub!(snippet, h_tag.to_html)
156
+ end
157
+
158
+ def title_to_idx(title)
159
+ title.strip.downcase.gsub(/\s+|_/, '-').delete('^a-z0-9-')
160
+ end
161
+ end
162
+ end
@@ -1,26 +1,66 @@
1
- require "active_support/inflector"
2
1
  require 'active_support/core_ext/object/blank'
3
2
 
4
3
  module Smartgen
4
+
5
+ # A MarkupFile is created for each file that will be generated.
6
+ #
7
+ # It is also exposed in layouts, as +markup_file+ variable.
8
+ #
9
+ # It receives the +path+ of the file and process it using the first suitable
10
+ # engine, based on the extension of the file.
11
+ #
12
+ # It can also receive an options hash with indexer options:
13
+ #
14
+ # MarkupFile.new 'some/path', :indexer => true
15
+ #
16
+ # Or, if you want to pass options for the Indexer:
17
+ #
18
+ # MarkupFile.new 'some/path', :indexer => { :numbered_index => true }
19
+ #
5
20
  class MarkupFile
6
- attr_accessor :path, :filename, :extension, :engine
7
21
 
8
- def initialize(path)
22
+ # The path of the file.
23
+ attr_accessor :path
24
+
25
+ # The name of the file, without extension.
26
+ attr_accessor :filename
27
+
28
+ # The extension of the file.
29
+ attr_accessor :extension
30
+
31
+ # The engine used to process the file.
32
+ attr_accessor :engine
33
+
34
+ # The contents of the file after processing.
35
+ attr_accessor :contents
36
+
37
+ # The indexer instance, if +options[:indexer]+ is given
38
+ attr_accessor :indexer
39
+
40
+ def initialize(path, options={})
9
41
  @path = path
10
42
  @extension = File.extname(path)
11
43
  @filename = File.basename(path, @extension)
12
44
  @engine = engine_for(@extension) || self.class.engines.first
45
+
46
+ @contents = engine.process(raw_contents)
47
+
48
+ if options[:indexer]
49
+ @indexer = Smartgen::Indexer.new(@contents, options[:indexer])
50
+ @contents = @indexer.result
51
+ end
13
52
  end
14
53
 
54
+ # The contents of the file before processing.
15
55
  def raw_contents
16
56
  File.read(path)
17
57
  end
18
58
 
19
- def contents
20
- engine.process(raw_contents)
21
- end
22
-
23
59
  class << self
60
+ # Returns an array with the supported engines.
61
+ #
62
+ # The priority of the engines is defined by their order in this array. The
63
+ # first ones are the ones with higher priority.
24
64
  def engines
25
65
  if @engines.blank?
26
66
  @engines = [Smartgen::Engine::Textile.new, Smartgen::Engine::Markdown.new]
@@ -29,6 +69,9 @@ module Smartgen
29
69
  @engines
30
70
  end
31
71
 
72
+ # Register an engine to be used when generating files.
73
+ #
74
+ # The engine will be added with the highest priority.
32
75
  def register(engine)
33
76
  engines.unshift engine.new
34
77
  end
@@ -1,6 +1,12 @@
1
1
  require 'active_support/core_ext/hash'
2
2
 
3
3
  module Smartgen
4
+ # A hash that has method accessors for each key.
5
+ #
6
+ # For example:
7
+ # hash = ObjectHash.new({:foo => 'bar'})
8
+ # puts hash.foo # outputs 'bar'
9
+ #
4
10
  class ObjectHash < HashWithIndifferentAccess
5
11
  def dup
6
12
  Smartgen::ObjectHash.new(self)
@@ -15,7 +21,8 @@ module Smartgen
15
21
  if has_key?(name)
16
22
  self[name]
17
23
  else
18
- super
24
+ puts "warning: key #{name} not found on #{inspect}"
25
+ Smartgen::ObjectHash.new
19
26
  end
20
27
  end
21
28
 
@@ -32,7 +39,7 @@ module Smartgen
32
39
  end
33
40
  end
34
41
 
35
- class Hash
42
+ class Hash # :nodoc: all
36
43
  def with_object_hash
37
44
  hash = Smartgen::ObjectHash.new(self)
38
45
  hash.default = self.default
@@ -5,6 +5,21 @@ require 'rake/tasklib'
5
5
  require 'smartgen'
6
6
 
7
7
  module Smartgen
8
+ # A rake task that generate files using smartgen.
9
+ #
10
+ # To use it, require this file instead of +smartgen+:
11
+ #
12
+ # require 'smartgen/rake_task'
13
+ #
14
+ # Then create the task and configuration:
15
+ #
16
+ # Smartgen::RakeTask.new :generate_doc do |config|
17
+ # config.src_files = ['doc/**/*']
18
+ # config.output_folder = 'public/doc'
19
+ # end
20
+ #
21
+ # It yields a Configuration, in fact it calls Resource#configure, so you
22
+ # can use any of the configurations here.
8
23
  class RakeTask < ::Rake::TaskLib
9
24
  def initialize(name=nil, &block)
10
25
  name = name || :smartgen
@@ -1,6 +1,10 @@
1
1
  module Smartgen
2
2
  module Renderer
3
+ # A renderer that uses ERB to render markup files.
3
4
  class ERB
5
+ # Renders the markup file using the given layout.
6
+ #
7
+ # It exposes +markup_file+ variable and its +metadata+ to the ERB layout.
4
8
  def render(layout, markup_file, metadata={})
5
9
  metadata = Smartgen::ObjectHash.new(metadata)
6
10
 
@@ -1,13 +1,30 @@
1
1
  module Smartgen
2
+
3
+ # A Resource holds a Configuration used when generating files.
4
+ #
5
+ # You create a resource and configure it like this:
6
+ #
7
+ # Smartgen[:my_resource].configure do |config|
8
+ # config.src_files = ['docs/**/*']
9
+ # config.output_folder = 'public/docs'
10
+ # end
11
+ #
12
+ # To generate files, just call Resource#generate! on it:
13
+ #
14
+ # Smartgen[:my_resource].generate!
2
15
  class Resource
16
+
17
+ # Yields a Configuration, so that you can configure the generation of files.
3
18
  def configure
4
19
  yield config
5
20
  end
6
21
 
22
+ # Returns the Configuration for this resource.
7
23
  def config
8
24
  @config ||= Smartgen::Configuration.new
9
25
  end
10
26
 
27
+ # Generate files, based on the current Configuration.
11
28
  def generate!
12
29
  generator.invoke_all
13
30
  end
@@ -1,4 +1,4 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  module Smartgen
3
- VERSION = "0.1.2"
3
+ VERSION = "0.2.0"
4
4
  end
@@ -0,0 +1,16 @@
1
+ <h1 id="here-is-some-header">Here is some header</h1>
2
+ <p>And here is a paragraph</p>
3
+ <ul>
4
+ <li>some bullet lists with <strong>style</strong></li>
5
+ <li>some bullet lists with <em>style</em></li>
6
+ </ul>
7
+ <h2 id="another-header">Another header</h2>
8
+ <p>With some text</p>
9
+ <h3 id="another-minor-header">Another minor header</h3>
10
+ <p>With some other text</p>
11
+ <h4 id="yet-another-smaller-header">Yet another smaller header</h4>
12
+ <p>Again some text</p>
13
+ <h3 id="a-minor-header">A minor header</h3>
14
+ <p>Something</p>
15
+ <h2 id="a-header">A header</h2>
16
+ <p>With something</p>
@@ -0,0 +1,16 @@
1
+ <h1 id="here-is-some-header">1 Here is some header</h1>
2
+ <p>And here is a paragraph</p>
3
+ <ul>
4
+ <li>some bullet lists with <strong>style</strong></li>
5
+ <li>some bullet lists with <em>style</em></li>
6
+ </ul>
7
+ <h2 id="another-header">1.1 Another header</h2>
8
+ <p>With some text</p>
9
+ <h3 id="another-minor-header">1.1.1 Another minor header</h3>
10
+ <p>With some other text</p>
11
+ <h4 id="yet-another-smaller-header">1.1.1.1 Yet another smaller header</h4>
12
+ <p>Again some text</p>
13
+ <h3 id="a-minor-header">1.1.2 A minor header</h3>
14
+ <p>Something</p>
15
+ <h2 id="a-header">1.2 A header</h2>
16
+ <p>With something</p>
@@ -0,0 +1,10 @@
1
+ h1. Here is some header
2
+
3
+ And here is a paragraph
4
+
5
+ * some bullet lists with *style*
6
+ * some bullet lists with _style_
7
+
8
+ h2. Another header
9
+
10
+ With some text
@@ -0,0 +1,26 @@
1
+ h1. Here is some header
2
+
3
+ And here is a paragraph
4
+
5
+ * some bullet lists with *style*
6
+ * some bullet lists with _style_
7
+
8
+ h2. Another header
9
+
10
+ With some text
11
+
12
+ h3. Another minor header
13
+
14
+ With some other text
15
+
16
+ h4. Yet another smaller header
17
+
18
+ Again some text
19
+
20
+ h3. A minor header
21
+
22
+ Something
23
+
24
+ h2. A header
25
+
26
+ With something
@@ -0,0 +1,26 @@
1
+ h1. Here is some header
2
+
3
+ And here is a paragraph
4
+
5
+ * some bullet lists with *style*
6
+ * some bullet lists with _style_
7
+
8
+ h2. Another header
9
+
10
+ With some text
11
+
12
+ h3. Another minor header
13
+
14
+ With some other text
15
+
16
+ h4. Yet another smaller header
17
+
18
+ Again some text
19
+
20
+ h3. A minor header
21
+
22
+ Something
23
+
24
+ h2. A header
25
+
26
+ With something
@@ -26,5 +26,13 @@ describe Smartgen::Configuration do
26
26
  it "should initialize metadata_file to nil" do
27
27
  subject.metadata_file.should be_nil
28
28
  end
29
+
30
+ it "should initialize use_indexer to false" do
31
+ subject.use_indexer.should be_false
32
+ end
33
+
34
+ it "should initialize numbered_index to false" do
35
+ subject.numbered_index.should be_false
36
+ end
29
37
  end
30
38
  end
@@ -85,6 +85,17 @@ describe Smartgen::Generator do
85
85
  end
86
86
  end
87
87
 
88
+ describe "inexistent file" do
89
+ def src_files
90
+ [fixture('src/common/inexistent_file.textile')]
91
+ end
92
+
93
+ it "should not generate html" do
94
+ subject.invoke_all
95
+ File.should_not be_file(output_folder_file("inexistent_file.html"))
96
+ end
97
+ end
98
+
88
99
  describe "with layout" do
89
100
  def src_files
90
101
  [fixture('src/with_layout/index.textile')]
@@ -158,6 +169,42 @@ describe Smartgen::Generator do
158
169
  read_output('javascripts/somelib.js').should == read_fixture('src/assets/javascripts/somelib.js')
159
170
  end
160
171
  end
172
+
173
+ describe "with indexer" do
174
+ def src_files
175
+ [fixture('src/indexer/index_with_indexer.textile')]
176
+ end
177
+
178
+ def options
179
+ { :use_indexer => true }
180
+ end
181
+
182
+ it "should add IDs to each <h> tag" do
183
+ capture(:stdout) { subject.invoke_all }
184
+
185
+ actual_src_filenames.each do |src_filename, src_ext|
186
+ read_output("#{src_filename}.html").should == read_fixture("expectations/indexer/#{src_filename}.html")
187
+ end
188
+ end
189
+
190
+ context "and numbered_index" do
191
+ def src_files
192
+ [fixture('src/indexer/index_with_indexer_and_numbered_index.textile')]
193
+ end
194
+
195
+ def options
196
+ { :use_indexer => true, :numbered_index => true }
197
+ end
198
+
199
+ it "should add numbered indexes on each <h> tag" do
200
+ capture(:stdout) { subject.invoke_all }
201
+
202
+ actual_src_filenames.each do |src_filename, src_ext|
203
+ read_output("#{src_filename}.html").should == read_fixture("expectations/indexer/#{src_filename}.html")
204
+ end
205
+ end
206
+ end
207
+ end
161
208
  end
162
209
 
163
210
  describe "renderer registration" do
@@ -176,4 +223,4 @@ describe Smartgen::Generator do
176
223
  Smartgen::Generator.renderer.render('some_layout', mock(Smartgen::MarkupFile)).should == "do some rendering stuff"
177
224
  end
178
225
  end
179
- end
226
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Smartgen::Indexer do
4
+ matcher :have_tag do |tag, attributes|
5
+ match do |actual|
6
+ tags = Nokogiri::HTML(actual).css(tag.to_s)
7
+ tags.present? && tags.any? do |tag|
8
+ attributes.all? do |attribute, value|
9
+ tag.has_attribute?(attribute.to_s) && tag[attribute.to_s] == value
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ matcher :have_tag_with_contents do |tag, content|
16
+ match do |actual|
17
+ tags = Nokogiri::HTML(actual).css(tag.to_s)
18
+ tags.present? && tags.any? do |tag|
19
+ tag.content == content
20
+ end
21
+ end
22
+ end
23
+
24
+ def html
25
+ "<h1>Some header</h1>"
26
+ end
27
+
28
+ subject { Smartgen::Indexer.new html }
29
+
30
+ describe "addition of IDs" do
31
+ def html
32
+ return <<-HTML
33
+ <html>
34
+ <body>
35
+ <h1>A h1 header</h1>
36
+ <h2>A h2 header</h2>
37
+ <h3>A h3 header</h3>
38
+ <h4>A h4 header</h4>
39
+ <h5>A h5 header</h5>
40
+ <h6>A h6 header</h6>
41
+ </body>
42
+ </html>
43
+ HTML
44
+ end
45
+
46
+ 1.upto(6).each do |header_level|
47
+ it "should add IDs for each <h#{header_level}> tag in the result" do
48
+ subject.result.should have_tag("h#{header_level}", :id => "a-h#{header_level}-header")
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "index" do
54
+ def html
55
+ return <<-HTML
56
+ <html>
57
+ <body>
58
+ <h1>A h1 header</h1>
59
+ <h2>A h2 header</h2>
60
+ <h3>A h3 header</h3>
61
+ <h3>Some other h3 header</h3>
62
+ <h2>Another h2 header</h2>
63
+ <h5>A h5 header</h5>
64
+ <h2>Yet Another h2 header</h2>
65
+ <h3>Yet Another h3 header</h3>
66
+ <h1>Other h1 header</h1>
67
+ </body>
68
+ </html>
69
+ HTML
70
+ end
71
+
72
+ it "should return an index with headers data hierarquically distributed" do
73
+ expected_index = [
74
+ { :text => 'A h1 header', :id => 'a-h1-header', :level => 1, :children => [
75
+ { :text => 'A h2 header', :id => 'a-h2-header', :level => 2, :children => [
76
+ { :text => 'A h3 header', :id => 'a-h3-header', :level => 3, :children => [] },
77
+ { :text => 'Some other h3 header', :id => 'some-other-h3-header', :level => 3, :children => [] }
78
+ ] },
79
+ { :text => 'Another h2 header', :id => 'another-h2-header', :level => 2, :children => [
80
+ { :text => 'A h5 header', :id => 'a-h5-header', :level => 5, :children => [] }
81
+ ] },
82
+ { :text => 'Yet Another h2 header', :id => 'yet-another-h2-header', :level => 2, :children => [
83
+ { :text => 'Yet Another h3 header', :id => 'yet-another-h3-header', :level => 3, :children => [] }
84
+ ] },
85
+ ]},
86
+ { :text => 'Other h1 header', :id => 'other-h1-header', :level => 1, :children => [] }
87
+ ]
88
+
89
+ subject.index.should == expected_index
90
+ end
91
+
92
+ context "when numbered_index options is given" do
93
+ subject { Smartgen::Indexer.new html, :numbered_index => true }
94
+
95
+ it "should add the numbered index to header contents" do
96
+ subject.result.should have_tag_with_contents("h1", "1 A h1 header")
97
+ subject.result.should have_tag_with_contents("h2", "1.1 A h2 header")
98
+ subject.result.should have_tag_with_contents("h3", "1.1.2 Some other h3 header")
99
+ end
100
+
101
+ it "should return an index with headers data hierarquically distributed with numbered index" do
102
+ expected_index = [
103
+ { :text => 'A h1 header', :id => 'a-h1-header', :level => 1, :numbered_index => '1', :children => [
104
+ { :text => 'A h2 header', :id => 'a-h2-header', :level => 2, :numbered_index => '1.1', :children => [
105
+ { :text => 'A h3 header', :id => 'a-h3-header', :level => 3, :numbered_index => '1.1.1', :children => [] },
106
+ { :text => 'Some other h3 header', :id => 'some-other-h3-header', :level => 3, :numbered_index => '1.1.2', :children => [] }
107
+ ] },
108
+ { :text => 'Another h2 header', :id => 'another-h2-header', :level => 2, :numbered_index => '1.2', :children => [
109
+ { :text => 'A h5 header', :id => 'a-h5-header', :level => 5, :numbered_index => '1.2.1', :children => [] }
110
+ ] },
111
+ { :text => 'Yet Another h2 header', :id => 'yet-another-h2-header', :level => 2, :numbered_index => '1.3', :children => [
112
+ { :text => 'Yet Another h3 header', :id => 'yet-another-h3-header', :level => 3, :numbered_index => '1.3.1', :children => [] }
113
+ ] },
114
+ ]},
115
+ { :text => 'Other h1 header', :id => 'other-h1-header', :level => 1, :numbered_index => '2', :children => [] }
116
+ ]
117
+
118
+ subject.index.should == expected_index
119
+ end
120
+ end
121
+ end
122
+ end
@@ -118,11 +118,31 @@ describe Smartgen::MarkupFile do
118
118
 
119
119
  it "should use textile as markup engine for files with .md" do
120
120
  def path
121
- fixture('src/common/other_index.md')
121
+ fixture('src/common/another_index.md')
122
122
  end
123
123
 
124
124
  subject.engine.should be_an_instance_of(Smartgen::Engine::Markdown)
125
125
  end
126
126
  end
127
127
  end
128
+
129
+ describe "indexer" do
130
+ subject { Smartgen::MarkupFile.new path, :indexer => true }
131
+
132
+ it "should be accessible when using indexer" do
133
+ subject.indexer.should be_an_instance_of(Smartgen::Indexer)
134
+ end
135
+
136
+ it "should use indexer" do
137
+ mock_indexer = mock(Smartgen::Indexer, :result => 'result')
138
+ Smartgen::Indexer.should_receive(:new).and_return(mock_indexer)
139
+ subject
140
+ end
141
+
142
+ it "should return indexer result as contents" do
143
+ mock_indexer = mock(Smartgen::Indexer, :result => 'result')
144
+ Smartgen::Indexer.should_receive(:new).and_return(mock_indexer)
145
+ subject.contents.should == 'result'
146
+ end
147
+ end
128
148
  end
@@ -14,7 +14,17 @@ describe Smartgen::ObjectHash do
14
14
  should respond_to(key)
15
15
  end
16
16
  end
17
-
17
+
18
+ describe "inexistent key" do
19
+ it "should not respond to" do
20
+ subject.should_not respond_to("invalid_key")
21
+ end
22
+
23
+ it "should return an empty ObjectHash" do
24
+ subject.invalid_key.should be_an_instance_of(Smartgen::ObjectHash)
25
+ end
26
+ end
27
+
18
28
  it "should respond to ancestor methods" do
19
29
  ancestor = Smartgen::ObjectHash.ancestors.first
20
30
  ancestor.instance_methods.each do |method|
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartgen
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
8
  - 2
10
- version: 0.1.2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Vicente Mundim
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-25 00:00:00 -02:00
18
+ date: 2011-01-27 00:00:00 -02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -99,9 +99,25 @@ dependencies:
99
99
  type: :runtime
100
100
  version_requirements: *id005
101
101
  - !ruby/object:Gem::Dependency
102
- name: rspec
102
+ name: nokogiri
103
103
  prerelease: false
104
104
  requirement: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 15
110
+ segments:
111
+ - 1
112
+ - 4
113
+ - 4
114
+ version: 1.4.4
115
+ type: :runtime
116
+ version_requirements: *id006
117
+ - !ruby/object:Gem::Dependency
118
+ name: rspec
119
+ prerelease: false
120
+ requirement: &id007 !ruby/object:Gem::Requirement
105
121
  none: false
106
122
  requirements:
107
123
  - - ">="
@@ -113,7 +129,7 @@ dependencies:
113
129
  - 0
114
130
  version: 2.3.0
115
131
  type: :development
116
- version_requirements: *id006
132
+ version_requirements: *id007
117
133
  description: Smartgen generates static HTML files from markup files, using textile or markdown, and ERB to create layout templates
118
134
  email:
119
135
  - vicente.mundim@gmail.com
@@ -130,6 +146,7 @@ files:
130
146
  - lib/smartgen/engines/textile.rb
131
147
  - lib/smartgen/engines.rb
132
148
  - lib/smartgen/generator.rb
149
+ - lib/smartgen/indexer.rb
133
150
  - lib/smartgen/markup_file.rb
134
151
  - lib/smartgen/object_hash.rb
135
152
  - lib/smartgen/rake_task.rb
@@ -146,6 +163,8 @@ files:
146
163
  - spec/fixtures/expectations/common/another_index.html
147
164
  - spec/fixtures/expectations/common/index.html
148
165
  - spec/fixtures/expectations/common/other_index.html
166
+ - spec/fixtures/expectations/indexer/index_with_indexer.html
167
+ - spec/fixtures/expectations/indexer/index_with_indexer_and_numbered_index.html
149
168
  - spec/fixtures/expectations/with_layout/index.html
150
169
  - spec/fixtures/expectations/with_layout/index_with_metadata.html
151
170
  - spec/fixtures/expectations/with_layout/index_with_specific_metadata.html
@@ -155,6 +174,9 @@ files:
155
174
  - spec/fixtures/src/common/another_index.md
156
175
  - spec/fixtures/src/common/index.textile
157
176
  - spec/fixtures/src/common/other_index.markdown
177
+ - spec/fixtures/src/common/somefile
178
+ - spec/fixtures/src/indexer/index_with_indexer.textile
179
+ - spec/fixtures/src/indexer/index_with_indexer_and_numbered_index.textile
158
180
  - spec/fixtures/src/layout.html.erb
159
181
  - spec/fixtures/src/layout_with_metadata.html.erb
160
182
  - spec/fixtures/src/layout_with_specific_metadata.html.erb
@@ -166,6 +188,7 @@ files:
166
188
  - spec/lib/smartgen/engines/markdown_spec.rb
167
189
  - spec/lib/smartgen/engines/textile_spec.rb
168
190
  - spec/lib/smartgen/generator_spec.rb
191
+ - spec/lib/smartgen/indexer_spec.rb
169
192
  - spec/lib/smartgen/markup_file_spec.rb
170
193
  - spec/lib/smartgen/object_hash_spec.rb
171
194
  - spec/lib/smartgen/renderers/erb_spec.rb
@@ -211,6 +234,8 @@ test_files:
211
234
  - spec/fixtures/expectations/common/another_index.html
212
235
  - spec/fixtures/expectations/common/index.html
213
236
  - spec/fixtures/expectations/common/other_index.html
237
+ - spec/fixtures/expectations/indexer/index_with_indexer.html
238
+ - spec/fixtures/expectations/indexer/index_with_indexer_and_numbered_index.html
214
239
  - spec/fixtures/expectations/with_layout/index.html
215
240
  - spec/fixtures/expectations/with_layout/index_with_metadata.html
216
241
  - spec/fixtures/expectations/with_layout/index_with_specific_metadata.html
@@ -220,6 +245,9 @@ test_files:
220
245
  - spec/fixtures/src/common/another_index.md
221
246
  - spec/fixtures/src/common/index.textile
222
247
  - spec/fixtures/src/common/other_index.markdown
248
+ - spec/fixtures/src/common/somefile
249
+ - spec/fixtures/src/indexer/index_with_indexer.textile
250
+ - spec/fixtures/src/indexer/index_with_indexer_and_numbered_index.textile
223
251
  - spec/fixtures/src/layout.html.erb
224
252
  - spec/fixtures/src/layout_with_metadata.html.erb
225
253
  - spec/fixtures/src/layout_with_specific_metadata.html.erb
@@ -231,6 +259,7 @@ test_files:
231
259
  - spec/lib/smartgen/engines/markdown_spec.rb
232
260
  - spec/lib/smartgen/engines/textile_spec.rb
233
261
  - spec/lib/smartgen/generator_spec.rb
262
+ - spec/lib/smartgen/indexer_spec.rb
234
263
  - spec/lib/smartgen/markup_file_spec.rb
235
264
  - spec/lib/smartgen/object_hash_spec.rb
236
265
  - spec/lib/smartgen/renderers/erb_spec.rb