Refactor: Extract YAML parser to shared module and improve user experience
Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
This commit is contained in:
parent
22e4bb21f0
commit
0202b019b2
@ -83,7 +83,7 @@ We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.
|
||||
|
||||
## 🌟 Getting Started
|
||||
|
||||
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).
|
||||
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).
|
||||
2. **Add to your editor**: Click the "Install" button to install to VS Code, or copy the file contents for other editors.
|
||||
3. **Start Using**: Copy prompts to use with `/` commands, let instructions enhance your coding experience, or activate chat modes for specialized assistance.
|
||||
|
||||
|
||||
@ -2,45 +2,91 @@
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const readline = require("readline");
|
||||
|
||||
function createCollectionTemplate(collectionId) {
|
||||
if (!collectionId) {
|
||||
console.error("Collection ID is required");
|
||||
console.log("Usage: node create-collection.js <collection-id>");
|
||||
process.exit(1);
|
||||
}
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// Validate collection ID format
|
||||
if (!/^[a-z0-9-]+$/.test(collectionId)) {
|
||||
console.error("Collection ID must contain only lowercase letters, numbers, and hyphens");
|
||||
process.exit(1);
|
||||
}
|
||||
function prompt(question) {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
const collectionsDir = path.join(__dirname, "collections");
|
||||
const filePath = path.join(collectionsDir, `${collectionId}.collection.yml`);
|
||||
async function createCollectionTemplate() {
|
||||
try {
|
||||
console.log("🎯 Collection Creator");
|
||||
console.log("This tool will help you create a new collection manifest.\n");
|
||||
|
||||
// Check if file already exists
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.error(`Collection ${collectionId} already exists at ${filePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
// Get collection ID
|
||||
let collectionId;
|
||||
if (process.argv[2]) {
|
||||
collectionId = process.argv[2];
|
||||
} else {
|
||||
collectionId = await prompt("Collection ID (lowercase, hyphens only): ");
|
||||
}
|
||||
|
||||
// Ensure collections directory exists
|
||||
if (!fs.existsSync(collectionsDir)) {
|
||||
fs.mkdirSync(collectionsDir, { recursive: true });
|
||||
}
|
||||
// Validate collection ID format
|
||||
if (!collectionId) {
|
||||
console.error("❌ Collection ID is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create a friendly name from the ID
|
||||
const friendlyName = collectionId
|
||||
.split("-")
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
if (!/^[a-z0-9-]+$/.test(collectionId)) {
|
||||
console.error("❌ Collection ID must contain only lowercase letters, numbers, and hyphens");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Template content
|
||||
const template = `id: ${collectionId}
|
||||
name: ${friendlyName}
|
||||
description: A collection of related prompts, instructions, and chat modes for ${friendlyName.toLowerCase()}.
|
||||
tags: [${collectionId.split("-").slice(0, 3).join(", ")}] # Add relevant tags
|
||||
const collectionsDir = path.join(__dirname, "collections");
|
||||
const filePath = path.join(collectionsDir, `${collectionId}.collection.yml`);
|
||||
|
||||
// Check if file already exists
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.log(`⚠️ Collection ${collectionId} already exists at ${filePath}`);
|
||||
console.log("💡 Please edit that file instead or choose a different ID.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Ensure collections directory exists
|
||||
if (!fs.existsSync(collectionsDir)) {
|
||||
fs.mkdirSync(collectionsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Get collection name
|
||||
const defaultName = collectionId
|
||||
.split("-")
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
|
||||
let collectionName = await prompt(`Collection name (default: ${defaultName}): `);
|
||||
if (!collectionName.trim()) {
|
||||
collectionName = defaultName;
|
||||
}
|
||||
|
||||
// Get description
|
||||
const defaultDescription = `A collection of related prompts, instructions, and chat modes for ${collectionName.toLowerCase()}.`;
|
||||
let description = await prompt(`Description (default: ${defaultDescription}): `);
|
||||
if (!description.trim()) {
|
||||
description = defaultDescription;
|
||||
}
|
||||
|
||||
// Get tags
|
||||
let tags = [];
|
||||
const tagInput = await prompt("Tags (comma-separated, or press Enter for defaults): ");
|
||||
if (tagInput.trim()) {
|
||||
tags = tagInput.split(",").map(tag => tag.trim()).filter(tag => tag);
|
||||
} else {
|
||||
// Generate some default tags from the collection ID
|
||||
tags = collectionId.split("-").slice(0, 3);
|
||||
}
|
||||
|
||||
// Template content
|
||||
const template = `id: ${collectionId}
|
||||
name: ${collectionName}
|
||||
description: ${description}
|
||||
tags: [${tags.join(", ")}]
|
||||
items:
|
||||
# Add your collection items here
|
||||
# Example:
|
||||
@ -55,22 +101,23 @@ display:
|
||||
show_badge: false # set to true to show collection badge on items
|
||||
`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(filePath, template);
|
||||
console.log(`✅ Created collection template: ${filePath}`);
|
||||
console.log("\nNext steps:");
|
||||
console.log("\n📝 Next steps:");
|
||||
console.log("1. Edit the collection manifest to add your items");
|
||||
console.log("2. Update the name, description, and tags as needed");
|
||||
console.log("3. Run 'node validate-collections.js' to validate");
|
||||
console.log("4. Run 'node update-readme.js' to generate documentation");
|
||||
console.log("\nCollection template contents:");
|
||||
console.log("\n📄 Collection template contents:");
|
||||
console.log(template);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error creating collection template: ${error.message}`);
|
||||
console.error(`❌ Error creating collection template: ${error.message}`);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Get collection ID from command line arguments
|
||||
const collectionId = process.argv[2];
|
||||
createCollectionTemplate(collectionId);
|
||||
// Run the interactive creation process
|
||||
createCollectionTemplate();
|
||||
114
update-readme.js
114
update-readme.js
@ -2,6 +2,7 @@
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { parseCollectionYaml } = require("./yaml-parser");
|
||||
|
||||
// Template sections for the README
|
||||
const TEMPLATES = {
|
||||
@ -68,6 +69,9 @@ Curated collections of related prompts, instructions, and chat modes organized a
|
||||
};
|
||||
|
||||
// Add error handling utility
|
||||
/**
|
||||
* Safe file operation wrapper
|
||||
*/
|
||||
function safeFileOperation(operation, filePath, defaultValue = null) {
|
||||
try {
|
||||
return operation();
|
||||
@ -77,110 +81,6 @@ function safeFileOperation(operation, filePath, defaultValue = null) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple YAML parser for collection manifests
|
||||
* Handles our specific YAML structure without external dependencies
|
||||
*/
|
||||
function parseCollectionYaml(filePath) {
|
||||
return safeFileOperation(
|
||||
() => {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const lines = content.split("\n");
|
||||
const result = {};
|
||||
let currentKey = null;
|
||||
let currentArray = null;
|
||||
let currentObject = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
|
||||
const leadingSpaces = line.length - line.trimLeft().length;
|
||||
|
||||
// Handle array items starting with -
|
||||
if (trimmed.startsWith("- ")) {
|
||||
if (currentKey === "items") {
|
||||
if (!currentArray) {
|
||||
currentArray = [];
|
||||
result[currentKey] = currentArray;
|
||||
}
|
||||
|
||||
// Parse item object
|
||||
const item = {};
|
||||
currentArray.push(item);
|
||||
currentObject = item;
|
||||
|
||||
// Handle inline properties on same line as -
|
||||
const restOfLine = trimmed.substring(2).trim();
|
||||
if (restOfLine) {
|
||||
const colonIndex = restOfLine.indexOf(":");
|
||||
if (colonIndex > -1) {
|
||||
const key = restOfLine.substring(0, colonIndex).trim();
|
||||
const value = restOfLine.substring(colonIndex + 1).trim();
|
||||
item[key] = value;
|
||||
}
|
||||
}
|
||||
} else if (currentKey === "tags") {
|
||||
if (!currentArray) {
|
||||
currentArray = [];
|
||||
result[currentKey] = currentArray;
|
||||
}
|
||||
const value = trimmed.substring(2).trim();
|
||||
currentArray.push(value);
|
||||
}
|
||||
}
|
||||
// Handle key-value pairs
|
||||
else if (trimmed.includes(":")) {
|
||||
const colonIndex = trimmed.indexOf(":");
|
||||
const key = trimmed.substring(0, colonIndex).trim();
|
||||
let value = trimmed.substring(colonIndex + 1).trim();
|
||||
|
||||
if (leadingSpaces === 0) {
|
||||
// Top-level property
|
||||
currentKey = key;
|
||||
currentArray = null;
|
||||
currentObject = null;
|
||||
|
||||
if (value) {
|
||||
// Handle array format [item1, item2, item3]
|
||||
if (value.startsWith("[") && value.endsWith("]")) {
|
||||
const arrayContent = value.slice(1, -1);
|
||||
if (arrayContent.trim()) {
|
||||
result[key] = arrayContent.split(",").map(item => item.trim());
|
||||
} else {
|
||||
result[key] = [];
|
||||
}
|
||||
currentKey = null; // Reset since we handled the array
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
} else if (key === "items" || key === "tags") {
|
||||
// Will be populated by array items
|
||||
result[key] = [];
|
||||
currentArray = result[key];
|
||||
} else if (key === "display") {
|
||||
result[key] = {};
|
||||
currentObject = result[key];
|
||||
}
|
||||
} else if (currentObject && leadingSpaces > 0) {
|
||||
// Property of current object (e.g., display properties)
|
||||
currentObject[key] = value === "true" ? true : value === "false" ? false : value;
|
||||
} else if (currentArray && currentObject && leadingSpaces > 2) {
|
||||
// Property of array item object
|
||||
currentObject[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
filePath,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function extractTitle(filePath) {
|
||||
return safeFileOperation(
|
||||
() => {
|
||||
@ -533,10 +433,10 @@ function generateChatModesSection(chatmodesDir) {
|
||||
* Generate the collections section with a table of all collections
|
||||
*/
|
||||
function generateCollectionsSection(collectionsDir) {
|
||||
// Check if collections directory exists
|
||||
// Check if collections directory exists, create it if it doesn't
|
||||
if (!fs.existsSync(collectionsDir)) {
|
||||
console.log("Collections directory does not exist");
|
||||
return "";
|
||||
console.log("Collections directory does not exist, creating it...");
|
||||
fs.mkdirSync(collectionsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Get all collection files
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { parseCollectionYaml } = require("./yaml-parser");
|
||||
|
||||
// Simple YAML parser (same as in update-readme.js)
|
||||
// Maximum number of items allowed in a collection
|
||||
const MAX_COLLECTION_ITEMS = 50;
|
||||
function safeFileOperation(operation, filePath, defaultValue = null) {
|
||||
try {
|
||||
return operation();
|
||||
@ -13,106 +15,6 @@ function safeFileOperation(operation, filePath, defaultValue = null) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseCollectionYaml(filePath) {
|
||||
return safeFileOperation(
|
||||
() => {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const lines = content.split("\n");
|
||||
const result = {};
|
||||
let currentKey = null;
|
||||
let currentArray = null;
|
||||
let currentObject = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
|
||||
const leadingSpaces = line.length - line.trimLeft().length;
|
||||
|
||||
// Handle array items starting with -
|
||||
if (trimmed.startsWith("- ")) {
|
||||
if (currentKey === "items") {
|
||||
if (!currentArray) {
|
||||
currentArray = [];
|
||||
result[currentKey] = currentArray;
|
||||
}
|
||||
|
||||
// Parse item object
|
||||
const item = {};
|
||||
currentArray.push(item);
|
||||
currentObject = item;
|
||||
|
||||
// Handle inline properties on same line as -
|
||||
const restOfLine = trimmed.substring(2).trim();
|
||||
if (restOfLine) {
|
||||
const colonIndex = restOfLine.indexOf(":");
|
||||
if (colonIndex > -1) {
|
||||
const key = restOfLine.substring(0, colonIndex).trim();
|
||||
const value = restOfLine.substring(colonIndex + 1).trim();
|
||||
item[key] = value;
|
||||
}
|
||||
}
|
||||
} else if (currentKey === "tags") {
|
||||
if (!currentArray) {
|
||||
currentArray = [];
|
||||
result[currentKey] = currentArray;
|
||||
}
|
||||
const value = trimmed.substring(2).trim();
|
||||
currentArray.push(value);
|
||||
}
|
||||
}
|
||||
// Handle key-value pairs
|
||||
else if (trimmed.includes(":")) {
|
||||
const colonIndex = trimmed.indexOf(":");
|
||||
const key = trimmed.substring(0, colonIndex).trim();
|
||||
let value = trimmed.substring(colonIndex + 1).trim();
|
||||
|
||||
if (leadingSpaces === 0) {
|
||||
// Top-level property
|
||||
currentKey = key;
|
||||
currentArray = null;
|
||||
currentObject = null;
|
||||
|
||||
if (value) {
|
||||
// Handle array format [item1, item2, item3]
|
||||
if (value.startsWith("[") && value.endsWith("]")) {
|
||||
const arrayContent = value.slice(1, -1);
|
||||
if (arrayContent.trim()) {
|
||||
result[key] = arrayContent.split(",").map(item => item.trim());
|
||||
} else {
|
||||
result[key] = [];
|
||||
}
|
||||
currentKey = null; // Reset since we handled the array
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
} else if (key === "items" || key === "tags") {
|
||||
// Will be populated by array items
|
||||
result[key] = [];
|
||||
currentArray = result[key];
|
||||
} else if (key === "display") {
|
||||
result[key] = {};
|
||||
currentObject = result[key];
|
||||
}
|
||||
} else if (currentObject && leadingSpaces > 0) {
|
||||
// Property of current object (e.g., display properties)
|
||||
currentObject[key] = value === "true" ? true : value === "false" ? false : value;
|
||||
} else if (currentArray && currentObject && leadingSpaces > 2) {
|
||||
// Property of array item object
|
||||
currentObject[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
filePath,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
// Validation functions
|
||||
function validateCollectionId(id) {
|
||||
if (!id || typeof id !== "string") {
|
||||
@ -177,8 +79,8 @@ function validateCollectionItems(items) {
|
||||
if (items.length < 1) {
|
||||
return "At least one item is required";
|
||||
}
|
||||
if (items.length > 50) {
|
||||
return "Maximum 50 items allowed";
|
||||
if (items.length > MAX_COLLECTION_ITEMS) {
|
||||
return `Maximum ${MAX_COLLECTION_ITEMS} items allowed`;
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
113
yaml-parser.js
Normal file
113
yaml-parser.js
Normal file
@ -0,0 +1,113 @@
|
||||
// Simple YAML parser for collection files
|
||||
const fs = require("fs");
|
||||
|
||||
function safeFileOperation(operation, filePath, defaultValue = null) {
|
||||
try {
|
||||
return operation();
|
||||
} catch (error) {
|
||||
console.error(`Error processing file ${filePath}: ${error.message}`);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
function parseCollectionYaml(filePath) {
|
||||
return safeFileOperation(
|
||||
() => {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const lines = content.split("\n");
|
||||
const result = {};
|
||||
let currentKey = null;
|
||||
let currentArray = null;
|
||||
let currentObject = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
|
||||
const leadingSpaces = line.length - line.trimLeft().length;
|
||||
|
||||
// Handle array items starting with -
|
||||
if (trimmed.startsWith("- ")) {
|
||||
if (currentKey === "items") {
|
||||
if (!currentArray) {
|
||||
currentArray = [];
|
||||
result[currentKey] = currentArray;
|
||||
}
|
||||
|
||||
// Parse item object
|
||||
const item = {};
|
||||
currentArray.push(item);
|
||||
currentObject = item;
|
||||
|
||||
// Handle inline properties on same line as -
|
||||
const restOfLine = trimmed.substring(2).trim();
|
||||
if (restOfLine) {
|
||||
const colonIndex = restOfLine.indexOf(":");
|
||||
if (colonIndex > -1) {
|
||||
const key = restOfLine.substring(0, colonIndex).trim();
|
||||
const value = restOfLine.substring(colonIndex + 1).trim();
|
||||
item[key] = value;
|
||||
}
|
||||
}
|
||||
} else if (currentKey === "tags") {
|
||||
if (!currentArray) {
|
||||
currentArray = [];
|
||||
result[currentKey] = currentArray;
|
||||
}
|
||||
const value = trimmed.substring(2).trim();
|
||||
currentArray.push(value);
|
||||
}
|
||||
}
|
||||
// Handle key-value pairs
|
||||
else if (trimmed.includes(":")) {
|
||||
const colonIndex = trimmed.indexOf(":");
|
||||
const key = trimmed.substring(0, colonIndex).trim();
|
||||
let value = trimmed.substring(colonIndex + 1).trim();
|
||||
|
||||
if (leadingSpaces === 0) {
|
||||
// Top-level property
|
||||
currentKey = key;
|
||||
currentArray = null;
|
||||
currentObject = null;
|
||||
|
||||
if (value) {
|
||||
// Handle array format [item1, item2, item3]
|
||||
if (value.startsWith("[") && value.endsWith("]")) {
|
||||
const arrayContent = value.slice(1, -1);
|
||||
if (arrayContent.trim()) {
|
||||
result[key] = arrayContent.split(",").map(item => item.trim());
|
||||
} else {
|
||||
result[key] = [];
|
||||
}
|
||||
currentKey = null; // Reset since we handled the array
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
} else if (key === "items" || key === "tags") {
|
||||
// Will be populated by array items
|
||||
result[key] = [];
|
||||
currentArray = result[key];
|
||||
} else if (key === "display") {
|
||||
result[key] = {};
|
||||
currentObject = result[key];
|
||||
}
|
||||
} else if (currentObject && leadingSpaces > 0) {
|
||||
// Property of current object (e.g., display properties)
|
||||
currentObject[key] = value === "true" ? true : value === "false" ? false : value;
|
||||
} else if (currentArray && currentObject && leadingSpaces > 2) {
|
||||
// Property of array item object
|
||||
currentObject[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
filePath,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { parseCollectionYaml, safeFileOperation };
|
||||
Loading…
x
Reference in New Issue
Block a user