refatoração do configs

This commit is contained in:
Lúcio Carvalho Almeida 2025-12-24 16:24:11 -03:00
parent 499c02b202
commit 4daa28e012
2 changed files with 143 additions and 126 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.txt
*.ini

247
microw.py
View File

@ -2,8 +2,10 @@ import sys
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
import codecs import codecs
import re
from textwrap import wrap
MAN = """ MAN_DESCRIPTION = """
NOME NOME
microw - convert data to MicroSIP accounts microw - convert data to MicroSIP accounts
@ -14,37 +16,9 @@ DESCRIÇÂO
Utilitário para conversão de dados tabulares (CSV, TXT) em arquivos de Utilitário para conversão de dados tabulares (CSV, TXT) em arquivos de
configuração (.ini) para o softphone MicroSIP e variantes. configuração (.ini) para o softphone MicroSIP e variantes.
Feito por Lúcio Carvalho Almeida, Free Software. Feito por Lúcio Carvalho Almeida, Free Software."""
ARGUMENTOS
--format <string> Define a ordem das colunas no arquivo de entrada.
Use nomes de variáveis (ex: ramal, password) ou
'_' para ignorar uma coluna específica.
Padrão: "ramal label"
Variáveis especiais: 'name', 'label', 'password',
'server'
--input <path> Caminho do arquivo de origem dos dados.
Padrão: "./input.txt"
--output <path> Caminho onde o arquivo .ini será gerado.
Padrão: "./output.ini"
--delimiter <sep> Caractere separador de colunas (aceita '\\t', ' ').
Padrão: ","
--label-pattern <q> Template para customizar o nome de exibição.
Substitui nomes de variáveis pelos seus valores.
Ex: "ramal - label (setor)"
--add-ghost Se presente, adiciona uma conta de 'Desconectado'
como o primeiro perfil da lista.
--set-template <t> Fornece o caminho para o arquivo <t> que será
usado no lugar de ACCOUNT_TEMPLATE
--read-encoding <ienc> Define a codificação de leitura
MAN_FOOTER = """
EXEMPLOS EXEMPLOS
1. Formato padrão com separador de ponto e vírgula: 1. Formato padrão com separador de ponto e vírgula:
python3 microw.py --delimiter ";" python3 microw.py --delimiter ";"
@ -96,102 +70,142 @@ password=1234
authID=0000 authID=0000
''' '''
class SchemaValue(Enum): class Flags(Enum):
Argument = 1 format = "format"
Bool = 2 input = "input"
add_ghost = "add-ghost"
delimiter = "delimiter"
output = "output"
label_pattern = "label-pattern"
help = "help"
set_template = "set-template"
read_encoding = "read-encoding"
write_encoding = "write-encoding"
sort_by = "sort-by"
sort = "sort"
@classmethod
def from_str(cls, name: str):
normalized_flag_name = name.replace("-", "_")
if not normalized_flag_name in cls.__members__:
error_string = f"O argumento '--{name}' não corresponde a uma flag válida."
raise ValueError(error_string)
return cls[normalized_flag_name]
def to_str(self):
return self._value_
# Quantidade de argumentos esperados
class FlagSchema(Enum):
NoArgument = 0
Argument = 1
# Os métodos dessa classe recebem instancias de Flags
class Config: class Config:
def __init__(self): def __init__(self):
self.config = { self.flags = {}
"format": { self.define_flag(Flags.format,FlagSchema.Argument, "ramal label", """Define a ordem das colunas no arquivo de entrada. Use nomes de variáveis (ex: ramal, password) ou '_' para ignorar uma coluna específica.""")
"default": "ramal label", self.define_flag(Flags.input, FlagSchema.Argument, "./input.txt", """Caminho do arquivo de origem dos dados.""")
"schema": SchemaValue.Argument, self.define_flag(Flags.delimiter, FlagSchema.Argument, ",", """Define qual string sera considerada como seprador das colunas de cada linha do input.""")
"value": None self.define_flag(Flags.add_ghost, FlagSchema.NoArgument, False, """Se presente, adiciona uma conta de 'Desconectado' como o primeiro perfil da lista.""")
}, self.define_flag(Flags.output, FlagSchema.Argument, "./output.ini", """Caminho onde o arquivo .ini será gerado.""")
"input": { self.define_flag(Flags.label_pattern, FlagSchema.Argument, "label", """Template para customizar o nome de exibição. Substitui nomes de variáveis pelos seus valores.""")
"default": "./input.txt", self.define_flag(Flags.help, FlagSchema.NoArgument, False, """Exibe o manual.""")
"schema": SchemaValue.Argument, self.define_flag(Flags.set_template, FlagSchema.Argument, None, """Forcene o caminho para um arquivo que servira como template.""")
"value": None self.define_flag(Flags.read_encoding, FlagSchema.Argument, "utf-8", "Codificação do arquivo lido por '--input'")
}, self.define_flag(Flags.write_encoding, FlagSchema.Argument, "utf-8", "Codificação do arquivos gerados.")
"add-ghost": { self.define_flag(Flags.sort, FlagSchema.NoArgument, True, """Ordena as contas no arquivo final.""")
"default": False, self.define_flag(Flags.sort_by, FlagSchema.Argument, "ramal", """Define qual a coluna usada para ordenação.""")
"schema": SchemaValue.Bool,
"value": None
},
"delimiter": {
"default": ",",
"schema": SchemaValue.Argument,
"value": None
},
"output": {
"default": "./output.ini",
"schema": SchemaValue.Argument,
"value": None
},
"label-pattern": {
"default": "label",
"schema": SchemaValue.Argument,
"value": None
},
"help": {
"default": None,
"schema": SchemaValue.Bool,
"value": None
},
"set-template": {
"default": None,
"schema": SchemaValue.Argument,
"value": None
}
}
def _validate_setting(self, setting): def generate_flags_man(self):
if not setting in self.config: res = [MAN_DESCRIPTION]
raise ValueError(f"'{setting}' is not a valid option.") for flag in Flags:
res.append(self.flag_man(flag))
def get(self, setting): res.append(MAN_FOOTER)
self._validate_setting(setting)
return self.config[setting]["value"] or self.config[setting]["default"]
def set(self, setting, value): return "\n".join(res)
self._validate_setting(setting)
self.config[setting]["value"] = value
def schema(self, setting): def flag_man(self, flag: Flags):
self._validate_setting(setting) ident = " " * 5
return self.config[setting]["schema"]
config = Config() lines = wrap(self.flags[flag]["man"], 60)
lines[0] = (f"--{flag.to_str()}{ident}"[0:len(ident)] + lines[0])
def main(): for i in range(len(lines)-1):
# Faz o parse manual das flags e argumentos lines[i+1] = ident + lines[i+1]
args = sys.argv[1:]
return "\n".join(lines)
def load_args(self, args: list[str]):
while len(args): while len(args):
argument = args.pop(0) argument = args.pop(0)
if argument[:2] == "--": if argument[:2] == "--":
argument = argument[2:] argument = argument[2:]
if argument == "help": flag = Flags.from_str(argument)
print(MAN)
if self.schema(flag) == FlagSchema.Argument:
if len(args) == 0:
msg_error = f"Flag '--{argument}' exige um argumento."
raise ValueError(msg_error)
self.set(flag, codecs.decode(args.pop(0), "unicode_escape"))
else:
self.set(flag, not self.getDefault(flag))
def define_flag(self, flag: Flags, schema: FlagSchema, default, man: str):
self.flags[flag] = {
"schema": schema,
"default": default,
"man": man
}
def _validate_setting(self, setting: Flags):
if not isinstance(setting, Flags):
raise ValueError(f"'{setting}' is not a valid flag.")
return setting.value
def get(self, setting):
self._validate_setting(setting)
return self.flags[setting].get("value", self.flags[setting]["default"])
def getDefault(self, setting):
self._validate_setting(setting)
return self.flags[setting]["default"]
def set(self, setting, value):
self._validate_setting(setting)
self.flags[setting]["value"] = value
def schema(self, setting):
self._validate_setting(setting)
return self.flags[setting]["schema"]
def main():
config = Config()
config.load_args(sys.argv[1:])
if config.get(Flags.help):
print(config.generate_flags_man())
return return
if config.schema(argument) == SchemaValue.Argument: output_file = Path(config.get(Flags.output))
config.set(argument, codecs.decode(args.pop(0), "unicode_escape")) input_file = Path(config.get(Flags.input))
else: if not input_file.exists():
config.set(argument, not config.get(argument)) error_msg = f"Arquivo de input especificado '{input_file.name}' não encontrado."
raise ValueError(error_msg)
output_file = Path(config.get("output")) input_lines = [line.strip() for line in input_file.open("r", encoding=config.get(Flags.read_encoding)).readlines()]
input_file = Path(config.get("input"))
input_lines = [line.strip() for line in input_file.open("r", encoding="utf-8").readlines()]
accounts_settings = [] accounts_settings = []
format_vars = config.get("format").split(" ") format_vars = config.get(Flags.format).split(" ")
label_pattern = config.get("label-pattern") label_pattern = config.get(Flags.label_pattern)
for line in input_lines: for line in input_lines:
if not line: continue if not line: continue
data = [field.strip() for field in line.split(config.get("delimiter"))] data = [field.strip() for field in line.split(config.get(Flags.delimiter))]
account_dict = {} account_dict = {}
# Mapeia os dados ignorando o caractere '_' # Mapeia os dados ignorando o caractere '_'
@ -201,28 +215,30 @@ def main():
account_dict[var_name] = data[i] account_dict[var_name] = data[i]
# Customização do $label # Customização do $label
if label_pattern: formated_pattern = label_pattern
pattern_parts = label_pattern.split(" ") for pattern_part in re.finditer(r"[a-zA-Z]+", label_pattern):
pattern = pattern_part.group()
if pattern in format_vars:
formated_pattern = formated_pattern.replace(pattern, data[format_vars.index(pattern)])
# Se a parte for uma variável conhecida, substitui pelo valor
new_label = " ".join([account_dict.get(name, name) for name in pattern_parts])
account_dict["label"] = new_label
account_dict["label"] = formated_pattern
accounts_settings.append(account_dict) accounts_settings.append(account_dict)
if config.get(Flags.sort) : accounts_settings.sort(key=lambda account : account[config.get(Flags.sort_by)])
result = "" result = ""
if config.get("add-ghost"): if config.get(Flags.add_ghost):
result += GHOST_TEMPLATE result += GHOST_TEMPLATE
current_account_template = ACCOUNT_TEMPLATE current_account_template = ACCOUNT_TEMPLATE
if not config.get("set-template") is None: if not config.get(Flags.set_template) is None:
current_account_template = Path(config.get("set-template")).read_text(encoding="utf-8") current_account_template = Path(config.get(Flags.set_template)).read_text(encoding=config.get(Flags.read_encoding))
for account in accounts_settings: for account in accounts_settings:
new_entry = current_account_template new_entry = current_account_template
# Substitui todas as variáveis encontradas
for var_name, value in account.items(): for var_name, value in account.items():
new_entry = new_entry.replace("$" + var_name, str(value)) new_entry = new_entry.replace("$" + var_name, str(value))
@ -230,13 +246,12 @@ def main():
result = result.strip() result = result.strip()
# Numeração sequencial das contas [Account1], [Account2]...
id = 1 id = 1
while "Account_" in result: while "Account_" in result:
result = result.replace("Account_", f"Account{id}", 1) result = result.replace("Account_", f"Account{id}", 1)
id += 1 id += 1
output_file.write_text(result, encoding="utf-8") output_file.write_text(result, encoding=config.get(Flags.write_encoding))
print(f"Sucesso: {id-1} contas criadas em '{output_file}'.") print(f"Sucesso: {id-1} contas criadas em '{output_file}'.")
main() main()