Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/gen snippet #153

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .vscode/custom.code-snippets
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"커스텀 스니펫 예시입니다.": {
"prefix": "tok_example1",
"description": "커스텀 스니펫 예시입니다.",
"body": [
"console.log('Hello, world!');"
]
},
"커스텀 스니펫(var적용) 예시입니다.": {
"prefix": "tok_example2",
"description": "커스텀 스니펫(var적용) 예시입니다.",
"body": [
"const handler${1:custom} = useCallback(()=>{},[]);"
]
},
"커스텀 스니펫(cursor적용) 예시입니다.": {
"prefix": "tok_example3",
"description": "커스텀 스니펫(cursor적용) 예시입니다.",
"body": [
"console.log('Hello, $2');"
]
}
}
34 changes: 34 additions & 0 deletions customSnippets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export {};
/*
사용법 설명
- 기본적으로 아래 example 코드의 형태를 따릅니다.
- prefix : 호출명령어.
- description : 스니펫 설명.
- body : 생성 코드

- 위와 같은 형태로 사용할 스니펫 형태를 해당 파일에(customSnippet.ts)에 이어서 작성 해주시면 됩니다.

- 작업 효율을 증가시키기위해 cursor위치 및 디스클로져 처럼 네이밍 지정이 필요한 경우 추가 기능을 사용할 수 있습니다.
- {cursor} : 스니펫 생성 이후 커서 위치를 지정하고 싶을때 사용합니다.
ex) console.log({cursor});
- {var} : 스니펫 생성 이후 네이밍 수정을 해야하는 경우 사용합니다.
ex) const {isOpen:{var}IsOpen,onOpen:{var}OnOpen,onClose:{var}OnClose} = useDisclosure();
*/

/**
* @prefix tok_example1
* @description 커스텀 스니펫 예시입니다.
* @body console.log('Hello, world!');
*/

/**
* @prefix tok_example2
* @description 커스텀 스니펫(var적용) 예시입니다.
* @body const handler{var} = useCallback(()=>{},[]);
*/

/**
* @prefix tok_example3
* @description 커스텀 스니펫(cursor적용) 예시입니다.
* @body console.log('Hello, {cursor}');
*/
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"test:watch": "jest --watch",
"clean-up": "node src/scripts/remove-unnecessary.js",
"tokript": "tokript",
"tokriptTest": "tokript2",
"theme": "chakra-cli tokens src/configs/theme/index.ts",
"theme:open": "code -r node_modules/@chakra-ui/styled-system/dist/theming.types.d.ts",
"gen:source": "tokript gen:source",
Expand All @@ -42,9 +43,11 @@
"@hookform/resolvers": "^2.8.8",
"@tanstack/react-query": "^4.2.3",
"@tanstack/react-query-devtools": "^4.2.3",
"@toktokhan-fe/cli": "^1.4.3",
"axios": "^1.3.4",
"chakra-react-select": "^4.6.0",
"dayjs": "^1.10.6",
"enquirer": "^2.4.1",
"framer-motion": "^10.5.0",
"globby": "^13.2.2",
"immer": "^10.0.1",
Expand Down
24 changes: 24 additions & 0 deletions src/genSnippet/defaultSnippets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export {};
/**
* @prefix tok_useCallbackSnippet
* @description useCallback 자동생성 스니펫입니다.
* @body useCallback(()=>{{cursor}},[]);
*/

/**
* @prefix tok_useEffectSnippet
* @description useEffect 자동생성 스니펫입니다.
* @body useEffect(()=>{{cursor}},[]);
*/

/**
* @prefix tok_useRouterSnippet
* @description useRouter 자동생성 스니펫입니다.
* @body useRouter();
*/

/**
* @prefix tok_useDisclosureSnippet
* @description useDisclosure 자동생성 스니펫입니다.
* @body const {isOpen:{var}IsOpen,onOpen:{var}OnOpen,onClose:{var}OnClose} =useDisclosure();
*/
179 changes: 179 additions & 0 deletions src/genSnippet/genSnippet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { defineCommand } from '@toktokhan-fe/cli';
import pkg from 'enquirer';
import fs from 'fs';
import path from 'path';

/**
* 플러그인의 config 타입을 정의합니다.
*
* - tok-cli.config.ts 에서 해당 플러그인의 option 을 정의할 때 사용됩니다.
* - config 파일은 js, ts 이기 때문에, 옵션 객체의 각 property 는 함수, 배열 등 어떤 타입이든 정의 가능합니다.
* - run 함수의 인자 type 으로 사용됩니다.
*/

export type GenSnippetConfig = {
output: string;
};

type SnippetType = 'custom' | 'default';

interface SnippetItem {
prefix: string;
body: string[];
description: string;
}

function getBasePath(type: SnippetType) {
const projectRoot = process.cwd();
const outputPath = path.join(projectRoot, '.vscode', `${type}.code-snippets`);
return outputPath;
}

function createSnippet({
type,
formattedSnippets,
}: {
type: SnippetType;
formattedSnippets: Record<string, SnippetItem>;
}) {
fs.mkdir(path.dirname(getBasePath(type)), { recursive: true }, (err) => {
if (err) throw err;

fs.writeFile(
getBasePath(type),
JSON.stringify(formattedSnippets, null, 2),
(err) => {
if (err) throw err;
console.log('Snippets saved to .vscode/project.code-snippets');
},
);
});
}

function parseSnippets(snippetString: string) {
const snippets = snippetString
.split('/**')
.filter((s) => s.trim().startsWith('* @prefix'));
return snippets.map((snippet) => {
const prefixMatch = snippet.match(/\* @prefix\s+(\w+)/);
const descriptionMatch = snippet.match(/\* @description\s+([^*]+)/);
const bodyMatch = snippet.match(/\* @body\s+([^*]+)/);

const isCursor = bodyMatch
? bodyMatch[1].trim().replaceAll('{cursor}', `$2`)
: '';
const isVar = isCursor ? isCursor.replaceAll('{var}', '${1:custom}') : '';

return {
prefix: prefixMatch ? prefixMatch[1].trim() : '',
description: descriptionMatch ? descriptionMatch[1].trim() : '',
body: [isVar],
};
});
}

function formatSnippets(snippetArray: SnippetItem[]) {
const formatted: Record<string, SnippetItem> = {};
snippetArray.forEach((snippet: SnippetItem) => {
formatted[snippet.description] = {
...snippet,
};
});
return formatted;
}

async function getTypeSnippet() {
const { prompt } = pkg;
const type = await prompt<{
snippetType: 'custom' | 'default';
}>({
type: 'autocomplete',
name: 'snippetType',
choices: ['default', 'custom'],
message: '스니펫 형태를 선택해 주세요',
required: true,
});
return type.snippetType;
}

export const genSnippet = defineCommand<'gen:snippet', GenSnippetConfig>({
/**
* 플러그인의 이름을 정의합니다.
*
* - tokript {command} 로 실행됩니다.
* - tok-cli.config 에서 옵션 정의시 해당 옵션의 key 값으로 사용됩니다.
*/
name: 'gen:snippet',
/**
* 플러그인의 설명을 정의합니다.
*
* - tokript help 실행시 표기됩니다.
*/
description: '프로젝트별 커스텀 스니펫 생성을 자동생성해줍니다.',
/**
* 플러그인 실행시 사용할 config 의 기본값을 정의합니다.
*
* - 특정 옵션이 `--output` 과 같은 `cli option` 이나 `tok-cli.config.ts` 에 정의 되지 않았을 때 사용됩니다.
*/
default: {
output: path.resolve('generated', 'my.txt'),
},
/**
* --output, -o 와 같은 cli option 을 정의합니다.
*
* - cli option 에 정의되지 않은 옵션은 오직 config 파일에서만 정의 가능합니다.
* - cli option 은 원시값, 원시값 배열과 같은 간단한 값만 사용 가능합니다. ex) string, string[]
* - tokript help {command} 시 정의한 alias, 설명, 기본값을 확인할 수 있습니다.
*/
cliOptions: [
{
name: 'output',
alias: 'o',
description: '텍스트 파일 생성 경로',
type: 'string',
},
],
/**
* 플러그인 실행 함수를 정의합니다.
*
* - config: GenTxtConfig 타입의 config 객체가 인자로 넘어옵니다.
* - config 객체는 default, cli option, tok-cli.config.ts 에 정의된 값들이 합쳐진 값입니다.
* - config 우선순위는 cli option > tok-cli.config.ts > default 입니다.
* - run 함수는 플러그인의 실제 동작을 정의합니다.
*/
run: async () => {
const type = await getTypeSnippet();
const isCustom = type === 'custom';
const isCustomFile = fs.existsSync('customSnippets.ts');

if (isCustom && !isCustomFile) {
const projectRoot = process.cwd();
const outputPath = path.join(projectRoot, `customSnippets.ts`);

const initData = fs.readFileSync(
path.resolve(__dirname, 'initSnippets.ts'),
{
encoding: 'utf8',
},
);
fs.writeFile(outputPath, initData, (err) => {
if (err) throw err;
console.log('Snippets saved to .vscode/project.code-snippets');
});
}

const data = isCustom
? isCustomFile
? fs.readFileSync('customSnippets.ts', { encoding: 'utf8' })
: fs.readFileSync(path.resolve(__dirname, 'initSnippets.ts'), {
encoding: 'utf8',
})
: fs.readFileSync(path.resolve(__dirname, 'defaultSnippets.ts'), {
encoding: 'utf8',
});

const formattedSnippets = formatSnippets(parseSnippets(data));

createSnippet({ type, formattedSnippets });
},
});
34 changes: 34 additions & 0 deletions src/genSnippet/initSnippets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export {};
/*
사용법 설명
- 기본적으로 아래 example 코드의 형태를 따릅니다.
- prefix : 호출명령어.
- description : 스니펫 설명.
- body : 생성 코드

- 위와 같은 형태로 사용할 스니펫 형태를 해당 파일에(customSnippet.ts)에 이어서 작성 해주시면 됩니다.

- 작업 효율을 증가시키기위해 cursor위치 및 디스클로져 처럼 네이밍 지정이 필요한 경우 추가 기능을 사용할 수 있습니다.
- {cursor} : 스니펫 생성 이후 커서 위치를 지정하고 싶을때 사용합니다.
ex) console.log({cursor});
- {var} : 스니펫 생성 이후 네이밍 수정을 해야하는 경우 사용합니다.
ex) const {isOpen:{var}IsOpen,onOpen:{var}OnOpen,onClose:{var}OnClose} = useDisclosure();
*/

/**
* @prefix tok_example1
* @description 커스텀 스니펫 예시입니다.
* @body console.log('Hello, world!');
*/

/**
* @prefix tok_example2
* @description 커스텀 스니펫(var적용) 예시입니다.
* @body const handler{var} = useCallback(()=>{},[]);
*/

/**
* @prefix tok_example3
* @description 커스텀 스니펫(cursor적용) 예시입니다.
* @body console.log('Hello, {cursor}');
*/
23 changes: 23 additions & 0 deletions tok-cli.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { RootConfig } from '@toktokhan-fe/cli';

import { genSnippet } from './src/genSnippet/genSnippet';

/**
* config 를 정의합니다.
*
* RootConfig 의 제네릭에 plugin 의 type 을 정의 함으로써 type safe 하게 option 을 정의할 수 있습니다.
*/
const config: RootConfig<{ plugins: [typeof genSnippet] }> = {
/**
* tokript 가 해당 플러그인을 실행시키기 위해 조회하는 플러그인 리스트입니다.
*/
plugins: [genSnippet],
/**
* 정의된 이름을 key 값으로 config type 을 value 로써 config 정의가 가능합니다.
*/
'gen:snippet': {
output: 'custom/path/my.txt',
},
};

export default config;
9 changes: 8 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "app.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"],
"include": [
"next-env.d.ts",
"app.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.d.ts",
"customSnippets"
],
"exclude": ["node_modules", "./public/mockServiceWorker.js"]
}
Loading
Loading