prt 0.3.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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.githooks/pre-commit +17 -0
  3. data/.github/workflows/ci.yml +29 -0
  4. data/.gitignore +24 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +28 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/CLAUDE.md +8 -0
  10. data/Gemfile +13 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +340 -0
  13. data/Rakefile +26 -0
  14. data/bin/setup +11 -0
  15. data/exe/parrot +12 -0
  16. data/lib/helpers.rb +19 -0
  17. data/lib/parrot/commands/build.rb +1274 -0
  18. data/lib/parrot/commands/new.rb +25 -0
  19. data/lib/parrot/commands/post.rb +83 -0
  20. data/lib/parrot/commands/serve.rb +129 -0
  21. data/lib/parrot/constants.rb +45 -0
  22. data/lib/parrot/file_cache.rb +45 -0
  23. data/lib/parrot/logger.rb +23 -0
  24. data/lib/parrot/metadata.rb +4 -0
  25. data/lib/parrot/runner.rb +29 -0
  26. data/lib/parrot/template_handler.rb +19 -0
  27. data/lib/parrot.rb +120 -0
  28. data/parrot.gemspec +36 -0
  29. data/skel/.gitignore +3 -0
  30. data/skel/config.yaml +68 -0
  31. data/skel/css/app.scss +189 -0
  32. data/skel/images/.keep +0 -0
  33. data/skel/images/apple-touch-icon.png +0 -0
  34. data/skel/images/favicon.ico +0 -0
  35. data/skel/images/favicon.svg +40 -0
  36. data/skel/images/parrot.jpeg +0 -0
  37. data/skel/javascripts/app.js +37 -0
  38. data/skel/public/.keep +0 -0
  39. data/skel/views/404.md +7 -0
  40. data/skel/views/about.md +17 -0
  41. data/skel/views/layout.html.erb +72 -0
  42. data/skel/views/posts/about_parrot.md +63 -0
  43. data/skel/views/posts/sample.md +37 -0
  44. data/spec/parrot/commands/build_spec.rb +747 -0
  45. data/spec/parrot/commands/new_spec.rb +43 -0
  46. data/spec/parrot/commands/post_spec.rb +80 -0
  47. data/spec/parrot/commands/serve_spec.rb +66 -0
  48. data/spec/parrot/logger_spec.rb +39 -0
  49. data/spec/parrot/metadata_spec.rb +11 -0
  50. data/spec/parrot/runner_spec.rb +28 -0
  51. data/spec/parrot_spec.rb +37 -0
  52. data/spec/spec_helper.rb +7 -0
  53. metadata +237 -0
data/skel/css/app.scss ADDED
@@ -0,0 +1,189 @@
1
+ // Write your custom css here
2
+
3
+ // Simple.css switches themes only via prefers-color-scheme. These let the
4
+ // header toggle (data-theme on <html>) override the system choice either way.
5
+ // The colors mirror simple.css's own light and dark palettes.
6
+ :root[data-theme="light"] {
7
+ color-scheme: light;
8
+ --bg: #fff;
9
+ --accent-bg: #f5f7ff;
10
+ --text: #212121;
11
+ --text-light: #585858;
12
+ --accent: #0d47a1;
13
+ --accent-hover: #1266e2;
14
+ --code: #d81b60;
15
+ --preformatted: #444;
16
+ --disabled: #efefef;
17
+
18
+ img,
19
+ video {
20
+ opacity: 1;
21
+ }
22
+ }
23
+
24
+ :root[data-theme="dark"] {
25
+ color-scheme: dark;
26
+ --bg: #212121;
27
+ --accent-bg: #2b2b2b;
28
+ --text: #dcdcdc;
29
+ --text-light: #ababab;
30
+ --accent: #ffb300;
31
+ --accent-hover: #ffe099;
32
+ --code: #f06292;
33
+ --preformatted: #ccc;
34
+ --disabled: #111;
35
+
36
+ img,
37
+ video {
38
+ opacity: 0.8;
39
+ }
40
+ }
41
+
42
+ .site-header {
43
+ position: relative;
44
+ text-align: center;
45
+ padding: 2.5rem 1rem 2rem;
46
+ margin-bottom: 2rem;
47
+ border-bottom: 1px solid var(--border);
48
+ }
49
+
50
+ .theme-toggle {
51
+ position: absolute;
52
+ top: 1rem;
53
+ right: 1rem;
54
+ display: inline-flex;
55
+ padding: 0.4rem;
56
+ border: 1px solid var(--border);
57
+ border-radius: 50%;
58
+ background: transparent;
59
+ color: var(--text-light);
60
+
61
+ &:enabled:hover {
62
+ background: transparent;
63
+ border-color: var(--accent);
64
+ color: var(--accent);
65
+ }
66
+
67
+ svg {
68
+ width: 1.1rem;
69
+ height: 1.1rem;
70
+ fill: none;
71
+ stroke: currentColor;
72
+ stroke-width: 2;
73
+ stroke-linecap: round;
74
+ stroke-linejoin: round;
75
+ }
76
+
77
+ // Show the theme you'd switch to: a moon in light mode, a sun in dark mode.
78
+ .icon-sun {
79
+ display: none;
80
+ }
81
+ }
82
+
83
+ @mixin show-sun {
84
+ .theme-toggle .icon-moon {
85
+ display: none;
86
+ }
87
+
88
+ .theme-toggle .icon-sun {
89
+ display: block;
90
+ }
91
+ }
92
+
93
+ :root[data-theme="dark"] {
94
+ @include show-sun;
95
+ }
96
+
97
+ @media (prefers-color-scheme: dark) {
98
+ :root:not([data-theme="light"]) {
99
+ @include show-sun;
100
+ }
101
+ }
102
+
103
+ .site-name {
104
+ display: inline-block;
105
+ font-size: 2rem;
106
+ font-weight: 700;
107
+ letter-spacing: -0.02em;
108
+ text-decoration: none;
109
+ color: var(--text);
110
+
111
+ &:hover {
112
+ color: var(--accent);
113
+ }
114
+ }
115
+
116
+ .tagline {
117
+ margin: 0.4rem auto 0;
118
+ color: var(--text-light);
119
+ font-size: 1.05rem;
120
+ }
121
+
122
+ .site-nav {
123
+ margin-top: 1rem;
124
+ font-size: 0.95rem;
125
+ }
126
+
127
+ .back-link {
128
+ margin-top: 0;
129
+ margin-bottom: 1.5rem;
130
+ font-size: 0.9rem;
131
+
132
+ a {
133
+ text-decoration: none;
134
+ }
135
+ }
136
+
137
+ // The date and category line Parrot adds under a post's title.
138
+ .post-meta {
139
+ margin-top: -0.5rem;
140
+ color: var(--text-light);
141
+ font-size: 0.95rem;
142
+ }
143
+
144
+ // A post's category, linking to that category's listing: under a post's
145
+ // title, and in listings via list_format's {post_category_tag}.
146
+ .category-tag {
147
+ display: inline-block;
148
+ padding: 0 0.5rem;
149
+ border: 1px solid var(--border);
150
+ border-radius: 1rem;
151
+ font-size: 0.85rem;
152
+ line-height: 1.6;
153
+ text-decoration: none;
154
+ vertical-align: middle;
155
+
156
+ &,
157
+ &:visited {
158
+ color: var(--text-light);
159
+ }
160
+
161
+ &:hover {
162
+ border-color: var(--accent);
163
+ color: var(--accent);
164
+ }
165
+ }
166
+
167
+ .post-tags {
168
+ margin-top: 2rem;
169
+ font-size: 0.9rem;
170
+ color: var(--text-light);
171
+
172
+ .tag {
173
+ display: inline-block;
174
+ padding: 0.1rem 0.5rem;
175
+ border: 1px solid var(--border);
176
+ border-radius: 1rem;
177
+ }
178
+ }
179
+
180
+ .pagination {
181
+ display: flex;
182
+ justify-content: space-between;
183
+ margin-top: 2rem;
184
+ font-size: 0.9rem;
185
+
186
+ a {
187
+ text-decoration: none;
188
+ }
189
+ }
data/skel/images/.keep ADDED
File without changes
Binary file
Binary file
@@ -0,0 +1,40 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
2
+ <defs>
3
+ <linearGradient id="badge" x1="0" y1="0" x2="0" y2="1">
4
+ <stop offset="0%" stop-color="#4bb45f"/>
5
+ <stop offset="100%" stop-color="#2f9e4d"/>
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <rect width="64" height="64" rx="12" fill="url(#badge)"/>
10
+
11
+ <!-- crest -->
12
+ <g fill="#ffd23f" stroke="#e0a91f" stroke-width="1.6" stroke-linejoin="round">
13
+ <path d="M26 13 C24 8 26 5 30 4 C31 9 30 13 28 16 Z"/>
14
+ <path d="M32 11 C32 5 35 3 39 4 C39 9 37 13 35 16 Z"/>
15
+ <path d="M38 13 C40 8 44 6 47 7 C45 12 42 15 40 17 Z"/>
16
+ </g>
17
+
18
+ <!-- head -->
19
+ <circle cx="32" cy="36" r="22.5" fill="#57c06a" stroke="#1f7a3c" stroke-width="2.6"/>
20
+
21
+ <!-- face -->
22
+ <path d="M32 19 C43 19 49 27 49 36 C49 46 42 52 32 52 C23 52 16 46 15 37 C14 27 21 19 32 19 Z" fill="#ffd94a"/>
23
+
24
+ <!-- cheeks -->
25
+ <circle cx="19" cy="40" r="4.2" fill="#ff9fb2" opacity="0.7"/>
26
+ <circle cx="45" cy="40" r="4.2" fill="#ff9fb2" opacity="0.7"/>
27
+
28
+ <!-- eyes (no outline, wide-set so they stay cute, not spectacled, when tiny) -->
29
+ <circle cx="23" cy="33" r="7" fill="#ffffff"/>
30
+ <circle cx="41" cy="33" r="7" fill="#ffffff"/>
31
+ <circle cx="24.4" cy="34" r="4" fill="#2b2b2b"/>
32
+ <circle cx="39.6" cy="34" r="4" fill="#2b2b2b"/>
33
+ <circle cx="22.6" cy="32.2" r="1.5" fill="#ffffff"/>
34
+ <circle cx="37.8" cy="32.2" r="1.5" fill="#ffffff"/>
35
+
36
+ <!-- beak -->
37
+ <path d="M30 41 C28.5 41 27.5 43.5 28.5 46 C29.5 49.5 34 50.5 36.5 47.5 C38.5 45.5 38.5 42 36.5 40.5 C35 39.5 32 41 30 41 Z"
38
+ fill="#ff8f3f" stroke="#d9691f" stroke-width="2" stroke-linejoin="round"/>
39
+ <path d="M29.5 44.5 C30.5 47 32.5 48.5 34.5 47.5 C33 50 30.5 49 29 46.5 Z" fill="#e0641c"/>
40
+ </svg>
Binary file
@@ -0,0 +1,37 @@
1
+ // Light/dark toggle. The saved choice is applied early by an inline script in
2
+ // layout.html.erb; this wires up the header button and remembers new choices.
3
+ (function () {
4
+ var root = document.documentElement;
5
+ var button = document.querySelector('.theme-toggle');
6
+ if (!button) return;
7
+
8
+ var systemDark = window.matchMedia('(prefers-color-scheme: dark)');
9
+ var themeColors = { light: '#ffffff', dark: '#212121' };
10
+
11
+ function currentTheme() {
12
+ return root.dataset.theme || (systemDark.matches ? 'dark' : 'light');
13
+ }
14
+
15
+ function render() {
16
+ var theme = currentTheme();
17
+ button.setAttribute('aria-pressed', theme === 'dark');
18
+ button.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
19
+ // Only override the browser chrome color once the visitor has picked a theme.
20
+ if (root.dataset.theme) {
21
+ document.querySelectorAll('meta[name="theme-color"]').forEach(function (meta) {
22
+ meta.content = themeColors[theme];
23
+ });
24
+ }
25
+ }
26
+
27
+ button.addEventListener('click', function () {
28
+ var theme = currentTheme() === 'dark' ? 'light' : 'dark';
29
+ root.dataset.theme = theme;
30
+ try { localStorage.setItem('theme', theme); } catch (e) {}
31
+ render();
32
+ });
33
+
34
+ systemDark.addEventListener('change', render);
35
+ button.hidden = false;
36
+ render();
37
+ })();
data/skel/public/.keep ADDED
File without changes
data/skel/views/404.md ADDED
@@ -0,0 +1,7 @@
1
+ # Page not found
2
+
3
+ ![A parrot](images/parrot.jpeg "This parrot is as lost as you are")
4
+
5
+ The page you were looking for isn't here — it may have moved or never existed.
6
+
7
+ [Back to the home page](/)
@@ -0,0 +1,17 @@
1
+ <!--
2
+ title: About
3
+ description: Who writes this blog, and where else to find them.
4
+ -->
5
+
6
+ # About
7
+
8
+ Write a few lines about yourself and this blog here. This page is built from
9
+ `views/about.md` and linked from the navigation at the top of every page.
10
+
11
+ ## Find me elsewhere
12
+
13
+ - [GitHub](https://github.com/your-username)
14
+ - [X](https://x.com/your-username)
15
+ - [LinkedIn](https://www.linkedin.com/in/your-username)
16
+ - [Mastodon](https://mastodon.social/@your-username)
17
+ - [RSS feed](feed.xml)
@@ -0,0 +1,72 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
+ <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
8
+ <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#212121">
9
+ <!-- Warm up the connections to the CDNs the page pulls from. Add
10
+ https://www.googletagmanager.com here too if you wire up GTM/Analytics. -->
11
+ <link rel="preconnect" href="https://cdn.simplecss.org" crossorigin>
12
+ <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
13
+ <link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
14
+ <link rel="icon" type="image/x-icon" href="images/favicon.ico" sizes="16x16 32x32 48x48">
15
+ <link rel="icon" type="image/svg+xml" href="images/favicon.svg">
16
+ <link rel="apple-touch-icon" href="images/apple-touch-icon.png">
17
+ <!-- Apply a saved light/dark choice before the first paint, so the page
18
+ doesn't flash the system theme first. Without one, the system theme wins. -->
19
+ <script>
20
+ try {
21
+ var theme = localStorage.getItem('theme');
22
+ if (theme === 'light' || theme === 'dark') document.documentElement.dataset.theme = theme;
23
+ } catch (e) {}
24
+ </script>
25
+ <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
26
+ <link rel="stylesheet" href="app.css">
27
+ <script src="app.js" defer></script>
28
+ <link rel="alternate" type="application/rss+xml" title="Parrot" href="feed.xml">
29
+ <meta name="generator" content="Parrot">
30
+ <meta name="author" content="Your name">
31
+ <!-- Site-wide fallback description. Each post overrides it from its header
32
+ `description` (or its first paragraph) at build time. -->
33
+ <meta name="description" content="A blog built with Parrot.">
34
+ <!-- Set this to your site's URL. Parrot appends each page's path when building. -->
35
+ <meta property="og:url" content="https://example.com">
36
+ <link rel="canonical" href="https://example.com">
37
+ <meta property="og:type" content="website">
38
+ <meta property="og:site_name" content="Parrot">
39
+ <meta property="og:title" content="Parrot">
40
+ <meta property="og:description" content="A blog built with Parrot.">
41
+ <meta property="og:locale" content="en_US">
42
+ <!-- Link-preview image. A relative images/… path is copied to the build and
43
+ rewritten to an absolute URL; you can also use a full URL. -->
44
+ <meta property="og:image" content="images/parrot.jpeg">
45
+ <meta name="twitter:card" content="summary_large_image">
46
+ <meta name="twitter:description" content="A blog built with Parrot.">
47
+ <title>Parrot</title>
48
+ </head>
49
+ <body>
50
+ <header class="site-header">
51
+ <button class="theme-toggle" type="button" aria-label="Toggle dark mode" hidden>
52
+ <svg class="icon-moon" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
53
+ <svg class="icon-sun" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
54
+ </button>
55
+ <a class="site-name" href="/">Your name</a>
56
+ <p class="tagline">Your tagline</p>
57
+ <nav class="site-nav">
58
+ <a href="/">Home</a>
59
+ <a href="/categories.html">Categories</a>
60
+ <a href="/about.html">About</a>
61
+ </nav>
62
+ </header>
63
+
64
+ <main>
65
+ <%= yield %>
66
+ </main>
67
+
68
+ <footer>
69
+ Made with love using <a href="https://github.com/deepakkumarnd/parrot">Parrot</a>
70
+ </footer>
71
+ </body>
72
+ </html>
@@ -0,0 +1,63 @@
1
+ <!--
2
+ title: About Parrot
3
+ date: 08/09/2026
4
+ lang: en
5
+ tags: parrot, markdown
6
+ category: Guides
7
+ -->
8
+
9
+ # About Parrot
10
+
11
+ Parrot turns a folder of Markdown into a static blog. This post is also a
12
+ cheat sheet: it shows the formatting you can use in any `views/posts/*.md`
13
+ file.
14
+
15
+ ## Headings and text
16
+
17
+ Use `#` for the post title and `##` / `###` for sections, like the ones on
18
+ this page. Inline styles work too: **bold**, _italic_, and `inline code`.
19
+
20
+ ## Code blocks
21
+
22
+ Tag a fenced block with a language and Parrot highlights it (Monokai theme by
23
+ default — change `HIGHLIGHT_THEME` in `lib/parrot/constants.rb`):
24
+
25
+ ```ruby
26
+ class Greeter
27
+ def initialize(name)
28
+ @name = name
29
+ end
30
+
31
+ def greet
32
+ puts "Hello, #{@name}!"
33
+ end
34
+ end
35
+
36
+ Greeter.new("Parrot").greet
37
+ ```
38
+
39
+ ```bash
40
+ parrot new blog
41
+ cd blog
42
+ parrot serve
43
+ ```
44
+
45
+ An untagged, indented block renders as plain preformatted text:
46
+
47
+ $ parrot build
48
+
49
+ ## Math
50
+
51
+ LaTeX between `$$ … $$` is rendered with MathJax. Inline, it flows with the
52
+ sentence: $$e^{i\pi} + 1 = 0$$.
53
+
54
+ On its own line it becomes a display block:
55
+
56
+ $$
57
+ W = W - \text{lr} \cdot \frac{\partial L}{\partial W}
58
+ $$
59
+
60
+ ## Links between posts
61
+
62
+ Link to another post by its Markdown filename and Parrot rewrites it to the
63
+ built page at build time: [read the next post](#post2.md).
@@ -0,0 +1,37 @@
1
+ <!--
2
+ title: Your second post
3
+ date: 08/09/2026
4
+ lang: en
5
+ category: Guides
6
+ -->
7
+
8
+ # Your second post
9
+
10
+ This is `views/posts/post2.md`. Edit it, or delete it and drop your own
11
+ `.md` files into `views/posts/` — each one is built into its own page and can
12
+ be listed from `views/index.md`.
13
+
14
+ For headings, code blocks and LaTeX math, see [About Parrot](#post1.md). This
15
+ post covers the rest of the everyday Markdown.
16
+
17
+ ## Images
18
+
19
+ Keep image files in `images/` and link them with a relative path. Parrot copies
20
+ the ones a page actually uses into `public/images/`:
21
+
22
+ ![A parrot](images/parrot.jpeg "Say hi to the parrot")
23
+
24
+ ## Lists, quotes and tables
25
+
26
+ - A bullet list
27
+ - with **bold**, _italic_ and a [link to another site](https://www.ruby-lang.org)
28
+
29
+ 1. A numbered list
30
+ 2. for steps in order
31
+
32
+ > Blockquotes are good for asides and pull quotes.
33
+
34
+ | Feature | Supported |
35
+ | ------- | --------- |
36
+ | Tables | yes |
37
+ | Footers | yes |