ssc_codegen 0.13.1__tar.gz → 0.13.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/PKG-INFO +1 -1
  2. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/pyproject.toml +1 -1
  3. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/__init__.py +1 -1
  4. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_build/main.py +4 -1
  5. ssc_codegen-0.13.2/ssc_codegen/ast_grep_rules/py_drop_prefix_suffix_backport.yml +47 -0
  6. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/cli/ast_grep.py +1 -0
  7. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/cli/main.py +8 -1
  8. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_lxml.py +52 -45
  9. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/lua_re_compat.py +1 -1
  10. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/logs.py +2 -2
  11. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/.gitignore +0 -0
  12. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/LICENSE +0 -0
  13. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/README.md +0 -0
  14. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/_compat.py +0 -0
  15. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/__init__.py +0 -0
  16. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/base.py +0 -0
  17. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_array.py +0 -0
  18. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_cast.py +0 -0
  19. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_core.py +0 -0
  20. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_filter.py +0 -0
  21. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_selectors.py +0 -0
  22. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_string.py +0 -0
  23. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_validate.py +0 -0
  24. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_build/__init__.py +0 -0
  25. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_build/builder.py +0 -0
  26. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_build/utils.py +0 -0
  27. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_grep_rules/js_rules.yml +0 -0
  28. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/ast_grep_rules/py_rules.yml +0 -0
  29. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/cli/__init__.py +0 -0
  30. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/cli/cli_callbacks.py +0 -0
  31. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/cli/cli_utils.py +0 -0
  32. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/cli/code_callbacks.py +0 -0
  33. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/cli/consts.py +0 -0
  34. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  35. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/compiler.py +0 -0
  36. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/__init__.py +0 -0
  37. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/base.py +0 -0
  38. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/go_goquery.py +0 -0
  39. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/helpers.py +0 -0
  40. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/js_pure.py +0 -0
  41. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/lua_htmlparser.py +0 -0
  42. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_base.py +0 -0
  43. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_bs4.py +0 -0
  44. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_parsel.py +0 -0
  45. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_selectolax.py +0 -0
  46. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/__init__.py +0 -0
  47. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/go_goquery.py +0 -0
  48. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/js_pure.py +0 -0
  49. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/lua_base.py +0 -0
  50. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/lua_css_compat.py +0 -0
  51. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/py_base.py +0 -0
  52. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/document.py +0 -0
  53. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/document_utlis.py +0 -0
  54. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/json_struct.py +0 -0
  55. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/json_to_scc.py +0 -0
  56. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/pseudo_selectors.py +0 -0
  57. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/schema.py +0 -0
  58. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/selector_utils.py +0 -0
  59. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/static_checker/__init__.py +0 -0
  60. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/static_checker/base.py +0 -0
  61. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/static_checker/callbacks.py +0 -0
  62. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/str_utils.py +0 -0
  63. {ssc_codegen-0.13.1 → ssc_codegen-0.13.2}/ssc_codegen/tokens.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssc_codegen
3
- Version: 0.13.1
3
+ Version: 0.13.2
4
4
  Summary: Python-dsl code converter to html parser for web scraping
5
5
  Project-URL: Documentation, https://github.com/vypivshiy/selector_schema_codegen#readme
6
6
  Project-URL: Issues, https://github.com/vypivshiy/selector_schema_codegen/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ssc_codegen"
3
- version = "0.13.1"
3
+ version = "0.13.2"
4
4
  description = "Python-dsl code converter to html parser for web scraping "
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -7,7 +7,7 @@ from ssc_codegen.schema import ItemSchema, DictSchema, ListSchema, FlatListSchem
7
7
 
8
8
  setup_logger()
9
9
 
10
- VERSION = "0.13.1"
10
+ VERSION = "0.13.2"
11
11
 
12
12
 
13
13
  class __MISSING(object):
@@ -13,7 +13,7 @@ from ssc_codegen.schema import BaseSchema
13
13
  from ssc_codegen.static_checker import run_analyze_schema
14
14
  import logging
15
15
 
16
- LOGGER = logging.getLogger("ssc-gen")
16
+ LOGGER = logging.getLogger("ssc_gen")
17
17
 
18
18
 
19
19
  def build_ast_module_parser(
@@ -37,6 +37,9 @@ def build_ast_module_parser(
37
37
  py_module = exec_module_code(path)
38
38
  # check code configs before build AST
39
39
  schemas = extract_schemas_from_module(py_module)
40
+ if len(schemas) == 0:
41
+ LOGGER.warning(f"{path} does not contains defined schemas")
42
+
40
43
  count_errors = 0
41
44
  for schema in schemas:
42
45
  errors = run_analyze_schema(schema)
@@ -0,0 +1,47 @@
1
+ id: py_drop_prefix_backport
2
+ language: python
3
+ rule:
4
+ pattern:
5
+ context: |-
6
+ def ssc_rm_prefix($$$) -> str:$$$
7
+ fix: ''
8
+ ---
9
+ id: py_fix_new_prefix_syntax
10
+ language: python
11
+ rule:
12
+ pattern:
13
+ context: ssc_rm_prefix($V, $P)
14
+ fix: '$V.removeprefix($P)'
15
+
16
+ ---
17
+ id: py_drop_suffix_backport
18
+ language: python
19
+ rule:
20
+ pattern:
21
+ context: |-
22
+ def ssc_rm_suffix($$$) -> str:$$$
23
+ fix: ''
24
+ ---
25
+ id: py_fix_new_suffix_syntax
26
+ language: python
27
+ rule:
28
+ pattern:
29
+ context: ssc_rm_suffix($V, $P)
30
+ fix: '$V.removesuffix($P)'
31
+ ---
32
+
33
+ id: py_drop_prefix_suffix_backport
34
+ language: python
35
+ rule:
36
+ pattern:
37
+ context: |-
38
+ def ssc_rm_prefix_and_suffix($$$) -> str:$$$
39
+ fix: ''
40
+ ---
41
+ id: py_fix_new_prefix_suffix_syntax
42
+ language: python
43
+ rule:
44
+ pattern:
45
+ context: ssc_rm_prefix_and_suffix($V, $P1, $P2)
46
+ fix:
47
+ '$V.removeprefix($P1).removesuffix($P2)'
@@ -4,4 +4,5 @@ from importlib import resources
4
4
  RULES_PATH = resources.files("ssc_codegen.ast_grep_rules")
5
5
 
6
6
  PY_RULES = str(RULES_PATH.joinpath("py_rules.yml"))
7
+ PY_NEW_RM_PREFIX_SUFFIX_SYNTAX = str(RULES_PATH.joinpath("py_drop_prefix_suffix_backport.yml"))
7
8
  JS_RULES = str(RULES_PATH.joinpath("js_rules.yml"))
@@ -11,7 +11,7 @@ from ichrome.exceptions import ChromeRuntimeError
11
11
  from typer import Argument, BadParameter, Option, Typer
12
12
 
13
13
  from ssc_codegen.ast_build import build_ast_module_parser
14
- from ssc_codegen.cli.ast_grep import JS_RULES, PY_RULES
14
+ from ssc_codegen.cli.ast_grep import JS_RULES, PY_RULES, PY_NEW_RM_PREFIX_SUFFIX_SYNTAX
15
15
  from ssc_codegen.cli.cli_callbacks import cb_check_ssc_files, cb_folder_out
16
16
  from ssc_codegen.cli.cli_utils import (
17
17
  ConverterLike,
@@ -168,6 +168,9 @@ def gen_py(
168
168
  docstring: Annotated[
169
169
  bool, Option(help=HELP_DOCSTRING, is_flag=True)
170
170
  ] = True,
171
+ fmt_prefix_suffix_backport: Annotated[
172
+ bool, Option("-frps", '--fmt-remove-prefix-suffix',
173
+ help="[PY39+]: replace backport funcs to str.removeprefix(...) and str.removesuffix(...)")] = False
171
174
  ) -> None:
172
175
  converter = import_converter(f"py_{lib.value}")
173
176
  if fmt:
@@ -176,10 +179,14 @@ def gen_py(
176
179
  "ruff check {} --fix",
177
180
  f"ast-grep scan --rule {PY_RULES} -U " + "{}",
178
181
  ]
182
+ if fmt_prefix_suffix_backport:
183
+ commands.append(f"ast-grep scan --rule {PY_NEW_RM_PREFIX_SUFFIX_SYNTAX} -U " + "{}")
179
184
  fmt_cmd = create_fmt_cmd(ssc_files, prefix, suffix, out, commands)
180
185
  else:
181
186
  fmt_cmd = []
182
187
 
188
+
189
+
183
190
  generate_code(
184
191
  converter=converter,
185
192
  out=out,
@@ -67,17 +67,16 @@ CONVERTER = BasePyCodeConverter()
67
67
  def pre_init(_node: StructInitMethod) -> str:
68
68
  return (
69
69
  INDENT_METHOD
70
- + "def __init__(self, document: Union[str, etree._Element]) -> None:\n"
70
+ + "def __init__(self, document: Union[str, html.HtmlElement]) -> None:\n"
71
71
  + INDENT_METHOD_BODY
72
- + "self._document = etree.HTML(document) if isinstance(document, str) else document"
72
+ + "self._document = html.fromstring(document) if isinstance(document, str) else document"
73
73
  )
74
74
 
75
75
 
76
76
  @CONVERTER(StructPreValidateMethod.kind)
77
77
  def pre_struct_pre_validate(_node: StructPreValidateMethod) -> str:
78
78
  return (
79
- INDENT_METHOD
80
- + "def _pre_validate(self, v: Union[etree._Element, List[etree._Element]]) -> None:"
79
+ INDENT_METHOD + "def _pre_validate(self, v: html.HtmlElement) -> None:"
81
80
  )
82
81
 
83
82
 
@@ -85,7 +84,7 @@ def pre_struct_pre_validate(_node: StructPreValidateMethod) -> str:
85
84
  def pre_struct_part_doc_method(_node: StructPartDocMethod) -> str:
86
85
  return (
87
86
  INDENT_METHOD
88
- + "def _split_doc(self, v: Union[etree._Element, List[etree._Element]]) -> List[etree._Element]:"
87
+ + "def _split_doc(self, v: html.HtmlElement) -> List[html.HtmlElement]:"
89
88
  )
90
89
 
91
90
 
@@ -96,13 +95,13 @@ def pre_struct_field_method(node: StructFieldMethod) -> str:
96
95
  name = MAGIC_METHODS.get(name, name)
97
96
  return (
98
97
  INDENT_METHOD
99
- + f"def _parse_{name}(self, v: Union[etree._Element, List[etree._Element]]) -> {type_}:"
98
+ + f"def _parse_{name}(self, v: html.HtmlElement) -> {type_}:"
100
99
  )
101
100
 
102
101
 
103
102
  @CONVERTER(ModuleImports.kind)
104
103
  def pre_imports(_: ModuleImports) -> str:
105
- return IMPORTS_MIN + """from lxml import etree, html"""
104
+ return IMPORTS_MIN + """from lxml import html, etree"""
106
105
 
107
106
 
108
107
  @CONVERTER(ExprCss.kind)
@@ -112,7 +111,7 @@ def pre_css(node: ExprCss) -> str:
112
111
  )
113
112
  prv, nxt = prev_next_var(node)
114
113
  query = py_get_classvar_hook_or_value(node, "query")
115
- return indent + f"{nxt} = etree.CSSSelector({query})({prv})[0]"
114
+ return indent + f"{nxt} = {prv}.cssselect({query})[0]"
116
115
 
117
116
 
118
117
  @CONVERTER(ExprCssAll.kind)
@@ -122,7 +121,7 @@ def pre_css_all(node: ExprCssAll) -> str:
122
121
  )
123
122
  prv, nxt = prev_next_var(node)
124
123
  query = py_get_classvar_hook_or_value(node, "query")
125
- return indent + f"{nxt} = etree.CSSSelector({query})({prv})"
124
+ return indent + f"{nxt} = {prv}.cssselect({query})"
126
125
 
127
126
 
128
127
  @CONVERTER(ExprXpath.kind)
@@ -154,8 +153,9 @@ def pre_html_attr(node: ExprGetHtmlAttr) -> str:
154
153
  keys = node.kwargs["key"]
155
154
  if len(keys) == 1:
156
155
  key = keys[0]
157
- return indent + f"{nxt} = {prv}.attrib[{key!r}]"
158
- return indent + f"{nxt} = [{prv}.attrib[k] for k in {keys}]"
156
+ return indent + f"{nxt} = {prv}.get({key!r})"
157
+ key_accesses = [f"{prv}.get({k!r})" for k in keys]
158
+ return indent + f"{nxt} = [{', '.join(key_accesses)}]"
159
159
 
160
160
 
161
161
  @CONVERTER(ExprGetHtmlAttrAll.kind)
@@ -167,8 +167,8 @@ def pre_html_attr_all(node: ExprGetHtmlAttrAll) -> str:
167
167
  keys = node.kwargs["key"]
168
168
  if len(keys) == 1:
169
169
  key = keys[0]
170
- return indent + f"{nxt} = [e.attrib[{key!r}] for e in {prv}]"
171
- return indent + f"{nxt} = [e.attrib[k] for e in {prv} for k in {keys}]"
170
+ return indent + f"{nxt} = [e.get({key!r}) for e in {prv}]"
171
+ return indent + f"{nxt} = [e.get(k) for e in {prv} for k in {keys}]"
172
172
 
173
173
 
174
174
  @CONVERTER(ExprGetHtmlText.kind)
@@ -177,7 +177,7 @@ def pre_html_text(node: ExprGetHtmlText) -> str:
177
177
  INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
178
178
  )
179
179
  prv, nxt = prev_next_var(node)
180
- return indent + f"{nxt} = ''.join({prv}.itertext())"
180
+ return indent + f"{nxt} = {prv}.text_content()"
181
181
 
182
182
 
183
183
  @CONVERTER(ExprGetHtmlTextAll.kind)
@@ -186,7 +186,7 @@ def pre_html_text_all(node: ExprGetHtmlTextAll) -> str:
186
186
  INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
187
187
  )
188
188
  prv, nxt = prev_next_var(node)
189
- return indent + f"{nxt} = [text for e in {prv} for text in e.itertext()]"
189
+ return indent + f"{nxt} = [e.text_content() for e in {prv}]"
190
190
 
191
191
 
192
192
  @CONVERTER(ExprGetHtmlRaw.kind)
@@ -195,7 +195,7 @@ def pre_html_raw(node: ExprGetHtmlRaw) -> str:
195
195
  INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
196
196
  )
197
197
  prv, nxt = prev_next_var(node)
198
- return indent + f"{nxt} = etree.tostring({prv}, encoding='unicode')"
198
+ return indent + f"{nxt} = html.tostring({prv}, encoding='unicode')"
199
199
 
200
200
 
201
201
  @CONVERTER(ExprGetHtmlRawAll.kind)
@@ -206,7 +206,7 @@ def pre_html_raw_all(node: ExprGetHtmlRawAll) -> str:
206
206
  prv, nxt = prev_next_var(node)
207
207
  return (
208
208
  indent
209
- + f"{nxt} = [etree.tostring(e, encoding='unicode') for e in {prv}]"
209
+ + f"{nxt} = [html.tostring(e, encoding='unicode') for e in {prv}]"
210
210
  )
211
211
 
212
212
 
@@ -220,9 +220,9 @@ def pre_is_css(node: ExprIsCss) -> str:
220
220
  msg = py_get_classvar_hook_or_value(node, "msg")
221
221
  invert = node.kwargs["invert"]
222
222
 
223
- expr = f"etree.CSSSelector({query})({prv})"
223
+ expr = f"{prv}.cssselect({query})"
224
224
  if invert:
225
- expr = f"not ({expr})"
225
+ expr = f"not {expr}"
226
226
 
227
227
  expr = indent + f"assert {expr}, {msg}"
228
228
  if is_last_var_no_ret(node):
@@ -255,19 +255,19 @@ def pre_has_attr(node: ExprHasAttr) -> str:
255
255
  INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
256
256
  )
257
257
  prv, nxt = prev_next_var(node)
258
- key, msg = node.unpack_args()
259
258
  key = py_get_classvar_hook_or_value(node, "key")
260
259
  msg = py_get_classvar_hook_or_value(node, "msg")
261
260
  invert = node.kwargs["invert"]
262
261
 
263
- expr = f"{prv}.attrib[{key}]"
262
+ expr = f"{prv}.get({key}) is not None"
264
263
  if invert:
265
264
  expr = f"not ({expr})"
266
265
 
267
266
  expr = indent + f"assert {expr}, {msg}"
268
267
  if is_last_var_no_ret(node):
269
268
  return expr
270
- return "\n".join(expr, indent + f"{nxt} = {prv}")
269
+ # Исправлено: добавлена запятая в join
270
+ return "\n".join([expr, indent + f"{nxt} = {prv}"])
271
271
 
272
272
 
273
273
  @CONVERTER(ExprListHasAttr.kind)
@@ -280,7 +280,7 @@ def pre_list_has_attr(node: ExprListHasAttr) -> str:
280
280
  msg = py_get_classvar_hook_or_value(node, "msg")
281
281
  invert = node.kwargs["invert"]
282
282
 
283
- expr = f"all(i.attrib[{key}] for i in {prv})"
283
+ expr = f"all(i.get({key}) is not None for i in {prv})"
284
284
  if invert:
285
285
  expr = f"not ({expr})"
286
286
 
@@ -318,7 +318,7 @@ def pre_css_remove(node: ExprCssElementRemove) -> str:
318
318
 
319
319
  return (
320
320
  indent
321
- + f"[e.getparent().remove(e) for e in etree.CSSSelector({query})({prv}) if e.getparent() is not None]\n"
321
+ + f"[e.getparent().remove(e) for e in {prv}.cssselect({query}) if e.getparent() is not None]\n"
322
322
  + indent
323
323
  + f"{nxt} = {prv}"
324
324
  )
@@ -365,9 +365,9 @@ def pre_doc_filter_css(node: FilterDocCss) -> str:
365
365
  def pre_doc_filter_has_attr(node: FilterDocHasAttr) -> str:
366
366
  keys = node.kwargs["keys"]
367
367
  if len(keys) == 1:
368
- expr = f"{keys[0]!r} in e.attrib"
368
+ expr = f"e.get({keys[0]!r}) is not None"
369
369
  else:
370
- expr = f"any(i in e.attrib for i in {keys})"
370
+ expr = f"any(e.get(i) is not None for i in {keys})"
371
371
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
372
372
  return "and " + expr
373
373
  return expr
@@ -377,9 +377,9 @@ def pre_doc_filter_has_attr(node: FilterDocHasAttr) -> str:
377
377
  def pre_doc_filter_attr_eq(node: FilterDocAttrEqual) -> str:
378
378
  key, values = node.unpack_args()
379
379
  if len(values) == 1:
380
- expr = f"e.attrib[{key!r}] == {values[0]!r}"
380
+ expr = f"e.get({key!r}) == {values[0]!r}"
381
381
  else:
382
- expr = f"e.attrib[{key!r}] in {values}"
382
+ expr = f"e.get({key!r}) in {values}"
383
383
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
384
384
  return "and " + expr
385
385
  return expr
@@ -389,9 +389,9 @@ def pre_doc_filter_attr_eq(node: FilterDocAttrEqual) -> str:
389
389
  def pre_doc_filter_attr_contains(node: FilterDocAttrContains) -> str:
390
390
  key, values = node.unpack_args()
391
391
  if len(values) == 1:
392
- expr = f"{values[0]!r} in e.attrib[{key!r}]"
392
+ expr = f"{values[0]!r} in (e.get({key!r}) or '')"
393
393
  else:
394
- expr = f"any(i in e.attrib[{key!r}] for i in {values})"
394
+ expr = f"any(i in (e.get({key!r}) or '') for i in {values})"
395
395
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
396
396
  return "and " + expr
397
397
  return expr
@@ -401,10 +401,10 @@ def pre_doc_filter_attr_contains(node: FilterDocAttrContains) -> str:
401
401
  def pre_doc_filter_attr_starts(node: FilterDocAttrContains) -> str:
402
402
  key, values = node.unpack_args()
403
403
  if len(values) == 1:
404
- expr = f"e.attrib[{key!r}].startswith({values[0]!r})"
404
+ expr = f"(e.get({key!r}) or '').startswith({values[0]!r})"
405
405
  else:
406
406
  # str.startswith() arg allow tuple[str, ...]
407
- expr = f"e.attrib[{key!r}].startswith({values})"
407
+ expr = f"(e.get({key!r}) or '').startswith({values})"
408
408
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
409
409
  return "and " + expr
410
410
  return expr
@@ -414,10 +414,10 @@ def pre_doc_filter_attr_starts(node: FilterDocAttrContains) -> str:
414
414
  def pre_doc_filter_attr_ends(node: FilterDocAttrContains) -> str:
415
415
  key, values = node.unpack_args()
416
416
  if len(values) == 1:
417
- expr = f"e.attrib[{key!r}].endswith({values[0]!r})"
417
+ expr = f"(e.get({key!r}) or '').endswith({values[0]!r})"
418
418
  else:
419
419
  # str.endswith() arg allow tuple[str, ...]
420
- expr = f"e.attrib[{key!r}].endswith({values})"
420
+ expr = f"(e.get({key!r}) or '').endswith({values})"
421
421
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
422
422
  return "and " + expr
423
423
  return expr
@@ -427,11 +427,12 @@ def pre_doc_filter_attr_ends(node: FilterDocAttrContains) -> str:
427
427
  def pre_doc_filter_attr_re(node: FilterDocAttrRegex) -> str:
428
428
  key, pattern, ignore_case = node.unpack_args()
429
429
  flags = py_regex_flags(ignore_case)
430
+
431
+ attr_value = f"e.get({key!r}) or ''"
430
432
  if flags:
431
- expr = f"re.search({pattern!r}, e.attrib[{key!r}], {flags})"
433
+ expr = f"re.search({pattern!r}, {attr_value}, {flags})"
432
434
  else:
433
- expr = f"re.search({pattern!r}, e.attrib[{key!r}], ''))"
434
- expr = f"bool({expr})"
435
+ expr = f"re.search({pattern!r}, {attr_value})"
435
436
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
436
437
  return "and " + expr
437
438
  return expr
@@ -442,10 +443,12 @@ def pre_doc_filter_is_regex_text(node: FilterDocIsRegexText) -> str:
442
443
  pattern, ignore_case = node.unpack_args()
443
444
 
444
445
  flags = py_regex_flags(ignore_case)
446
+
447
+ text_content_call = "e.text_content()"
445
448
  if flags:
446
- expr = f"re.search({pattern!r}, ''.join(e.itertext()), {flags})"
449
+ expr = f"re.search({pattern!r}, {text_content_call}, {flags})"
447
450
  else:
448
- expr = f"re.search({pattern!r}, ''.join(e.itertext()))"
451
+ expr = f"re.search({pattern!r}, {text_content_call})"
449
452
  expr = f"bool({expr})"
450
453
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
451
454
  return "and " + expr
@@ -457,10 +460,11 @@ def pre_doc_filter_is_regex_raw(node: FilterDocIsRegexText) -> str:
457
460
  pattern, ignore_case = node.unpack_args()
458
461
 
459
462
  flags = py_regex_flags(ignore_case)
463
+ raw_content_call = "etree.tostring(e, encoding='unicode')"
460
464
  if flags:
461
- expr = f"re.search({pattern!r}, etree.tostring(e, encoding='unicode'), {flags})"
465
+ expr = f"re.search({pattern!r}, {raw_content_call}, {flags})"
462
466
  else:
463
- expr = f"re.search({pattern!r}, etree.tostring(e, encoding='unicode'))"
467
+ expr = f"re.search({pattern!r}, {raw_content_call})"
464
468
  expr = f"bool({expr})"
465
469
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
466
470
  return "and " + expr
@@ -470,10 +474,12 @@ def pre_doc_filter_is_regex_raw(node: FilterDocIsRegexText) -> str:
470
474
  @CONVERTER(FilterDocHasText.kind)
471
475
  def pre_doc_filter_has_text(node: FilterDocHasText) -> str:
472
476
  values = node.kwargs["values"]
477
+
478
+ text_content_call = "e.text_content()"
473
479
  if len(values) == 1:
474
- expr = f"{values[0]!r} in ''.join(e.itertext())"
480
+ expr = f"{values[0]!r} in {text_content_call}"
475
481
  else:
476
- expr = f"any(i in ''.join(e.itertext()) for i in {values})"
482
+ expr = f"any(i in {text_content_call} for i in {values})"
477
483
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
478
484
  return "and " + expr
479
485
  return expr
@@ -482,11 +488,12 @@ def pre_doc_filter_has_text(node: FilterDocHasText) -> str:
482
488
  @CONVERTER(FilterDocHasRaw.kind)
483
489
  def pre_doc_filter_has_raw(node: FilterDocHasText) -> str:
484
490
  values = node.kwargs["values"]
491
+
485
492
  if len(values) == 1:
486
- expr = f"{values[0]!r} in etree.tostring(e, encoding='unicode')"
493
+ expr = f"{values[0]!r} in html.tostring(e, encoding='unicode')"
487
494
  else:
488
495
  expr = (
489
- f"any(i in etree.tostring(e, encoding='unicode') for i in {values})"
496
+ f"any(i in html.tostring(e, encoding='unicode') for i in {values})"
490
497
  )
491
498
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
492
499
  return "and " + expr
@@ -79,7 +79,7 @@ import logging
79
79
  __all__ = ["py_regex_to_lua_pattern"]
80
80
 
81
81
 
82
- LOGGER = logging.getLogger("ssc-gen")
82
+ LOGGER = logging.getLogger("ssc_gen")
83
83
 
84
84
 
85
85
  @dataclass
@@ -26,8 +26,8 @@ def setup_logger(
26
26
  name: str = "ssc_gen",
27
27
  level: int = logging.INFO,
28
28
  colored: bool = True,
29
- fmt: str = "[%(levelname)-8s] %(name)s: %(asctime)s - %(message)s",
30
- datefmt: str = "%Y-%m-%d %H:%M:%S",
29
+ fmt: str = "[%(levelname)-8s] %(name)s: - %(message)s", # %(asctime)s
30
+ datefmt: str | None = None, # "%Y-%m-%d %H:%M:%S",
31
31
  ) -> logging.Logger:
32
32
  logger = logging.getLogger(name)
33
33
  logger.setLevel(level)
File without changes
File without changes
File without changes