├── README.md
└── createcomponent.py
/README.md:
--------------------------------------------------------------------------------
1 | Video: [https://www.youtube.com/watch?v=zU1kf3Qjtdk](https://www.youtube.com/watch?v=zU1kf3Qjtdk)
2 |
3 | Fast TypeScript react component generator written in Python 3.10.
4 |
5 | For usage you need `python3.10` in your `$PATH` environment variable.
6 |
7 | Usage:
8 |
9 | ```bash
10 | ./createcomponent.py
11 | ```
12 |
13 |
--------------------------------------------------------------------------------
/createcomponent.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3.10
2 | """Хелпер для создания React компонентов.
3 | Спрашивает, создаём компонент в components или pages,
4 | затем спрашивает, какой элемент создаём.
5 |
6 | Если для pages элемент указываем MyComponent, создаст:
7 | pages/MyComponent/MyComponent.tsx
8 | pages/MyComponent/MyComponent.module.css
9 | pages/MyComponent/index.ts (опционально)
10 |
11 | Если указать MyComponent/Element, создаст:
12 | pages/MyComponent/Element.tsx
13 | pages/MyComponent/Element.module.css
14 | pages/MyComponent/index.ts (опционально)
15 | """
16 | from abc import abstractmethod, ABC
17 | from dataclasses import dataclass
18 | from pathlib import Path
19 | from typing import Literal, TypeAlias, Iterable
20 |
21 |
22 | SRC_DIR = Path(__file__).parent / "src"
23 |
24 | BaseFolder: TypeAlias = Literal["components", "pages"]
25 |
26 |
27 | class Colors:
28 | HEADER = '\033[95m'
29 | OKBLUE = '\033[94m'
30 | OKCYAN = '\033[96m'
31 | OKGREEN = '\033[92m'
32 | WARNING = '\033[93m'
33 | FAIL = '\033[91m'
34 | ENDC = '\033[0m'
35 | BOLD = '\033[1m'
36 | UNDERLINE = '\033[4m'
37 |
38 |
39 | @dataclass
40 | class Element:
41 | full_path: Path
42 | name: str
43 |
44 |
45 | class FileCreator(ABC):
46 | def __init__(self, element: Element):
47 | self._element = element
48 |
49 | def create(self) -> None:
50 | """Creates empty file and then fill with contents"""
51 | self._create_empty_file()
52 | self._write_file_contents()
53 |
54 | def get_relative_filename(self) -> str:
55 | """Returns relative filename as str for logging"""
56 | relative_path_start_index = 1 + len(str(SRC_DIR.resolve()))
57 | result = str(
58 | self.get_absolute_filename().resolve()
59 | )[relative_path_start_index:]
60 | return result
61 |
62 | def _create_empty_file(self):
63 | """Init file if not exists"""
64 | self.get_absolute_filename().parent.mkdir(parents=True, exist_ok=True)
65 | self.get_absolute_filename().touch(exist_ok=True)
66 |
67 | @abstractmethod
68 | def get_absolute_filename(self) -> Path:
69 | """Returns file in Path format"""
70 | pass
71 |
72 | @abstractmethod
73 | def _write_file_contents(self) -> None:
74 | """Fill file with contents"""
75 | pass
76 |
77 |
78 | class TSXFileCreator(FileCreator):
79 | """Element.tsx file creator"""
80 | def get_absolute_filename(self) -> Path:
81 | return self._element.full_path / (self._element.name + ".tsx")
82 |
83 | def _write_file_contents(self):
84 | self.get_absolute_filename().write_text(
85 | f"""import styles from "./{self._element.name}.module.css"
86 |
87 | interface {self._element.name}Props {{
88 |
89 | }}
90 |
91 | const {self._element.name} = ({{}}: {self._element.name}Props) => (
92 |
{self._element.name}
93 | )
94 |
95 | export default {self._element.name}
96 | """.strip())
97 |
98 |
99 | class CSSFileCreator(FileCreator):
100 | """Element.module.css file creator"""
101 | def get_absolute_filename(self) -> Path:
102 | return self._element.full_path / (self._element.name + ".module.css")
103 |
104 | def _write_file_contents(self):
105 | pass
106 |
107 |
108 | class IndexFileCreator(FileCreator):
109 | """Optional index.ts file creator"""
110 | def get_absolute_filename(self) -> Path:
111 | return self._element.full_path / "index.ts"
112 |
113 | def _write_file_contents(self):
114 | current_file_contents = self.get_absolute_filename().read_text()
115 | if current_file_contents.strip():
116 | return
117 | self.get_absolute_filename().write_text(
118 | f"""export {{default}} from "./{self._element.name}";"""
119 | )
120 |
121 |
122 | class AskParams:
123 | """Ask params from user, parse it and create Element structure"""
124 | def __init__(self):
125 | self._element: Element
126 |
127 | def ask(self) -> Element:
128 | """Ask all parameters — element folder and name"""
129 | base_folder = self._ask_base_folder()
130 | self._element = self._parse_as_element(
131 | self._ask_element(base_folder),
132 | base_folder
133 | )
134 | return self._element
135 |
136 | def _parse_as_element(self,
137 | element_str: str,
138 | base_folder: BaseFolder) -> Element:
139 | element_as_list = element_str.split("/")
140 | element_name = element_as_list[-1]
141 | if len(element_as_list) > 1:
142 | # user entered: MyCourses/AuthorCourses
143 | relative_path = "/".join(element_as_list[:-1])
144 | else:
145 | # user entered: AuthorCourses
146 | relative_path = element_name
147 | return Element(
148 | full_path=SRC_DIR / base_folder / relative_path,
149 | name=element_name
150 | )
151 |
152 | def ask_ok(self, filenames: Iterable[str]) -> None:
153 | filenames = "\n\t".join(filenames)
154 | while True:
155 | print(f"\nИтак, создаём файлы:\n"
156 | f"{Colors.HEADER}\t{filenames} {Colors.ENDC}\n\n")
157 | match input("Ок? [Y]/N: ").strip().lower():
158 | case "y" | "": return
159 | case "n": exit("Ок, выходим, ничего не создал.")
160 | case _: print("Не понял, давай ещё раз.")
161 |
162 | def _ask_base_folder(self) -> BaseFolder:
163 | while True:
164 | match input("c — components, p — pages: ").strip().lower():
165 | case "c": return "components"
166 | case "p": return "pages"
167 | case _: print("Не понял, давай ещё раз.")
168 |
169 | def _ask_element(self, base_folder: BaseFolder) -> str:
170 | return input(f"Куда кладём? {base_folder}/").strip()
171 |
172 |
173 | class ElementFilesCreator:
174 | """Handles files creation"""
175 | def __init__(self, element: Element):
176 | self._element = element
177 | self._file_creators: list[FileCreator] = []
178 |
179 | def create(self):
180 | for file_creator in self._file_creators:
181 | file_creator.create()
182 |
183 | def register_file_creators(self, *file_creators: type[FileCreator]):
184 | for fc in file_creators:
185 | self._file_creators.append(fc(
186 | element=self._element
187 | ))
188 |
189 | def get_relative_filenames(self) -> tuple[str, ...]:
190 | return tuple(fc.get_relative_filename() for fc in self._file_creators)
191 |
192 |
193 | def main():
194 | asker = AskParams()
195 | element = asker.ask()
196 |
197 | element_creator = ElementFilesCreator(element)
198 | element_creator.register_file_creators(
199 | TSXFileCreator,
200 | CSSFileCreator,
201 | IndexFileCreator
202 | )
203 | asker.ask_ok(element_creator.get_relative_filenames())
204 | element_creator.create()
205 | print(f"Всё создал и весь такой молодец!")
206 |
207 |
208 | if __name__ == "__main__":
209 | try:
210 | main()
211 | except KeyboardInterrupt:
212 | pass
213 |
--------------------------------------------------------------------------------