forematter 0.1.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/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +9 -0
- data/LICENSE.md +20 -0
- data/README.md +126 -0
- data/Rakefile +3 -0
- data/bin/fore +14 -0
- data/forematter.gemspec +29 -0
- data/lib/forematter/command_runner.rb +67 -0
- data/lib/forematter/commands/add.rb +33 -0
- data/lib/forematter/commands/classify.rb +80 -0
- data/lib/forematter/commands/cleanup.rb +67 -0
- data/lib/forematter/commands/count.rb +32 -0
- data/lib/forematter/commands/fore.rb +22 -0
- data/lib/forematter/commands/list.rb +24 -0
- data/lib/forematter/commands/merge.rb +34 -0
- data/lib/forematter/commands/remove.rb +30 -0
- data/lib/forematter/commands/rename.rb +21 -0
- data/lib/forematter/commands/search.rb +62 -0
- data/lib/forematter/commands/set.rb +29 -0
- data/lib/forematter/commands/suggest.rb +53 -0
- data/lib/forematter/commands/touch.rb +65 -0
- data/lib/forematter/commands/unset.rb +20 -0
- data/lib/forematter/core_ext.rb +47 -0
- data/lib/forematter/cri_ext.rb +33 -0
- data/lib/forematter/file_wrapper.rb +51 -0
- data/lib/forematter/frontmatter.rb +90 -0
- data/lib/forematter/version.rb +5 -0
- data/lib/forematter.rb +68 -0
- metadata +160 -0
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
_You_ can make Forematter even awesomer!
|
4
|
+
|
5
|
+
1. [Fork Forematter](https://github.com/bobthecow/forematter/fork)
|
6
|
+
2. Create your feature branch: `git checkout -b feature/awesomesauce`
|
7
|
+
3. Commit your changes: `git commit -am 'Add awesomesauce feature'`
|
8
|
+
4. Push to the branch: `git push origin feature/awesomesauce`
|
9
|
+
5. [Submit a new Pull Request](http://help.github.com/send-pull-requests/)!
|
data/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2013 Justin Hileman
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
18
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
19
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
20
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# Forematter
|
2
|
+
|
3
|
+
Forematter is the frontmatter-aware friend for your static site.
|
4
|
+
|
5
|
+
|
6
|
+
## Install Forematter
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```rb
|
11
|
+
gem 'forematter'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
```bash
|
17
|
+
$ bundle
|
18
|
+
```
|
19
|
+
|
20
|
+
Or install it yourself, if that's what you're into:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
$ gem install forematter
|
24
|
+
```
|
25
|
+
|
26
|
+
If you're not running a Ruby version manager, you might need to use `sudo gem install forematter` instead. But try it without the `sudo` first.
|
27
|
+
|
28
|
+
|
29
|
+
## Use Forematter for Good
|
30
|
+
|
31
|
+
Forematter makes it easy to add tags I forgot:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
$ find . -name "*twitter*.markdown" | xargs fore add tags twitter
|
35
|
+
```
|
36
|
+
|
37
|
+
And find all of my dumb:
|
38
|
+
|
39
|
+
```bash
|
40
|
+
$ find . -name "*twitter*.markdown" | xargs fore list tags | grep -i tw
|
41
|
+
```
|
42
|
+
|
43
|
+
> Twitpocalypse
|
44
|
+
> Twitter
|
45
|
+
> twitter
|
46
|
+
|
47
|
+
... whoops. Let's fix that:
|
48
|
+
|
49
|
+
```bash
|
50
|
+
$ fore merge tags Twitter twitter *.markdown
|
51
|
+
$ find . -name "*twitter*.markdown" | xargs fore list tags | grep -i tw
|
52
|
+
```
|
53
|
+
|
54
|
+
> Twitpocalypse
|
55
|
+
> Twitter
|
56
|
+
|
57
|
+
Much better!
|
58
|
+
|
59
|
+
|
60
|
+
## Use Forematter for Awesome
|
61
|
+
|
62
|
+
Forematter lets me automatically categorize all my blog posts, based on the tags I use:
|
63
|
+
|
64
|
+
```bash
|
65
|
+
# Grab the 10 most common tags from my blog
|
66
|
+
for c in $(fore count tags *.markdown | tail -10 | sort -r | awk '{print($2)}'); do
|
67
|
+
# Set the category on each article with one of these tags
|
68
|
+
fore search -l tags "$c" *.markdown | xargs fore set category "$c"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Format categories as title case
|
72
|
+
fore cleanup category --titlecase *.markdown
|
73
|
+
|
74
|
+
# Automatically classify all articles which don't already have a category
|
75
|
+
fore classify category *.markdown
|
76
|
+
```
|
77
|
+
|
78
|
+
It does a lot more, too:
|
79
|
+
|
80
|
+
```bash
|
81
|
+
fore add tags foo bar content/*.md
|
82
|
+
fore remove tags bacon content/*.md
|
83
|
+
fore merge tags foo Foo content/*.md
|
84
|
+
fore count tags content/*.md
|
85
|
+
fore search tags content/*.md
|
86
|
+
fore list tags content/*.md
|
87
|
+
|
88
|
+
fore set title 'title!' content/foo.md
|
89
|
+
fore unset title content/bar.md
|
90
|
+
fore list title content/*.md
|
91
|
+
|
92
|
+
fore touch updated_at content/baz.md
|
93
|
+
|
94
|
+
fore cleanup title --titlecase content/*.md
|
95
|
+
fore cleanup name --capitalize content/*.md
|
96
|
+
fore cleanup tags --downcase content/*.md
|
97
|
+
fore cleanup slug --url content/*.md
|
98
|
+
fore cleanup title --trim content/*.md
|
99
|
+
fore cleanup tags --sort content/*.md
|
100
|
+
|
101
|
+
fore classify category content/*.md
|
102
|
+
fore classify category --override content/*.md
|
103
|
+
|
104
|
+
fore --help
|
105
|
+
```
|
106
|
+
|
107
|
+
|
108
|
+
## Use it in your shell
|
109
|
+
|
110
|
+
Forematter tries to be a good *nix citizen. It plays nice with `find` and `grep` and `awk`. Mix and match with all your favorite command line tools!
|
111
|
+
|
112
|
+
|
113
|
+
## Use it everywhere
|
114
|
+
|
115
|
+
Forematter isn't tied to any particular static site generator. If your files have [YAML frontmatter](http://jekyllrb.com/docs/frontmatter/), Forematter is the tool for you.
|
116
|
+
|
117
|
+
If you're looking for a great static site generator, go [check out this list — 210 of 'em and counting](http://staticsitegenerators.net)!
|
118
|
+
|
119
|
+
If Forematter doesn't work with your favorite, [let us know](https://github.com/bobthecow/forematter/issues/new).
|
120
|
+
|
121
|
+
|
122
|
+
## Buyer beware
|
123
|
+
|
124
|
+
Forematter works by _editing your site's content files_. There is a nonzero chance that you'll do something you regret, and Forematter can't help you ⌘Z.
|
125
|
+
|
126
|
+
If you're not keeping things under version control, this would be a good time to start!
|
data/Rakefile
ADDED
data/bin/fore
ADDED
data/forematter.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'forematter/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'forematter'
|
9
|
+
spec.version = Forematter::VERSION.gsub('-', '.')
|
10
|
+
spec.authors = ['Justin Hileman']
|
11
|
+
spec.email = 'justin@justinhileman.info'
|
12
|
+
spec.homepage = 'https://github.com/bobthecow/forematter'
|
13
|
+
spec.summary = 'the frontmatter-aware friend for your static site'
|
14
|
+
spec.description = 'Forematter is the frontmatter-aware friend for your static site'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = %w(CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md Rakefile forematter.gemspec)
|
18
|
+
spec.files += Dir['lib/**/*.rb']
|
19
|
+
spec.files += Dir['spec/**/*.rb']
|
20
|
+
|
21
|
+
spec.executables = %w(fore)
|
22
|
+
|
23
|
+
spec.add_dependency 'cri', '~> 2.4'
|
24
|
+
spec.add_dependency 'stringex'
|
25
|
+
spec.add_dependency 'titleize'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Forematter
|
4
|
+
class CommandRunner < ::Cri::CommandRunner
|
5
|
+
def call
|
6
|
+
run
|
7
|
+
rescue UsageException
|
8
|
+
path = [command.supercommand]
|
9
|
+
path.unshift(path[0].supercommand) until path[0].nil?
|
10
|
+
super_usage = path[1..-1].map { |c| c.name + ' ' }.join
|
11
|
+
$stderr.puts "usage: #{super_usage}#{command.usage}"
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def field
|
18
|
+
partition
|
19
|
+
fail UsageException, 'Missing field name' unless @field
|
20
|
+
@field
|
21
|
+
end
|
22
|
+
|
23
|
+
def value
|
24
|
+
fail 'ARGS!' unless command.value_args == :one
|
25
|
+
partition
|
26
|
+
fail UsageException, 'Missing argument' unless @value
|
27
|
+
@value
|
28
|
+
end
|
29
|
+
|
30
|
+
def values
|
31
|
+
fail 'ARGS!' unless command.value_args == :many
|
32
|
+
partition
|
33
|
+
fail UsageException, 'Missing argument' if @values.empty?
|
34
|
+
@values
|
35
|
+
end
|
36
|
+
|
37
|
+
def files
|
38
|
+
partition
|
39
|
+
fail UsageException, 'No file(s) specified' if @files.empty?
|
40
|
+
@files
|
41
|
+
end
|
42
|
+
|
43
|
+
def files_with(key)
|
44
|
+
files.select { |f| f.key?(key) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def partition
|
48
|
+
return if @args_partitioned
|
49
|
+
args = arguments.dup
|
50
|
+
@field = args.shift
|
51
|
+
@value = args.shift if command.value_args == :one
|
52
|
+
@values, args = guess_split(args) if command.value_args == :many
|
53
|
+
@files = args.map { |f| Forematter::FileWrapper.new(f) }
|
54
|
+
@args_partitioned = true
|
55
|
+
end
|
56
|
+
|
57
|
+
def guess_split(args)
|
58
|
+
if (i = args.index('--'))
|
59
|
+
[args[0..i], args[i..-1]]
|
60
|
+
else
|
61
|
+
files = []
|
62
|
+
files.unshift(args.pop) while !args.empty? && File.exist?(args.last)
|
63
|
+
[args, files]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :many
|
4
|
+
summary 'add values to a field'
|
5
|
+
description <<-EOS
|
6
|
+
Add values to a frontmatter field in to a set of files.
|
7
|
+
|
8
|
+
By default, values will only be added if they don't already exist in a set. This
|
9
|
+
can be overridden with `--allow-dupes`.
|
10
|
+
|
11
|
+
If the specified frontmatter field is present on a file, but the value isn't a
|
12
|
+
YAML sequence (array), `fore add` will exit with an error.
|
13
|
+
EOS
|
14
|
+
|
15
|
+
flag nil, :'allow-dupes', 'allow duplicate values'
|
16
|
+
|
17
|
+
module Forematter::Commands
|
18
|
+
class Add < Forematter::CommandRunner
|
19
|
+
def run
|
20
|
+
files.each do |file|
|
21
|
+
old = file[field].to_ruby || []
|
22
|
+
fail "#{field} is not an array" unless old.is_a?(Array)
|
23
|
+
add = options[:'allow-dupes'] ? values : values.select { |v| !old.include?(v) }
|
24
|
+
next if add.empty?
|
25
|
+
add.each { |v| old << v }
|
26
|
+
file[field] = old
|
27
|
+
file.write
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
runner Forematter::Commands::Add
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :none
|
4
|
+
aliases :categorize
|
5
|
+
summary 'classify files by the given field'
|
6
|
+
description <<-EOS
|
7
|
+
Classify a set of files by the given frontmatter field.
|
8
|
+
|
9
|
+
For example, automatic classification could be used to select categories for
|
10
|
+
articles or blog posts:
|
11
|
+
|
12
|
+
fore classify category articles/*.md
|
13
|
+
|
14
|
+
By default, files which already have a value for the given field are used to
|
15
|
+
train the classifier, and files without a value are automatically classified.
|
16
|
+
|
17
|
+
If the `--override` option is passed, the classifier will be trained on the
|
18
|
+
available files as usual, and every file will be re-classified.
|
19
|
+
|
20
|
+
If the given frontmatter field is present on a file, but the value isn't a
|
21
|
+
string, `fore classify` will exit with an error.
|
22
|
+
EOS
|
23
|
+
|
24
|
+
flag nil, :override, 'Override existing values'
|
25
|
+
|
26
|
+
module Forematter::Commands
|
27
|
+
class Classify < Forematter::CommandRunner
|
28
|
+
def run
|
29
|
+
load_classifier
|
30
|
+
|
31
|
+
puts 'Getting categories'
|
32
|
+
categories_for(files_with(field)).each do |cat|
|
33
|
+
bayes.add_category(cat.to_sym)
|
34
|
+
end
|
35
|
+
|
36
|
+
puts 'Training classifier'
|
37
|
+
files_with(field).each { |file| train(file) }
|
38
|
+
|
39
|
+
puts 'Classifying files'
|
40
|
+
files_to_classify.each { |file| file.write if classify(file) }
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def load_classifier
|
46
|
+
require 'classifier'
|
47
|
+
rescue LoadError
|
48
|
+
$stderr.puts 'Install "classifier" gem to generate suggestions'
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def bayes
|
53
|
+
@bayes ||= Classifier::Bayes.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def categories_for(files)
|
57
|
+
files.map { |file| file[field].to_ruby }.reject(&:nil?).uniq
|
58
|
+
end
|
59
|
+
|
60
|
+
def files_to_classify
|
61
|
+
files.reject do |file|
|
62
|
+
file.key?(field) unless options[:override]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def train(file)
|
67
|
+
val = file[field].to_ruby
|
68
|
+
fail 'Unable to classify by non-string fields' unless val.is_a?(String)
|
69
|
+
bayes.train(val.to_sym, file.content)
|
70
|
+
end
|
71
|
+
|
72
|
+
def classify(file)
|
73
|
+
old = file[field].to_ruby
|
74
|
+
file[field] = bayes.classify(file.content).to_s
|
75
|
+
file[field].to_ruby != old
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
runner Forematter::Commands::Classify
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :none
|
4
|
+
summary 'clean up a field'
|
5
|
+
description <<-EOS
|
6
|
+
Normalize values in a frontmatter field in a set of files.
|
7
|
+
|
8
|
+
If the given frontmatter field is present on a file, but the value isn't a
|
9
|
+
string, `fore cleanup --sort` will exit with an error.
|
10
|
+
EOS
|
11
|
+
|
12
|
+
flag nil, :downcase, 'change to lower case'
|
13
|
+
flag nil, :capitalize, 'change to sentence case'
|
14
|
+
flag nil, :titlecase, 'change to title case'
|
15
|
+
flag nil, :sort, 'sort field values'
|
16
|
+
# flag nil, :translit, 'transliterate values'
|
17
|
+
flag nil, :trim, 'trim whitespace'
|
18
|
+
flag nil, :url, 'sluggify'
|
19
|
+
|
20
|
+
module Forematter::Commands
|
21
|
+
class Cleanup < Forematter::CommandRunner
|
22
|
+
def run
|
23
|
+
require 'stringex_lite'
|
24
|
+
require 'titleize'
|
25
|
+
|
26
|
+
files_with(field).each do |file|
|
27
|
+
old = file[field].to_ruby
|
28
|
+
val = cleanup(old)
|
29
|
+
unless val == old
|
30
|
+
file[field] = val
|
31
|
+
file.write
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
CLEANUP_MAP = {
|
39
|
+
downcase: :downcase,
|
40
|
+
capitalize: :capitalize,
|
41
|
+
titlecase: :titleize,
|
42
|
+
# translit: :translit,
|
43
|
+
trim: :strip,
|
44
|
+
url: :to_url,
|
45
|
+
}
|
46
|
+
|
47
|
+
def cleanup(val)
|
48
|
+
val = val.dup
|
49
|
+
return cleanup_array(val) if val.is_a?(Array)
|
50
|
+
fail 'Unable to sort non-array values' if options[:sort]
|
51
|
+
options.keys.each do |option|
|
52
|
+
val = val.method(CLEANUP_MAP[option]).call if CLEANUP_MAP.key?(option) && options[option]
|
53
|
+
end
|
54
|
+
val
|
55
|
+
end
|
56
|
+
|
57
|
+
def cleanup_array(val)
|
58
|
+
options.keys.each do |option|
|
59
|
+
val.map!(&CLEANUP_MAP[option]) if CLEANUP_MAP.key?(option) && options[option]
|
60
|
+
end
|
61
|
+
val.sort_by!(&:downcase) if options[:sort]
|
62
|
+
val
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
runner Forematter::Commands::Cleanup
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :none
|
4
|
+
summary 'count values in a field'
|
5
|
+
description <<-EOS
|
6
|
+
Count the number of times each value of a given field appears in a set of files.
|
7
|
+
EOS
|
8
|
+
|
9
|
+
module Forematter::Commands
|
10
|
+
class Count < Forematter::CommandRunner
|
11
|
+
def run
|
12
|
+
counts = tags.reduce({}) { |a, e| a.merge(e => (a[e] || 0) + 1) }
|
13
|
+
fmt = format(counts)
|
14
|
+
|
15
|
+
counts.sort_by { |tag, count| count }.each do |tag, count|
|
16
|
+
puts sprintf(fmt, count, tag)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def tags
|
23
|
+
files_with(field).map { |file| file[field].to_ruby }.flatten.map(&:to_sym)
|
24
|
+
end
|
25
|
+
|
26
|
+
def format(counts)
|
27
|
+
"%#{counts.values.max.to_s.length}d %s"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
runner Forematter::Commands::Count
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
usage 'fore command [options] [arguments]'
|
4
|
+
summary 'Forematter, the frontmatter-aware friend for your static site'
|
5
|
+
|
6
|
+
flag :h, :help, 'show the help message and quit' do |value, cmd|
|
7
|
+
puts cmd.help
|
8
|
+
exit 0
|
9
|
+
end
|
10
|
+
|
11
|
+
# flag :V, :verbose, 'enable verbose output' do
|
12
|
+
# Forematter.verbose = true
|
13
|
+
# end
|
14
|
+
|
15
|
+
flag :v, :version, 'show version' do
|
16
|
+
puts Forematter::VERSION
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
|
20
|
+
run do |opts, args, cmd|
|
21
|
+
puts cmd.help
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :none
|
4
|
+
aliases :ls
|
5
|
+
summary 'list values for a field'
|
6
|
+
description <<-EOS
|
7
|
+
List the values for a given frontmatter field in a set of files.
|
8
|
+
EOS
|
9
|
+
|
10
|
+
module Forematter::Commands
|
11
|
+
class List < Forematter::CommandRunner
|
12
|
+
def run
|
13
|
+
puts tags.uniq.sort_by(&:downcase).join("\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def tags
|
19
|
+
files_with(field).map { |file| file[field].to_ruby }.flatten
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
runner Forematter::Commands::List
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :many
|
4
|
+
summary 'combine multiple values for a field'
|
5
|
+
description <<-EOS
|
6
|
+
Combine multiple values for a frontmatter field in a set of files.
|
7
|
+
|
8
|
+
If the given frontmatter field is present on a file, but the value isn't a
|
9
|
+
string, `fore merge` will exit with an error.
|
10
|
+
EOS
|
11
|
+
|
12
|
+
module Forematter::Commands
|
13
|
+
class Merge < Forematter::CommandRunner
|
14
|
+
def run
|
15
|
+
dups = values.dup
|
16
|
+
canonical = dups.shift
|
17
|
+
|
18
|
+
files_with(field).each do |file|
|
19
|
+
old = file[field].to_ruby
|
20
|
+
fail "#{field} is not an array" unless old.is_a?(Array)
|
21
|
+
|
22
|
+
# Continue unless unless field had one of the values to remove
|
23
|
+
next if (old & dups).empty?
|
24
|
+
dups.each { |v| file[field].delete(v) }
|
25
|
+
|
26
|
+
# Save the original canonical for later
|
27
|
+
file[field] << canonical unless file[field].include?(canonical)
|
28
|
+
file.write
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
runner Forematter::Commands::Merge
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :many
|
4
|
+
aliases :rm
|
5
|
+
summary 'remove values from a field'
|
6
|
+
description <<-EOS
|
7
|
+
Remove values from a frontmatter field in a set of files.
|
8
|
+
|
9
|
+
If the given frontmatter field is present on a file, but the value isn't a
|
10
|
+
string, `fore remove` will exit with an error.
|
11
|
+
EOS
|
12
|
+
|
13
|
+
module Forematter::Commands
|
14
|
+
class Remove < Forematter::CommandRunner
|
15
|
+
def run
|
16
|
+
files_with(field).each do |file|
|
17
|
+
old = file[field].to_ruby
|
18
|
+
fail "#{field} is not an array" unless old.is_a?(Array)
|
19
|
+
|
20
|
+
# Continue unless old contains elements of values
|
21
|
+
next if (old & values).empty?
|
22
|
+
values.each { |v| file[field].delete(v) }
|
23
|
+
|
24
|
+
file.write
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
runner Forematter::Commands::Remove
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :one
|
4
|
+
aliases :mv
|
5
|
+
summary 'rename a field'
|
6
|
+
description <<-EOS
|
7
|
+
Rename a frontmatter field in a set of files.
|
8
|
+
EOS
|
9
|
+
|
10
|
+
module Forematter::Commands
|
11
|
+
class Rename < Forematter::CommandRunner
|
12
|
+
def run
|
13
|
+
files_with(field).each do |file|
|
14
|
+
file.rename(field, value)
|
15
|
+
file.write
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
runner Forematter::Commands::Rename
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :one
|
4
|
+
value_name 'pattern'
|
5
|
+
summary 'search for values in a field'
|
6
|
+
description <<-EOS
|
7
|
+
Search for values for a given frontmatter field in a set of files.
|
8
|
+
|
9
|
+
The search pattern may be a string or regular expression, e.g. `/foo\.bar/`.
|
10
|
+
EOS
|
11
|
+
|
12
|
+
flag :i, :'ignore-case', 'perform case insensitive matching'
|
13
|
+
flag :l, :'files-with-matches', 'only list the names of files with matches'
|
14
|
+
flag nil, :print0, 'list file names followed by an ASCII NUL character (for use with `xargs -0`)'
|
15
|
+
|
16
|
+
module Forematter::Commands
|
17
|
+
class Search < Forematter::CommandRunner
|
18
|
+
def run
|
19
|
+
files_with(field).sort_by(&:filename).each do |file|
|
20
|
+
field_val = file[field].to_ruby
|
21
|
+
if field_val.is_a? Array
|
22
|
+
next unless field_val.any? { |v| pattern =~ v }
|
23
|
+
else
|
24
|
+
next unless pattern =~ field_val
|
25
|
+
end
|
26
|
+
write(file.filename, field_val)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def write(filename, val)
|
33
|
+
if options[:print0]
|
34
|
+
print "#{filename}\0"
|
35
|
+
elsif options[:'files-with-matches']
|
36
|
+
puts filename
|
37
|
+
else
|
38
|
+
puts "#{filename}: #{val.to_json}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def pattern
|
43
|
+
@pattern ||= parse_pattern
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_pattern
|
47
|
+
opts = options[:'ignore-case'] ? Regexp::IGNORECASE : 0
|
48
|
+
|
49
|
+
if %r{^/(.*)/([im]{0,2})$} =~ value
|
50
|
+
val = Regexp.last_match[1]
|
51
|
+
opts |= Regexp::IGNORECASE if Regexp.last_match[2].include? 'i'
|
52
|
+
opts |= Regexp::MULTILINE if Regexp.last_match[2].include? 'm'
|
53
|
+
else
|
54
|
+
val = Regexp.escape(value)
|
55
|
+
end
|
56
|
+
|
57
|
+
Regexp.new(val, opts)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
runner Forematter::Commands::Search
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :one
|
4
|
+
summary 'set a field value'
|
5
|
+
description <<-EOS
|
6
|
+
Set a frontmatter field value on a set of files.
|
7
|
+
EOS
|
8
|
+
|
9
|
+
module Forematter::Commands
|
10
|
+
class Set < Forematter::CommandRunner
|
11
|
+
def run
|
12
|
+
files.each do |file|
|
13
|
+
file[field] = value
|
14
|
+
file.write
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def value
|
21
|
+
val = super
|
22
|
+
YAML.load(val)
|
23
|
+
rescue Psych::SyntaxError
|
24
|
+
val
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
runner Forematter::Commands::Set
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :none
|
4
|
+
summary 'suggest values for a field'
|
5
|
+
description <<-EOS
|
6
|
+
Suggest values for a given frontmatter field based on other files in a set.
|
7
|
+
EOS
|
8
|
+
|
9
|
+
flag nil, :override, 'Override existing values'
|
10
|
+
|
11
|
+
module Forematter::Commands
|
12
|
+
class Suggest < Forematter::CommandRunner
|
13
|
+
def run
|
14
|
+
load_classifier
|
15
|
+
|
16
|
+
# Seed LSI index
|
17
|
+
files_with(field).each { |file| seed_index(file) }
|
18
|
+
|
19
|
+
# This takes days:
|
20
|
+
puts 'Building index... eesh'
|
21
|
+
lsi.build_index
|
22
|
+
puts "And we're done!"
|
23
|
+
|
24
|
+
files.each { |file| get_recs(file) }
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def load_classifier
|
30
|
+
require 'classifier'
|
31
|
+
rescue LoadError
|
32
|
+
$stderr.puts 'Install "classifier" gem to generate suggestions'
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def lsi
|
37
|
+
@lsi ||= Classifier::LSI.new(auto_rebuild: false)
|
38
|
+
end
|
39
|
+
|
40
|
+
def seed_index(file)
|
41
|
+
return unless file[:meta].key?(field)
|
42
|
+
lsi.add_item file[:file], *file[:meta][field].to_ruby { |i| file[:content] }
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_recs(file)
|
46
|
+
return if file[:meta].key?(field) unless options[:override]
|
47
|
+
|
48
|
+
# TODO: something here :)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
runner Forematter::Commands::Suggest
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :none
|
4
|
+
summary 'update a field timestamp'
|
5
|
+
|
6
|
+
i = "\xC2\xA0" * 4
|
7
|
+
description <<-EOS
|
8
|
+
Update a frontmatter field timestamp for a set of files.
|
9
|
+
|
10
|
+
The new defaults to the current time, but may be overridden by passing
|
11
|
+
`--time`.
|
12
|
+
|
13
|
+
By default, an ISO-8601 string is used. Alternate formats may be specified:
|
14
|
+
|
15
|
+
--format iso8601
|
16
|
+
|
17
|
+
#{i}ISO-8601 date format, e.g. 2001-02-03T04:05:06+07:00 (default)
|
18
|
+
|
19
|
+
--format date
|
20
|
+
|
21
|
+
#{i}A YAML date literal.
|
22
|
+
|
23
|
+
--format db
|
24
|
+
|
25
|
+
#{i}ANSI SQL date format, e.g. 2001-02-03 04:05:06
|
26
|
+
|
27
|
+
--format '%Y-%m-%d'
|
28
|
+
|
29
|
+
#{i}Or any valid strftime format string
|
30
|
+
EOS
|
31
|
+
|
32
|
+
required :t, :time, 'new timestamp value (default: now)'
|
33
|
+
required :f, :format, 'timestamp format (default: iso8601)'
|
34
|
+
|
35
|
+
module Forematter::Commands
|
36
|
+
class Touch < Forematter::CommandRunner
|
37
|
+
def run
|
38
|
+
files_with(field).each do |file|
|
39
|
+
file[field] = now
|
40
|
+
file.write
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def now
|
47
|
+
@now ||= format_now
|
48
|
+
end
|
49
|
+
|
50
|
+
def format_now
|
51
|
+
format = options[:format] || 'iso8601'
|
52
|
+
now = options.key?(:time) ? Time.new(options[:time]) : Time.now
|
53
|
+
|
54
|
+
case format
|
55
|
+
when 'date' then now
|
56
|
+
when 'iso8601' then now.to_datetime.iso8601
|
57
|
+
when 'db' then now.strftime('%Y-%m-%d %H:%M:%S')
|
58
|
+
else
|
59
|
+
now.strftime(format)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
runner Forematter::Commands::Touch
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
value_args :none
|
4
|
+
summary 'remove a field'
|
5
|
+
description <<-EOS
|
6
|
+
Remove a frontmatter field from a set of files.
|
7
|
+
EOS
|
8
|
+
|
9
|
+
module Forematter::Commands
|
10
|
+
class Unset < Forematter::CommandRunner
|
11
|
+
def run
|
12
|
+
files_with(field).each do |file|
|
13
|
+
file.delete(field)
|
14
|
+
file.write
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
runner Forematter::Commands::Unset
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
fail 'Forematter requires Psych' unless defined?(Psych)
|
4
|
+
|
5
|
+
# class String
|
6
|
+
# def translit
|
7
|
+
# convert_smart_punctuation.
|
8
|
+
# convert_accented_html_entities.
|
9
|
+
# convert_vulgar_fractions.
|
10
|
+
# convert_miscellaneous_html_entities.
|
11
|
+
# convert_miscellaneous_characters.
|
12
|
+
# to_ascii.
|
13
|
+
# convert_miscellaneous_characters.
|
14
|
+
# collapse
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
|
18
|
+
module Psych::Nodes
|
19
|
+
class Sequence
|
20
|
+
def include?(val)
|
21
|
+
children.any? { |c| c.to_ruby == val }
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(val)
|
25
|
+
return unless include?(val)
|
26
|
+
children.each_index do |i|
|
27
|
+
return children.delete_at(i) if children[i].to_ruby == val
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](index)
|
32
|
+
fail "Unexpected index: #{index}" unless index.is_a? Integer
|
33
|
+
children[index]
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(index = nil, val)
|
37
|
+
return push(val) if index.nil?
|
38
|
+
fail "Unexpected index: #{index}" unless index.is_a? Integer
|
39
|
+
children[index] = YAML.parse(YAML.dump(val)).children.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def push(val)
|
43
|
+
children.push(YAML.parse(YAML.dump(val)).children.first)
|
44
|
+
end
|
45
|
+
alias_method :<<, :push
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cri
|
4
|
+
class Command
|
5
|
+
attr_accessor :value_args
|
6
|
+
attr_accessor :value_name
|
7
|
+
end
|
8
|
+
|
9
|
+
class CommandDSL
|
10
|
+
def value_args(count)
|
11
|
+
@command.value_args = count
|
12
|
+
end
|
13
|
+
|
14
|
+
def value_name(name)
|
15
|
+
@command.value_name = name
|
16
|
+
end
|
17
|
+
|
18
|
+
NBSP = "\xC2\xA0"
|
19
|
+
|
20
|
+
def auto_usage
|
21
|
+
name = @command.name
|
22
|
+
value = @command.value_name || 'value'
|
23
|
+
case @command.value_args
|
24
|
+
when :none
|
25
|
+
usage "#{name} [options] field file [file#{NBSP}...]"
|
26
|
+
when :one
|
27
|
+
usage "#{name} [options] field #{value} file [file#{NBSP}...]"
|
28
|
+
when :many
|
29
|
+
usage "#{name} [options] field #{value} [#{value}#{NBSP}...] file [file#{NBSP}...]"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Forematter
|
4
|
+
class FileWrapper
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def initialize(filename)
|
8
|
+
fail "File not found: #{filename}" unless File.exist?(filename)
|
9
|
+
@filename = filename
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :filename
|
13
|
+
|
14
|
+
def_delegators :meta, :key?, :has_key?, :[], :[]=, :delete, :rename
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"#{meta.to_yaml}---\n#{content}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def write
|
21
|
+
File.open(filename, 'w+') { |f| f << to_s }
|
22
|
+
end
|
23
|
+
|
24
|
+
def content
|
25
|
+
parse_file
|
26
|
+
@content
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def meta
|
32
|
+
parse_file
|
33
|
+
@meta
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_file
|
37
|
+
return if @is_parsed
|
38
|
+
|
39
|
+
data = '--- {}'
|
40
|
+
content = IO.read(@filename)
|
41
|
+
if content =~ /\A(---\s*\n.*?\n?)^(?:(?:---|\.\.\.)\s*$\n?)/m
|
42
|
+
data = Regexp.last_match[1]
|
43
|
+
content = $POSTMATCH
|
44
|
+
end
|
45
|
+
|
46
|
+
@meta = Forematter::Frontmatter.new(data)
|
47
|
+
@content = content
|
48
|
+
@is_parsed = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Forematter
|
4
|
+
class Frontmatter
|
5
|
+
def initialize(input)
|
6
|
+
init_stream(input)
|
7
|
+
end
|
8
|
+
|
9
|
+
def key?(key)
|
10
|
+
data.children.each_index do |i|
|
11
|
+
next unless i % 2
|
12
|
+
return true if data.children[i].to_ruby == key
|
13
|
+
end
|
14
|
+
|
15
|
+
false
|
16
|
+
end
|
17
|
+
alias_method :has_key?, :key?
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
data.children.each_index do |i|
|
21
|
+
next unless i % 2
|
22
|
+
return data.children[i + 1] if data.children[i].to_ruby == key
|
23
|
+
end
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(key, val)
|
28
|
+
data.children.each_index do |i|
|
29
|
+
next unless i % 2
|
30
|
+
if data.children[i].to_ruby == key
|
31
|
+
data.children[i + 1] = thunk(val, data.children[i + 1])
|
32
|
+
return
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
data.children << Psych::Nodes::Scalar.new(key)
|
37
|
+
data.children << thunk(val)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(key)
|
41
|
+
data.children.each_index do |i|
|
42
|
+
next unless i % 2
|
43
|
+
if data.children[i].to_ruby == key
|
44
|
+
val = data.children.delete_at(i + 1)
|
45
|
+
data.children.delete_at(i)
|
46
|
+
return val
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def rename(key, new_key)
|
52
|
+
data.children.each_index do |i|
|
53
|
+
next unless i % 2
|
54
|
+
if data.children[i].to_ruby == key
|
55
|
+
data.children[i].value = new_key
|
56
|
+
return
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_yaml
|
62
|
+
@stream.to_yaml
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def thunk(val, old = nil)
|
68
|
+
return val if val.is_a?(Psych::Nodes::Node)
|
69
|
+
val = parse_yaml(val)
|
70
|
+
if old.is_a?(Psych::Nodes::Sequence) && val.is_a?(Psych::Nodes::Sequence)
|
71
|
+
old.children.replace(val.children)
|
72
|
+
val = old
|
73
|
+
end
|
74
|
+
val
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_yaml(val)
|
78
|
+
YAML.parse(YAML.dump(val)).children.first
|
79
|
+
end
|
80
|
+
|
81
|
+
attr_reader :data
|
82
|
+
|
83
|
+
def init_stream(input)
|
84
|
+
doc = YAML.parse_stream(input).children.first
|
85
|
+
@data = doc.children.first
|
86
|
+
@stream = YAML.parse_stream('')
|
87
|
+
@stream.children << doc
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/forematter.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# stdlib
|
4
|
+
require 'date'
|
5
|
+
require 'English'
|
6
|
+
require 'forwardable'
|
7
|
+
require 'yaml'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
# third party
|
11
|
+
require 'cri'
|
12
|
+
|
13
|
+
# forematter
|
14
|
+
require 'forematter/version'
|
15
|
+
require 'forematter/core_ext'
|
16
|
+
require 'forematter/cri_ext'
|
17
|
+
require 'forematter/frontmatter'
|
18
|
+
require 'forematter/file_wrapper'
|
19
|
+
require 'forematter/command_runner'
|
20
|
+
|
21
|
+
module Forematter
|
22
|
+
module Commands
|
23
|
+
end
|
24
|
+
|
25
|
+
class UsageException < Exception
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
attr_reader :root_command
|
30
|
+
# attr_accessor :verbose
|
31
|
+
|
32
|
+
def run(args)
|
33
|
+
# Remove the signal trap we set in the bin file.
|
34
|
+
Signal.trap('INT', 'DEFAULT')
|
35
|
+
setup
|
36
|
+
root_command.run(args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_command(cmd)
|
40
|
+
root_command.add_command(cmd)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def setup
|
46
|
+
root_cmd_filename = File.dirname(__FILE__) + '/forematter/commands/fore.rb'
|
47
|
+
|
48
|
+
# Add help and root commands
|
49
|
+
@root_command = load_command_at(root_cmd_filename)
|
50
|
+
add_command(Cri::Command.new_basic_help)
|
51
|
+
|
52
|
+
cmd_filenames.each do |filename|
|
53
|
+
add_command(load_command_at(filename)) unless filename == root_cmd_filename
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def cmd_filenames
|
58
|
+
Dir[File.dirname(__FILE__) + '/forematter/commands/*.rb']
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_command_at(filename, command_name = nil)
|
62
|
+
Cri::Command.define(File.read(filename), filename).modify do
|
63
|
+
name command_name || File.basename(filename, '.rb')
|
64
|
+
auto_usage
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: forematter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Justin Hileman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-12-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.4'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.4'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: stringex
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: titleize
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: bundler
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: Forematter is the frontmatter-aware friend for your static site
|
95
|
+
email: justin@justinhileman.info
|
96
|
+
executables:
|
97
|
+
- fore
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- CHANGELOG.md
|
102
|
+
- CONTRIBUTING.md
|
103
|
+
- LICENSE.md
|
104
|
+
- README.md
|
105
|
+
- Rakefile
|
106
|
+
- forematter.gemspec
|
107
|
+
- lib/forematter/command_runner.rb
|
108
|
+
- lib/forematter/commands/add.rb
|
109
|
+
- lib/forematter/commands/classify.rb
|
110
|
+
- lib/forematter/commands/cleanup.rb
|
111
|
+
- lib/forematter/commands/count.rb
|
112
|
+
- lib/forematter/commands/fore.rb
|
113
|
+
- lib/forematter/commands/list.rb
|
114
|
+
- lib/forematter/commands/merge.rb
|
115
|
+
- lib/forematter/commands/remove.rb
|
116
|
+
- lib/forematter/commands/rename.rb
|
117
|
+
- lib/forematter/commands/search.rb
|
118
|
+
- lib/forematter/commands/set.rb
|
119
|
+
- lib/forematter/commands/suggest.rb
|
120
|
+
- lib/forematter/commands/touch.rb
|
121
|
+
- lib/forematter/commands/unset.rb
|
122
|
+
- lib/forematter/core_ext.rb
|
123
|
+
- lib/forematter/cri_ext.rb
|
124
|
+
- lib/forematter/file_wrapper.rb
|
125
|
+
- lib/forematter/frontmatter.rb
|
126
|
+
- lib/forematter/version.rb
|
127
|
+
- lib/forematter.rb
|
128
|
+
- bin/fore
|
129
|
+
homepage: https://github.com/bobthecow/forematter
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
hash: 2379320938142761946
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ! '>='
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
segments:
|
152
|
+
- 0
|
153
|
+
hash: 2379320938142761946
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 1.8.23
|
157
|
+
signing_key:
|
158
|
+
specification_version: 3
|
159
|
+
summary: the frontmatter-aware friend for your static site
|
160
|
+
test_files: []
|