Commit d4881a09 authored by Roman Alifanov's avatar Roman Alifanov

rewrite FileBackend

parent 76aaecd3
from gi.repository import Gio, GLib from gi.repository import Gio, GLib
import json import os, re
import yaml
import os
from configparser import ConfigParser
class Backend: class Backend:
...@@ -58,155 +55,134 @@ class GSettingsBackend(Backend): ...@@ -58,155 +55,134 @@ class GSettingsBackend(Backend):
class FileBackend(Backend): class FileBackend(Backend):
def __init__(self, params=None): def __init__(self, params=None):
super().__init__(params) super().__init__(params)
self.file_path = os.path.expanduser(self.params.get('file_path')) self.file_path = os.path.expanduser(params.get('file_path'))
self.encoding = self.params.get('encoding', 'utf-8') self.file_path = os.path.expandvars(self.file_path)
self.file_type = self._get_file_type()
self.lines = []
def _get_file_type(self): self.vars = {}
_, ext = os.path.splitext(self.file_path) self._parse_file()
ext = ext.lower()
if ext == '.json': def _parse_file(self):
return 'json' if os.path.exists(self.file_path):
elif ext in ['.yaml', '.yml']: with open(self.file_path, 'r') as f:
return 'yaml' self.lines = f.readlines()
elif ext == '.ini':
return 'ini' for line_num, line in enumerate(self.lines):
elif ext in ['.sh', '.conf']: self._parse_line(line_num, line)
return 'text'
else: def _parse_line(self, line_num, line):
return 'text' line = line.rstrip('\n')
parsed = {
'raw': line,
'active': True,
'var_name': None,
'value': None,
'comment': '',
'style': {}
}
def _read_file(self): if re.match(r'^\s*#', line):
try: parsed['active'] = False
with open(self.file_path, 'r', encoding=self.encoding) as file: line = re.sub(r'^\s*#', '', line, count=1)
if self.file_type == 'json':
return json.load(file)
elif self.file_type == 'yaml':
return yaml.safe_load(file)
elif self.file_type == 'ini':
config = ConfigParser()
config.read_file(file)
return config
elif self.file_type == 'text':
return self._parse_text_config(file)
else:
raise ValueError(f"Unsupported file type: {self.file_type}")
except Exception as e:
print(f"[ERROR] Ошибка при чтении файла {self.file_path}: {e}")
return None
def _write_file(self, data): var_match = re.match(
try: r'^\s*([A-Za-z_][A-Za-z0-9_]*)\s*([=])\s*(.*?)(\s*(#.*)?)$',
with open(self.file_path, 'r+', encoding=self.encoding) as file: line
if self.file_type == 'json': )
file.seek(0)
json.dump(data, file, indent=4)
elif self.file_type == 'yaml':
file.seek(0)
yaml.dump(data, file, default_flow_style=False)
elif self.file_type == 'ini':
file.seek(0)
config = ConfigParser()
for section, values in data.items():
config[section] = values
config.write(file)
elif self.file_type == 'text':
self._write_text_config(file, data)
else:
raise ValueError(f"Unsupported file type: {self.file_type}")
except Exception as e:
print(f"[ERROR] Ошибка при записи в файл {self.file_path}: {e}")
def _parse_text_config(self, file):
config = {}
for line in file:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
config[key.strip()] = value.strip()
return config
def _write_text_config(self, file, data):
existing_lines = file.readlines()
style = self._detect_text_style(existing_lines)
file.seek(0)
file.truncate()
for line in existing_lines:
if not line.strip() or line.startswith('#'):
file.write(line)
elif '=' in line:
key, _ = line.split('=', 1)
if key.strip() in data:
if style == 'space_around':
file.write(f"{key.strip()} = {data[key.strip()]}\n")
elif style == 'no_space':
file.write(f"{key.strip()}={data[key.strip()]}\n")
else:
file.write(f"{key.strip()} = {data[key.strip()]}\n")
else:
file.write(line)
for key, value in data.items():
if not any(key.strip() == line.split('=', 1)[0].strip() for line in existing_lines):
file.write(f"{key} = {value}\n")
def _detect_text_style(self, lines):
for line in lines:
line = line.strip()
if '=' in line:
if line.startswith(' ') and line.endswith(' '):
return 'space_around'
elif line.find('=') == len(line.split('=')[0]):
return 'no_space'
return 'space_around'
def get_value(self, key, gtype): if var_match:
data = self._read_file() parsed['var_name'] = var_match.group(1)
if data is None: parsed['value'] = self._parse_value(var_match.group(3))
return None parsed['comment'] = var_match.group(4) or ''
if self.file_type in ['json', 'yaml']: value_part = var_match.group(3)
return data.get(key, None) parsed['style'] = {
elif self.file_type == 'ini': 'space_before': ' ' if ' ' in var_match.group(0).split('=')[0][-1:] else '',
section, key_name = key.split('.', 1) 'space_after': ' ' if ' ' in var_match.group(0).split('=')[1][:1] else '',
if section in data:
return data[section].get(key_name, None)
elif self.file_type == 'text':
return data.get(key, None)
return None
def get_range(self, key, gtype): 'quote': self._detect_quote(var_match.group(3)),
data = self._read_file() 'commented': not parsed['active']
if data is None: }
return None
if parsed['var_name'] not in self.vars:
self.vars[parsed['var_name']] = []
self.vars[parsed['var_name']].append((line_num, parsed))
@staticmethod
def _parse_value(value_str):
value_str = value_str.strip()
for quote in ['"', "'"]:
if value_str.startswith(quote) and value_str.endswith(quote):
return value_str[1:-1]
return value_str
@staticmethod
def _detect_quote(value_str):
value_str = value_str.strip()
if value_str.startswith('"') and value_str.endswith('"'):
return '"'
if value_str.startswith("'") and value_str.endswith("'"):
return "'"
return ''
def _get_style_template(self):
if not self.vars:
return {
'space_before': '',
'space_after': ' ',
'quote': '"',
'commented': False
}
last_var = next(reversed(self.vars.values()))[-1][1]
return last_var['style']
@staticmethod
def _build_line(key, value, style):
quote = style['quote']
value_str = f"{quote}{value}{quote}" if quote else str(value)
return (
f"{key}{style['space_before']}="
f"{style['space_after']}{value_str}"
)
def _save_file(self):
with open(self.file_path, 'w') as f:
f.writelines(self.lines)
def get_value(self, key, gtype):
entries = self.vars.get(key, [])
for entry in reversed(entries):
return entry[1]['value']
if self.file_type in ['json', 'yaml']:
if isinstance(data.get(key), list):
return (min(data[key]), max(data[key]))
elif self.file_type == 'ini':
pass
return None return None
def set_value(self, key, value, gtype): def set_value(self, key, value, gtype):
data = self._read_file() entries = self.vars.get(key, [])
if data is None: style = self._get_style_template()
return
if entries:
if self.file_type in ['json', 'yaml']: line_num, last_entry = entries[-1]
data[key] = value style = last_entry['style']
elif self.file_type == 'ini': line = self._build_line(key, value, style)
section, key_name = key.split('.', 1)
if section not in data: self.lines[line_num] = line + '\n'
data[section] = {}
data[section][key_name] = value if last_entry['style']['commented']:
elif self.file_type == 'text': self.lines[line_num] = self.lines[line_num].lstrip('#')
data[key] = value else:
line = self._build_line(key, value, style)
self._write_file(data) self.lines.append(line + '\n')
self._save_file()
class BackendFactory: class BackendFactory:
def __init__(self): def __init__(self):
......
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