ottogen 0.1.0 → 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.
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ RSpec.describe Ottogen::Post do
6
+ describe '.read' do
7
+ it 'parses YYYY-MM-DD-slug from the filename' do
8
+ in_tmp_dir do
9
+ FileUtils.mkdir_p('_posts')
10
+ File.write('_posts/2026-01-15-hello-world.adoc', "= Hello\n\nBody.\n")
11
+
12
+ post = described_class.read('_posts/2026-01-15-hello-world.adoc')
13
+
14
+ expect(post.date).to eq(Date.new(2026, 1, 15))
15
+ expect(post.slug).to eq('hello-world')
16
+ end
17
+ end
18
+
19
+ it 'parses front matter via the shared FrontMatter parser' do
20
+ in_tmp_dir do
21
+ FileUtils.mkdir_p('_posts')
22
+ File.write('_posts/2026-01-15-hi.adoc', "---\ntitle: Greetings\n---\n= Hi\n\nBody.\n")
23
+
24
+ post = described_class.read('_posts/2026-01-15-hi.adoc')
25
+
26
+ expect(post.front_matter).to eq('title' => 'Greetings')
27
+ expect(post.body).to eq("= Hi\n\nBody.\n")
28
+ end
29
+ end
30
+
31
+ it 'raises Post::Error when the filename does not match YYYY-MM-DD-slug.adoc' do
32
+ in_tmp_dir do
33
+ FileUtils.mkdir_p('_posts')
34
+ File.write('_posts/not-a-post.adoc', "= Hi\n")
35
+
36
+ expect { described_class.read('_posts/not-a-post.adoc') }
37
+ .to raise_error(Ottogen::Post::Error)
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#title' do
43
+ it 'falls back to a titleized slug when not in front matter' do
44
+ in_tmp_dir do
45
+ FileUtils.mkdir_p('_posts')
46
+ File.write('_posts/2026-01-15-hello-world.adoc', "= Hi\n")
47
+
48
+ post = described_class.read('_posts/2026-01-15-hello-world.adoc')
49
+
50
+ expect(post.title).to eq('Hello World')
51
+ end
52
+ end
53
+
54
+ it 'uses the front matter title when present' do
55
+ in_tmp_dir do
56
+ FileUtils.mkdir_p('_posts')
57
+ File.write('_posts/2026-01-15-hi.adoc', "---\ntitle: Greetings\n---\nBody.\n")
58
+
59
+ post = described_class.read('_posts/2026-01-15-hi.adoc')
60
+
61
+ expect(post.title).to eq('Greetings')
62
+ end
63
+ end
64
+ end
65
+
66
+ describe '#url' do
67
+ it 'is /<slug>.html' do
68
+ in_tmp_dir do
69
+ FileUtils.mkdir_p('_posts')
70
+ File.write('_posts/2026-01-15-hello-world.adoc', "= Hi\n")
71
+
72
+ post = described_class.read('_posts/2026-01-15-hello-world.adoc')
73
+
74
+ expect(post.url).to eq('/hello-world.html')
75
+ end
76
+ end
77
+ end
78
+
79
+ describe '.discover' do
80
+ it 'returns all posts from _posts/' do
81
+ in_tmp_dir do
82
+ FileUtils.mkdir_p('_posts')
83
+ File.write('_posts/2026-01-15-a.adoc', "= A\n")
84
+ File.write('_posts/2026-02-15-b.adoc', "= B\n")
85
+
86
+ slugs = described_class.discover.map(&:slug)
87
+
88
+ expect(slugs).to contain_exactly('a', 'b')
89
+ end
90
+ end
91
+
92
+ it 'returns an empty array when _posts/ is missing' do
93
+ in_tmp_dir do
94
+ expect(described_class.discover).to eq([])
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '.read_draft' do
100
+ it 'parses a draft filename and uses today as the date' do
101
+ in_tmp_dir do
102
+ FileUtils.mkdir_p('_drafts')
103
+ File.write('_drafts/in-progress.adoc', "= WIP\n\nBody.\n")
104
+
105
+ draft = described_class.read_draft('_drafts/in-progress.adoc')
106
+
107
+ expect(draft.slug).to eq('in-progress')
108
+ expect(draft.date).to eq(Date.today)
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#tags' do
114
+ it 'returns front matter tags as an array (list form)' do
115
+ in_tmp_dir do
116
+ FileUtils.mkdir_p('_posts')
117
+ File.write('_posts/2026-01-15-hi.adoc', "---\ntags:\n - ruby\n - cli\n---\nBody.\n")
118
+
119
+ expect(described_class.read('_posts/2026-01-15-hi.adoc').tags).to eq(%w[ruby cli])
120
+ end
121
+ end
122
+
123
+ it 'returns empty array when not present' do
124
+ in_tmp_dir do
125
+ FileUtils.mkdir_p('_posts')
126
+ File.write('_posts/2026-01-15-hi.adoc', "= Hi\n")
127
+
128
+ expect(described_class.read('_posts/2026-01-15-hi.adoc').tags).to eq([])
129
+ end
130
+ end
131
+
132
+ it 'wraps a single-string tag value into a one-element array' do
133
+ in_tmp_dir do
134
+ FileUtils.mkdir_p('_posts')
135
+ File.write('_posts/2026-01-15-hi.adoc', "---\ntags: ruby\n---\nBody.\n")
136
+
137
+ expect(described_class.read('_posts/2026-01-15-hi.adoc').tags).to eq(%w[ruby])
138
+ end
139
+ end
140
+ end
141
+
142
+ describe '#categories' do
143
+ it 'returns front matter categories as an array' do
144
+ in_tmp_dir do
145
+ FileUtils.mkdir_p('_posts')
146
+ File.write('_posts/2026-01-15-hi.adoc', "---\ncategories:\n - dev\n - ruby\n---\nBody.\n")
147
+
148
+ expect(described_class.read('_posts/2026-01-15-hi.adoc').categories).to eq(%w[dev ruby])
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '.discover_drafts' do
154
+ it 'returns drafts from _drafts/' do
155
+ in_tmp_dir do
156
+ FileUtils.mkdir_p('_drafts')
157
+ File.write('_drafts/a.adoc', "= A\n")
158
+ File.write('_drafts/b.adoc', "= B\n")
159
+
160
+ slugs = described_class.discover_drafts.map(&:slug)
161
+
162
+ expect(slugs).to contain_exactly('a', 'b')
163
+ end
164
+ end
165
+
166
+ it 'returns an empty array when _drafts/ is missing' do
167
+ in_tmp_dir do
168
+ expect(described_class.discover_drafts).to eq([])
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'stringio'
5
+ require 'tmpdir'
6
+
7
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
8
+
9
+ require 'ottogen/ottogen'
10
+ require 'ottogen/otto'
11
+
12
+ module Ottogen
13
+ module SpecHelpers
14
+ def in_tmp_dir
15
+ Dir.mktmpdir('ottogen-spec') do |dir|
16
+ Dir.chdir(dir) do
17
+ yield dir
18
+ end
19
+ end
20
+ end
21
+
22
+ def capture_stdout
23
+ original = $stdout
24
+ $stdout = StringIO.new
25
+ yield
26
+ $stdout.string
27
+ ensure
28
+ $stdout = original
29
+ end
30
+
31
+ def in_otto_project(config: "title: Test site\n", &block)
32
+ in_tmp_dir do |dir|
33
+ FileUtils.touch('.otto')
34
+ File.write('config.yml', config)
35
+ FileUtils.mkdir_p('pages')
36
+ FileUtils.mkdir_p('assets')
37
+ block.call(dir)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ RSpec.configure do |config|
44
+ config.include Ottogen::SpecHelpers
45
+ config.disable_monkey_patching!
46
+ config.expect_with :rspec do |expectations|
47
+ expectations.syntax = :expect
48
+ end
49
+ config.order = :random
50
+ Kernel.srand config.seed
51
+ end
metadata CHANGED
@@ -1,104 +1,209 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ottogen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Johnson
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-04-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: rspec
13
+ name: asciidoctor
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
17
19
  - - ">="
18
20
  - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
+ version: 2.0.20
22
+ type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
23
25
  requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '2.0'
24
29
  - - ">="
25
30
  - !ruby/object:Gem::Version
26
- version: '0'
31
+ version: 2.0.20
32
+ - !ruby/object:Gem::Dependency
33
+ name: listen
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '3.7'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '3.7'
46
+ - !ruby/object:Gem::Dependency
47
+ name: logger
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.6'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.6'
60
+ - !ruby/object:Gem::Dependency
61
+ name: thor
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.2'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.2'
74
+ - !ruby/object:Gem::Dependency
75
+ name: webrick
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.7'
81
+ type: :runtime
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.7'
88
+ - !ruby/object:Gem::Dependency
89
+ name: rspec
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '3.13'
95
+ type: :development
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '3.13'
102
+ - !ruby/object:Gem::Dependency
103
+ name: rubocop
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '1.60'
109
+ type: :development
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '1.60'
27
116
  description: |
28
117
  # Otto
29
118
 
30
- AsciiDoc static site generator.
119
+ AsciiDoc-powered static site generator with Jekyll-style conventions: layouts, includes, data files, posts, drafts, permalinks, and custom collections.
31
120
 
32
- # Quickstart
121
+ ## Install
33
122
 
34
- **Install Otto**
35
-
36
- ``` sh
123
+ ```sh
37
124
  gem install ottogen
38
125
  ```
39
126
 
40
- **Initialize a new site**
127
+ Requires Ruby 3.0 or newer.
128
+
129
+ ## Quickstart
41
130
 
42
- ``` sh
131
+ ```sh
43
132
  mkdir mysite && cd mysite
44
133
  otto init
45
- ```
46
-
47
- **Build the site**
48
-
49
- ``` sh
50
134
  otto build
51
- ```
52
-
53
- **Serve the site**
54
-
55
- ``` sh
56
135
  otto serve
57
- ```
58
-
59
- **View the site**
60
-
61
- ``` sh
62
136
  open http://127.0.0.1:8778/
63
137
  ```
64
138
 
65
- # AsciiDoc Crash Course
66
-
67
- TODO
139
+ For a longer walkthrough including AsciiDoc syntax, see [GUIDE.md](GUIDE.md).
68
140
 
69
- **Paragraphs**
141
+ ## Commands
70
142
 
71
- **Text formatting**
143
+ | Command | Description |
144
+ |---|---|
145
+ | `otto init [DIR]` | Scaffold a new site (current dir if omitted) |
146
+ | `otto build` | Render the site to `_build/` |
147
+ | `otto build --drafts` | Include posts from `_drafts/` |
148
+ | `otto watch` | Rebuild on file change |
149
+ | `otto serve` | Serve `_build/` on port 8778 |
150
+ | `otto generate PAGE` | Create a new page in `pages/` |
151
+ | `otto post "Title"` | Create a new dated post in `_posts/` |
152
+ | `otto clean` | Delete `_build/` |
153
+ | `otto doctor` | Sanity-check project layout |
72
154
 
73
- **Links**
155
+ ## Project layout
74
156
 
75
- **Document header**
76
-
77
- **Section titles**
157
+ ```
158
+ my-site/
159
+ ├── .otto # marker
160
+ ├── config.yml # site config
161
+ ├── assets/ # copied verbatim into _build/
162
+ ├── pages/ # AsciiDoc pages, output mirrors path
163
+ ├── _layouts/ # ERB layouts (.html.erb)
164
+ ├── _includes/ # ERB partials
165
+ ├── _data/ # YAML/JSON files exposed as site.data.*
166
+ ├── _posts/ # YYYY-MM-DD-slug.adoc
167
+ └── _drafts/ # undated drafts (excluded by default)
168
+ ```
78
169
 
79
- **Automatic TOC**
170
+ ## Configuration (`config.yml`)
80
171
 
81
- **Includes**
172
+ ```yaml
173
+ title: My Otto Site
174
+ description: Things I write
175
+ url: https://example.com
176
+ baseurl: ""
82
177
 
83
- **Lists**
178
+ permalink: /:year/:month/:day/:slug/
84
179
 
85
- **Images**
180
+ collections:
181
+ recipes:
182
+ output: true
183
+ ```
86
184
 
87
- **Audio**
185
+ `permalink` accepts these tokens: `:year`, `:month`, `:day`, `:slug`, `:title`. Templates ending in `/` produce pretty URLs (`<path>/index.html`).
88
186
 
89
- **Videos**
187
+ ## Pages and posts
90
188
 
91
- **Keyboard, button, and menu macros**
189
+ Both support YAML front matter:
92
190
 
93
- **Literals and source code**
191
+ ```adoc
192
+ ---
193
+ layout: default
194
+ title: Hello
195
+ tags: [ruby, cli]
196
+ ---
197
+ = Hello
94
198
 
95
- **Admonitions**
199
+ Welcome to {site_title}. This page is at {page_url}.
200
+ ```
96
201
 
97
- **More delimited blocks**
202
+ Pages live under `pages/`; posts under `_posts/` with `YYYY-MM-DD-slug.adoc` names. Layouts wrap rendered AsciiDoc; partials in `_includes/` are pulled in via `<%= partial 'header.html' %>`.
98
203
 
99
- **Tables**
204
+ ## License
100
205
 
101
- **IDs, roles, and options**
206
+ MIT
102
207
  email: tacoda@hey.com
103
208
  executables:
104
209
  - otto
@@ -108,14 +213,30 @@ files:
108
213
  - LICENSE
109
214
  - README.md
110
215
  - bin/otto
216
+ - lib/ottogen/collection.rb
217
+ - lib/ottogen/collection_item.rb
218
+ - lib/ottogen/config.rb
219
+ - lib/ottogen/front_matter.rb
220
+ - lib/ottogen/layout.rb
111
221
  - lib/ottogen/otto.rb
112
222
  - lib/ottogen/ottogen.rb
223
+ - lib/ottogen/page.rb
224
+ - lib/ottogen/permalink.rb
225
+ - lib/ottogen/post.rb
226
+ - lib/ottogen/scaffold.rb
227
+ - spec/ottogen/collection_spec.rb
228
+ - spec/ottogen/config_spec.rb
229
+ - spec/ottogen/layout_spec.rb
113
230
  - spec/ottogen/ottogen_spec.rb
231
+ - spec/ottogen/page_spec.rb
232
+ - spec/ottogen/permalink_spec.rb
233
+ - spec/ottogen/post_spec.rb
234
+ - spec/spec_helper.rb
114
235
  homepage: https://www.tacoda.dev/otto/
115
236
  licenses:
116
237
  - MIT
117
- metadata: {}
118
- post_install_message:
238
+ metadata:
239
+ rubygems_mfa_required: 'true'
119
240
  rdoc_options: []
120
241
  require_paths:
121
242
  - lib
@@ -123,16 +244,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
244
  requirements:
124
245
  - - ">="
125
246
  - !ruby/object:Gem::Version
126
- version: '1.9'
247
+ version: '3.0'
127
248
  required_rubygems_version: !ruby/object:Gem::Requirement
128
249
  requirements:
129
250
  - - ">="
130
251
  - !ruby/object:Gem::Version
131
252
  version: '0'
132
253
  requirements: []
133
- rubygems_version: 3.4.10
134
- signing_key:
254
+ rubygems_version: 4.0.3
135
255
  specification_version: 4
136
256
  summary: AsciiDoc static site generator
137
- test_files:
138
- - spec/ottogen/ottogen_spec.rb
257
+ test_files: []