Skip to content

Commit e583eb1

Browse files
authored
feat: article — local file creation and deletion (#275)
feat: `article` — local file creation and deletion
2 parents af42589 + c7ac624 commit e583eb1

File tree

4 files changed

+400
-0
lines changed

4 files changed

+400
-0
lines changed

bin/ewTools.class.js

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import fs from "fs"
2+
import path from "path"
3+
import dotenv from "dotenv"
4+
5+
class Configuration {
6+
static load() {
7+
dotenv.config()
8+
}
9+
10+
static get(key) {
11+
return process.env[key]
12+
}
13+
}
14+
15+
class Logger {
16+
static isDevelopment = Configuration.get("TOOLS_ENV") === "development"
17+
18+
static Types = Object.freeze({
19+
INFO: "Info",
20+
WARN: "Warning",
21+
ERROR: "Error",
22+
EMPTY: ""
23+
})
24+
25+
static log(text, type = Logger.Types.EMPTY) {
26+
const typeFormatted = type ? `${type}:` : ""
27+
if (this.isDevelopment) console.log(`${typeFormatted}`, text)
28+
}
29+
}
30+
31+
class FlagsProcessor {
32+
static formatFlagsString(args) {
33+
if (!Array.isArray(args) || args.length === 0) return ""
34+
35+
return args.map((token, index) =>
36+
index === args.length - 1 ? token : index % 2 === 0 ? `${token}` : `${token}+`
37+
).join("")
38+
}
39+
40+
static parseFlags(flags) {
41+
const paramWithValue = flags.split("+")
42+
const flagsObject = {}
43+
paramWithValue.forEach(token => {
44+
const [attr, value] = token.split("")
45+
flagsObject[attr] = value
46+
})
47+
return flagsObject
48+
}
49+
}
50+
51+
class FileHandler {
52+
static ActionTypes = Object.freeze({ MAKE: "make", DELETE: "delete", REMOVE: "remove" })
53+
static EntityTypes = Object.freeze({ BLOG: "blog", PROJECT: "project", PROTOTYPE: "prototype" })
54+
55+
static generateFilePath(title, type, flags) {
56+
let date = flags.d || new Date().toISOString()
57+
const [year, month, day] = date.split("T")[0].split("-")
58+
59+
if (!(year && month && day)) {
60+
console.log("Invalid date format. Please provide -d yyyy-mm-dd")
61+
process.exit(1)
62+
}
63+
64+
const formattedDate = type === this.EntityTypes.PROTOTYPE ? year : `${year}-${month}-${day}`
65+
const filename = `${formattedDate}-${title.replace(/\s+/g, "-").toLowerCase()}.md`.replace(/[<>:"\/\\|?*]+/g, "")
66+
const filepath = path.join(process.cwd(), `src/articles/${type}`, filename)
67+
68+
return { filename, filepath }
69+
}
70+
71+
static handleError(error, filename, action = this.ActionTypes.MAKE) {
72+
if (error) return console.error(`Error creating file: ${error.message}`)
73+
console.log(`File "${filename}" ${action === this.ActionTypes.MAKE ? "created" : "removed"} successfully.`)
74+
}
75+
76+
static generateFile(title, type, flags, getContentFunction) {
77+
const { filename, filepath } = this.generateFilePath(title, type, flags)
78+
const content = getContentFunction(title, flags)
79+
fs.writeFile(filepath, content, (error) => this.handleError(error, filename))
80+
}
81+
82+
static removeFile(title, type, flags) {
83+
const { filename, filepath } = this.generateFilePath(title, type, flags)
84+
fs.rm(filepath, (error) => this.handleError(error, filename, this.ActionTypes.DELETE))
85+
}
86+
87+
static getAllActionTypes() {
88+
return Object.values(this.ActionTypes).map((action, index, arr) => (index === arr.length - 1 ? ` and \`${action}\`` : ` \`${action}\`,`)).join("")
89+
}
90+
91+
static getAllEntityTypes() {
92+
return Object.values(this.EntityTypes).map((entity, index, arr) => (index === arr.length - 1 ? ` and \`${entity}\`` : ` \`${entity}\`,`)).join("")
93+
}
94+
}
95+
96+
class ContentGenerator {
97+
static getDefaultBlogContent(title, flags = {}) {
98+
return `---
99+
title: ${title}
100+
description:
101+
author:
102+
draft: true
103+
date: ${new Date().toISOString()}
104+
tags:
105+
- post
106+
${typeof flags.t === "string" ? flags.t.split(",").map(tag => ` - ${tag}`).join("\n") : ""}
107+
---\n`
108+
}
109+
110+
static getDefaultProjectContent(title, flags = {}) {
111+
return `---
112+
title: ${title}
113+
description:
114+
date:
115+
endDate:
116+
image:
117+
linkDemo:
118+
linkCode:
119+
tags:
120+
- project
121+
${typeof flags.t === "string" ? flags.t.split(",").map(tag => ` - ${tag}`).join("\n") : ""}
122+
---\n`
123+
}
124+
125+
static getDefaultPrototypeContent(title, flags = {}) {
126+
return `---
127+
title: ${title}
128+
status: 1
129+
description:
130+
date:
131+
linkDemo:
132+
language:
133+
code: |-
134+
tags:
135+
- prototype
136+
${typeof flags.t === "string" ? flags.t.split(",").map(tag => ` - ${tag}`).join("\n") : ""}
137+
---\n`
138+
}
139+
}
140+
141+
class Ewtools {
142+
constructor(args) {
143+
this.args = args
144+
this.command = args[0]
145+
this.title = args[1]
146+
this.restArgs = args.slice(2)
147+
this.flags = FlagsProcessor.parseFlags(FlagsProcessor.formatFlagsString(this.restArgs))
148+
this.action = this.command.split(":")[0]
149+
this.type = this.command.split(":")[1]
150+
}
151+
152+
validateArgs() {
153+
if (this.args.length < 2) {
154+
console.log("Usage: ./ewtools.bat make:blog \"title\"")
155+
process.exit(1)
156+
}
157+
}
158+
159+
execute() {
160+
this.validateArgs()
161+
Logger.log(this.args, Logger.Types.INFO)
162+
Logger.log(this.flags, Logger.Types.INFO)
163+
164+
switch (this.action) {
165+
case FileHandler.ActionTypes.MAKE:
166+
this.handleMake()
167+
break
168+
169+
case FileHandler.ActionTypes.REMOVE:
170+
case FileHandler.ActionTypes.DELETE:
171+
this.handleDelete()
172+
break
173+
174+
default:
175+
console.log(`Invalid Action. Valid Actions: ${FileHandler.getAllActionTypes()}`)
176+
}
177+
}
178+
179+
handleMake() {
180+
switch (this.type) {
181+
case FileHandler.EntityTypes.BLOG:
182+
FileHandler.generateFile(this.title, this.type, this.flags, ContentGenerator.getDefaultBlogContent)
183+
break
184+
185+
case FileHandler.EntityTypes.PROJECT:
186+
FileHandler.generateFile(this.title, this.type, this.flags, ContentGenerator.getDefaultProjectContent)
187+
break
188+
189+
case FileHandler.EntityTypes.PROTOTYPE:
190+
FileHandler.generateFile(this.title, this.type, this.flags, ContentGenerator.getDefaultPrototypeContent)
191+
break
192+
193+
default:
194+
console.log(`Invalid Type, Valid Types: ${FileHandler.getAllEntityTypes()}`)
195+
}
196+
}
197+
198+
handleDelete() {
199+
if (this.type === "" || this.type === undefined) return console.log(`Invalid Type, Valid Types: ${FileHandler.getAllEntityTypes()}`)
200+
FileHandler.removeFile(this.title, this.type, this.flags)
201+
}
202+
}
203+
204+
Configuration.load()
205+
const args = process.argv.slice(2)
206+
207+
if (args.length <= 0) {
208+
console.log("Usage: ./ewtools.bat make:blog \"title\"")
209+
} else {
210+
new Ewtools(args).execute()
211+
}

bin/ewTools.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import fs from "fs"
2+
import path from "path"
3+
import dotenv from "dotenv"
4+
5+
dotenv.config()
6+
const isDevelopment = process.env.TOOLS_ENV === "development"
7+
const LoggerType = Object.freeze({
8+
INFO: "Info",
9+
WARN: "Warning",
10+
ERROR: "Error",
11+
EMPTY: ""
12+
})
13+
14+
const getAllActionTypes = () => Object.values(ActionTypes).map((action, index, arr) => (index === arr.length - 1 ? ` and \`${action}\`` : ` \`${action}\`,`)).join("")
15+
16+
const getAllEntityTypes = () => Object.values(EntityTypes).map((entity, index, arr) => (index === arr.length - 1 ? ` and \`${entity}\`` : ` \`${entity}\`,`)).join("")
17+
18+
function Logger(text, type = LoggerType.EMPTY) {
19+
const typeFormatted = (type === LoggerType.EMPTY) ? `${type}` : `${type}:`
20+
if (isDevelopment) console.log(`${typeFormatted}`, text)
21+
}
22+
23+
const formatFlagsString = (args) => {
24+
if (!Array.isArray(args) || args.length === 0) return "";
25+
26+
return args.map((token, index) => {
27+
if (index === args.length - 1) return token;
28+
return (index % 2 == 0) ? `${token};` : `${token}+`
29+
}).join("")
30+
}
31+
32+
const flagsHandler = (flags) => {
33+
const paramWithValue = flags.split("+")
34+
const flagsObject = new Object();
35+
paramWithValue.forEach(token => {
36+
const [attr, value] = token.split(";")
37+
flagsObject[attr[1]] = value
38+
})
39+
return flagsObject
40+
}
41+
42+
const args = process.argv.slice(2)
43+
if (args.length <= 0) {
44+
console.log("Usage: ./ewtools.bat make:blog \"title\"")
45+
process.exit()
46+
}
47+
const [command, title, ...restArgs] = args
48+
const [action, type] = command.split(':')
49+
const formattedFlags = formatFlagsString(restArgs)
50+
const flagsObject = flagsHandler(formattedFlags)
51+
Logger(args, LoggerType.INFO)
52+
Logger(flagsObject, LoggerType.INFO)
53+
54+
const ActionTypes = Object.freeze({
55+
MAKE: "make",
56+
DELETE: "delete",
57+
REMOVE: "remove"
58+
})
59+
60+
const EntityTypes = Object.freeze({
61+
BLOG: "blog",
62+
PROJECT: "project",
63+
PROTOTYPE: "prototype"
64+
})
65+
66+
if (args.length < 2) {
67+
console.log("Usage: ./ewtools.bat make:blog \"title\"")
68+
process.exit(1)
69+
}
70+
71+
const handleError = (error, filename, action = ActionTypes.MAKE) => {
72+
if (error) return console.error(`Error creating file: ${error.message}`)
73+
const currentAction = (action === ActionTypes.MAKE) ? "created" : "removed"
74+
console.log(`File "${filename}" ${currentAction} successfully.`)
75+
}
76+
77+
const generateFile = (title, entityType, getContent) => {
78+
const { filename, filepath } = generateFilePath(title, entityType);
79+
const content = getContent(title, flagsObject);
80+
fs.writeFile(filepath, content, error => handleError(error, filename));
81+
};
82+
83+
const generateBlogFile = (title) => generateFile(title, EntityTypes.BLOG, getDefaultBlogContent)
84+
85+
const generateProjectFile = (title) => generateFile(title, EntityTypes.PROJECT, getDefaultProjectContent)
86+
87+
const generatePrototypeFile = (title) => generateFile(title, EntityTypes.PROTOTYPE, getDefaultPrototypeContent)
88+
89+
const removeFile = (title, type) => {
90+
const { filename, filepath } = generateFilePath(title, type)
91+
fs.rm(filepath, (error) => handleError(error, filename, ActionTypes.DELETE))
92+
}
93+
94+
const generateFilePath = (title, type) => {
95+
let date = flagsObject.d || new Date().toISOString()
96+
const [year, month, day] = date.split("T")[0].split("-")
97+
if (!(year && month && day)) {
98+
console.log("Invalid date format. Please provide -d yyyy-mm-dd")
99+
process.exit(1)
100+
}
101+
const formattedDate = type === EntityTypes.PROTOTYPE ? year : `${year}-${month}-${day}`
102+
const filename = `${formattedDate}-${title.replace(/\s+/g, "-").toLowerCase()}.md`.replace(/[<>:"\/\\|?*]+/g, "")
103+
const filepath = path.join(process.cwd(), `src/articles/${type}`, filename)
104+
105+
return { filename, filepath }
106+
}
107+
108+
const getDefaultBlogContent = (title, flags = {}) => `---
109+
title: ${title}
110+
description:
111+
author:
112+
draft: true
113+
date: ${new Date().toISOString()}
114+
tags:
115+
- post
116+
${typeof flags.t === "string" ? flags.t.split(",").map(tag => ` - ${tag}`).join("\n") : ""}---
117+
118+
`
119+
const getDefaultProjectContent = (title, flags = {}) => `---
120+
title: ${title}
121+
description:
122+
date:
123+
endDate:
124+
image:
125+
linkDemo:
126+
linkCode:
127+
tags:
128+
- project
129+
${typeof flags.t === "string" ? flags.t.split(",").map(tag => ` - ${tag}`).join("\n") : ""}---`;
130+
const getDefaultPrototypeContent = (title, flags = {}) => `---
131+
title: ${title}
132+
status: 1
133+
description:
134+
date:
135+
linkDemo:
136+
language:
137+
code: |-
138+
tags:
139+
- prototype
140+
${typeof flags.t === "string" ? flags.t.split(",").map(tag => ` - ${tag}`).join("\n") : ""}---
141+
`
142+
143+
const handleMake = (type, title) => {
144+
switch (type) {
145+
case EntityTypes.BLOG:
146+
generateBlogFile(title)
147+
break
148+
case EntityTypes.PROJECT:
149+
generateProjectFile(title)
150+
break
151+
case EntityTypes.PROTOTYPE:
152+
generatePrototypeFile(title)
153+
break
154+
default:
155+
console.log(`Invalid Type, Valid Types: ${getAllEntityTypes()}`)
156+
}
157+
}
158+
159+
const handleDelete = (type, title) => {
160+
switch (type) {
161+
case EntityTypes.BLOG:
162+
removeFile(title, EntityTypes.BLOG)
163+
break
164+
case EntityTypes.PROJECT:
165+
removeFile(title, EntityTypes.PROJECT)
166+
break
167+
case EntityTypes.PROTOTYPE:
168+
removeFile(title, EntityTypes.PROTOTYPE)
169+
break
170+
default:
171+
console.log(`Invalid Type, Valid Types: ${getAllEntityTypes()}`)
172+
}
173+
}
174+
175+
switch (action) {
176+
case ActionTypes.MAKE:
177+
handleMake(type, title)
178+
break
179+
case ActionTypes.DELETE:
180+
case ActionTypes.REMOVE:
181+
handleDelete(type, title)
182+
break
183+
default:
184+
console.log(`Invalid Action. Valid Actions: ${getAllActionTypes()}`)
185+
}

0 commit comments

Comments
 (0)