Skip to content

Commit 0202b01

Browse files
Copilotaaronpowell
andcommitted
Refactor: Extract YAML parser to shared module and improve user experience
Co-authored-by: aaronpowell <[email protected]>
1 parent 22e4bb2 commit 0202b01

File tree

5 files changed

+213
-251
lines changed

5 files changed

+213
-251
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.
8383

8484
## 🌟 Getting Started
8585

86-
1. **Browse the Collections**: Check out our comprehensive lists of [collections](README.collections.md), [prompts](README.prompts.md), [instructions](README.instructions.md), and [chat modes](README.chatmodes.md).
86+
1. **Browse the Collections**: Check out our comprehensive lists of [prompts](README.prompts.md), [instructions](README.instructions.md), [chat modes](README.chatmodes.md), and [collections](README.collections.md).
8787
2. **Add to your editor**: Click the "Install" button to install to VS Code, or copy the file contents for other editors.
8888
3. **Start Using**: Copy prompts to use with `/` commands, let instructions enhance your coding experience, or activate chat modes for specialized assistance.
8989

create-collection.js

Lines changed: 87 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,91 @@
22

33
const fs = require("fs");
44
const path = require("path");
5+
const readline = require("readline");
56

6-
function createCollectionTemplate(collectionId) {
7-
if (!collectionId) {
8-
console.error("Collection ID is required");
9-
console.log("Usage: node create-collection.js <collection-id>");
10-
process.exit(1);
11-
}
7+
const rl = readline.createInterface({
8+
input: process.stdin,
9+
output: process.stdout
10+
});
1211

13-
// Validate collection ID format
14-
if (!/^[a-z0-9-]+$/.test(collectionId)) {
15-
console.error("Collection ID must contain only lowercase letters, numbers, and hyphens");
16-
process.exit(1);
17-
}
12+
function prompt(question) {
13+
return new Promise((resolve) => {
14+
rl.question(question, resolve);
15+
});
16+
}
1817

19-
const collectionsDir = path.join(__dirname, "collections");
20-
const filePath = path.join(collectionsDir, `${collectionId}.collection.yml`);
18+
async function createCollectionTemplate() {
19+
try {
20+
console.log("🎯 Collection Creator");
21+
console.log("This tool will help you create a new collection manifest.\n");
2122

22-
// Check if file already exists
23-
if (fs.existsSync(filePath)) {
24-
console.error(`Collection ${collectionId} already exists at ${filePath}`);
25-
process.exit(1);
26-
}
23+
// Get collection ID
24+
let collectionId;
25+
if (process.argv[2]) {
26+
collectionId = process.argv[2];
27+
} else {
28+
collectionId = await prompt("Collection ID (lowercase, hyphens only): ");
29+
}
2730

28-
// Ensure collections directory exists
29-
if (!fs.existsSync(collectionsDir)) {
30-
fs.mkdirSync(collectionsDir, { recursive: true });
31-
}
31+
// Validate collection ID format
32+
if (!collectionId) {
33+
console.error("❌ Collection ID is required");
34+
process.exit(1);
35+
}
3236

33-
// Create a friendly name from the ID
34-
const friendlyName = collectionId
35-
.split("-")
36-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
37-
.join(" ");
38-
39-
// Template content
40-
const template = `id: ${collectionId}
41-
name: ${friendlyName}
42-
description: A collection of related prompts, instructions, and chat modes for ${friendlyName.toLowerCase()}.
43-
tags: [${collectionId.split("-").slice(0, 3).join(", ")}] # Add relevant tags
37+
if (!/^[a-z0-9-]+$/.test(collectionId)) {
38+
console.error("❌ Collection ID must contain only lowercase letters, numbers, and hyphens");
39+
process.exit(1);
40+
}
41+
42+
const collectionsDir = path.join(__dirname, "collections");
43+
const filePath = path.join(collectionsDir, `${collectionId}.collection.yml`);
44+
45+
// Check if file already exists
46+
if (fs.existsSync(filePath)) {
47+
console.log(`⚠️ Collection ${collectionId} already exists at ${filePath}`);
48+
console.log("💡 Please edit that file instead or choose a different ID.");
49+
process.exit(1);
50+
}
51+
52+
// Ensure collections directory exists
53+
if (!fs.existsSync(collectionsDir)) {
54+
fs.mkdirSync(collectionsDir, { recursive: true });
55+
}
56+
57+
// Get collection name
58+
const defaultName = collectionId
59+
.split("-")
60+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
61+
.join(" ");
62+
63+
let collectionName = await prompt(`Collection name (default: ${defaultName}): `);
64+
if (!collectionName.trim()) {
65+
collectionName = defaultName;
66+
}
67+
68+
// Get description
69+
const defaultDescription = `A collection of related prompts, instructions, and chat modes for ${collectionName.toLowerCase()}.`;
70+
let description = await prompt(`Description (default: ${defaultDescription}): `);
71+
if (!description.trim()) {
72+
description = defaultDescription;
73+
}
74+
75+
// Get tags
76+
let tags = [];
77+
const tagInput = await prompt("Tags (comma-separated, or press Enter for defaults): ");
78+
if (tagInput.trim()) {
79+
tags = tagInput.split(",").map(tag => tag.trim()).filter(tag => tag);
80+
} else {
81+
// Generate some default tags from the collection ID
82+
tags = collectionId.split("-").slice(0, 3);
83+
}
84+
85+
// Template content
86+
const template = `id: ${collectionId}
87+
name: ${collectionName}
88+
description: ${description}
89+
tags: [${tags.join(", ")}]
4490
items:
4591
# Add your collection items here
4692
# Example:
@@ -55,22 +101,23 @@ display:
55101
show_badge: false # set to true to show collection badge on items
56102
`;
57103

58-
try {
59104
fs.writeFileSync(filePath, template);
60105
console.log(`✅ Created collection template: ${filePath}`);
61-
console.log("\nNext steps:");
106+
console.log("\n📝 Next steps:");
62107
console.log("1. Edit the collection manifest to add your items");
63108
console.log("2. Update the name, description, and tags as needed");
64109
console.log("3. Run 'node validate-collections.js' to validate");
65110
console.log("4. Run 'node update-readme.js' to generate documentation");
66-
console.log("\nCollection template contents:");
111+
console.log("\n📄 Collection template contents:");
67112
console.log(template);
113+
68114
} catch (error) {
69-
console.error(`Error creating collection template: ${error.message}`);
115+
console.error(`Error creating collection template: ${error.message}`);
70116
process.exit(1);
117+
} finally {
118+
rl.close();
71119
}
72120
}
73121

74-
// Get collection ID from command line arguments
75-
const collectionId = process.argv[2];
76-
createCollectionTemplate(collectionId);
122+
// Run the interactive creation process
123+
createCollectionTemplate();

update-readme.js

Lines changed: 7 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const fs = require("fs");
44
const path = require("path");
5+
const { parseCollectionYaml } = require("./yaml-parser");
56

67
// Template sections for the README
78
const TEMPLATES = {
@@ -68,6 +69,9 @@ Curated collections of related prompts, instructions, and chat modes organized a
6869
};
6970

7071
// Add error handling utility
72+
/**
73+
* Safe file operation wrapper
74+
*/
7175
function safeFileOperation(operation, filePath, defaultValue = null) {
7276
try {
7377
return operation();
@@ -77,110 +81,6 @@ function safeFileOperation(operation, filePath, defaultValue = null) {
7781
}
7882
}
7983

80-
/**
81-
* Simple YAML parser for collection manifests
82-
* Handles our specific YAML structure without external dependencies
83-
*/
84-
function parseCollectionYaml(filePath) {
85-
return safeFileOperation(
86-
() => {
87-
const content = fs.readFileSync(filePath, "utf8");
88-
const lines = content.split("\n");
89-
const result = {};
90-
let currentKey = null;
91-
let currentArray = null;
92-
let currentObject = null;
93-
94-
for (let i = 0; i < lines.length; i++) {
95-
const line = lines[i];
96-
const trimmed = line.trim();
97-
98-
if (!trimmed || trimmed.startsWith("#")) continue;
99-
100-
const leadingSpaces = line.length - line.trimLeft().length;
101-
102-
// Handle array items starting with -
103-
if (trimmed.startsWith("- ")) {
104-
if (currentKey === "items") {
105-
if (!currentArray) {
106-
currentArray = [];
107-
result[currentKey] = currentArray;
108-
}
109-
110-
// Parse item object
111-
const item = {};
112-
currentArray.push(item);
113-
currentObject = item;
114-
115-
// Handle inline properties on same line as -
116-
const restOfLine = trimmed.substring(2).trim();
117-
if (restOfLine) {
118-
const colonIndex = restOfLine.indexOf(":");
119-
if (colonIndex > -1) {
120-
const key = restOfLine.substring(0, colonIndex).trim();
121-
const value = restOfLine.substring(colonIndex + 1).trim();
122-
item[key] = value;
123-
}
124-
}
125-
} else if (currentKey === "tags") {
126-
if (!currentArray) {
127-
currentArray = [];
128-
result[currentKey] = currentArray;
129-
}
130-
const value = trimmed.substring(2).trim();
131-
currentArray.push(value);
132-
}
133-
}
134-
// Handle key-value pairs
135-
else if (trimmed.includes(":")) {
136-
const colonIndex = trimmed.indexOf(":");
137-
const key = trimmed.substring(0, colonIndex).trim();
138-
let value = trimmed.substring(colonIndex + 1).trim();
139-
140-
if (leadingSpaces === 0) {
141-
// Top-level property
142-
currentKey = key;
143-
currentArray = null;
144-
currentObject = null;
145-
146-
if (value) {
147-
// Handle array format [item1, item2, item3]
148-
if (value.startsWith("[") && value.endsWith("]")) {
149-
const arrayContent = value.slice(1, -1);
150-
if (arrayContent.trim()) {
151-
result[key] = arrayContent.split(",").map(item => item.trim());
152-
} else {
153-
result[key] = [];
154-
}
155-
currentKey = null; // Reset since we handled the array
156-
} else {
157-
result[key] = value;
158-
}
159-
} else if (key === "items" || key === "tags") {
160-
// Will be populated by array items
161-
result[key] = [];
162-
currentArray = result[key];
163-
} else if (key === "display") {
164-
result[key] = {};
165-
currentObject = result[key];
166-
}
167-
} else if (currentObject && leadingSpaces > 0) {
168-
// Property of current object (e.g., display properties)
169-
currentObject[key] = value === "true" ? true : value === "false" ? false : value;
170-
} else if (currentArray && currentObject && leadingSpaces > 2) {
171-
// Property of array item object
172-
currentObject[key] = value;
173-
}
174-
}
175-
}
176-
177-
return result;
178-
},
179-
filePath,
180-
null
181-
);
182-
}
183-
18484
function extractTitle(filePath) {
18585
return safeFileOperation(
18686
() => {
@@ -533,10 +433,10 @@ function generateChatModesSection(chatmodesDir) {
533433
* Generate the collections section with a table of all collections
534434
*/
535435
function generateCollectionsSection(collectionsDir) {
536-
// Check if collections directory exists
436+
// Check if collections directory exists, create it if it doesn't
537437
if (!fs.existsSync(collectionsDir)) {
538-
console.log("Collections directory does not exist");
539-
return "";
438+
console.log("Collections directory does not exist, creating it...");
439+
fs.mkdirSync(collectionsDir, { recursive: true });
540440
}
541441

542442
// Get all collection files

0 commit comments

Comments
 (0)