marsdawn 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +15 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +20 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +119 -0
  8. data/Rakefile +12 -0
  9. data/bin/marsdawn +6 -0
  10. data/lib/marsdawn/builder.rb +35 -0
  11. data/lib/marsdawn/command.rb +154 -0
  12. data/lib/marsdawn/config.rb +31 -0
  13. data/lib/marsdawn/search/rroonga.rb +83 -0
  14. data/lib/marsdawn/search.rb +16 -0
  15. data/lib/marsdawn/site/breadcrumb.rb +30 -0
  16. data/lib/marsdawn/site/indexer.rb +78 -0
  17. data/lib/marsdawn/site/link.rb +28 -0
  18. data/lib/marsdawn/site/page.rb +118 -0
  19. data/lib/marsdawn/site/page_nav.rb +24 -0
  20. data/lib/marsdawn/site/search_box.rb +27 -0
  21. data/lib/marsdawn/site/search_page.rb +35 -0
  22. data/lib/marsdawn/site.rb +70 -0
  23. data/lib/marsdawn/source/document.rb +36 -0
  24. data/lib/marsdawn/source/front_matter.rb +25 -0
  25. data/lib/marsdawn/source/kramdown/parser.rb +56 -0
  26. data/lib/marsdawn/source.rb +164 -0
  27. data/lib/marsdawn/storage/active_record/marsdawn.rb +20 -0
  28. data/lib/marsdawn/storage/active_record.rb +83 -0
  29. data/lib/marsdawn/storage/base.rb +44 -0
  30. data/lib/marsdawn/storage/file_system.rb +83 -0
  31. data/lib/marsdawn/storage/redis.rb +0 -0
  32. data/lib/marsdawn/storage/test.rb +20 -0
  33. data/lib/marsdawn/storage/test_not_implemented_error.rb +6 -0
  34. data/lib/marsdawn/storage.rb +22 -0
  35. data/lib/marsdawn/util.rb +39 -0
  36. data/lib/marsdawn/version.rb +3 -0
  37. data/lib/marsdawn.rake +9 -0
  38. data/lib/marsdawn.rb +55 -0
  39. data/marsdawn.gemspec +25 -0
  40. data/spec/_compiled_doc/.gitkeep +0 -0
  41. data/spec/_test_doc/config.yml +10 -0
  42. data/spec/_test_doc/docs01/.index.md +1 -0
  43. data/spec/_test_doc/docs01/.marsdawn.yml +4 -0
  44. data/spec/_test_doc/docs01/010_about.md +4 -0
  45. data/spec/_test_doc/docs01/020_tutorial/.index.md +0 -0
  46. data/spec/_test_doc/docs01/020_tutorial/010_install.md +6 -0
  47. data/spec/_test_doc/docs01/020_tutorial/020_getting_start.md +9 -0
  48. data/spec/_test_doc/docs01/030_reference/1up.md +0 -0
  49. data/spec/_test_doc/docs01/030_reference/each.md +0 -0
  50. data/spec/_test_doc/docs01/030_reference/z-index.md +0 -0
  51. data/spec/_test_doc/docs01/040_appendix.md +3 -0
  52. data/spec/_test_doc/no_config/.gitkeep +0 -0
  53. data/spec/_test_doc/no_key/.marsdawn.yml +3 -0
  54. data/spec/_test_doc/samples/010_sample-document.md +4 -0
  55. data/spec/_test_doc/samples/test_document.md +1 -0
  56. data/spec/_tmp/.gitkeep +0 -0
  57. data/spec/lib/marsdawn/command_spec.rb +144 -0
  58. data/spec/lib/marsdawn/config_spec.rb +41 -0
  59. data/spec/lib/marsdawn/site/page_spec.rb +122 -0
  60. data/spec/lib/marsdawn/site_spec.rb +67 -0
  61. data/spec/lib/marsdawn/source/document_spec.rb +178 -0
  62. data/spec/lib/marsdawn/source/front_matter_spec.rb +55 -0
  63. data/spec/lib/marsdawn/source_spec.rb +40 -0
  64. data/spec/lib/marsdawn/storage/active_record_spec.rb +47 -0
  65. data/spec/lib/marsdawn/storage/file_system_spec.rb +46 -0
  66. data/spec/lib/marsdawn/storage_spec.rb +29 -0
  67. data/spec/lib/marsdawn/util_spec.rb +57 -0
  68. data/spec/spec_helper.rb +12 -0
  69. data/spec/stubs/groonga.rb +17 -0
  70. data/spec/stubs/marsdawn_docs.rb +40 -0
  71. metadata +203 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTI0YWM4Nzg0OWIxYTBiZmM3MjIyZjVjN2U0Yjg3N2FhY2YzZWMzYg==
5
+ data.tar.gz: !binary |-
6
+ NzAzOTEyZTkwNWNkZjQ2ZjQwMGExOTk1YzNmYTA2MGJlNTNhOTE2OQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTJiMDlmODk3MGU5MmI1OTRmOWQ2NWE3MjA4YmUyODA5MDIzNGEzODU3OGU3
10
+ ZjE2Mzg5OTk1MDRjZTY0YWI4NTI2YTVlMDZhN2EzMmVjYzZlNmQ0OTZmYWVi
11
+ Nzg1YzEzMTc0YWMxYjBlZGY5ZjZiYmU0Y2U1YTRmNWFiMjA5ZDY=
12
+ data.tar.gz: !binary |-
13
+ OGE0YzBiYmFiYzY1NGEyMzAzOTc5ZjU1Mzg1Y2Y1ZjNjZDJmZjI2MGNhZTJi
14
+ OWJmOWVhZGQ4ODgwYjljNzc2ZmUzYWZhZGNhZTM0OGVkOTE1MDU3NzhhYjAx
15
+ Y2FjNmI5MWUwYjNkMDE4OWRlMGVkNTczZGU5ODJiZTk0MDMxMDY=
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: rh7Ee5DRpsg5CVHPPCrj07cAUyIqsnWiI
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor
19
+ spec/_compiled_doc/*/
20
+ spec/_tmp/*/
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 1.9.3
5
+ addons:
6
+ code_climate:
7
+ repo_token: 1ba540ca1a06382432295a8c6afa3d6ffccda11fa9b43a429db604545a5c3ee4
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in marsdawn.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'coveralls', :require => false
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ [![Build Status](https://travis-ci.org/nao58/marsdawn.svg?branch=master)](https://travis-ci.org/nao58/marsdawn)
2
+ [![Coverage Status](https://coveralls.io/repos/nao58/marsdawn/badge.png?branch=master)](https://coveralls.io/r/nao58/marsdawn?branch=master)
3
+ [![Code Climate](https://codeclimate.com/github/nao58/marsdawn.png)](https://codeclimate.com/github/nao58/marsdawn)
4
+
5
+ # Marsdawn
6
+
7
+ MarsDawn is simple static document builder and traverser. You can create your own document site easily using this gem.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'marsdawn'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install marsdawn
22
+
23
+ ## Usage
24
+
25
+ ### Create your document source
26
+
27
+ Use marsdawn command.
28
+
29
+ ~~~
30
+ marsdawn create "My Document"
31
+ ~~~
32
+
33
+ Then the document and files will be created like below.
34
+
35
+ ~~~
36
+ └── my_document
37
+ ├── .marsdawn.yml
38
+ └── .index.md
39
+ ~~~
40
+
41
+ The file `.marsdawn.yml` includes the information of whole document set. `.index.md` is the top page. You can edit the file to add your content.
42
+
43
+ To create new page.
44
+
45
+ ~~~
46
+ marsdawn page "About the document"
47
+ ~~~
48
+
49
+ ~~~
50
+ └── my_document
51
+ ├── .marsdawn.yml
52
+ ├── .index.md
53
+ └── 010_about-the-document.md
54
+ ~~~
55
+
56
+ To create new directory.
57
+
58
+ ~~~
59
+ marsdawn dir "Tutorial"
60
+ ~~~
61
+
62
+ ~~~
63
+ └── my_document
64
+ ├── .marsdawn.yml
65
+ ├── .index.md
66
+ ├── 010_about-the-document.md
67
+ └── 020_tutorial
68
+ └── .index.md
69
+ ~~~
70
+
71
+ ### Build the documents
72
+
73
+ Setting config file is the easiest way to build the document.
74
+
75
+ Create config file names "marsdawn.yml" under "config" directory of your framework.
76
+
77
+ ~~~config/marsdawn.yml
78
+ your_document_key:
79
+ source: path/to/your/source/documents
80
+ storage:
81
+ path: path/to/build/directory
82
+ ~~~
83
+
84
+ This time we will build the document onto the file system. You can choose RDBMS storage instead.
85
+
86
+ Then simply hit the rake command.
87
+
88
+ ~~~
89
+ rake marsdawn:build
90
+ ~~~
91
+
92
+ ### Traverse the documents
93
+
94
+ Once you have built the document, you can traverse the document easily from your own site.
95
+
96
+ This is a sample controller for Padrino.
97
+
98
+ ~~~ruby
99
+ YourWeb::App.controllers :docs do
100
+
101
+ get :index, :map => '/docs/*path' do
102
+ docset = Marsdawn::Site.new(key: 'your_document_key', lang: 'en', version: '1.0.0', base_path: '/docs')
103
+ @page = docset.page("/#{params[:path].join('/')}")
104
+ @title = @page.title
105
+ render 'docs'
106
+ end
107
+
108
+ end
109
+ ~~~
110
+
111
+ Now you can create your templete for the document page.
112
+
113
+ ## Contributing
114
+
115
+ 1. Fork it ( http://github.com/nao58/marsdawn/fork )
116
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
117
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
118
+ 4. Push to the branch (`git push origin my-new-feature`)
119
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => [:spec]
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = 'spec/**/*_spec.rb'
8
+ spec.rspec_opts = ['-cfd']
9
+ end
10
+ rescue LoadError => e
11
+ p e
12
+ end
data/bin/marsdawn ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'marsdawn/command'
4
+
5
+ command = Marsdawn::Command.exec(ARGV)
6
+ exec command unless command.nil?
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require "marsdawn/source"
4
+
5
+ class Marsdawn::Builder
6
+
7
+ def self.build config
8
+ raise "No specification for source path." unless config.key?(:source)
9
+ raise "No specification for storage config." unless config.key?(:storage)
10
+ source = Marsdawn::Source.new(config[:source])
11
+ storage = Marsdawn::Storage.get(config[:storage], source.doc_info)
12
+ build_source source, storage, config[:build_options]
13
+ create_search_index storage, config[:search] if config.key?(:search)
14
+ end
15
+
16
+ def self.build_source source, storage, options
17
+ options ||= {}
18
+ kramdown_options = options[:kramdown] || {}
19
+ storage.prepare
20
+ storage.set_document_info source.doc_info
21
+ source.each_contents(kramdown_options) do |uri, content, front_matter, sysinfo|
22
+ storage.set uri, content, front_matter, sysinfo
23
+ end
24
+ storage.finalize
25
+ rescue => ex
26
+ storage.clean_up
27
+ raise ex
28
+ end
29
+
30
+ def self.create_search_index storage, options
31
+ options ||= {}
32
+ Marsdawn::Search.create_index storage, options
33
+ end
34
+
35
+ end
@@ -0,0 +1,154 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ module Marsdawn
6
+ class Command
7
+ attr_accessor :editor, :auto_editor
8
+
9
+ class ParamError < Exception; end
10
+ class RuntimeError < Exception; end
11
+
12
+ def self.exec argv
13
+ raise ParamError.new("No command is specified.") if argv.empty?
14
+ cmd = new
15
+ command = argv.shift.to_sym
16
+ raise ParamError.new("Unknown command '#{command}'.") unless cmd.respond_to?(command)
17
+ value = (argv.size > 0 ? argv.shift : nil)
18
+ opts = parse_options(argv)
19
+ cmd.__send__ command, value, opts
20
+ end
21
+
22
+ def initialize
23
+ @editor = 'vim'
24
+ @auto_editor = true
25
+ read_dot_marsdawn
26
+ end
27
+
28
+ def create title, opts={}
29
+ key = (opts.key?(:file) ? opts[:file] : file_namize(title))
30
+ path = File.expand_path(key)
31
+ raise "The directory '#{doc_name}' already exists." if File.exists?(path)
32
+ Dir.mkdir path
33
+ data = {key: key, title: title, lang: 'en', version: '0.0.1'}
34
+ dot_file = File.join(path, '.marsdawn.yml')
35
+ index_file = File.join(path, '.index.md')
36
+ File.write dot_file, YAML.dump(data)
37
+ create_page index_file, title, 'title' => title
38
+ edit_cmd dot_file, index_file if @auto_editor || opts[:edit]
39
+ end
40
+
41
+ def dir title, opts={}
42
+ dir = (opts.key?(:file) ? opts[:file] : file_namize(title))
43
+ dir = add_num(dir, opts)
44
+ path = File.expand_path(dir)
45
+ Dir.mkdir path
46
+ index_file = File.join(path, '.index.md')
47
+ create_page index_file, title, 'title' => title
48
+ edit_cmd index_file if @auto_editor || opts[:edit]
49
+ end
50
+
51
+ def page title, opts={}
52
+ file = (opts.key?(:file) ? "#{opts[:file]}.md" : file_namize(title, '.md'))
53
+ file = File.expand_path(add_num(file, opts))
54
+ create_page file, title, 'title' => title
55
+ edit_cmd file if @auto_editor || opts[:edit]
56
+ end
57
+
58
+ def renum step, opts={}
59
+ step = (step.nil? ? 10 : step.to_i)
60
+ num = 0
61
+ list = Dir.glob('*').sort.each_with_object({}) do |item, ret|
62
+ num += step
63
+ ret[item] = add_num(item, num: num, step: step)
64
+ end
65
+ list.each do |src, dest|
66
+ FileUtils.mv src, dest unless src == dest
67
+ end
68
+ 'ls -1'
69
+ end
70
+
71
+ def debug type, opts={}
72
+ case type
73
+ when 'options'
74
+ opts
75
+ end
76
+ end
77
+
78
+ private
79
+ def self.parse_options argv
80
+ opts = {}
81
+ while argv.size > 0 do
82
+ key = opt_key(argv.shift)
83
+ val = true
84
+ val = argv.shift if argv.size > 0 && !argv.first.start_with?('-')
85
+ opts[key] = val
86
+ end
87
+ opts
88
+ end
89
+
90
+ def self.opt_key switch
91
+ opt_keys = {
92
+ 'e' => 'edit',
93
+ 'f' => 'file',
94
+ 'n' => 'num',
95
+ 'o' => 'no-num',
96
+ 's' => 'step'
97
+ }
98
+ key = switch
99
+ if key =~ /^--(.+)$/
100
+ key = $1
101
+ elsif key =~ /^-(.+)$/
102
+ key = opt_keys[$1]
103
+ end
104
+ raise ParamError.new("Unknown option '#{switch}'.") unless opt_keys.values.include?(key)
105
+ key.to_sym
106
+ end
107
+
108
+ def read_dot_marsdawn
109
+ dotfile = File.expand_path("#{ENV['HOME']}/.marsdawn")
110
+ instance_eval(open(dotfile).read) if File.exists?(dotfile)
111
+ end
112
+
113
+ def file_namize title, ext=''
114
+ "#{title.downcase.gsub(' ', '-')}#{ext}"
115
+ end
116
+
117
+ def create_page path, title, front_matter={}
118
+ File.write(path, "#{YAML.dump(front_matter)}---\n\n# #{title}\n\n")
119
+ end
120
+
121
+ def file_num file
122
+ file =~ /^(\d+)_(.+)$/ ? $1.to_i : 0
123
+ end
124
+
125
+ def file_name file
126
+ file =~ /^(\d+)_(.+)$/ ? $2 : file
127
+ end
128
+
129
+
130
+ def step opts
131
+ opts.key?(:step) ? opts[:step] : 10
132
+ end
133
+
134
+ def add_num file, opts
135
+ if opts.key?(:num)
136
+ num = opts[:num]
137
+ num = max_num + step(opts) if num.is_a?(TrueClass)
138
+ num = "000#{num}".slice(-3, 3)
139
+ file = "#{num}_#{file_name(file)}"
140
+ end
141
+ file
142
+ end
143
+
144
+ def max_num
145
+ list = Dir.glob('*')
146
+ list.size > 0 ? list.map{|item| file_num(item)}.max : 0
147
+ end
148
+
149
+ def edit_cmd *args
150
+ "#{@editor} #{args.join(" ")}"
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ class Marsdawn::Config
4
+
5
+ def initialize file='./config/marsdawn.yml'
6
+ file = File.absolute_path(file)
7
+ raise "Cannot find a config file for marsdawn." unless File.exists?(file)
8
+ @config = YAML.load_file(file)
9
+ end
10
+
11
+ def get key, entry, default=nil
12
+ conf = to_hash(key)
13
+ conf[entry] = default if !default.nil? && !conf.key?(entry)
14
+ raise "No entry '#{entry}' in the setting file of marsdawn." unless conf.key?(entry)
15
+ ret = conf[entry]
16
+ ret = Marsdawn::Util.hash_symbolize_keys(ret) if ret.kind_of?(Hash)
17
+ ret
18
+ end
19
+
20
+ def to_hash key
21
+ raise "Cannot find the configuration for '#{key}' in marsdawn.yml." unless @config.key?(key)
22
+ Marsdawn::Util.hash_symbolize_keys(@config[key])
23
+ end
24
+
25
+ def each
26
+ @config.keys.each do |key|
27
+ yield key, to_hash(key)
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+
3
+ class Marsdawn::Search::Rroonga
4
+
5
+ def initialize opts, storage
6
+ unless Module.const_defined?('Groonga')
7
+ raise "Gem 'rroonga' should be installed." unless require 'groonga'
8
+ end
9
+ raise "The groonga database path should be specified" unless opts.key?(:path)
10
+ @opts = {
11
+ index_table: {}
12
+ }.merge(opts)
13
+ @storage = storage
14
+ Groonga::Database.open(opts[:path])
15
+ @table_prefix = "#{@storage.key}-#{@storage.lang}-#{@storage.version.tr('.','_')}"
16
+ end
17
+
18
+ def create_tables
19
+ index_table = {
20
+ :type => :patricia_trie,
21
+ :normalizer => :NormalizerAuto,
22
+ :default_tokenizer => 'TokenBigram'
23
+ }.merge(@opts[:index_table])
24
+ Groonga::Schema.create_table(table_name('documents'), :type => :hash) do |tbl|
25
+ tbl.text('title')
26
+ tbl.text('content')
27
+ end
28
+ Groonga::Schema.create_table(table_name('terms'), index_table) do |tbl|
29
+ tbl.index(col_name('documents', 'title'))
30
+ tbl.index(col_name('documents', 'content'))
31
+ end
32
+ end
33
+
34
+ def drop_tables
35
+ Groonga::Schema.remove_table(table_name('documents'))
36
+ Groonga::Schema.remove_table(table_name('terms'))
37
+ rescue Groonga::Schema::TableNotExists
38
+ end
39
+
40
+ def create_index
41
+ drop_tables
42
+ create_tables
43
+ docs = table('documents')
44
+ index = @storage.get_document_info[:site_index]
45
+ index.each do |uri, title|
46
+ page = @storage.get(uri)
47
+ docs.add(uri, :title => title, :content => Marsdawn::Util.strip_tags(page[:content]))
48
+ end
49
+ end
50
+
51
+ def search keyword, opts={}
52
+ snippet = Groonga::Snippet.new(html_escape: true, default_open_tag: '<strong>', default_close_tag: '</strong>', normalize: true)
53
+ words = keyword.scan(/(?:\w|"[^"]*")+/).map{|w| w.delete('"')}
54
+ words.each{|word| snippet.add_keyword(word, opts)}
55
+ docs = table('documents')
56
+ docs.select do |rec|
57
+ expression = nil
58
+ words.each do |word|
59
+ sub_exp = (rec.content =~ word)
60
+ if expression.nil?
61
+ expression = sub_exp
62
+ else
63
+ expression &= sub_exp
64
+ end
65
+ end
66
+ expression
67
+ end.map{|rec| {uri: rec.key.key, title: rec.title, results: snippet.execute(rec.content)}}
68
+ end
69
+
70
+ private
71
+ def table_name name
72
+ "#{@table_prefix}-#{name}"
73
+ end
74
+
75
+ def table name
76
+ Groonga[table_name(name)]
77
+ end
78
+
79
+ def col_name table, column
80
+ "#{table_name(table)}.#{column}"
81
+ end
82
+
83
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ class Marsdawn::Search
4
+
5
+ def self.create_index storage, opts
6
+ self.get(storage, opts).create_index
7
+ end
8
+
9
+ def self.get storage, opts
10
+ opts = Marsdawn::Util.hash_symbolize_keys(opts)
11
+ key = opts[:type]
12
+ @@base_path ||= File.join(File.dirname(__FILE__), 'search')
13
+ Marsdawn::Util.adapter(self, class_name, @@base_path).new config, opts
14
+ end
15
+
16
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ class Marsdawn::Site
4
+ class Breadcrumb < Hash
5
+
6
+ def initialize site, crumb
7
+ @site = site
8
+ crumb.each do |path|
9
+ self[path] = Marsdawn::Site::Link.new(@site, path)
10
+ end
11
+ end
12
+
13
+ def to_s
14
+ to_html
15
+ end
16
+
17
+ def to_html
18
+ words = []
19
+ if self.size > 0
20
+ words << '<ul>'
21
+ self.each do |uri, link|
22
+ words << "<li>#{link.to_html}</li>"
23
+ end
24
+ words << '</ul>'
25
+ end
26
+ words.join('')
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ class Marsdawn::Site
4
+ class Indexer < Hash
5
+
6
+ def initialize site, index={}
7
+ @site = site
8
+ @current_page = nil
9
+ index.each do |uri, title|
10
+ self[uri] = Marsdawn::Site::Link.new(@site, uri, title)
11
+ end
12
+ end
13
+
14
+ def current_page path
15
+ @current_page = path
16
+ self
17
+ end
18
+
19
+ #def parent path
20
+ # parent_path = "#{File.dirname(path)}/"
21
+ # self.each_with_object(Marsdawn::Site::Indexer.new(@site).current_page(path)) do |(uri, link), ret|
22
+ # ret[uri] = link if uri == parent_path
23
+ # end
24
+ #end
25
+
26
+ def under path
27
+ base = path + (path == '/' ? '' : '/')
28
+ self.each_with_object(Marsdawn::Site::Indexer.new(@site).current_page(path)) do |(uri, link), ret|
29
+ ret[uri] = link if uri.start_with?(base) && uri != base
30
+ end
31
+ end
32
+
33
+ def neighbor path
34
+ level = path.count('/')
35
+ base = File.dirname(path)
36
+ base = "#{base}/" if base != '/'
37
+ self.each_with_object(Marsdawn::Site::Indexer.new(@site).current_page(path)) do |(uri, link), ret|
38
+ ret[uri] = link if uri.start_with?(base) && uri.count('/') == level && uri != base
39
+ end
40
+ end
41
+
42
+ def to_s
43
+ to_html
44
+ end
45
+
46
+ def to_html options={}
47
+ if self.size > 0
48
+ opts = {
49
+ }.merge(options)
50
+ create_link_html File.dirname(self.keys.first), opts
51
+ else
52
+ ""
53
+ end
54
+ end
55
+
56
+ private
57
+ def create_link_html path, opts={}
58
+ base_level = path.count('/')
59
+ words = []
60
+ words << '<ul>'
61
+ self.each do |uri, link|
62
+ next unless uri.start_with?(path)
63
+ level = uri.count('/')
64
+ if base_level < level
65
+ words.insert -2, create_link_html(uri)
66
+ elsif base_level == level
67
+ attr = (uri == @current_page ? ' class="current-page"' : '')
68
+ words << "<li#{attr}>"
69
+ words << link.to_html
70
+ words << '</li>'
71
+ end
72
+ end
73
+ words << '</ul>'
74
+ words.size > 2 ? words.join('') : ""
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ class Marsdawn::Site
4
+ class Link
5
+ attr_reader :uri, :title, :site, :full_path
6
+
7
+ def initialize site, uri, title=nil
8
+ @uri = uri
9
+ @title = (title.nil? ? site.page_title(uri) : title)
10
+ @site = site
11
+ @full_path = @site.full_path(@uri)
12
+ end
13
+
14
+ def page
15
+ @site.page @uri
16
+ end
17
+
18
+ def to_s
19
+ to_html
20
+ end
21
+
22
+ def to_html
23
+ t = CGI.escapeHTML(@title)
24
+ %!<a href="#{@full_path}" title="#{t}">#{t}</a>!
25
+ end
26
+
27
+ end
28
+ end