Commit daedc86a authored by Roman Alifanov's avatar Roman Alifanov

Add json.get, str.urlencode and Telegram echobot example

parent 8e34932a
......@@ -584,6 +584,33 @@ result = json.stringify (data)
# {"name":"Alice","age":30,"city":"NYC"}
```
**json.get — извлечение из вложенного JSON:**
```
json_str = "\{\"user\": \{\"name\": \"Bob\", \"age\": 25\}\}"
name = json.get (json_str, ".user.name") # "Bob"
age = json.get (json_str, ".user.age") # 25
# Работа с массивами
items = "\{\"items\": [1, 2, 3]\}"
first = json.get (items, ".items[0]") # 1
count = json.get (items, ".items | length") # 3
```
Использует jq под капотом — поддерживает все jq-пути.
**Пример: Telegram бот**
```
response = http.get ("{base_url}/getUpdates")
chat_id = json.get (response, ".result[0].message.chat.id")
text = json.get (response, ".result[0].message.text")
encoded = text.urlencode ()
http.get ("{base_url}/sendMessage?chat_id={chat_id}&text={encoded}")
```
**Примечание:** фигурные скобки в строках нужно экранировать как `\{` и `\}`.
### logger
......@@ -622,6 +649,9 @@ char = text.charAt (0) # H
replaced = text.replace ("World", "ContenT")
parts = text.split (", ") # массив ["Hello", "World!"]
# URL-кодирование
encoded = text.urlencode () # "Hello%2C%20World%21"
# Символы (глобальные функции)
code = ord ("A") # 65
char = chr (65) # "A"
......@@ -929,12 +959,12 @@ Error: Unknown method 'badMethod' for type 'fs'. Available: append, exists, list
Проверяются методы для:
- **Массивов**`filter`, `get`, `join`, `len`, `map`, `pop`, `push`, `set`, `shift`, `slice`
- **Словарей**`del`, `get`, `has`, `keys`, `set`
- **Строк**`charAt`, `contains`, `ends`, `index`, `len`, `lower`, `replace`, `split`, `starts`, `substr`, `trim`, `upper`
- **Строк**`charAt`, `contains`, `ends`, `index`, `len`, `lower`, `replace`, `split`, `starts`, `substr`, `trim`, `upper`, `urlencode`
- **Файловых дескрипторов**`close`, `read`, `readline`, `write`, `writeln`
- **Stdlib namespaces**:
- `fs``append`, `exists`, `list`, `mkdir`, `open`, `read`, `remove`, `write`
- `http``delete`, `get`, `post`, `put`
- `json``parse`, `stringify`
- `json``get`, `parse`, `stringify`
- `logger``debug`, `error`, `info`, `warn`
- `regex``extract`, `match`
- `args``count`, `get`
......
......@@ -199,8 +199,8 @@ try {
| **HTTP** | `http.get/post/put/delete` |
| **Filesystem** | `fs.read/write/append/exists/remove/mkdir/list`, `fs.open()` |
| **File handles** | `f.read()`, `f.readline()`, `f.write()`, `f.writeln()`, `f.close()` |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string |
| **Strings** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()` |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string, `json.get(str, path)` → extract by jq path |
| **Strings** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()`, `.urlencode()` |
| **Arrays** | `.push()`, `.pop()`, `.shift()`, `.len()`, `.get()`, `.set()`, `.join()`, `.slice()`, `.map()`, `.filter()` |
| **Dicts** | `.get()`, `.set()`, `.has()`, `.del()`, `.keys()` |
| **Regex** | `regex.match/extract` |
......@@ -338,6 +338,19 @@ FAIL failing test (1ms)
2 of 3 tests passed
```
## Examples
### Telegram Echo Bot
A simple Telegram bot that echoes messages back (`examples/telegram_echobot/`):
```bash
export TELEGRAM_BOT_TOKEN="your_token"
python3 content run examples/telegram_echobot/echobot.ct
```
Uses `json.get()` for parsing Telegram API responses and `str.urlencode()` for URL encoding.
## Documentation
- [Language Specification](LANGUAGE_SPEC.md)
......
......@@ -199,8 +199,8 @@ try {
| **HTTP** | `http.get/post/put/delete` |
| **Файловая система** | `fs.read/write/append/exists/remove/mkdir/list`, `fs.open()` |
| **Файловые дескрипторы** | `f.read()`, `f.readline()`, `f.write()`, `f.writeln()`, `f.close()` |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string |
| **Строки** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()` |
| **JSON** | `json.parse()` → dict, `json.stringify()` → string, `json.get(str, path)` → извлечь по jq-пути |
| **Строки** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()`, `.urlencode()` |
| **Массивы** | `.push()`, `.pop()`, `.shift()`, `.len()`, `.get()`, `.set()`, `.join()`, `.slice()`, `.map()`, `.filter()` |
| **Словари** | `.get()`, `.set()`, `.has()`, `.del()`, `.keys()` |
| **Regex** | `regex.match/extract` |
......@@ -338,6 +338,19 @@ FAIL падающий тест (1ms)
2 of 3 tests passed
```
## Примеры
### Telegram эхо-бот
Простой Telegram-бот, который отправляет сообщения обратно (`examples/telegram_echobot/`):
```bash
export TELEGRAM_BOT_TOKEN="your_token"
python3 content run examples/telegram_echobot/echobot.ct
```
Использует `json.get()` для парсинга ответов Telegram API и `str.urlencode()` для URL-кодирования.
## Документация
- [Спецификация языка](LANGUAGE_SPEC.md)
......
......@@ -349,7 +349,7 @@ class UsageAnalyzer:
def _check_method(self, method: str):
string_methods = {'upper', 'lower', 'trim', 'len', 'contains', 'starts',
'ends', 'index', 'replace', 'substr', 'split', 'charAt'}
'ends', 'index', 'replace', 'substr', 'split', 'charAt', 'urlencode'}
array_methods = {'push', 'pop', 'shift', 'join', 'get', 'set', 'slice', 'len'}
dict_methods = {'get', 'set', 'has', 'del', 'keys'}
......
......@@ -18,6 +18,7 @@ STR_METHODS = {
"trim": "__ct_str_trim", "contains": "__ct_str_contains", "starts": "__ct_str_starts",
"ends": "__ct_str_ends", "index": "__ct_str_index", "replace": "__ct_str_replace",
"substr": "__ct_str_substr", "split": "__ct_str_split", "charAt": "__ct_str_char_at",
"urlencode": "__ct_str_urlencode",
}
FILE_HANDLE_METHODS = {
......@@ -32,7 +33,7 @@ BUILTIN_FUNCS = {"print", "exit", "len", "range", "ngrep", "is_number", "is_empt
FS_METHODS = {"read", "write", "append", "exists", "remove", "mkdir", "list", "open"}
HTTP_METHODS = {"get", "post", "put", "delete"}
JSON_METHODS = {"parse", "stringify"}
JSON_METHODS = {"parse", "stringify", "get"}
LOGGER_METHODS = {"info", "warn", "error", "debug"}
REGEX_METHODS = {"match", "extract"}
ARGS_METHODS = {"count", "get"}
......@@ -656,6 +657,9 @@ class DispatchMixin:
dict_name = expr.arguments[0].name
if dict_name in self.dict_vars:
return f'__ct_json_stringify "{dict_name}"'
if callee.object.name == "json" and callee.member == "get":
args = [self.generate_expr(arg) for arg in expr.arguments]
return f'__ct_json_get "{args[0]}" "{args[1]}"'
args = [self.generate_expr(arg) for arg in expr.arguments]
args_str = " ".join([f'"{a}"' for a in args])
......
......@@ -330,6 +330,15 @@ class StdlibMixin:
self.emit ("}")
self.emit ()
self.emit ("__ct_json_get () {")
self.indent_level += 1
self.emit ('local __json="$1"')
self.emit ('local __path="$2"')
self.emit ('echo "$__json" | jq -r "$__path" 2>/dev/null')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_json_stringify () {")
self.indent_level += 1
self.emit ('local -n __d="$1"')
......@@ -454,6 +463,7 @@ class StdlibMixin:
self.emit ("__ct_str_upper () { __CT_RET=\"${1^^}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_lower () { __CT_RET=\"${1,,}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_char_at () { __CT_RET=\"${1:$2:1}\"; echo \"$__CT_RET\"; }")
self.emit ("__ct_str_urlencode () { __CT_RET=$(printf '%s' \"$1\" | jq -sRr @uri); echo \"$__CT_RET\"; }")
self.emit ("__ct_str_concat () { __CT_RET=\"$1$2\"; echo \"$__CT_RET\"; }")
self.emit ()
......
......@@ -516,6 +516,7 @@ class StmtMixin:
"trim": "__ct_str_trim", "contains": "__ct_str_contains", "starts": "__ct_str_starts",
"ends": "__ct_str_ends", "index": "__ct_str_index", "replace": "__ct_str_replace",
"substr": "__ct_str_substr", "split": "__ct_str_split", "charAt": "__ct_str_char_at",
"urlencode": "__ct_str_urlencode",
}
args_str = " ".join([f'"{a}"' for a in args])
......
token = shell.capture ("printenv TELEGRAM_BOT_TOKEN")
if token == "" {
print ("Set TELEGRAM_BOT_TOKEN environment variable")
exit (1)
}
base_url = "https://api.telegram.org/bot{token}"
func get_username () {
response = http.get ("{base_url}/getMe")
username = json.get (response, ".result.username")
return username
}
func get_updates (offset) {
url = "{base_url}/getUpdates?timeout=30&offset={offset}"
response = http.get (url)
return response
}
func send_message (chat_id, text) {
encoded = text.urlencode ()
url = "{base_url}/sendMessage?chat_id={chat_id}&text={encoded}"
http.get (url)
}
username = get_username ()
if username == "null" {
print ("Invalid token")
exit (1)
}
print ("Bot @{username} started")
print ("Waiting for messages...")
offset = "0"
while true {
response = get_updates (offset)
count = json.get (response, ".result | length")
if count != "0" {
i = 0
while i < count {
update_id = json.get (response, ".result[{i}].update_id")
chat_id = json.get (response, ".result[{i}].message.chat.id")
text = json.get (response, ".result[{i}].message.text")
first_name = json.get (response, ".result[{i}].message.from.first_name")
offset = update_id + 1
if text != "null" {
print ("{first_name}: {text}")
send_message (chat_id, text)
}
i = i + 1
}
}
}
......@@ -922,6 +922,87 @@ print(double(-1))
assert code != 0 or "must be" in stderr
class TestJsonGet:
def test_json_get_simple(self):
code, stdout, _ = run_ct(r'''
json_str = "\{\"name\": \"Alice\"\}"
result = json.get(json_str, ".name")
print(result)
''')
assert code == 0
assert "Alice" in stdout
def test_json_get_nested(self):
code, stdout, _ = run_ct(r'''
json_str = "\{\"user\": \{\"name\": \"Bob\", \"age\": 25\}\}"
name = json.get(json_str, ".user.name")
age = json.get(json_str, ".user.age")
print(name)
print(age)
''')
assert code == 0
assert "Bob" in stdout
assert "25" in stdout
def test_json_get_array(self):
code, stdout, _ = run_ct(r'''
json_str = "\{\"items\": [1, 2, 3]\}"
first = json.get(json_str, ".items[0]")
print(first)
''')
assert code == 0
assert "1" in stdout
def test_json_get_length(self):
code, stdout, _ = run_ct(r'''
json_str = "\{\"items\": [1, 2, 3, 4, 5]\}"
count = json.get(json_str, ".items | length")
print(count)
''')
assert code == 0
assert "5" in stdout
class TestUrlencode:
def test_urlencode_simple(self):
code, stdout, _ = run_ct('''
text = "hello world"
encoded = text.urlencode()
print(encoded)
''')
assert code == 0
assert "hello%20world" in stdout
def test_urlencode_special_chars(self):
code, stdout, _ = run_ct('''
text = "a=b&c=d"
encoded = text.urlencode()
print(encoded)
''')
assert code == 0
assert "%3D" in stdout or "%3d" in stdout
assert "%26" in stdout
def test_urlencode_cyrillic(self):
code, stdout, _ = run_ct('''
text = "привет"
encoded = text.urlencode()
print(encoded)
''')
assert code == 0
assert "%" in stdout
assert "привет" not in stdout
def test_urlencode_empty(self):
code, stdout, _ = run_ct('''
text = ""
encoded = text.urlencode()
print("result: {encoded}")
''')
assert code == 0
assert "result:" in stdout
class TestMethodValidation:
def test_unknown_array_method(self):
code, stdout, stderr = compile_ct_check('''
......@@ -956,6 +1037,7 @@ a = text.upper()
b = text.lower()
c = text.len()
d = text.trim()
e = text.urlencode()
''')
assert code == 0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment