-
Notifications
You must be signed in to change notification settings - Fork 0
/
fs_nested_bem.py
205 lines (161 loc) · 10.8 KB
/
fs_nested_bem.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#!/usr/bin/env python3
# Скрипт для создания файловой структуры "Nested", методология БЭМ.
# В рамках данного скрипта я использую термин "БЭМ-компонент", имея ввиду любую
# БЭМ-сущность, т.е. и БЭМ-блок и БЭМ-элемент и БЭМ-модификатор.
# Импорт библиотеки "BeautifulSoup4" для извлечения данных из HTML-файла и
# модулей, являющихся частью стандартной библиотеки Python 3.
from bs4 import BeautifulSoup
import json
import os
import re
# Требуем от пользователя ввести путь к HTML-файлу.
HTML_FILE = input('Введите путь к HTML-файлу: ')
# Проверяем наличие HTML-файла. В случае его отсутствия печатаем на экране
# служебное сообщение и завершаем работу скрипта.
if not os.path.exists(HTML_FILE):
print('HTML-файл {} не найден.'.format(HTML_FILE))
else:
with open(HTML_FILE, encoding='utf-8') as fp:
soup = BeautifulSoup(fp, 'html.parser')
def get_list_of_tags() -> list:
""" Функция возвращает список объектов, являющихся HTML-элементами,
содержащимися в HTML-файле """
return soup.find_all(True)
def get_list_of_tags_having_classes() -> list:
""" Функция возвращает список объектов, являющихся HTML-элементами,
имеющими атрибут class """
return list(filter(lambda tag: tag.has_attr('class'), get_list_of_tags()))
def get_set_of_classes() -> set:
""" Функция возвращает множество строк, содержащих имена классов
HTML-элементов """
list_of_classes = []
for tag in get_list_of_tags_having_classes():
list_of_classes.extend(tag.get('class'))
return set(list_of_classes)
def get_json_file_path() -> str:
""" Функция возвращает строку, содержащую путь к JSON-файлу """
return 'listOfUsedClasses.json'
def write_json_file(list_of_classes: list = []) -> None:
""" Функция, получив путь к JSON-файлу с помощью функции get_json_file_path,
преобразовывает исходный тип данных в JSON-строку и записывает ее в
данный файл """
with open(get_json_file_path(), 'wt') as json_file:
json_file.write(json.dumps({'used_classes': list_of_classes}, indent='\t'))
def read_json_file() -> dict:
""" Функция, получив путь к JSON-файлу с помощью функции get_json_file_path,
прочитывает такой файл и преобразовывает JSON-строку в исходный тип
данных, возвращаяя словарь """
with open(get_json_file_path()) as json_file:
return json.loads(json_file.read())
def get_set_of_used_classes() -> set:
""" Функция возвращает множество строк, содержащих имена классов HTML-элементов,
имеющих файловую структуру """
return set(read_json_file()['used_classes'])
def get_set_of_new_classes() -> set:
""" Функция возвращает множество строк, содержащих имена классов HTML-элементов,
не имеющих файловой структуры """
return get_set_of_classes() - get_set_of_used_classes()
def get_name_pattern() -> str:
""" Функция возвращает строку, содержащую шаблон, который используется
для создания шаблонов имен БЭМ-компонентов """
return '[a-z]+(-[a-z]+)?'
def get_bem_component_name_pattern(component_name: str) -> str:
""" Функция, руководствуясь значением параметра component_name, возвращает
строку, содержащую либо шаблон имени БЭМ-компонента либо сообщение об
ошибке """
NAME_PATTERN = get_name_pattern()
dict_of_patterns = {
'block': '{}'.format(NAME_PATTERN),
'block modifier': '{0}(_{0}){1}'.format(NAME_PATTERN, '{1,2}'),
'element': '{0}__{0}'.format(NAME_PATTERN),
'element modifier': '{0}__{0}(_{0}){1}'.format(NAME_PATTERN, '{1,2}')
}
ERROR_MSG = '[ValueError]: component_name is undefined'
return dict_of_patterns.get(component_name, ERROR_MSG)
def is_bem_component(component_name: str, class_name: str) -> bool:
""" Функция определяет соответствует ли HTML-элемент с классом class_name
БЭМ-компоненту, определенному параметром component_name, возвращая
одно из двух булевых значений """
COMPONENT_NAME_PATTERN = \
re.compile('^{}$'.format(get_bem_component_name_pattern(component_name)))
return bool(COMPONENT_NAME_PATTERN.match(class_name))
def get_list_of_bem_components(component_name: str, classes) -> list:
""" Функция производит фильтрацию списка (множества) classes и возвращает
список строк, содержащих имена классов HTML-элементов, соответствующих
БЭМ-компоненту, определенному параметром component_name """
return list(filter(
lambda class_name: is_bem_component(component_name, class_name), classes
))
def get_bem_component_path_pattern(component_name: str) -> str:
""" Функция, руководствуясь значением параметра component_name, возвращает
строку, содержащую либо шаблон пути к БЭМ-компоненту либо сообщение
об ошибке """
dict_of_patterns = {
'block': './{}',
'block modifier': './{}/{}',
'element': './{}/{}',
'element modifier': './{}/{}/{}'
}
ERROR_MSG = '[ValueError]: component_name is undefined'
return dict_of_patterns.get(component_name, ERROR_MSG)
def create_css_file(class_name: str) -> None:
""" Функция, руководствуясь значением параметра class_name, создает CSS-файл,
записывая в него селектор класса """
with open('{}.css'.format(class_name), 'wt') as css_file:
css_file.write('.{} {}'.format(class_name, '{\n\n}'))
def create_dirs_and_files_for_bem_components(component_name: str) -> None:
""" Функция, руководствуясь значением параметра component_name, создает
каталоги и CSS-файлы для БЭМ-компонентов, не имеющих файловой структуры """
new_components = \
get_list_of_bem_components(component_name, get_set_of_new_classes())
PATH_PATTERN = get_bem_component_path_pattern(component_name)
NAME_PATTERN = get_name_pattern()
for component in new_components:
if component_name == 'block':
DIR_PATH = PATH_PATTERN.format(component)
elif component_name == 'block modifier':
DIR_PATH = PATH_PATTERN.format(
re.match(NAME_PATTERN, component).group(),
re.search('_{}'.format(NAME_PATTERN), component).group()
)
elif component_name == 'element':
DIR_PATH = PATH_PATTERN.format(
re.match(NAME_PATTERN, component).group(),
re.search('__{}'.format(NAME_PATTERN), component).group()
)
elif component_name == 'element modifier':
DIR_PATH = PATH_PATTERN.format(
re.match(NAME_PATTERN, component).group(),
re.search('__{}'.format(NAME_PATTERN), component).group(),
[result.group() for result in re.finditer('_{}'.format(NAME_PATTERN), component)][1]
)
if not os.path.exists(DIR_PATH):
os.mkdir(DIR_PATH)
FILE_PATH = '{}/{}.css'.format(DIR_PATH, component)
if not os.path.exists(FILE_PATH):
BLOCKS_DIR = os.path.abspath('./')
os.chdir(DIR_PATH)
create_css_file(component)
os.chdir(BLOCKS_DIR)
def create_fs_nested() -> None:
""" Функция, используя функцию create_dirs_and_files_for_bem_components,
создает файловую структуру Nested, соответствующую HTML-файлу """
create_dirs_and_files_for_bem_components('block')
create_dirs_and_files_for_bem_components('block modifier')
create_dirs_and_files_for_bem_components('element')
create_dirs_and_files_for_bem_components('element modifier')
# Проверяем наличие каталога blocks. В случае его отсутствия, создаем в каталоге,
# содержащем данный сценарий, и переходим в него.
if not os.path.exists('blocks'):
os.mkdir('blocks')
os.chdir('blocks')
# Проверяем наличие JSON-файла. В случае его отсутствия, создаем в текущем
# каталоге, т.е. каталоге blocks.
if not os.path.exists(get_json_file_path()):
write_json_file()
# Создаем файловую структуру. Подробности в документациях функций.
create_fs_nested()
# Записываем множество классов HTML-элементов в JSON-файл, отсортировав их
# в алфавитном порядке.
write_json_file(sorted(list(get_set_of_classes())))
print('Сценарий завершил работу.')