maneki 1.0.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.
data/README.markdown ADDED
@@ -0,0 +1,105 @@
1
+ Maneki
2
+ ======
3
+
4
+ Maneki is a simple file-based model for your Ruby projects. Give it a directory and it will parse any relevant files within.
5
+
6
+ For example you might have a model like this:
7
+
8
+ class Document < Maneki
9
+ path_is '../content/documents'
10
+ end
11
+
12
+ ...and a directory, `documents`, that contains a bunch of markdown documents:
13
+
14
+ documents/
15
+ first-document.text
16
+ second-document.text
17
+ third-document.text
18
+
19
+ ...and each file might be in a form similar to:
20
+
21
+ # This is the title
22
+
23
+ - published: yes
24
+ - authors: Aristoclea Cattington, Sebastian Fluffybottom
25
+
26
+ This is the content of the document. It is written in Markdown.
27
+
28
+ This example parsed document would have the following properties:
29
+
30
+ - Title: This is the title
31
+ - Headers:
32
+ - Published: True
33
+ - Authors:
34
+ - 'Aristoclea Cattington'
35
+ - 'Sebastian Fluffybottom'
36
+ - Body: This is the content of the document. It is written in Markdown.
37
+
38
+
39
+ Getting some documents
40
+ ----------------------
41
+
42
+ If you want all of the documents.
43
+
44
+ Document.all
45
+
46
+ Or you can find items by their slug.
47
+
48
+ Document.find :slug => 'test-document'
49
+
50
+ Or you can also search for the inclusion of certain headers.
51
+
52
+ Document.find :headers => { :published => true }
53
+ Document.find :headers => { :authors => 'Aristoclea Cattington' }
54
+
55
+ You can also grab any documents from a given category (read on).
56
+
57
+
58
+ Document categories
59
+ -------------------
60
+
61
+ If documents are found within folders then they will also be added to a categorised list.
62
+
63
+ For example, given this directory structure:
64
+
65
+ content/
66
+ documents/
67
+ first-document.text
68
+ second-document.text
69
+ third-document.text
70
+ projects/
71
+ a-project.text
72
+ here-is-another-project.text
73
+
74
+ Now assume that Maneki has its path set to `content`:
75
+
76
+ Maneki.path = 'content'
77
+
78
+ Here if we want to grab any posts we can just ask for the category.
79
+
80
+ Maneki.documents
81
+
82
+ Here we will get:
83
+
84
+ - First document
85
+ - Second document
86
+ - Third document
87
+
88
+ Whereas if you did:
89
+
90
+ Maneki.projects
91
+
92
+ You would get:
93
+
94
+ - A Project
95
+ - Here is another project
96
+
97
+ If you want to get a list of the found categories then ask for it.
98
+
99
+ Maneki.categories
100
+
101
+
102
+ Suggestions?
103
+ ------------
104
+
105
+ [Find me on Twitter](http://twitter.com/nathanhoad)
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ task :default => :test
2
+
3
+
4
+ ### TESTING ###
5
+ desc 'Run unit tests'
6
+ task 'test' do |t|
7
+ sh 'ruby test/test_all.rb'
8
+ end
9
+
10
+
11
+ ### PACKAGING ###
12
+ begin
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gemspec|
15
+ gemspec.name = "maneki"
16
+ gemspec.summary = "A simple file-based model for your Ruby projects"
17
+ gemspec.description = "Maneki loads and parses any relevant text files in a given directory"
18
+ gemspec.email = "nathan@nathanhoad.net"
19
+ gemspec.homepage = "http://github.com/nathanhoad/maneki"
20
+ gemspec.authors = ["Nathan Hoad"]
21
+ gemspec.files = `git ls-files`.split("\n").sort.reject{ |file| file =~ /^\./ }
22
+ end
23
+ rescue LoadError
24
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
25
+ end
26
+
27
+
28
+ ### DOCUMENTATION ###
29
+ require 'rake/rdoctask'
30
+ Rake::RDocTask.new do |rdoc|
31
+ if File.exist? 'VERSION'
32
+ version = File.read('VERSION')
33
+ else
34
+ version = ""
35
+ end
36
+
37
+ rdoc.rdoc_dir = 'rdoc'
38
+ rdoc.title = "Maneki #{version}"
39
+ rdoc.rdoc_files.include('README*')
40
+ rdoc.rdoc_files.include('lib/**/*.rb')
41
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/lib/maneki.rb ADDED
@@ -0,0 +1,241 @@
1
+ # = Maneki
2
+ #
3
+ # Load documents from a directory and parse them. The idea is that
4
+ # any of the given documents (Markdown only for now) should be readable
5
+ # in their own format without parsing but be formatted in such way as
6
+ # to present meta information (title, headers, etc) in an easily parsable
7
+ # way.
8
+ class Maneki
9
+ # Set the search path
10
+ def self.path= (value)
11
+ path value
12
+ end
13
+
14
+ # Set or return the search path
15
+ def self.path (value = nil)
16
+ @path = value if value
17
+ @category = File.basename(value) unless @category
18
+ @path
19
+ end
20
+
21
+
22
+ # Set the general category for these documents
23
+ def self.category (value = nil)
24
+ @category = value
25
+ @category
26
+ end
27
+
28
+
29
+ # Get a list of all categories
30
+ def self.categories
31
+ prepare
32
+ @categorised.keys
33
+ end
34
+
35
+
36
+ # Grab all documents
37
+ def self.all
38
+ prepare
39
+ end
40
+
41
+
42
+ # Search for any documents
43
+ def self.find (args = {})
44
+ if args[:slug] or args.is_a? String
45
+ return find_by_slug(args[:slug] || args)
46
+ end
47
+
48
+ match = args[:match] || :any
49
+ args.delete(:match)
50
+
51
+ all.select do |item|
52
+ matches = 0
53
+
54
+ if args[:title]
55
+ if item.title == args[:title]
56
+ matched ||= true if match == :any
57
+ matches += 1 if match == :all
58
+ else
59
+ matched ||= false if match == :all
60
+ end
61
+ end
62
+
63
+ if args[:body]
64
+ if item.body.downcase.include? args[:body].downcase
65
+ matched ||= true if match == :any
66
+ matches += 1 if match == :all
67
+ else
68
+ matched ||= false if match == :all
69
+ end
70
+ end
71
+
72
+ if args[:headers]
73
+ args[:headers].each_pair do |header, value|
74
+ if item.headers[header] and (item.headers[header] == value or item.headers[header].include? value)
75
+ matched ||= true if match == :any
76
+ matches += 1 if match == :all
77
+ else
78
+ matched ||= false if match == :all
79
+ end
80
+ end
81
+ end
82
+
83
+ matched || (matches >= args.size)
84
+ end
85
+ end
86
+
87
+
88
+ # The previous document before a given document
89
+ def self.previous_before (document, args = {})
90
+ prepare
91
+ if args[:category]
92
+ index = @categorised[args[:category]].index(document)
93
+ else
94
+ index = @documents.index(document)
95
+ end
96
+ @documents[index - 1] unless index == 0
97
+ end
98
+
99
+ # The next document after a given document
100
+ def self.next_after (document, args = {})
101
+ prepare
102
+ if args[:category]
103
+ index = @categorised[args[:category]].index(document)
104
+ else
105
+ index = @documents.index(document)
106
+ end
107
+ @documents[index + 1] unless index == @documents.length
108
+ end
109
+
110
+
111
+ # Look up a document category
112
+ def self.method_missing (method, *args)
113
+ prepare
114
+ @categorised[method.to_s] unless @categorised.nil?
115
+ end
116
+
117
+
118
+ attr_accessor :filename, :category, :slug, :headers, :title, :body
119
+
120
+ # Create a new document
121
+ def initialize (args = {})
122
+ @filename = File.expand_path(args[:filename])
123
+
124
+ @body = ''
125
+ if File.exists? @filename
126
+ @category = File.dirname(@filename).split('/').last unless @category
127
+ @slug = @filename.gsub(File.expand_path(self.class.path) + '/', '').gsub(/\..+$/, '')
128
+
129
+ body = File.open(@filename).readlines
130
+ body.each do |line|
131
+ line.gsub!("\r", '') # fix up any DOS EOLs
132
+ end
133
+
134
+ title = body.find { |item| /^\#.+/.match item }
135
+ body = body[2..body.length-1] if title # consume title
136
+
137
+ # check for headers
138
+ @headers = Hash.new
139
+ if /^\s?\-.+/.match(body[0])
140
+ headers_end = body.index("\n") || 1
141
+ headers = body[0..headers_end-1] if headers_end # next blank line is end of headers
142
+
143
+ body_start = 1
144
+ body_start = body.index(headers.last) + 1 if headers
145
+ body = body[body_start..body.length - 1] if title
146
+
147
+ headers.each do |header|
148
+ unless header.strip == '' or /^\#.+/.match header
149
+ values = header.gsub(/^\s?\-/, '').strip.split(/:\s/)
150
+ key = values.shift.downcase.gsub(/[^\w\_]+/, '_').to_sym
151
+ value = values.join(': ').strip
152
+ # check for special header values (true, false, lists, etc)
153
+ if ['true', 't', 'yes', 'y'].include? value
154
+ value = true
155
+ elsif ['false', 'f', 'no', 'n'].include? value
156
+ value = false
157
+ elsif value.include? ','
158
+ value = value.split(/\,\s?/)
159
+ end
160
+ @headers[key] = value
161
+ end
162
+ end if headers
163
+ end
164
+
165
+ # title
166
+ @title = title.gsub(/^\#\s/, '').strip if title
167
+
168
+ # content
169
+ @body = body.join("").strip
170
+ end
171
+ end
172
+
173
+
174
+ # A callback to see if this document should be added to the list
175
+ def valid?
176
+ true
177
+ end
178
+
179
+
180
+ # Documents are the same if their IDs are the same
181
+ def == (rhs)
182
+ @slug == rhs.slug
183
+ end
184
+
185
+
186
+ # Sort by filename
187
+ def <=> (rhs)
188
+ @filename <=> rhs.filename
189
+ end
190
+
191
+
192
+ # Stringify this document
193
+ def to_s
194
+ title
195
+ end
196
+
197
+
198
+ protected
199
+
200
+ # Grab the @path with a slash if needed
201
+ def self.path_with_slash
202
+ files_path = File.expand_path(@path)
203
+ files_path += '/' unless files_path.end_with? '/'
204
+ files_path
205
+ end
206
+
207
+
208
+ # Find a document by its slug
209
+ def self.find_by_slug (slug)
210
+ filename = path_with_slash + slug + '.text'
211
+ if File.exists? filename
212
+ document = new(:filename => filename)
213
+ document if document.valid?
214
+ end
215
+ end
216
+
217
+
218
+ # Load the any documents in the files path
219
+ def self.prepare (args = {})
220
+ if @documents.nil? or args[:force]
221
+ files_path = path_with_slash
222
+
223
+ @documents = []
224
+ @categorised = {}
225
+
226
+ # find some documents
227
+ Dir.glob(["#{files_path}*", "#{files_path}*/*"]).each do |filename|
228
+ if filename.split('.').last == 'text'
229
+ document = new(:filename => filename)
230
+ if document.valid?
231
+ @documents << document
232
+ @categorised[document.category] ||= []
233
+ @categorised[document.category] << document
234
+ end
235
+ end
236
+ end
237
+ @documents.sort!
238
+ end
239
+ @documents
240
+ end
241
+ end
data/maneki.gemspec ADDED
@@ -0,0 +1,58 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{maneki}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Nathan Hoad"]
12
+ s.date = %q{2010-05-22}
13
+ s.description = %q{Maneki loads and parses any relevant text files in a given directory}
14
+ s.email = %q{nathan@nathanhoad.net}
15
+ s.extra_rdoc_files = [
16
+ "README.markdown"
17
+ ]
18
+ s.files = [
19
+ "README.markdown",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "lib/maneki.rb",
23
+ "maneki.gemspec",
24
+ "test/chapter.rb",
25
+ "test/content/chapters/01-the-first-chapter.text",
26
+ "test/content/chapters/02-chapter-2.text",
27
+ "test/content/chapters/03-chapter-with-headers.text",
28
+ "test/content/chapters/04-different-chapter.text",
29
+ "test/content/documents/first-document.text",
30
+ "test/content/documents/second-document.text",
31
+ "test/content/documents/third-document.text",
32
+ "test/test_all.rb",
33
+ "test/test_chapters.rb",
34
+ "test/test_maneki.rb"
35
+ ]
36
+ s.homepage = %q{http://github.com/nathanhoad/maneki}
37
+ s.rdoc_options = ["--charset=UTF-8"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.3.6}
40
+ s.summary = %q{A simple file-based model for your Ruby projects}
41
+ s.test_files = [
42
+ "test/chapter.rb",
43
+ "test/test_all.rb",
44
+ "test/test_chapters.rb",
45
+ "test/test_maneki.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
53
+ else
54
+ end
55
+ else
56
+ end
57
+ end
58
+
data/test/chapter.rb ADDED
@@ -0,0 +1,3 @@
1
+ class Chapter < Maneki
2
+ path File.dirname(__FILE__) + '/content/chapters'
3
+ end
@@ -0,0 +1,3 @@
1
+ # Chapter 1: The first chapter
2
+
3
+ This is the content of this chapter.
@@ -0,0 +1,3 @@
1
+ # Chapter 2: Crazy cats
2
+
3
+ This is the second chapter.
@@ -0,0 +1,8 @@
1
+ # Chapter 3: Chapter with Headers
2
+
3
+ - Author: Nathan Hoad
4
+ - List values: These, are, list, items
5
+ - Boolean value: yes
6
+ - Another boolean: no
7
+
8
+ Here is a chapter written by a guest author. It has some headers.
@@ -0,0 +1,6 @@
1
+ # Alpha sorted first title
2
+
3
+ - Boolean value: yes
4
+ - Facts: This chapter should be sorted first if by title
5
+
6
+ Here is some text.
@@ -0,0 +1,6 @@
1
+ # This is the first document
2
+
3
+ - published: yes
4
+ - author: Nathan Hoad
5
+
6
+ Here is some content for this document.
@@ -0,0 +1,3 @@
1
+ # Here is the second document
2
+
3
+ The content for this document is this.
@@ -0,0 +1,3 @@
1
+ # Third document
2
+
3
+ Yep. This is the third one.
data/test/test_all.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'test/unit'
2
+
3
+ Dir.glob('test/test_*.rb').each do |filename|
4
+ require filename if filename != __FILE__
5
+ end
@@ -0,0 +1,50 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/maneki'
3
+ require File.dirname(__FILE__) + '/chapter'
4
+
5
+
6
+ class TestChapters < Test::Unit::TestCase
7
+ def test_document_load
8
+ chapter = Chapter.new :filename => File.dirname(__FILE__) + '/content/chapters/01-the-first-chapter.text'
9
+ assert_kind_of Chapter, chapter
10
+ assert_equal '01-the-first-chapter', chapter.slug
11
+ assert_equal 'Chapter 1: The first chapter', chapter.title
12
+ assert_equal 'This is the content of this chapter.', chapter.body
13
+ assert_equal({}, chapter.headers)
14
+ end
15
+
16
+ def test_chapters_load
17
+ assert_not_equal 0, Chapter.all.size
18
+ end
19
+
20
+ def test_find
21
+ chapter = Chapter.find '01-the-first-chapter'
22
+ assert_kind_of Chapter, chapter
23
+ assert_equal 'Chapter 1: The first chapter', chapter.title
24
+
25
+ chapters = Chapter.find :headers => { :boolean_value => true }, :body => 'some headers', :match => :all
26
+ assert_equal 1, chapters.size
27
+
28
+ chapters = Chapter.find :headers => { :boolean_value => true }, :body => 'some headers', :match => :any
29
+ assert_equal 2, chapters.size
30
+
31
+ chapters = Chapter.find :headers => { :boolean_value => true }
32
+ assert_not_equal 0, chapters.size
33
+
34
+ chapters = Chapter.find :headers => { :list_values => 'are' }
35
+ assert_not_equal 0, chapters.size
36
+ end
37
+
38
+ def test_headers
39
+ chapter = Chapter.find '01-the-first-chapter'
40
+ assert_equal({}, chapter.headers)
41
+
42
+ chapter = Chapter.find '03-chapter-with-headers'
43
+ assert_not_equal({}, chapter.headers)
44
+ assert_equal 'Here is a chapter written by a guest author. It has some headers.', chapter.body
45
+
46
+ assert_kind_of Array, chapter.headers[:list_values]
47
+ assert_kind_of TrueClass, chapter.headers[:boolean_value]
48
+ assert_kind_of FalseClass, chapter.headers[:another_boolean]
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/maneki'
3
+
4
+
5
+ class TestManeki < Test::Unit::TestCase
6
+ def setup
7
+ @content_path = File.dirname(__FILE__) + '/content'
8
+ Maneki.path = @content_path
9
+ end
10
+
11
+ def test_documents_categories
12
+ assert Maneki.categories.include?('documents')
13
+ assert_not_equal 0, Maneki.documents.size
14
+
15
+ documents = Maneki.documents
16
+ assert_kind_of Array, documents
17
+ end
18
+
19
+ def test_previous_and_next
20
+ documents = Maneki.documents
21
+ first = documents[0]
22
+ second = documents[1]
23
+ other_second = Maneki.find 'documents/second-document'
24
+ third = documents[2]
25
+
26
+ assert_not_nil Maneki.previous_before(first)
27
+ assert_nil Maneki.previous_before(first, :category => 'documents')
28
+ assert_equal second, Maneki.next_after(first)
29
+ assert_equal other_second, Maneki.next_after(first)
30
+ assert_equal third, Maneki.next_after(second)
31
+ assert_equal third, Maneki.next_after(other_second)
32
+ assert_nil Maneki.next_after(third)
33
+
34
+ assert_equal first, Maneki.next_after(Maneki.previous_before(first))
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maneki
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Nathan Hoad
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-22 00:00:00 +10:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Maneki loads and parses any relevant text files in a given directory
22
+ email: nathan@nathanhoad.net
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.markdown
29
+ files:
30
+ - README.markdown
31
+ - Rakefile
32
+ - VERSION
33
+ - lib/maneki.rb
34
+ - maneki.gemspec
35
+ - test/chapter.rb
36
+ - test/content/chapters/01-the-first-chapter.text
37
+ - test/content/chapters/02-chapter-2.text
38
+ - test/content/chapters/03-chapter-with-headers.text
39
+ - test/content/chapters/04-different-chapter.text
40
+ - test/content/documents/first-document.text
41
+ - test/content/documents/second-document.text
42
+ - test/content/documents/third-document.text
43
+ - test/test_all.rb
44
+ - test/test_chapters.rb
45
+ - test/test_maneki.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/nathanhoad/maneki
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.6
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: A simple file-based model for your Ruby projects
76
+ test_files:
77
+ - test/chapter.rb
78
+ - test/test_all.rb
79
+ - test/test_chapters.rb
80
+ - test/test_maneki.rb