resumeforge 0.1.0__tar.gz

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 (33) hide show
  1. resumeforge-0.1.0/LICENSE +21 -0
  2. resumeforge-0.1.0/PKG-INFO +330 -0
  3. resumeforge-0.1.0/README.md +307 -0
  4. resumeforge-0.1.0/pyproject.toml +32 -0
  5. resumeforge-0.1.0/setup.cfg +4 -0
  6. resumeforge-0.1.0/src/resumeforge/__init__.py +0 -0
  7. resumeforge-0.1.0/src/resumeforge/adapters/__init__.py +0 -0
  8. resumeforge-0.1.0/src/resumeforge/adapters/fpdf_adapter.py +63 -0
  9. resumeforge-0.1.0/src/resumeforge/adapters/heading_adapter.py +44 -0
  10. resumeforge-0.1.0/src/resumeforge/adapters/layout_adapter.py +33 -0
  11. resumeforge-0.1.0/src/resumeforge/cli.py +109 -0
  12. resumeforge-0.1.0/src/resumeforge/constants.py +13 -0
  13. resumeforge-0.1.0/src/resumeforge/engines/__init__.py +0 -0
  14. resumeforge-0.1.0/src/resumeforge/engines/fpdf_engine.py +152 -0
  15. resumeforge-0.1.0/src/resumeforge/mappers/__init__.py +0 -0
  16. resumeforge-0.1.0/src/resumeforge/mappers/heading_mapper.py +23 -0
  17. resumeforge-0.1.0/src/resumeforge/mappers/section_mapper.py +92 -0
  18. resumeforge-0.1.0/src/resumeforge/models.py +89 -0
  19. resumeforge-0.1.0/src/resumeforge/parser.py +39 -0
  20. resumeforge-0.1.0/src/resumeforge/renderer.py +80 -0
  21. resumeforge-0.1.0/src/resumeforge/transformer.py +158 -0
  22. resumeforge-0.1.0/src/resumeforge/validator.py +12 -0
  23. resumeforge-0.1.0/src/resumeforge.egg-info/PKG-INFO +330 -0
  24. resumeforge-0.1.0/src/resumeforge.egg-info/SOURCES.txt +31 -0
  25. resumeforge-0.1.0/src/resumeforge.egg-info/dependency_links.txt +1 -0
  26. resumeforge-0.1.0/src/resumeforge.egg-info/entry_points.txt +2 -0
  27. resumeforge-0.1.0/src/resumeforge.egg-info/requires.txt +7 -0
  28. resumeforge-0.1.0/src/resumeforge.egg-info/top_level.txt +1 -0
  29. resumeforge-0.1.0/tests/test_cli.py +66 -0
  30. resumeforge-0.1.0/tests/test_parser.py +331 -0
  31. resumeforge-0.1.0/tests/test_renderer.py +204 -0
  32. resumeforge-0.1.0/tests/test_transformer.py +435 -0
  33. resumeforge-0.1.0/tests/test_validator.py +55 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 John Joseph Strong
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,330 @@
1
+ Metadata-Version: 2.4
2
+ Name: resumeforge
3
+ Version: 0.1.0
4
+ Summary: Convert plain-text CVs into styled A4 PDFs using RCSS
5
+ Author: John Strong
6
+ License: MIT
7
+ Project-URL: Homepage, https://resume-forge-cli.web.app/
8
+ Project-URL: Repository, https://github.com/JohnStrong/ResumeForge
9
+ Project-URL: Issues, https://github.com/JohnStrong/ResumeForge/issues
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: lark
17
+ Requires-Dist: fpdf2
18
+ Provides-Extra: test
19
+ Requires-Dist: pytest; extra == "test"
20
+ Requires-Dist: pytest-cov; extra == "test"
21
+ Requires-Dist: pypdf; extra == "test"
22
+ Dynamic: license-file
23
+
24
+ # ResumeForge — README
25
+
26
+ ![build](https://github.com/JohnStrong/ResumeForge/actions/workflows/python-package.yml/badge.svg?branch=main)
27
+ ![coverage](https://codecov.io/gh/JohnStrong/ResumeForge/branch/main/graph/badge.svg)
28
+ ![version](https://img.shields.io/badge/version-0.1.0-blue)
29
+ ![python](https://img.shields.io/badge/python-3.12+-yellow)
30
+ ![license](https://img.shields.io/badge/license-MIT-green)
31
+
32
+ > 🌐 **Website:** https://resume-forge-cli.web.app/
33
+
34
+ ## Table of Contents
35
+ - [About](#about)
36
+ - [Setup](#setup)
37
+ - [Usage](#usage)
38
+ - [Troubleshooting](#troubleshooting)
39
+ - [Testing](#testing)
40
+ - [RCSS DSL](#rcss-dsl)
41
+ - [Section identification](#section-identification)
42
+ - [.rcss basics (MVP)](#rcss-basics-mvp)
43
+ - [Example .rcss snippets](#example-rcss-snippets)
44
+ - [Contributing](#contributing)
45
+ - [Examples](#examples)
46
+
47
+ ## About
48
+ ResumeForge converts a plain UTF-8 text CV into a styled multi-page A4 PDF using a small CSS-like DSL (.rcss). Supports two layout modes: standard (single-column) and grid (2-column). MVP excludes font-face loading and decorative assets.
49
+
50
+ ## Setup
51
+
52
+ ```bash
53
+ python3 -m venv .venv
54
+ source .venv/bin/activate
55
+ pip install -e .
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```bash
61
+ # Render a plain-text CV to styled PDF
62
+ resumeforge render --input examples/resume.txt --style examples/valid.rcss --output resume.pdf
63
+
64
+ # Validate an RCSS style file for syntax errors
65
+ resumeforge validate --style examples/valid.rcss
66
+
67
+ # Print CLI version
68
+ resumeforge version
69
+ ```
70
+
71
+ ### Troubleshooting
72
+
73
+ **"Unexpected token ... Expected one of: HEADING, LAYOUT, @font-face, SECTION"**
74
+ Your `.rcss` file has an invalid selector. Only `layout { ... }`, `heading { ... }`, `@font-face { ... }`, and `section[name="..."] { ... }` are valid. Check for typos in the selector keyword.
75
+
76
+ **"Unexpected token ... Expected one of: SEMICOLON"**
77
+ A property declaration is missing its trailing semicolon. Every declaration must end with `;`.
78
+
79
+ **"RCSS must contain a layout { ... } rule"**
80
+ The transformer could not find a `layout` block in your `.rcss` file. Every stylesheet requires one.
81
+
82
+ **"RCSS must contain at least one section[name=...] rule"**
83
+ Your `.rcss` defines a layout but no section rules. Add at least one `section[name="..."] { ... }` block.
84
+
85
+ **"No sections found in CV text matching the stylesheet"**
86
+ The headings in your `.txt` file don't match any `section[name="..."]` values in the stylesheet. Headings must match exactly (case-sensitive, full line).
87
+
88
+ **"CV text is missing one or more sections defined in the stylesheet"**
89
+ Your `.txt` file is missing a heading that the stylesheet expects. Ensure every `section[name="..."]` in the `.rcss` has a corresponding heading line in the CV text.
90
+
91
+ **"No raw sections to apply rules to"**
92
+ The section mapper received no parsed sections to style. This typically means your CV text was empty or contained no lines matching any stylesheet section names.
93
+
94
+ **"No matching stylesheet rule for one or more sections"**
95
+ A section was parsed from the CV text but has no corresponding `section[name="..."]` rule in the stylesheet. Ensure every heading in your `.txt` file has a matching rule in the `.rcss`.
96
+
97
+ **"column-widths must sum to 100%"**
98
+ The percentage values in `column-widths` do not add up to 100. For example, `column-widths: 30% 60%;` totals 90%. Adjust so they equal 100%.
99
+
100
+ **"column-widths values must be whole numbers with %"**
101
+ Each value in `column-widths` must be an integer followed by `%`. Decimal values like `33.3%` and bare numbers like `35` are not allowed.
102
+
103
+ **"layout property '...' is not valid"**
104
+ The layout adapter encountered an unrecognised property in `layout { ... }`. Check for typos. Valid properties: `mode`, `columns`, `column-widths`, `column-gap`, `margins`, `font-family`.
105
+
106
+ **"CV text must have heading content (name/contact) before the first section"**
107
+ Your `.txt` file begins immediately with a section heading (e.g. `Skills` or `Experience`) with no name or contact information above it. Every CV must have at least one line of text before the first section — typically your full name, job title, and contact details (email, phone, LinkedIn). This heading block is rendered at the top of the PDF before any sections.
108
+
109
+ ## Testing
110
+
111
+ ```bash
112
+ pip install pytest
113
+ pytest
114
+ ```
115
+
116
+ ## RCSS DSL
117
+
118
+ > Grammar definition: [`src/resumeforge/grammar/rcss.lark`](src/resumeforge/grammar/rcss.lark)
119
+
120
+ ### Section identification
121
+ - A section begins at a heading line that matches the pattern ^{HEADING} (a full-line header like: LINKS, WORK EXPERIENCE, EDUCATION).
122
+ - A section contains all text from that heading line up to the next heading line or EOF.
123
+ - Section selectors in .rcss match the heading text exactly (e.g., section[name="WORK EXPERIENCE"]).
124
+
125
+ ### .rcss basics (MVP)
126
+ - File extension: .rcss
127
+ - Grid mode supports exactly 2 columns. grid-column must be 1 or 2 for each section in grid mode.
128
+
129
+ #### Layout properties (in `layout { ... }`)
130
+ | Property | Values | Description |
131
+ |---|---|---|
132
+ | `mode` | `single`, `grid` | Page layout mode |
133
+ | `columns` | `2` | Number of columns (grid mode) |
134
+ | `column-widths` | e.g. `35% 65%` | Width of each column as percentages (grid mode, must sum to 100%) |
135
+ | `column-gap` | e.g. `6mm` | Gap between columns |
136
+ | `margins` | e.g. `20mm 18mm 20mm 18mm` | Page margins (top right bottom left) |
137
+ | `font-family` | e.g. `"Helvetica"` | Default font (overridden by @font-face) |
138
+
139
+ #### Font face properties (in `@font-face { ... }`) — optional
140
+ | Property | Values | Description |
141
+ |---|---|---|
142
+ | `font-family` | e.g. `"Carlito"` | Font family name to register |
143
+ | `src` | e.g. `"fonts/Carlito-Regular.ttf"` | Path to regular weight TTF |
144
+ | `src-bold` | e.g. `"fonts/Carlito-Bold.ttf"` | Path to bold weight TTF |
145
+
146
+ #### Heading properties (in `heading { ... }`) — optional
147
+ The `heading` block styles the resume header (name, title, contact info) that appears before the first section. If omitted, ATS-friendly defaults are applied automatically.
148
+
149
+ | Property | Values | Default | Description |
150
+ |---|---|---|---|
151
+ | `font-size` | e.g. `20pt` | `20pt` | Name/first line font size. Subsequent lines (contact info, title) are scaled down proportionally |
152
+ | `align` | `left`, `center`, `right` | `center` | Text alignment |
153
+ | `line-height` | e.g. `7` | `7` | Line height in mm |
154
+ | `color` | e.g. `#333333` | black | Text color (hex) |
155
+
156
+ #### Section properties (in `section[name="..."] { ... }`)
157
+
158
+ **Style properties** (PDF render mode — applied as PDF state before writing):
159
+ | Property | Values | Default | Description |
160
+ |---|---|---|---|
161
+ | `font-size` | e.g. `12pt` | `11pt` | Text size |
162
+ | `color` | e.g. `#333333` | black | Text color (hex) |
163
+ | `background-color` | e.g. `#f0f0f0` | none | Section fill color (hex) |
164
+
165
+ **Write properties** (PDF render mode — control how content is rendered):
166
+ | Property | Values | Default | Description |
167
+ |---|---|---|---|
168
+ | `align` | `left`, `center`, `right` | `left` | Text alignment |
169
+ | `line-height` | e.g. `7` | `5` | Line height in mm |
170
+ | `display` | `block`, `inline` | `block` | Block wraps text (multi-line), inline flows horizontally |
171
+
172
+ **Layout positioning** (grid mode only):
173
+ | Property | Values | Default | Description |
174
+ |---|---|---|---|
175
+ | `grid-column` | `1`, `2` | — | Which column to place the section in |
176
+ | `padding` | e.g. `8mm` | `0` | Inner spacing |
177
+ | `width` | e.g. `1fr` | `1fr` | Proportional column width |
178
+
179
+ ### Example .rcss snippets
180
+ Single-column (resume-single.rcss)
181
+ ```css
182
+ layout { mode: single; margins: 20mm 18mm 20mm 18mm; }
183
+
184
+ section[name="HEADER"] {
185
+ padding: 8mm;
186
+ align: center;
187
+ }
188
+ ```
189
+
190
+ Two-column grid (resume-grid.rcss)
191
+ ```css
192
+ layout { mode: grid; columns: 2; column-widths: 35% 65%; column-gap: 6mm; margins: 20mm 18mm 20mm 18mm; }
193
+
194
+ /* Place by heading text and explicit column (1 or 2) */
195
+ section[name="SIDEBAR"] {
196
+ grid-column: 1;
197
+ padding: 6mm;
198
+ width: 1fr;
199
+ }
200
+
201
+ section[name="MAIN"] {
202
+ grid-column: 2;
203
+ padding: 6mm;
204
+ width: 1fr;
205
+ }
206
+
207
+ section[name="HEADER"] {
208
+ grid-column: 1;
209
+ padding: 8mm;
210
+ align: center;
211
+ }
212
+ ```
213
+
214
+ ## Contributing
215
+
216
+ 1. Create a feature branch from `main`: `git checkout -b feature/your-feature`
217
+ 2. Make changes, commit using [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`)
218
+ 3. Ensure all tests pass: `pytest`
219
+ 4. Open a pull request for code review before merging
220
+ 5. Merge to `main` with `--no-ff` to preserve branch history
221
+
222
+ ## Examples
223
+
224
+ ### Step 1: Write your CV as plain text (`examples/resume.txt`)
225
+
226
+ ```
227
+ Lorem Ipsum
228
+ Senior Software Engineer
229
+ lorem.ipsum@fakeemail.xyz | +44 0000 000000
230
+
231
+ Links
232
+ github.com/loremipsum
233
+ linkedin.com/in/loremipsum
234
+ loremipsum.dev
235
+
236
+ Skills
237
+ - Python,
238
+ - TypeScript
239
+ - Go
240
+ - Rust
241
+ - AWS (Lambda, DynamoDB, ECS, CDK)
242
+ - Terraform
243
+ - PostgreSQL,
244
+ - Redis,
245
+ - Kafka
246
+ - System Design
247
+ - CI/CD
248
+ - Kubernetes
249
+ - Observability
250
+
251
+ Work Experience
252
+ Senior Software Engineer - Acme Widget Corp
253
+ Jan 2021 - Present
254
+ - Architected event-driven microservices processing 2M+ events/day
255
+ - Led monolith-to-ECS migration reducing deploy times by 70%
256
+ - Designed real-time analytics pipeline using Kafka and Flink
257
+ - Mentored 4 junior engineers through pairing and code review
258
+
259
+ Software Engineer - Placeholder Technologies Inc
260
+ Mar 2018 - Dec 2020
261
+ - Built REST and gRPC APIs serving 500K daily active users
262
+ - Implemented canary deployments reducing rollback incidents by 85%
263
+ - Developed internal CLI tooling adopted by 3 engineering teams
264
+
265
+ Software Engineer - Foobar Systems Ltd
266
+ Sep 2015 - Feb 2018
267
+ - Developed customer-facing dashboard using React and TypeScript
268
+ - Designed multi-tenant SaaS schema in PostgreSQL
269
+ - Reduced API response times by 40% through caching
270
+
271
+ Education
272
+ MSc Computer Science - University of Nowhere, 2015
273
+ BSc Mathematics - University of Somewhere, 2013
274
+
275
+ References
276
+ Dolor Sit Amet
277
+ Engineering Director, Acme Widget Corp
278
+ dolor.sit@fakecorp.xyz
279
+
280
+ Consectetur Adipiscing
281
+ CTO, Placeholder Technologies Inc
282
+ consectetur@faketech.xyz
283
+ ```
284
+
285
+ ### Step 2: Style it with RCSS (`examples/valid.rcss`)
286
+
287
+ ```css
288
+ @font-face { font-family: "Carlito"; src: "examples/fonts/Carlito-Regular.ttf"; src-bold: "examples/fonts/Carlito-Bold.ttf"; }
289
+
290
+ layout { mode: grid; columns: 2; column-widths: 30% 70%; column-gap: 6mm; margins: 20mm 18mm 20mm 18mm; font-family: "Carlito"; }
291
+
292
+ heading { font-size: 20pt; align: center; line-height: 7; color: #555555; }
293
+
294
+ section[name="Links"] {
295
+ color: #336699;
296
+ line-height: 5;
297
+ grid-column: 1;
298
+ }
299
+
300
+ section[name="Skills"] {
301
+ line-height: 5;
302
+ grid-column: 1;
303
+ }
304
+
305
+ section[name="Work Experience"] {
306
+ line-height: 5;
307
+ grid-column: 2;
308
+ }
309
+
310
+ section[name="Education"] {
311
+ line-height: 5;
312
+ grid-column: 2;
313
+ }
314
+
315
+ section[name="References"] {
316
+ color: #555555;
317
+ line-height: 5;
318
+ grid-column: 2;
319
+ }
320
+ ```
321
+
322
+ ### Step 3: Render to PDF
323
+
324
+ ```bash
325
+ resumeforge render --input examples/resume.txt --style examples/valid.rcss --output examples/resume.pdf
326
+ ```
327
+
328
+ ### Result
329
+
330
+ ![Resume PDF output](docs/resume.png)
@@ -0,0 +1,307 @@
1
+ # ResumeForge — README
2
+
3
+ ![build](https://github.com/JohnStrong/ResumeForge/actions/workflows/python-package.yml/badge.svg?branch=main)
4
+ ![coverage](https://codecov.io/gh/JohnStrong/ResumeForge/branch/main/graph/badge.svg)
5
+ ![version](https://img.shields.io/badge/version-0.1.0-blue)
6
+ ![python](https://img.shields.io/badge/python-3.12+-yellow)
7
+ ![license](https://img.shields.io/badge/license-MIT-green)
8
+
9
+ > 🌐 **Website:** https://resume-forge-cli.web.app/
10
+
11
+ ## Table of Contents
12
+ - [About](#about)
13
+ - [Setup](#setup)
14
+ - [Usage](#usage)
15
+ - [Troubleshooting](#troubleshooting)
16
+ - [Testing](#testing)
17
+ - [RCSS DSL](#rcss-dsl)
18
+ - [Section identification](#section-identification)
19
+ - [.rcss basics (MVP)](#rcss-basics-mvp)
20
+ - [Example .rcss snippets](#example-rcss-snippets)
21
+ - [Contributing](#contributing)
22
+ - [Examples](#examples)
23
+
24
+ ## About
25
+ ResumeForge converts a plain UTF-8 text CV into a styled multi-page A4 PDF using a small CSS-like DSL (.rcss). Supports two layout modes: standard (single-column) and grid (2-column). MVP excludes font-face loading and decorative assets.
26
+
27
+ ## Setup
28
+
29
+ ```bash
30
+ python3 -m venv .venv
31
+ source .venv/bin/activate
32
+ pip install -e .
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ # Render a plain-text CV to styled PDF
39
+ resumeforge render --input examples/resume.txt --style examples/valid.rcss --output resume.pdf
40
+
41
+ # Validate an RCSS style file for syntax errors
42
+ resumeforge validate --style examples/valid.rcss
43
+
44
+ # Print CLI version
45
+ resumeforge version
46
+ ```
47
+
48
+ ### Troubleshooting
49
+
50
+ **"Unexpected token ... Expected one of: HEADING, LAYOUT, @font-face, SECTION"**
51
+ Your `.rcss` file has an invalid selector. Only `layout { ... }`, `heading { ... }`, `@font-face { ... }`, and `section[name="..."] { ... }` are valid. Check for typos in the selector keyword.
52
+
53
+ **"Unexpected token ... Expected one of: SEMICOLON"**
54
+ A property declaration is missing its trailing semicolon. Every declaration must end with `;`.
55
+
56
+ **"RCSS must contain a layout { ... } rule"**
57
+ The transformer could not find a `layout` block in your `.rcss` file. Every stylesheet requires one.
58
+
59
+ **"RCSS must contain at least one section[name=...] rule"**
60
+ Your `.rcss` defines a layout but no section rules. Add at least one `section[name="..."] { ... }` block.
61
+
62
+ **"No sections found in CV text matching the stylesheet"**
63
+ The headings in your `.txt` file don't match any `section[name="..."]` values in the stylesheet. Headings must match exactly (case-sensitive, full line).
64
+
65
+ **"CV text is missing one or more sections defined in the stylesheet"**
66
+ Your `.txt` file is missing a heading that the stylesheet expects. Ensure every `section[name="..."]` in the `.rcss` has a corresponding heading line in the CV text.
67
+
68
+ **"No raw sections to apply rules to"**
69
+ The section mapper received no parsed sections to style. This typically means your CV text was empty or contained no lines matching any stylesheet section names.
70
+
71
+ **"No matching stylesheet rule for one or more sections"**
72
+ A section was parsed from the CV text but has no corresponding `section[name="..."]` rule in the stylesheet. Ensure every heading in your `.txt` file has a matching rule in the `.rcss`.
73
+
74
+ **"column-widths must sum to 100%"**
75
+ The percentage values in `column-widths` do not add up to 100. For example, `column-widths: 30% 60%;` totals 90%. Adjust so they equal 100%.
76
+
77
+ **"column-widths values must be whole numbers with %"**
78
+ Each value in `column-widths` must be an integer followed by `%`. Decimal values like `33.3%` and bare numbers like `35` are not allowed.
79
+
80
+ **"layout property '...' is not valid"**
81
+ The layout adapter encountered an unrecognised property in `layout { ... }`. Check for typos. Valid properties: `mode`, `columns`, `column-widths`, `column-gap`, `margins`, `font-family`.
82
+
83
+ **"CV text must have heading content (name/contact) before the first section"**
84
+ Your `.txt` file begins immediately with a section heading (e.g. `Skills` or `Experience`) with no name or contact information above it. Every CV must have at least one line of text before the first section — typically your full name, job title, and contact details (email, phone, LinkedIn). This heading block is rendered at the top of the PDF before any sections.
85
+
86
+ ## Testing
87
+
88
+ ```bash
89
+ pip install pytest
90
+ pytest
91
+ ```
92
+
93
+ ## RCSS DSL
94
+
95
+ > Grammar definition: [`src/resumeforge/grammar/rcss.lark`](src/resumeforge/grammar/rcss.lark)
96
+
97
+ ### Section identification
98
+ - A section begins at a heading line that matches the pattern ^{HEADING} (a full-line header like: LINKS, WORK EXPERIENCE, EDUCATION).
99
+ - A section contains all text from that heading line up to the next heading line or EOF.
100
+ - Section selectors in .rcss match the heading text exactly (e.g., section[name="WORK EXPERIENCE"]).
101
+
102
+ ### .rcss basics (MVP)
103
+ - File extension: .rcss
104
+ - Grid mode supports exactly 2 columns. grid-column must be 1 or 2 for each section in grid mode.
105
+
106
+ #### Layout properties (in `layout { ... }`)
107
+ | Property | Values | Description |
108
+ |---|---|---|
109
+ | `mode` | `single`, `grid` | Page layout mode |
110
+ | `columns` | `2` | Number of columns (grid mode) |
111
+ | `column-widths` | e.g. `35% 65%` | Width of each column as percentages (grid mode, must sum to 100%) |
112
+ | `column-gap` | e.g. `6mm` | Gap between columns |
113
+ | `margins` | e.g. `20mm 18mm 20mm 18mm` | Page margins (top right bottom left) |
114
+ | `font-family` | e.g. `"Helvetica"` | Default font (overridden by @font-face) |
115
+
116
+ #### Font face properties (in `@font-face { ... }`) — optional
117
+ | Property | Values | Description |
118
+ |---|---|---|
119
+ | `font-family` | e.g. `"Carlito"` | Font family name to register |
120
+ | `src` | e.g. `"fonts/Carlito-Regular.ttf"` | Path to regular weight TTF |
121
+ | `src-bold` | e.g. `"fonts/Carlito-Bold.ttf"` | Path to bold weight TTF |
122
+
123
+ #### Heading properties (in `heading { ... }`) — optional
124
+ The `heading` block styles the resume header (name, title, contact info) that appears before the first section. If omitted, ATS-friendly defaults are applied automatically.
125
+
126
+ | Property | Values | Default | Description |
127
+ |---|---|---|---|
128
+ | `font-size` | e.g. `20pt` | `20pt` | Name/first line font size. Subsequent lines (contact info, title) are scaled down proportionally |
129
+ | `align` | `left`, `center`, `right` | `center` | Text alignment |
130
+ | `line-height` | e.g. `7` | `7` | Line height in mm |
131
+ | `color` | e.g. `#333333` | black | Text color (hex) |
132
+
133
+ #### Section properties (in `section[name="..."] { ... }`)
134
+
135
+ **Style properties** (PDF render mode — applied as PDF state before writing):
136
+ | Property | Values | Default | Description |
137
+ |---|---|---|---|
138
+ | `font-size` | e.g. `12pt` | `11pt` | Text size |
139
+ | `color` | e.g. `#333333` | black | Text color (hex) |
140
+ | `background-color` | e.g. `#f0f0f0` | none | Section fill color (hex) |
141
+
142
+ **Write properties** (PDF render mode — control how content is rendered):
143
+ | Property | Values | Default | Description |
144
+ |---|---|---|---|
145
+ | `align` | `left`, `center`, `right` | `left` | Text alignment |
146
+ | `line-height` | e.g. `7` | `5` | Line height in mm |
147
+ | `display` | `block`, `inline` | `block` | Block wraps text (multi-line), inline flows horizontally |
148
+
149
+ **Layout positioning** (grid mode only):
150
+ | Property | Values | Default | Description |
151
+ |---|---|---|---|
152
+ | `grid-column` | `1`, `2` | — | Which column to place the section in |
153
+ | `padding` | e.g. `8mm` | `0` | Inner spacing |
154
+ | `width` | e.g. `1fr` | `1fr` | Proportional column width |
155
+
156
+ ### Example .rcss snippets
157
+ Single-column (resume-single.rcss)
158
+ ```css
159
+ layout { mode: single; margins: 20mm 18mm 20mm 18mm; }
160
+
161
+ section[name="HEADER"] {
162
+ padding: 8mm;
163
+ align: center;
164
+ }
165
+ ```
166
+
167
+ Two-column grid (resume-grid.rcss)
168
+ ```css
169
+ layout { mode: grid; columns: 2; column-widths: 35% 65%; column-gap: 6mm; margins: 20mm 18mm 20mm 18mm; }
170
+
171
+ /* Place by heading text and explicit column (1 or 2) */
172
+ section[name="SIDEBAR"] {
173
+ grid-column: 1;
174
+ padding: 6mm;
175
+ width: 1fr;
176
+ }
177
+
178
+ section[name="MAIN"] {
179
+ grid-column: 2;
180
+ padding: 6mm;
181
+ width: 1fr;
182
+ }
183
+
184
+ section[name="HEADER"] {
185
+ grid-column: 1;
186
+ padding: 8mm;
187
+ align: center;
188
+ }
189
+ ```
190
+
191
+ ## Contributing
192
+
193
+ 1. Create a feature branch from `main`: `git checkout -b feature/your-feature`
194
+ 2. Make changes, commit using [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`)
195
+ 3. Ensure all tests pass: `pytest`
196
+ 4. Open a pull request for code review before merging
197
+ 5. Merge to `main` with `--no-ff` to preserve branch history
198
+
199
+ ## Examples
200
+
201
+ ### Step 1: Write your CV as plain text (`examples/resume.txt`)
202
+
203
+ ```
204
+ Lorem Ipsum
205
+ Senior Software Engineer
206
+ lorem.ipsum@fakeemail.xyz | +44 0000 000000
207
+
208
+ Links
209
+ github.com/loremipsum
210
+ linkedin.com/in/loremipsum
211
+ loremipsum.dev
212
+
213
+ Skills
214
+ - Python,
215
+ - TypeScript
216
+ - Go
217
+ - Rust
218
+ - AWS (Lambda, DynamoDB, ECS, CDK)
219
+ - Terraform
220
+ - PostgreSQL,
221
+ - Redis,
222
+ - Kafka
223
+ - System Design
224
+ - CI/CD
225
+ - Kubernetes
226
+ - Observability
227
+
228
+ Work Experience
229
+ Senior Software Engineer - Acme Widget Corp
230
+ Jan 2021 - Present
231
+ - Architected event-driven microservices processing 2M+ events/day
232
+ - Led monolith-to-ECS migration reducing deploy times by 70%
233
+ - Designed real-time analytics pipeline using Kafka and Flink
234
+ - Mentored 4 junior engineers through pairing and code review
235
+
236
+ Software Engineer - Placeholder Technologies Inc
237
+ Mar 2018 - Dec 2020
238
+ - Built REST and gRPC APIs serving 500K daily active users
239
+ - Implemented canary deployments reducing rollback incidents by 85%
240
+ - Developed internal CLI tooling adopted by 3 engineering teams
241
+
242
+ Software Engineer - Foobar Systems Ltd
243
+ Sep 2015 - Feb 2018
244
+ - Developed customer-facing dashboard using React and TypeScript
245
+ - Designed multi-tenant SaaS schema in PostgreSQL
246
+ - Reduced API response times by 40% through caching
247
+
248
+ Education
249
+ MSc Computer Science - University of Nowhere, 2015
250
+ BSc Mathematics - University of Somewhere, 2013
251
+
252
+ References
253
+ Dolor Sit Amet
254
+ Engineering Director, Acme Widget Corp
255
+ dolor.sit@fakecorp.xyz
256
+
257
+ Consectetur Adipiscing
258
+ CTO, Placeholder Technologies Inc
259
+ consectetur@faketech.xyz
260
+ ```
261
+
262
+ ### Step 2: Style it with RCSS (`examples/valid.rcss`)
263
+
264
+ ```css
265
+ @font-face { font-family: "Carlito"; src: "examples/fonts/Carlito-Regular.ttf"; src-bold: "examples/fonts/Carlito-Bold.ttf"; }
266
+
267
+ layout { mode: grid; columns: 2; column-widths: 30% 70%; column-gap: 6mm; margins: 20mm 18mm 20mm 18mm; font-family: "Carlito"; }
268
+
269
+ heading { font-size: 20pt; align: center; line-height: 7; color: #555555; }
270
+
271
+ section[name="Links"] {
272
+ color: #336699;
273
+ line-height: 5;
274
+ grid-column: 1;
275
+ }
276
+
277
+ section[name="Skills"] {
278
+ line-height: 5;
279
+ grid-column: 1;
280
+ }
281
+
282
+ section[name="Work Experience"] {
283
+ line-height: 5;
284
+ grid-column: 2;
285
+ }
286
+
287
+ section[name="Education"] {
288
+ line-height: 5;
289
+ grid-column: 2;
290
+ }
291
+
292
+ section[name="References"] {
293
+ color: #555555;
294
+ line-height: 5;
295
+ grid-column: 2;
296
+ }
297
+ ```
298
+
299
+ ### Step 3: Render to PDF
300
+
301
+ ```bash
302
+ resumeforge render --input examples/resume.txt --style examples/valid.rcss --output examples/resume.pdf
303
+ ```
304
+
305
+ ### Result
306
+
307
+ ![Resume PDF output](docs/resume.png)
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "resumeforge"
7
+ version = "0.1.0"
8
+ description = "Convert plain-text CVs into styled A4 PDFs using RCSS"
9
+ requires-python = ">=3.10"
10
+ license = {text = "MIT"}
11
+ authors = [{name = "John Strong"}]
12
+ readme = "README.md"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ dependencies = ["lark", "fpdf2"]
19
+
20
+ [project.urls]
21
+ Homepage = "https://resume-forge-cli.web.app/"
22
+ Repository = "https://github.com/JohnStrong/ResumeForge"
23
+ Issues = "https://github.com/JohnStrong/ResumeForge/issues"
24
+
25
+ [project.scripts]
26
+ resumeforge = "resumeforge.cli:main"
27
+
28
+ [project.optional-dependencies]
29
+ test = ["pytest", "pytest-cov", "pypdf"]
30
+
31
+ [tool.pytest.ini_options]
32
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
File without changes