lua major update

This commit is contained in:
Gregory Bednov 2025-02-15 02:42:21 +03:00
commit 4a0879e81c
7 changed files with 389 additions and 224 deletions

View file

@ -1,23 +0,0 @@
name: Pylint
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')

3
.gitignore vendored
View file

@ -157,4 +157,5 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
.vscode/

View file

@ -1 +1,19 @@
1. Требуется установить Pandoc
Pandoc - универсальный конвертер документов между различными форматами и разметками.
2. Для того чтобы воспользоваться скриптом, скачайте репозиторий и введите команду:
```
pandoc --lua-filter=filter2.lua --from markdown --to docx --reference-doc=custom-reference3.docx < отчёт.md > отчёт.docx
```
*Уточнение*. Установка Lua не требуется, так как интерпретатор этого языка программирования "зашит" внутрь Pandoc, хотя сам Pandoc написан на Haskell.
Программа должна работать в Linux, macOS. По идее, она будет работать и в Windows, но важно сохранить файл .md в кодировке UTF-8.
3. Есть файл "правила оформления", он частично видоизменен, он неполный, там больший упор на то чего НЕ удалось реализовать в скрипте в автоматическом виде, но на что нужно обратить внимание (odg был создан в libreoffice draw, спокойно конвертируется в PDF).
__ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ__. Программа является всего лишь ИНСТРУМЕНТОМ, и не заменяет голову на плечах, она предоставляется в том виде, в котором она была создана, она гарантированно многого все ещё *не* умеет (например, пока не придумал способ уместить в pandoc создание титульников)
Часть первоначальной версии программы доступна в прошлых ревизиях Git и написана на Python. Текущая версия не требует установки Python в систему (и вообще никаких программ, кроме Pandoc), но была переписана на Lua, возможны регрессии функциональности.

257
filter.py
View file

@ -1,213 +1,82 @@
#!/usr/bin/env python3
# import json
# import sys
# import re
import json
import sys
import re
# body = json.loads(input())
# blocks = body['blocks']
sys.stdin.reconfigure(encoding='utf-8') # for windows
# def eprint(*args, **kwargs):
# print(*args, file=sys.stderr, **kwargs)
image, formula, table, code, section = 0, 0, 0, 0, [0, 0, 0]
E, W = "ОШИБКА: ", "ПРЕДУПРЕЖДЕНИЕ: "
SP = [{'t' : 'Space'}]
unnumbered = True
HEADERSEP = ' '
hdlis = [ 'АННОТАЦИЯ', 'ВВЕДЕНИЕ', 'ЗАКЛЮЧЕНИЕ', 'ВЫВОДЫ', 'СПИСОК-ИСПОЛЬЗОВАННЫХ-ИСТОЧНИКОВ' ]
body = json.loads(input())
blocks = body['blocks']
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
# def str2pd(str_to_pandoc):
# def intersperse(lst, item):
# result = [item] * (len(lst) * 2 - 1)
# result[0::2] = lst
# return result
# return intersperse(list(map(
# lambda x: {'t': 'Str', 'c': x},
# list(filter(lambda x: x != '', re.split('[\t\n\r ]', str_to_pandoc))))),
# {'t': 'Space'})
def str2pd(str_to_pandoc):
def intersperse(lst, item):
result = [item] * (len(lst) * 2 - 1)
result[0::2] = lst
return result
return intersperse(list(map(
lambda x: {'t': 'Str', 'c': x},
list(filter(lambda x: x != '', re.split('[\t\n\r ]', str_to_pandoc))))),
{'t': 'Space'})
# def upper_first_token(tokens):
# for token in tokens:
# if token['t'] == 'Str':
# token['c'] = token['c'][0].upper() + token['c'][1:]
# break
# return tokens
def section_inc (index):
if index <= 1:
global image, formula, table, code
image, formula, table, code = 0, 0, 0, 0
global section
section[index-1] += 1
section[index:] = [0]*len(section[index:])
# def append_reference (mb_text_block, name, section0, num):
# if mb_text_block['t'] == 'Div':
# mb_text_block = mb_text_block['c'][-1][0]
# if mb_text_block['t'] == 'Para':
# for token in mb_text_block['c'][::-1]:
# if token['t'] == 'Str':
# token['c'] = token['c'].rstrip('.?!…\t ')
# break
def section_str (index):
return str(section[0]) if index <= 1 else section_str(index-1)+"."+str(section[index-1])
def phrase_list (phrase, listtype, islast):
if listtype == 'OrderedList':
inl, start = 'в нумерованном списке ', 'большой (заглавной) '
else:
inl, start = 'в маркированном списке ', 'маленькой (строчной) '
endswith = "пункт заканчивается знаком "
ender, subender = '.', ';'
if not islast and listtype != 'OrderedList':
ender, subender = subender, ender
if phrase[0]['t'] == 'Plain':
if phrase[0]['c'][0]['c'][0].islower() and (listtype == 'OrderedList'):
phrase[0]['c'][0]['c'] = phrase[0]['c'][0]['c'][0].upper() + phrase[0]['c'][0]['c'][1:]
if phrase[0]['c'][0]['c'][0].isupper() != (listtype == 'OrderedList'):
eprint(W+inl+'текст должен начинаться с ' + start + 'буквы')
if phrase[0]['c'][-1]['c'][-1] == subender:
if islast:
eprint(W + inl + "последний " + endswith + ender,
", хотя остальных пунктах используется ", subender)
else:
eprint(W+inl+"каждый" + endswith + ender,
" , а не знаком ", subender)
elif phrase[0]['c'][-1]['c'][-1] != ender:
phrase[0]['c'][-1]['c'] += ender
return phrase
def text_ref (prev, caption, section, figure):
return str2pd(f'{caption} {section}.{figure}') + SP + prev
def delete_end_marks (tokens, chars='.?!…\t '):
for token in tokens[::-1]:
if token['t'] == 'Str':
token['c'] = token['c'].rstrip(chars)
break
return tokens
def upper_first_token(tokens):
for token in tokens:
if token['t'] == 'Str':
token['c'] = token['c'][0].upper() + token['c'][1:]
break
return tokens
def append_reference (mb_text_block, name, section0, num):
if mb_text_block['t'] == 'Div':
mb_text_block = mb_text_block['c'][-1][0]
if mb_text_block['t'] == 'Para':
delete_end_marks(mb_text_block['c'])
mb_text_block['c'] += SP + upper_first_token(str2pd(f'({name} {section0}.{num}):'))
return True
return False
# mb_text_block['c'] += [{'t' : 'Space'}] # a space after a paragraph and...
# + upper_first_token(str2pd(f'({name} {section0}.{num}):'))
# return True
# return False
#for i, b in enumerate(blocks):
i = 0
while i < len(blocks):
b = blocks[i]
# #for i, b in enumerate(blocks):
# i = 0
# image, formula, table, code, section = 0, 0, 0, 0, [0, 0, 0]
# E, W = "ОШИБКА: ", "ПРЕДУПРЕЖДЕНИЕ: "
# while i < len(blocks):
# b = blocks[i]
# btype = b['t']
btype = b['t']
if btype == 'Header':
order = b['c'][0]
refname = b['c'][1][0]
headerTokens = b['c'][2]
delete_end_marks(headerTokens)
# if btype == 'Table':
# if not append_reference(blocks[i-1], 'Таблица', section[0], table):
# eprint(E+"перед таблицей", b['c'],"нет абзаца, в который можно встроить ссылку на таблицу!")
if btype == 'Header' and order == 1:
unnumbered = (refname.strip()).upper() in hdlis
if not unnumbered:
section_inc(order)
headerTokens.insert(0, {'t': 'Str', 'c': str(section_str(order)+HEADERSEP)})
# blocks[i+1] = {'t':'Div', 'c': [["",[],[['custom-style', 'aftertable']]], [blocks[i+1]]]}
for token in headerTokens:
if token['t'] == 'Str':
token['c'] = token['c'].upper()
# if btype == 'Figure':
# if not append_reference(blocks[i-1], 'Рисунок', section[0], image):
# eprint(E+"перед рисунком", b['c'], "нет абзаца, в который можно встроить ссылку на рисунок!")
if btype == 'Header' and order != 1:
if unnumbered:
eprint(E+
"Используется заголовок 2 и ниже уровней",
b['c'][1],
"в ненумерованном разделе (или вовсе вне разделов). \
Проверьте структуру разделов.")
sys.exit(1)
# if btype == 'CodeBlock':
# if len(b['c'][0][2]) == 0:
# eprint(E+"нет подписи у листинга:\n", b['c'][1], "\nЧТОБЫ ДОБАВИТЬ ПОДПИСЬ, используйте ключ caption:\n~~~{.lang caption=\"Hello world\"}")
upper_first_token(headerTokens)
# if not('caption' in dict(b['c'][0][2])):
# eprint(E+"нет подписи у листинга:\n", b['c'][1], "\nЧТОБЫ ДОБАВИТЬ ПОДПИСЬ, используйте ключ caption:\n~~~{.lang caption=\"Hello world\"}")
section_inc(order)
headerTokens.insert(0, {'t': 'Str', 'c': str(section_str(order)+HEADERSEP)})
# code += 1
# caption = f'Листинг {section[0]}.{code} — ' + dict(b['c'][0][2])['caption']
# if not append_reference(blocks[i-1], 'Листинг', section[0], code):
# eprint(E+"перед листингом", b['c'], "нет абзаца, в который можно встроить ссылку на листинг!")
if btype == 'Para':
# if unnumbered:
# eprint(E+"Текст или картинка вне раздела \
# или в ненумерованном разделе",
# b['c'][1],
# ". Проверьте структуру разделов.")
# sys.exit(1)
# blocks.insert(i, {"t":"Div","c":[["",[],[["custom-style","Code Caption"]]], [{"t":"Para","c":str2pd(caption)}]]})
# i += 1
# blocks[i+1] = {'t':'Div', 'c': [["",[],[['custom-style', 'aftertable']]], [blocks[i+1]]]}
for bb in b['c']:
bbtype = bb['t']
if bbtype == 'Image':
image += 1
b['c'][0]['c'][1] = text_ref(b['c'][0]['c'][1], 'Рисунок', section[0], image) # TODO error if fail
delete_end_marks(blocks[i-1]['c'])
blocks[i-1]['c'] += SP + str2pd(f'(Рисунок {section[0]}.{image}):')
# i += 1
if btype == 'Table':
if unnumbered:
eprint(E+"Таблица вне раздела или в ненумерованном разделе:\n", b['c'], "\nПровертье структуру разделов.")
sys.exit(1)
table += 1
b['c'][1][1][0]['c'] = str2pd(f'Таблица {section[0]}.{table}') + SP + b['c'][1][1][0]['c']
if not append_reference(blocks[i-1], 'Таблица', section[0], table):
eprint(E+"перед таблицей", b['c'],"нет абзаца, в который можно встроить ссылку на таблицу!")
sys.exit(1)
blocks[i+1] = {'t':'Div', 'c': [["",[],[['custom-style', 'aftertable']]], [blocks[i+1]]]}
if btype == 'Figure':
if unnumbered:
eprint(E+"Рисунок вне раздела или в ненумерованном разделе:\n", b['c'], "\nПровертье структуру разделов.")
sys.exit(1)
image += 1
b['c'][1][1][0]['c'] = str2pd(f'Рисунок {section[0]}.{image}') + SP + b['c'][1][1][0]['c']
if not append_reference(blocks[i-1], 'Рисунок', section[0], image):
eprint(E+"перед рисунком", b['c'], "нет абзаца, в который можно встроить ссылку на рисунок!")
sys.exit(1)
if btype == 'CodeBlock':
if unnumbered:
eprint(E+"Листинг вне раздела или в ненумерованном разделе:\n", b['c'], "\nПровертье структуру разделов.")
sys.exit(1)
if len(b['c'][0][2]) == 0:
eprint(E+"нет подписи у листинга:\n", b['c'][1], "\nЧТОБЫ ДОБАВИТЬ ПОДПИСЬ, используйте ключ caption:\n~~~{.lang caption=\"Hello world\"}")
sys.exit(1)
if not('caption' in dict(b['c'][0][2])):
eprint(E+"нет подписи у листинга:\n", b['c'][1], "\nЧТОБЫ ДОБАВИТЬ ПОДПИСЬ, используйте ключ caption:\n~~~{.lang caption=\"Hello world\"}")
sys.exit(1)
code += 1
caption = f'Листинг {section[0]}.{code}' + dict(b['c'][0][2])['caption']
if not append_reference(blocks[i-1], 'Листинг', section[0], code):
eprint(E+"перед листингом", b['c'], "нет абзаца, в который можно встроить ссылку на листинг!")
sys.exit(1)
blocks.insert(i, {"t":"Div","c":[["",[],[["custom-style","Code Caption"]]], [{"t":"Para","c":str2pd(caption)}]]})
i += 1
blocks[i+1] = {'t':'Div', 'c': [["",[],[['custom-style', 'aftertable']]], [blocks[i+1]]]}
# eprint(blocks[i+1])
# .insert(i, {"t":"Div","c":[["",[],[["custom-style","Code Caption"]]], [{"t":"Para","c":str2pd(caption)}]]})
if btype == 'BulletList':
for j, phrase in enumerate(b['c']):
phrase = phrase_list(phrase, btype, j == (len(b['c']) - 1))
if btype == 'OrderedList':
for phrase in b['c'][1]:
phrase = phrase_list(phrase, btype, False)
i += 1
body['blocks'] = blocks
print(json.dumps(body))
# body['blocks'] = blocks
# print(json.dumps(body))

267
filter2.lua Normal file
View file

@ -0,0 +1,267 @@
IMAGE, FORMULA, TABLE, CODE = 0, 0, 0, 0
UNNUMBERED = true
local hdlis = {
['АННОТАЦИЯ'] = true,
['ВВЕДЕНИЕ'] = true,
['ЗАКЛЮЧЕНИЕ'] = true,
['ВЫВОДЫ'] = true,
['СПИСОК-ИСПОЛЬЗОВАННЫХ-ИСТОЧНИКОВ'] = true
}
local section = {0, 0, 0}
local function remove_trailing_punctuation(text)
return text:gsub("[,;%.!?]+$", "")
end
local function capitalize_first_letter(text)
local first_char = pandoc.text.sub(text, 1, 1)
local rest = pandoc.text.sub(text, 2)
return pandoc.text.upper(first_char) .. rest
end
local function is_unnumbered(header_text)
return hdlis[pandoc.text.upper(header_text)] ~= nil
end
local function get_section_number(level)
if level == 1 then
section[1] = section[1] + 1
section[2] = 0
section[3] = 0
IMAGE = 0
return tostring(section[1])
elseif level == 2 then
if section[1] == 0 then
io.stderr:write("Ошибка: Второй уровень без первого запрещен\n")
return nil
end
section[2] = section[2] + 1
section[3] = 0
return section[1] .. "." .. section[2]
elseif level >= 3 then
if section[1] == 0 or section[2] == 0 then
io.stderr:write("Ошибка: Третий уровень без первого или второго запрещен\n")
return nil
end
section[3] = section[3] + 1
return section[1] .. "." .. section[2] .. "." .. section[3]
end
end
function Header(el)
local cleaned_text = capitalize_first_letter(remove_trailing_punctuation(pandoc.utils.stringify(el.content)))
local is_unnumbered_section = is_unnumbered(cleaned_text)
UNNUMBERED = is_unnumbered_section
local level = math.min(el.level, 3) -- Принудительно ограничиваем вложенность до 3
if level > 1 and section[1] == 0 then
io.stderr:write("Ошибка: Заголовки 2 и ниже без нумерованного заголовка 1 уровня\n")
return el
end
if not is_unnumbered_section then
local section_number = get_section_number(level)
cleaned_text = section_number .. " " .. capitalize_first_letter(cleaned_text)
elseif level > 1 then
io.stderr:write("Ошибка: В ненумеруемом разделе не должно быть вложенных заголовков\n")
return el
end
el.content = pandoc.Inlines(cleaned_text)
return el
end
function Figure(el)
IMAGE = IMAGE + 1
local str = pandoc.utils.stringify(el.caption)
str = capitalize_first_letter(str)
str = remove_trailing_punctuation(str)
if UNNUMBERED then
io.stderr:write("Ошибка: Рисунок " .. str .. " в ненумерованном разделе!")
return el
end
str = "Рисунок " .. section[1] .. "." .. IMAGE .. "" .. str
el.caption = pandoc.Blocks(str)
return el
end
function Table(el)
TABLE = TABLE + 1
local str = pandoc.utils.stringify(el.caption)
str = capitalize_first_letter(str)
str = remove_trailing_punctuation(str)
if UNNUMBERED then
io.stderr:write("Ошибка: Таблица " .. str .. " в ненумерованном разделе!")
return el
end
str = "Таблица " .. section[1] .. "." .. IMAGE .. "" .. str
el.caption = pandoc.Blocks(str)
return el
end
--[[
Предполагается, что ранее в фильтре уже определены:
IMAGE, FORMULA, TABLE, CODE = 0, 0, 0, 0
UNNUMBERED, hdlis, section
функции Header, Figure, Table (обрабатывающие подписи и прочее)
Этот блок добавляет модификацию соседних блоков (предыдущего Para для ссылки,
а также оборачивание следующего блока для Table и CodeBlock).
--]]
-- Функция для приведения первой буквы строки к заглавной
function capitalize_first_letter(text)
local first = pandoc.text.sub(text, 1, 1)
local rest = pandoc.text.sub(text, 2)
return pandoc.text.upper(first) .. rest
end
-- Функция оборачивает блок в Div с кастомным стилем "aftertable"
function wrap_aftertable(block)
return pandoc.Div(block, pandoc.Attr("", {}, {["custom-style"] = "aftertable"}))
end
-- Функция, которая ищет в списке блоков блок по индексу index и, если это Para (или вложенный Para в Div),
-- удаляет завершающую пунктуацию у последнего текстового элемента и дописывает ссылку в виде:
-- " (Тип section.num):"
function append_reference_to_block(blocks, index, ref_name, section_num, num)
if index < 1 or index > #blocks then
return false
end
local blk = blocks[index]
-- Если блок Div, пробуем взять последний вложенный блок из его содержимого
if blk.t == "Div" then
if blk.c and #blk.c >= 2 and type(blk.c[2]) == "table" and #blk.c[2] > 0 then
blk = blk.c[2][#blk.c[2]]
else
return false
end
end
if blk.t ~= "Para" then
return false
end
-- Удаляем завершающую пунктуацию у последнего Str
local inlines = blk.c
for j = #inlines, 1, -1 do
if inlines[j].t == "Str" then
inlines[j].text = inlines[j].text:gsub("[%.?!…%s]+$", "")
break
end
end
-- Формируем текст ссылки, например: "(Рисунок 1.2):"
local ref_text = "(" .. ref_name .. " " .. section_num .. "." .. num .. "):"
ref_text = capitalize_first_letter(ref_text)
table.insert(inlines, pandoc.Space())
table.insert(inlines, pandoc.Str(ref_text))
return true
end
-- Главная функция фильтра, которая проходит по всем блокам документа и в нужных местах
-- модифицирует соседние блоки.
function Pandoc(doc)
local blocks = doc.blocks
local i = 1
while i <= #blocks do
local blk = blocks[i]
if blk.t == "Table" then
-- Для таблиц: добавляем ссылку в предыдущий абзац и оборачиваем следующий блок
if not append_reference_to_block(blocks, i - 1, "Таблица", section[1], TABLE) then
io.stderr:write("ОШИБКА: перед таблицей нет абзаца для ссылки\n")
end
if i + 1 <= #blocks then
blocks[i + 1] = wrap_aftertable(blocks[i + 1])
end
elseif blk.t == "Figure" then
-- Для рисунков: добавляем ссылку в предыдущий абзац
if not append_reference_to_block(blocks, i - 1, "Рисунок", section[1], IMAGE) then
io.stderr:write("ОШИБКА: перед рисунком нет абзаца для ссылки\n")
end
elseif blk.t == "CodeBlock" then
-- Для листингов: проверяем наличие подписи (caption) в атрибутах
local attr = blk.attr or {"", {}, {}}
local attributes = attr[3] or {}
local caption = attributes.caption
if not caption then
io.stderr:write("ОШИБКА: Листинг без подписи!\nИспользуйте ключ caption в CodeBlock\n")
else
CODE = CODE + 1
local ref_text = "Листинг " .. section[1] .. "." .. CODE .. "" .. caption
if not append_reference_to_block(blocks, i - 1, "Листинг", section[1], CODE) then
io.stderr:write("ОШИБКА: перед листингом нет абзаца для ссылки\n")
end
-- Вставляем блок с подписью перед текущим CodeBlock.
local caption_block = pandoc.Div(
pandoc.Para({ pandoc.Str(capitalize_first_letter(ref_text)) }),
pandoc.Attr("", {}, {["custom-style"] = "Code Caption"})
)
table.insert(blocks, i, caption_block)
i = i + 1 -- пропускаем вставленный блок
if i + 1 <= #blocks then
blocks[i + 1] = wrap_aftertable(blocks[i + 1])
end
end
end
i = i + 1
end
return pandoc.Pandoc(blocks, doc.meta)
end
function Meta(m)
if m.date == nil then
m.date = os.date("%e «%m» %Y")
end
return m
end
local function remove_trailing_punctuation_li (li)
return pandoc.Blocks(remove_trailing_punctuation(pandoc.utils.stringify(li)))
end
local function add_trailing_semicolon_li (li)
return pandoc.Blocks(pandoc.utils.stringify(li) .. ";")
end
local function add_trailing_stop_li (li)
return pandoc.Blocks(pandoc.utils.stringify(li) .. ".")
end
local function capitalize_first_letter_li (li)
return pandoc.Blocks(capitalize_first_letter(pandoc.utils.stringify(li)))
end
local function warnCapitalizedStart (li)
local str = pandoc.utils.stringify(li)
local capitalized = capitalize_first_letter(str)
if str == capitalized
then
io.stderr:write("Предупреждение: необходимо исправить вручную. В маркированном списке предложение должно начинаться со строчной (маленькой) буквы. Если текст пункта стартует с аббревиатуры, проигнорируйте данное предупреждение, иначе - исправьте вручную. Текст пункта:\n" .. str)
end
return li
end
function BulletList(el)
local li = el.content
li = pandoc.List.map(li, remove_trailing_punctuation_li)
li = pandoc.List.map(li, add_trailing_semicolon_li)
local ending = pandoc.List.at(el.content, -1)
pandoc.List.remove(li)
ending = add_trailing_stop_li(remove_trailing_punctuation_li(ending))
pandoc.List.insert(li, ending)
li = pandoc.List.map(li, warnCapitalizedStart);
el.content = li
return el
end
function OrderedList(el)
local li = el.content
li = pandoc.List.map(li, remove_trailing_punctuation_li)
li = pandoc.List.map(li, add_trailing_stop_li)
li = pandoc.List.map(li, capitalize_first_letter_li)
el.content = li
return el
end

BIN
отчёт.docx Normal file

Binary file not shown.

View file

@ -2,21 +2,54 @@
title: Методика кодирования информации
author: Студентов У.M.
teacher: Беднов Г.А.
type: Курсовая
---
# введение
# введение.
<!-- это комментарий Pandoc Markdown. Вы его не увидите -->
<!-- кстати, в заголовках первого уровня можно даже писать с маленькой буквы, но только в них -->
<!-- кстати, в заголовках первого уровня можно даже писать с маленькой буквы, но только в них. -->
<!--знаки препинания на конце удаляются -->
Привет мир.
# Практическая работа
Здесь должен быть азбац с текстом
![Тебя что, в Гугле забанили](./google.png)
# практическая работа!
## очень маленький заголовок
## подраздельчик
1. f
1. 2
1. 2
1. 4
* Ф;
* b,
Здесь обязан быть абзац с текстом, так как он предваряет картинку
![Тебя что, в Гугле забанили?](./google.png)
Формулы оно тоже *может* поддерживать, но их обязательно надо доделывать!
$$E=mc^2$$
: пример таблички
Right Left Center Default
------- ------ ---------- -------
12 12 12 12
123 123 123 123
1 1 1 1
```cpp
int main(void) {
std::cout << "Hello world";
return 0;
}
```