forked from folke/devmoji
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): ✨ added cli for working with devmoji 🚀
- Loading branch information
Showing
7 changed files
with
406 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { Config } from "./config" | ||
import { Command } from "commander" | ||
import readline = require("readline") | ||
import { Devmoji } from "./devmoji" | ||
import chalk from "chalk" | ||
import { ConventionalCommits } from "./conventional-commits" | ||
import * as path from "path" | ||
import * as fs from "fs" | ||
|
||
export class Cli { | ||
commits: ConventionalCommits | ||
constructor(public program: Command, public devmoji: Devmoji) { | ||
this.commits = new ConventionalCommits(devmoji) | ||
} | ||
|
||
format(text: string, format = "unicode", processCommit = false) { | ||
if (processCommit) text = this.commits.format(text) | ||
switch (format) { | ||
case "unicode": | ||
return text | ||
case "shortcode": | ||
return this.devmoji.demojify(text) | ||
case "devmoji": | ||
return this.devmoji.devmojify(text) | ||
} | ||
throw `Invalid format '${format}'` | ||
} | ||
|
||
list() { | ||
console.log(chalk.magenta.underline("all configured devmoji")) | ||
for (const code of this.devmoji.config.codes.values()) { | ||
console.log( | ||
this.devmoji.get(code.emoji), | ||
" ", | ||
chalk.blue(`:${code.code}:`.padEnd(10)), | ||
code.description | ||
) | ||
} | ||
} | ||
|
||
error(msg: string) { | ||
console.error(chalk.red("[error] ") + msg) | ||
process.exit(1) | ||
} | ||
|
||
gitRoot(cwd = process.cwd()): string | undefined { | ||
if (cwd == "/") return undefined | ||
const p = path.posix.resolve(cwd, "./.git") | ||
if (fs.existsSync(p) && fs.lstatSync(p).isDirectory()) return p | ||
return this.gitRoot(path.resolve(cwd, "../")) | ||
} | ||
|
||
static async create(argv = process.argv, exitOverride = false) { | ||
const program = new Command() | ||
if (exitOverride) program.exitOverride() | ||
program | ||
.version(require("../package.json").version) | ||
.option("-g|--config <file>", "location of the devmoji.config.js file") | ||
.option("-l|--list", "list all known devmojis") | ||
.option( | ||
"-t|--text <text>", | ||
"text to format. reads from stdin when omitted" | ||
) | ||
.option( | ||
"-f|--format <format>", | ||
"format should be one of: unicode, shortcode, devmoji", | ||
"unicode" | ||
) | ||
.option( | ||
"-c|--commit", | ||
"automatically add a devmoji to the conventional commit header", | ||
true | ||
) | ||
.option( | ||
"-e|--edit", | ||
"read last commit message from the specified file or fallbacks to ./.git/COMMIT_EDITMSG" | ||
) | ||
.parse(argv) | ||
// console.log(program.opts()) | ||
const config = await Config.load(program.config) | ||
return new Cli(program, new Devmoji(config)) | ||
} | ||
|
||
run() { | ||
if (this.program.list) return this.list() | ||
|
||
if (this.program.text) | ||
return console.log( | ||
this.format(this.program.text, this.program.format, this.program.commit) | ||
) | ||
|
||
if (this.program.edit) { | ||
let commitMsgFile = this.gitRoot() | ||
if (commitMsgFile) { | ||
commitMsgFile = path.resolve(commitMsgFile, "COMMIT_EDITMSG") | ||
} | ||
if (commitMsgFile && fs.existsSync(commitMsgFile)) { | ||
let text = fs.readFileSync(commitMsgFile, "utf-8") | ||
text = this.format(text, this.program.format, this.program.commit) | ||
fs.writeFileSync(commitMsgFile, text, "utf-8") | ||
return console.log(text) | ||
} else { | ||
this.error("Couldn't find .git/COMMIT_EDITMSG") | ||
} | ||
return | ||
} | ||
|
||
if (!process.stdin.isTTY) { | ||
const rl = readline.createInterface({ | ||
input: process.stdin, | ||
terminal: false, | ||
}) | ||
|
||
let lineNumber = 0 | ||
rl.on("line", line => { | ||
try { | ||
console.log(this.format(line, this.program.format, !lineNumber++)) | ||
} catch (err) { | ||
this.error(err) | ||
} | ||
}) | ||
return | ||
} | ||
|
||
this.program.outputHelp() | ||
return process.exit(1) | ||
} | ||
} | ||
|
||
export function run(argv = process.argv) { | ||
Cli.create(argv).then(cli => cli.run()) | ||
} | ||
|
||
if (module === require.main) { | ||
run() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
export type TDevmoji = { code: string; emoji: string; description: string } | ||
|
||
export const codes: TDevmoji[] = [ | ||
{ code: "feat", description: "a new feature", emoji: "sparkles" }, | ||
{ code: "fix", description: "a bug fix", emoji: "bug" }, | ||
{ code: "docs", description: "documentation only changes", emoji: "books" }, | ||
{ | ||
code: "style", | ||
description: | ||
"changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)", | ||
emoji: "art", | ||
}, | ||
{ | ||
code: "refactor", | ||
description: "a code change that neither fixes a bug nor adds a feature", | ||
emoji: "hammer", | ||
}, | ||
{ | ||
code: "perf", | ||
description: "a code change that improves performance", | ||
emoji: "zap", | ||
}, | ||
{ | ||
code: "test", | ||
description: "adding missing or correcting existing tests", | ||
emoji: "rotating_light", | ||
}, | ||
{ | ||
code: "chore", | ||
description: | ||
"changes to the build process or auxiliary tools and libraries such as documentation generation", | ||
emoji: "wrench", | ||
}, | ||
{ | ||
code: "build", | ||
description: "changes related to build processes", | ||
emoji: "package", | ||
}, | ||
{ | ||
code: "ci", | ||
description: "updates to the continuous integration system", | ||
emoji: "construction_worker", | ||
}, | ||
{ | ||
code: "release", | ||
description: "code deployment or publishing to external repositories", | ||
emoji: "rocket", | ||
}, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { codes, TDevmoji } from "./codes" | ||
import path = require("path") | ||
import fs = require("fs") | ||
|
||
export class Config { | ||
codes = new Map<string, TDevmoji>() | ||
emojis = new Map<string, TDevmoji>() | ||
|
||
constructor(config?: { codes: TDevmoji[] }) { | ||
codes.forEach(c => this.codes.set(c.code, c)) | ||
|
||
if (config?.codes) { | ||
for (let code of config.codes) { | ||
if (!code.code) | ||
throw `Missing property 'code' for ${JSON.stringify(code)}` | ||
if (!code.emoji) | ||
throw `Missing property 'emoji' for ${JSON.stringify(code)}` | ||
code = { | ||
...{ | ||
description: "", | ||
}, | ||
...this.codes.get(code.code), | ||
...code, | ||
} | ||
this.codes.set(code.code, code) | ||
} | ||
} | ||
for (const code of this.codes.values()) { | ||
this.emojis.set(code.emoji, code) | ||
} | ||
} | ||
|
||
static async load(configFile?: string, cwd = process.cwd()) { | ||
if (configFile && !fs.existsSync(configFile)) | ||
throw `Config file not found ${configFile}` | ||
|
||
if (!configFile) { | ||
const defaultFile = path.resolve(cwd, "./devmoji.config.js") | ||
if (fs.existsSync(defaultFile)) configFile = defaultFile | ||
} | ||
|
||
if (configFile) { | ||
configFile = path.resolve(cwd, configFile) | ||
return new Config(await import(configFile)) | ||
} | ||
|
||
return new Config() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Devmoji } from "./devmoji" | ||
|
||
export class ConventionalCommits { | ||
constructor(public devmoji: Devmoji) {} | ||
|
||
format(text: string) { | ||
text = this.devmoji.devmojify(text) | ||
const regex = /^(?<type>[a-z]+)(?:\((?<scope>[a-z]+)\))?:\s*/g | ||
const match = regex.exec(text) | ||
if (match) { | ||
const [all, type, scope] = match | ||
const code = this.devmoji.get(type) | ||
if (!code.startsWith(":")) { | ||
text = | ||
text.slice(0, all.length).trimRight() + | ||
` ${code} ` + | ||
text.slice(all.length).trimLeft() | ||
} | ||
} | ||
return this.devmoji.emojify(text) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Config } from "./config" | ||
import { emoji } from "./emoji" | ||
|
||
export class Devmoji { | ||
shortcodeRegex = /:([a-zA-Z0-9_\-+]+):/g | ||
unicodeRegex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g | ||
|
||
constructor(public config: Config) {} | ||
|
||
get(code: string): string { | ||
const ret = this.config.codes.get(code) | ||
if (ret) return this.get(ret.emoji) | ||
return emoji.get(code) | ||
} | ||
|
||
emojify(text: string): string { | ||
return text.replace(this.shortcodeRegex, (match, code) => { | ||
return this.get(code) | ||
}) | ||
} | ||
|
||
demojify(text: string): string { | ||
return text.replace(this.unicodeRegex, s => { | ||
return emoji.getCode(s) | ||
}) | ||
} | ||
|
||
devmojify(text: string): string { | ||
text = this.demojify(text) | ||
return text.replace(this.shortcodeRegex, (match, code) => { | ||
const unicode = emoji.get(code) | ||
const codes = emoji.getCodes(unicode) | ||
for (const c of codes) { | ||
const ret = this.config.emojis.get(emoji.unwrap(c)) | ||
if (ret) return `:${ret.code}:` | ||
} | ||
return match | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import * as emojis from "./data/github.emoji.json" | ||
import * as gitmojis from "./data/gitmoji.emoji.json" | ||
|
||
class Emoji { | ||
codes = new Map<string, string>() | ||
emojis = new Map<string, string[]>() | ||
|
||
constructor() { | ||
for (const [code, emoji] of Object.entries(emojis)) { | ||
this.codes.set(this.wrap(code), emoji) | ||
this.emojis.set(emoji, [ | ||
...(this.emojis.get(emoji) || []), | ||
this.wrap(code), | ||
]) | ||
} | ||
// Make sure the shortcodes for gitmojis are the first ones in the list | ||
for (const { emoji, code } of gitmojis.gitmojis) { | ||
const codes = new Set(this.getCodes(emoji)) | ||
codes.delete(code) | ||
this.emojis.set(emoji, [code, ...codes]) | ||
} | ||
} | ||
|
||
wrap(code: string) { | ||
return code.startsWith(":") ? code : `:${code}:` | ||
} | ||
|
||
unwrap(code: string) { | ||
return code.startsWith(":") ? code.slice(1, code.length - 1) : code | ||
} | ||
|
||
getCodes(emoji: string): string[] { | ||
return this.emojis.get(emoji) || [emoji] | ||
} | ||
|
||
getCode(emoji: string): string { | ||
return this.getCodes(emoji)[0] | ||
} | ||
|
||
get(code: string): string { | ||
code = this.wrap(code) | ||
return this.codes.get(code) || code | ||
} | ||
} | ||
|
||
export const emoji = new Emoji() |
Oops, something went wrong.