code2logic 1.0.40__tar.gz → 1.0.41__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 (60) hide show
  1. {code2logic-1.0.40 → code2logic-1.0.41}/PKG-INFO +1 -1
  2. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/__init__.py +1 -1
  3. code2logic-1.0.41/code2logic/base_generator.py +6 -0
  4. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/benchmarks/common.py +38 -16
  5. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/benchmarks/runner.py +27 -2
  6. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/parsers.py +12 -12
  7. {code2logic-1.0.40 → code2logic-1.0.41}/pyproject.toml +1 -1
  8. code2logic-1.0.40/code2logic/llm_clients_new.py +0 -44
  9. {code2logic-1.0.40 → code2logic-1.0.41}/LICENSE +0 -0
  10. {code2logic-1.0.40 → code2logic-1.0.41}/README.md +0 -0
  11. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/__main__.py +0 -0
  12. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/adaptive.py +0 -0
  13. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/analyzer.py +0 -0
  14. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/base.py +0 -0
  15. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/benchmark.py +0 -0
  16. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/benchmarks/__init__.py +0 -0
  17. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/benchmarks/results.py +0 -0
  18. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/chunked_reproduction.py +0 -0
  19. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/cli.py +0 -0
  20. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/code_review.py +0 -0
  21. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/config.py +0 -0
  22. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/core/__init__.py +0 -0
  23. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/dependency.py +0 -0
  24. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/errors.py +0 -0
  25. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/file_formats.py +0 -0
  26. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/formats/__init__.py +0 -0
  27. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/function_logic.py +0 -0
  28. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/generators.py +0 -0
  29. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/gherkin.py +0 -0
  30. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/integrations/__init__.py +0 -0
  31. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/intent.py +0 -0
  32. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/llm/__init__.py +0 -0
  33. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/llm.py +0 -0
  34. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/llm_clients.py +0 -0
  35. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/llm_profiler.py +0 -0
  36. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/logicml.py +0 -0
  37. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/markdown_format.py +0 -0
  38. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/mcp_server.py +0 -0
  39. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/metrics.py +0 -0
  40. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/models.py +0 -0
  41. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/project_comparison.md +0 -0
  42. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/project_reproducer.py +0 -0
  43. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/prompts.py +0 -0
  44. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/py.typed +0 -0
  45. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/quality.py +0 -0
  46. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/refactor.py +0 -0
  47. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/reproducer.py +0 -0
  48. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/reproduction.py +0 -0
  49. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/schemas/__init__.py +0 -0
  50. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/schemas/json_schema.py +0 -0
  51. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/schemas/logicml_schema.py +0 -0
  52. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/schemas/markdown_schema.py +0 -0
  53. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/schemas/yaml_schema.py +0 -0
  54. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/shared_utils.py +0 -0
  55. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/similarity.py +0 -0
  56. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/terminal.py +0 -0
  57. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/tools/__init__.py +0 -0
  58. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/toon_format.py +0 -0
  59. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/universal.py +0 -0
  60. {code2logic-1.0.40 → code2logic-1.0.41}/code2logic/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2logic
3
- Version: 1.0.40
3
+ Version: 1.0.41
4
4
  Summary: Code2Logic - Source code to logical representation converter for LLM analysis, featuring Tree-sitter parsing, dependency graph analysis, and multi-language support.
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -18,7 +18,7 @@ Example:
18
18
  >>> print(output)
19
19
  """
20
20
 
21
- __version__ = "1.0.40"
21
+ __version__ = "1.0.41"
22
22
  __author__ = "Softreck"
23
23
  __email__ = "info@softreck.dev"
24
24
  __license__ = "MIT"
@@ -0,0 +1,6 @@
1
+ from typing import Protocol, Any
2
+ from .models import ProjectInfo
3
+
4
+ class ProjectGenerator(Protocol):
5
+ def generate(self, project: ProjectInfo, **kwargs: Any) -> Any:
6
+ ...
@@ -189,14 +189,21 @@ Name the test class Test<ClassName> or TestFunctions."""
189
189
 
190
190
  def get_token_reproduction_prompt(spec: str, fmt: str, file_name: str, language: str = "python") -> str:
191
191
  format_hints = {
192
- "json": "Parse the JSON structure and implement all classes and functions.",
193
- "json_compact": "Parse the compact JSON and implement all elements.",
192
+ "json": "Parse the JSON structure and implement all classes and functions with exact signatures.",
193
+ "json_compact": "Parse the compact JSON and implement all elements with exact signatures.",
194
194
  "yaml": "Parse the YAML structure and implement all classes and functions with exact signatures.",
195
- "gherkin": "Implement scenarios as SIMPLE, MINIMAL code. NO over-engineering. Keep code short and direct.",
196
- "markdown": "Parse embedded Gherkin (behaviors) and YAML (structures).",
195
+ "gherkin": """Parse Gherkin/BDD scenarios and implement them as working code:
196
+ - Each Feature maps to a class or module
197
+ - Each Scenario maps to a function
198
+ - Given/When/Then steps describe the logic flow
199
+ - Implement actual logic, not just stubs
200
+ Focus on the described behavior and implement it directly.""",
201
+ "markdown": "Parse embedded Gherkin (behaviors) and YAML (structures). Implement all described classes and functions.",
197
202
  "logicml": """Parse LogicML and generate VALID code:
198
203
  - 'sig:' lines describe function signatures (translate to the target language)
199
204
  - 'type: re-export' means this module primarily re-exports symbols
205
+ - 'attrs:' = instance attributes to set in constructor
206
+ - 'bases:' = parent classes to inherit from
200
207
  CRITICAL: Ensure valid syntax - balanced brackets, proper indentation, no undefined variables.""",
201
208
  "toon": """Parse TOON (Token-Oriented Object Notation) format carefully:
202
209
 
@@ -216,20 +223,32 @@ DECORATORS:
216
223
  - 'decorators: @staticmethod|@cache' = multiple decorators
217
224
 
218
225
  CRITICAL: Use imports[], function_docs, and exact signatures to reproduce code accurately.""",
226
+ "csv": """Parse the CSV table where each row describes a code element:
227
+ - Columns: path, type (class/method/function), name, signature, language, intent, category, domain, imports
228
+ - 'method' rows belong to the class in the preceding 'class' row
229
+ - Implement all elements with the exact signatures shown
230
+ Generate complete code with all classes, methods, and functions.""",
231
+ "function.toon": """Parse the function-logic TOON format:
232
+ - 'modules[N]{path,lang,items}:' lists files
233
+ - 'function_details:' contains per-module function listings
234
+ - Each function has: line number, name, signature, description
235
+ - 'ClassName.method_name' = method of that class
236
+ - 'cc:N' after name = cyclomatic complexity
237
+ Implement all listed functions with matching signatures and described behavior.""",
238
+ }
219
239
 
220
- "function.toon": """Parse function-logic TOON carefully (function/method index):
221
-
222
- STRUCTURE:
223
- - 'modules[N]{path,lang,items}:' module index
224
- - 'function_details:' per-module tables
225
-
226
- CRITICAL:
227
- - Use the tabular rows (line,name,sig,does,decorators,calls,raises)
228
- - Reconstruct the full module code even if class bodies are not explicitly described
229
- - Preserve exact function signatures from 'sig'""",
240
+ # Language-specific guidance appended to prompt
241
+ lang_hints = {
242
+ "javascript": "Use ES6+ syntax (const/let, arrow functions, classes). Use module.exports or export.",
243
+ "typescript": "Use TypeScript syntax with interfaces, type annotations, and export statements.",
244
+ "go": "Use proper Go syntax: package declaration, func receivers for methods, error returns.",
245
+ "rust": "Use proper Rust syntax: impl blocks for methods, pub fn, Result/Option types, ownership.",
246
+ "java": "Use proper Java syntax: public class, access modifiers, typed parameters, semicolons.",
247
+ "csharp": "Use proper C# syntax: namespaces, access modifiers, typed parameters, semicolons.",
248
+ "sql": "Use standard SQL: CREATE TABLE/VIEW/FUNCTION, proper column types, constraints.",
230
249
  }
231
250
 
232
- max_spec = 5000
251
+ max_spec = 8000
233
252
  spec_truncated = spec[:max_spec] if len(spec) > max_spec else spec
234
253
 
235
254
  language_norm = (language or "python").strip().lower()
@@ -245,8 +264,11 @@ CRITICAL:
245
264
  }
246
265
  lang_label = lang_label_map.get(language_norm, language_norm)
247
266
 
267
+ lang_hint = lang_hints.get(language_norm, '')
268
+ lang_hint_line = f"\n{lang_hint}" if lang_hint else ''
269
+
248
270
  prompt = f"""Generate {lang_label} code from this {fmt.upper()} specification.
249
- {format_hints.get(fmt, '')}
271
+ {format_hints.get(fmt, '')}{lang_hint_line}
250
272
 
251
273
  {spec_truncated}
252
274
 
@@ -217,12 +217,37 @@ class BenchmarkRunner:
217
217
  classes: List[str] = []
218
218
  functions: List[str] = []
219
219
 
220
- # Common patterns
220
+ # Common patterns across formats
221
221
  classes.extend(re.findall(r"\bclass\s+([A-Za-z_][A-Za-z0-9_]*)", spec))
222
- classes.extend(re.findall(r"^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*$", spec, re.MULTILINE))
222
+ classes.extend(re.findall(r'"name":\s*"([A-Z][A-Za-z0-9_]*)"', spec))
223
223
  functions.extend(re.findall(r"\bdef\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(", spec))
224
224
  functions.extend(re.findall(r"\bFunction:\s*([A-Za-z_][A-Za-z0-9_]*)", spec))
225
225
  functions.extend(re.findall(r"\bScenario:\s*([A-Za-z_][A-Za-z0-9_]*)", spec))
226
+ # TOON format: function lines like " 42,func_name,(params)"
227
+ functions.extend(re.findall(r'^\s+\d+,([A-Za-z_][A-Za-z0-9_]*),', spec, re.MULTILINE))
228
+ # TOON class.method: " 42,ClassName.method_name,"
229
+ for cm in re.findall(r'^\s+\d+,([A-Z][A-Za-z0-9_]*)\.([a-z_][A-Za-z0-9_]*),', spec, re.MULTILINE):
230
+ classes.append(cm[0])
231
+ functions.append(cm[1])
232
+ # YAML/JSON: "n: ClassName" or name patterns
233
+ classes.extend(re.findall(r'(?:^|\s)n:\s*([A-Z][A-Za-z0-9_]*)', spec, re.MULTILINE))
234
+ # CSV format: path,type,name columns
235
+ for row_m in re.findall(r',class,([A-Z][A-Za-z0-9_]*),', spec):
236
+ classes.append(row_m)
237
+ for row_m in re.findall(r',(?:function|method),([a-z_][A-Za-z0-9_]*),', spec):
238
+ functions.append(row_m)
239
+ # Gherkin: Feature/Scenario names
240
+ functions.extend(re.findall(r'Scenario(?:\s+Outline)?:\s*(?:Test\s+)?([A-Za-z_][A-Za-z0-9_]*)', spec))
241
+ # Non-Python: func/fn/function declarations
242
+ functions.extend(re.findall(r"\bfunc\s+([A-Za-z_][A-Za-z0-9_]*)", spec))
243
+ functions.extend(re.findall(r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)", spec))
244
+ functions.extend(re.findall(r"\bfunction\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(", spec))
245
+ # Java/C# style: "public static Type methodName("
246
+ functions.extend(re.findall(r"(?:public|private)\s+(?:static\s+)?\w+\s+([a-z][A-Za-z0-9_]*)\s*\(", spec))
247
+ # Interfaces/structs/enums
248
+ classes.extend(re.findall(r"\binterface\s+([A-Za-z_][A-Za-z0-9_]*)", spec))
249
+ classes.extend(re.findall(r"\bstruct\s+([A-Za-z_][A-Za-z0-9_]*)", spec))
250
+ classes.extend(re.findall(r"\benum\s+([A-Za-z_][A-Za-z0-9_]*)", spec))
226
251
 
227
252
  # Deduplicate while preserving order
228
253
  def uniq(items: List[str]) -> List[str]:
@@ -2212,30 +2212,30 @@ class UniversalParser:
2212
2212
  for m in re.finditer(r'^import\s+([^;]+);', content, re.MULTILINE):
2213
2213
  imports.append(m.group(1).strip())
2214
2214
 
2215
- for m in re.finditer(r'^(?:public\s+)?(?:abstract\s+)?class\s+(\w+)', content, re.MULTILINE):
2215
+ for m in re.finditer(r'^\s*(?:public\s+)?(?:abstract\s+)?class\s+(\w+)', content, re.MULTILINE):
2216
2216
  name = m.group(1)
2217
2217
  classes.append(ClassInfo(name=name, is_abstract='abstract' in m.group(0)))
2218
2218
  exports.append(name)
2219
2219
 
2220
- for m in re.finditer(r'^(?:public\s+)?interface\s+(\w+)', content, re.MULTILINE):
2220
+ for m in re.finditer(r'^\s*(?:public\s+)?interface\s+(\w+)', content, re.MULTILINE):
2221
2221
  name = m.group(1)
2222
2222
  classes.append(ClassInfo(name=name, is_interface=True))
2223
2223
  exports.append(name)
2224
2224
  types.append(TypeInfo(name=name, kind='interface', definition=''))
2225
2225
 
2226
- for m in re.finditer(r'^(?:public\s+)?enum\s+(\w+)', content, re.MULTILINE):
2226
+ for m in re.finditer(r'^\s*(?:public\s+)?enum\s+(\w+)', content, re.MULTILINE):
2227
2227
  name = m.group(1)
2228
2228
  types.append(TypeInfo(name=name, kind='enum', definition=''))
2229
2229
  exports.append(name)
2230
2230
 
2231
- for m in re.finditer(r'^(?:public\s+)?record\s+(\w+)\s*\(', content, re.MULTILINE):
2231
+ for m in re.finditer(r'^\s*(?:public\s+)?record\s+(\w+)\s*\(', content, re.MULTILINE):
2232
2232
  name = m.group(1)
2233
2233
  classes.append(ClassInfo(name=name))
2234
2234
  types.append(TypeInfo(name=name, kind='record', definition=''))
2235
2235
  exports.append(name)
2236
2236
 
2237
- # Very rough method detection (only top-level class members are not tracked here)
2238
- for m in re.finditer(r'^(?:public|protected|private)\s+(?:static\s+)?([\w<>\[\]]+)\s+(\w+)\s*\(([^)]*)\)\s*\{', content, re.MULTILINE):
2237
+ # Method detection allow indented declarations inside classes
2238
+ for m in re.finditer(r'^\s*(?:@\w+\s+)*(?:public|protected|private)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?([\w<>\[\],\s]+?)\s+(\w+)\s*\(([^)]*)\)\s*(?:throws\s+[\w,\s]+)?\s*\{', content, re.MULTILINE):
2239
2239
  ret_type = m.group(1)
2240
2240
  name = m.group(2)
2241
2241
  params = [p.strip() for p in (m.group(3) or '').split(',') if p.strip()][:8]
@@ -2260,7 +2260,7 @@ class UniversalParser:
2260
2260
  is_private=False,
2261
2261
  ))
2262
2262
 
2263
- for m in re.finditer(r'^(?:public\s+)?static\s+final\s+[\w<>\[\]]+\s+([A-Z][A-Z0-9_]*)\b', content, re.MULTILINE):
2263
+ for m in re.finditer(r'^\s*(?:public\s+)?static\s+final\s+[\w<>\[\]]+\s+([A-Z][A-Z0-9_]*)\b', content, re.MULTILINE):
2264
2264
  constants.append(m.group(1))
2265
2265
 
2266
2266
  lines = content.split('\n')
@@ -2290,24 +2290,24 @@ class UniversalParser:
2290
2290
  for m in re.finditer(r'^using\s+([^;]+);', content, re.MULTILINE):
2291
2291
  imports.append(m.group(1).strip())
2292
2292
 
2293
- for m in re.finditer(r'^(?:public\s+)?interface\s+(I\w+)', content, re.MULTILINE):
2293
+ for m in re.finditer(r'^\s*(?:public\s+)?interface\s+(I\w+)', content, re.MULTILINE):
2294
2294
  name = m.group(1)
2295
2295
  classes.append(ClassInfo(name=name, is_interface=True))
2296
2296
  exports.append(name)
2297
2297
  types.append(TypeInfo(name=name, kind='interface', definition=''))
2298
2298
 
2299
- for m in re.finditer(r'^(?:public\s+)?(?:abstract\s+)?class\s+(\w+)', content, re.MULTILINE):
2299
+ for m in re.finditer(r'^\s*(?:public\s+)?(?:abstract\s+)?class\s+(\w+)', content, re.MULTILINE):
2300
2300
  name = m.group(1)
2301
2301
  classes.append(ClassInfo(name=name, is_abstract='abstract' in m.group(0)))
2302
2302
  exports.append(name)
2303
2303
 
2304
- for m in re.finditer(r'^(?:public\s+)?record\s+(\w+)', content, re.MULTILINE):
2304
+ for m in re.finditer(r'^\s*(?:public\s+)?record\s+(\w+)', content, re.MULTILINE):
2305
2305
  name = m.group(1)
2306
2306
  classes.append(ClassInfo(name=name))
2307
2307
  exports.append(name)
2308
2308
  types.append(TypeInfo(name=name, kind='record', definition=''))
2309
2309
 
2310
- for m in re.finditer(r'^(?:public|private|protected|internal)\s+(?:static\s+)?([\w<>\[\]?]+)\s+(\w+)\s*\(([^)]*)\)\s*\{', content, re.MULTILINE):
2310
+ for m in re.finditer(r'^\s*(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:override\s+)?(?:virtual\s+)?([\w<>\[\]?]+)\s+(\w+)\s*\(([^)]*)\)\s*\{', content, re.MULTILINE):
2311
2311
  ret_type = m.group(1)
2312
2312
  name = m.group(2)
2313
2313
  params = [p.strip() for p in (m.group(3) or '').split(',') if p.strip()][:8]
@@ -2332,7 +2332,7 @@ class UniversalParser:
2332
2332
  is_private=False,
2333
2333
  ))
2334
2334
 
2335
- for m in re.finditer(r'^(?:public\s+)?const\s+[\w<>\[\]]+\s+([A-Z][A-Z0-9_]*)\b', content, re.MULTILINE):
2335
+ for m in re.finditer(r'^\s*(?:public\s+)?const\s+[\w<>\[\]]+\s+([A-Z][A-Z0-9_]*)\b', content, re.MULTILINE):
2336
2336
  constants.append(m.group(1))
2337
2337
 
2338
2338
  lines = content.split('\n')
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "code2logic"
7
- version = "1.0.40"
7
+ version = "1.0.41"
8
8
  description = "Code2Logic - Source code to logical representation converter for LLM analysis, featuring Tree-sitter parsing, dependency graph analysis, and multi-language support."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -1,44 +0,0 @@
1
- from .llm_clients import (
2
- DEFAULT_MODELS,
3
- DEFAULT_PROVIDER_PRIORITIES,
4
- RECOMMENDED_MODELS,
5
- BaseLLMClient,
6
- LiteLLMClient,
7
- LLMConfig,
8
- LLMManager,
9
- OllamaLocalClient,
10
- OpenRouterClient,
11
- get_client,
12
- get_provider_model,
13
- list_available_providers,
14
- )
15
-
16
-
17
- def get_provider_priorities_from_litellm() -> dict[str, int]:
18
- try:
19
- from lolm import get_provider_priorities_from_litellm as _get
20
- except ImportError:
21
- return {}
22
-
23
- try:
24
- raw = _get() or {}
25
- return {str(k): int(v) for k, v in raw.items()}
26
- except Exception:
27
- return {}
28
-
29
- __all__ = [
30
- 'DEFAULT_MODELS',
31
- 'DEFAULT_PROVIDER_PRIORITIES',
32
- 'RECOMMENDED_MODELS',
33
- 'BaseLLMClient',
34
- 'LiteLLMClient',
35
- 'LLMConfig',
36
- 'LLMManager',
37
- 'OpenRouterClient',
38
- 'OllamaLocalClient',
39
- 'get_client',
40
- 'get_provider_model',
41
- 'get_provider_priorities_from_litellm',
42
- 'list_available_providers',
43
- ]
44
-
File without changes
File without changes