Skip to content

Commit

Permalink
336 WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Utumno committed Jul 4, 2022
1 parent 93b844d commit 358c239
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 282 deletions.
8 changes: 4 additions & 4 deletions Mopy/bash/basher/ini_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ class INI_ListErrors(EnabledLink):
_help = _(u'Lists any errors in the tweak file causing it to be invalid.')

def _enable(self):
return any(map(lambda inf: inf.tweak_status() < 0,
self.iselected_infos()))
self._erroneous = [inf for inf in self.iselected_infos() if
not inf.is_default_tweak and inf.tweak_status() < 0]
return bool(self._erroneous)

def Execute(self):
"""Handle printing out the errors."""
error_text = u'\n'.join(inf.listErrors() for inf in
self.iselected_infos())
error_text = '\n'.join(inf.listErrors() for inf in self._erroneous)
copy_text_to_clipboard(error_text)
self._showLog(error_text, title=_(u'INI Tweak Errors'),
fixedFont=False)
Expand Down
109 changes: 108 additions & 1 deletion Mopy/bash/bolt.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@

# Internal
from . import exception
from .exception import AbstractError

# structure aliases, mainly introduced to reduce uses of 'pack' and 'unpack'
struct_pack = struct.pack
Expand Down Expand Up @@ -1468,7 +1469,6 @@ def pop(self,key,default=None):
class AFile(object):
"""Abstract file, supports caching - beta."""
_null_stat = (-1, None)
__slots__ = (u'_file_key', u'fsize', u'_file_mod_time')

def _stat_tuple(self): return self.abs_path.size_mtime()

Expand Down Expand Up @@ -1526,6 +1526,113 @@ def _reset_cache(self, stat_tuple, load_cache):
def __repr__(self): return f'{self.__class__.__name__}<' \
f'{self.abs_path.stail}>'

#------------------------------------------------------------------------------
class ListInfo(object):
"""Info object displayed in Wrye Bash list."""
__slots__ = ('ci_key', )
_valid_exts_re = ''
_is_filename = True
_has_digits = False

def __init__(self, fn_key):
self.fn_key = FName(fn_key)

@classmethod
def validate_filename_str(cls, name_str, allowed_exts=frozenset()):
"""Basic validation of list item name - those are usually filenames so
they should contain valid chars. We also optionally check for match
with an extension group (apart from projects and markers). Returns
a tuple - if the second element is None validation failed and the first
element is the message to show - if not the meaning varies per override
:type name_str: str"""
if not name_str:
return _(u'Empty name !'), None
char = cls._is_filename and Path.has_invalid_chars(name_str)
if char:
return _(u'%(new_name)s contains invalid character (%(char)s)') % {
u'new_name': name_str, u'char': char}, None
rePattern = cls._name_re(allowed_exts)
maPattern = rePattern.match(name_str)
if maPattern:
ma_groups = maPattern.groups(default=u'')
root = ma_groups[0]
num_str = ma_groups[1] if cls._has_digits else None
if not (root or num_str):
pass # will return the error message at the end
elif cls._has_digits: return FName(root + ma_groups[2]), num_str
else: return FName(name_str), root
return (_(u'Bad extension or file root: ') + name_str), None

@classmethod
def _name_re(cls, allowed_exts):
exts_re = fr'(\.(?:{"|".join(e[1:] for e in allowed_exts)}))' \
if allowed_exts else cls._valid_exts_re
# The reason we do the regex like this is to support names like
# foo.ess.ess.ess etc.
final_regex = '^%s(.*?)' % (r'(?=.+\.)' if exts_re else '')
if cls._has_digits: final_regex += r'(\d*)'
final_regex += f'{exts_re}$'
return re.compile(final_regex, re.I)

# Generate unique filenames when duplicating files etc
@staticmethod
def _new_name(base_name, count):
r, e = os.path.splitext(base_name)
return f'{r} ({count}){e}'

@classmethod
def unique_name(cls, name_str, check_exists=False):
base_name = name_str
unique_counter = 0
store = cls.get_store()
while (store.store_dir.join(name_str).exists() if check_exists else
name_str in store): # must wrap a FNDict
unique_counter += 1
name_str = cls._new_name(base_name, unique_counter)
return FName(name_str)

# Gui renaming stuff ------------------------------------------------------
@classmethod
def rename_area_idxs(cls, text_str, start=0, stop=None):
"""Return the selection span of item being renamed - usually to
exclude the extension."""
if cls._valid_exts_re and not start: # start == 0
return 0, len(GPath(text_str[:stop]).sbody)
return 0, len(text_str) # if selection not at start reset

@classmethod
def get_store(cls):
raise AbstractError(f'{type(cls)} does not provide a data store')

# Instance methods --------------------------------------------------------
def get_rename_paths(self, newName):
"""Return possible paths this file's renaming might affect (possibly
omitting some that do not exist)."""
return [(self.abs_path, self.get_store().store_dir.join(newName))]

def unique_key(self, new_root, ext=u'', add_copy=False):
if self.__class__._valid_exts_re and not ext:
ext = self.fn_key.fn_ext
new_name = FName(
new_root + (_(u' Copy') if add_copy else u'') + ext)
if new_name == self.fn_key: # new and old names are ci-same
return None
return self.unique_name(new_name)

def get_table_prop(self, prop, default=None): ##: optimize self.get_store().table
return self.get_store().table.getItem(self.fn_key, prop, default)

def set_table_prop(self, prop, val):
return self.get_store().table.setItem(self.fn_key, prop, val)

def __str__(self):
"""Alias for self.ci_key."""
return self.fn_key

def __repr__(self):
return f'{self.__class__.__name__}<{self.fn_key}>'

#------------------------------------------------------------------------------
class MainFunctions(object):
"""Encapsulates a set of functions and/or object instances so that they can
Expand Down

0 comments on commit 358c239

Please sign in to comment.