diff --git a/config/mypy/config b/config/mypy/config new file mode 100644 index 0000000..88f2fe6 --- /dev/null +++ b/config/mypy/config @@ -0,0 +1,6 @@ +[mypy] +ignore_missing_imports = True +disallow_untyped_defs = True +disallow_untyped_calls = True +disallow_incomplete_defs = True +disallow_untyped_decorators = True diff --git a/config/scripts/mel b/config/scripts/mel index 04ed91a..693d6c2 100755 --- a/config/scripts/mel +++ b/config/scripts/mel @@ -603,6 +603,6 @@ if __name__ == "__main__": perfstep("exec") # DEBUG - sys.exit(0) for kv in sorted(perf_dict.items(), key=lambda p: p[1]): log.debug("{1:.6f} {0}".format(*kv)) + sys.exit(0) diff --git a/config/scripts/ovhcli b/config/scripts/ovhcli new file mode 100755 index 0000000..f081737 --- /dev/null +++ b/config/scripts/ovhcli @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import os +import ovh +import xdg.BaseDirectory +import urllib.request +from pprint import pprint +import json +import logging +import coloredlogs +import argparse + +coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') +log = logging.getLogger() + +debug = None + +class OvhCli(): + ROOT = "https://api.ovh.com/1.0?null" + def __init__(self): + self.cacheDir = os.path.join(xdg.BaseDirectory.xdg_cache_home, 'ovhcli') + # TODO Corner cases: links, cache dir not done, configurable cache + if not os.path.isdir(self.cacheDir): + assert not os.path.exists(self.cacheDir) + os.makedirs(self.cacheDir) + + def updateCache(self): + log.info("Downloading the API description") + rootJsonPath = os.path.join(self.cacheDir, 'root.json') + log.debug(f"{self.ROOT} -> {rootJsonPath}") + urllib.request.urlretrieve(self.ROOT, rootJsonPath) + with open(rootJsonPath, 'rt') as rootJson: + root = json.load(rootJson) + basePath = root['basePath'] + + for apiRoot in root['apis']: + fmt = 'json' + assert fmt in apiRoot['format'] + path = apiRoot['path'] + schema = apiRoot['schema'].format(format=fmt, path=path) + apiJsonPath = os.path.join(self.cacheDir, schema[1:]) + apiJsonUrl = basePath + schema + log.debug(f"{apiJsonUrl} -> {apiJsonPath}") + apiJsonPathDir = os.path.dirname(apiJsonPath) + if not os.path.isdir(apiJsonPathDir): + os.makedirs(apiJsonPathDir) + urllib.request.urlretrieve(apiJsonUrl, apiJsonPath) + + def createParser(self): + parser = argparse.ArgumentParser(description='Access the OVH API') + return parser + + +if __name__ == '__main__': + cli = OvhCli() + # cli.updateCache() + parser = cli.createParser() + args = parser.parse_args() + print(args) diff --git a/config/scripts/rmf b/config/scripts/rmf index 3a49991..099601a 100755 --- a/config/scripts/rmf +++ b/config/scripts/rmf @@ -13,7 +13,7 @@ import progressbar import logging progressbar.streams.wrap_stderr() -coloredlogs.install(level='INFO', fmt='%(levelname)s %(message)s') +coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') log = logging.getLogger() # 1) Create file list with conflict files @@ -34,12 +34,24 @@ class Table(): def __init__(self, width, height): self.width = width self.height = height - self.data = [['' ** self.height] ** self.width] + self.data = [['' for _ in range(self.height)] + for _ in range(self.width)] - def set(x, y, data): + def set(self, x, y, data): self.data[x][y] = str(data) - + def print(self): + widths = [max([len(cell) for cell in column]) for column in self.data] + for y in range(self.height): + for x in range(self.width): + cell = self.data[x][y] + l = len(cell) + width = widths[x] + if x > 0: + cell = ' | ' + cell + cell = cell + ' ' * (width - l) + print(cell, end='\t') + print() class Database(): @@ -137,7 +149,8 @@ class Database(): max_value=self.totalChecksumSize(), widgets=widgets).start() f = 0 for databaseFile in self.data.values(): - bar.update(f, dir=databaseFile.root[(len(self.directory)+1):], file=databaseFile.filename) + bar.update(f, dir=databaseFile.root[( + len(self.directory)+1):], file=databaseFile.filename) f += databaseFile.totalChecksumSize() try: databaseFile.getChecksums() @@ -148,14 +161,20 @@ class Database(): pass bar.finish() - def act(self): - pass + def printDifferences(self): + for databaseFile in self.data.values(): + print() + databaseFile.printInfos(diff=True) + + def takeAction(self, execute=False, *args, **kwargs): + for databaseFile in self.data.values(): + databaseFile.decideAction(*args, **kwargs) + databaseFile.takeAction(execute=execute) class DatabaseFile(): BLOCK_SIZE = 4096 - RELEVANT_STATS = ('st_mode', 'st_uid', 'st_gid', - 'st_size', 'st_mtime', 'st_ctime') + RELEVANT_STATS = ('st_mode', 'st_uid', 'st_gid', 'st_size', 'st_mtime') def __init__(self, root, filename): self.root = root @@ -163,6 +182,7 @@ class DatabaseFile(): self.stats = [] self.conflicts = [] self.checksums = [] + self.action = None log.debug(f"{self.root}/{self.filename} - new") def addConflict(self, conflict): @@ -190,16 +210,16 @@ class DatabaseFile(): del self.checksums[f] log.debug(f"{self.root}/{self.filename} - del: {conflict}") - def getPathFile(self, conflict): + def getPath(self, conflict): return os.path.join(self.root, conflict) - def getPathFiles(self): - return [self.getPathFile(conflict) for conflict in self.conflicts] + def getPaths(self): + return [self.getPath(conflict) for conflict in self.conflicts] def prune(self): toPrune = list() for conflict in self.conflicts: - if not os.path.isfile(self.getPathFile(conflict)): + if not os.path.isfile(self.getPath(conflict)): toPrune.append(conflict) if len(toPrune): @@ -236,7 +256,7 @@ class DatabaseFile(): def getStats(self): for f, conflict in enumerate(self.conflicts): oldStat = self.stats[f] - newStat = os.stat(self.getPathFile(conflict)) + newStat = os.stat(self.getPath(conflict)) oldChecksum = self.checksums[f] # If it's been already summed, and we have the same inode and same ctime, don't resum @@ -262,7 +282,7 @@ class DatabaseFile(): if self.checksums[f] is not None: continue self.checksums[f] = 1 - filedescs[f] = open(self.getPathFile(conflict), 'rb') + filedescs[f] = open(self.getPath(conflict), 'rb') while len(filedescs): toClose = set() @@ -285,10 +305,12 @@ class DatabaseFile(): def getFeatures(self): features = dict() + features['name'] = self.conflicts features['sum'] = self.checksums - for stat in DatabaseFile.RELEVANT_STATS: - features[stat] = [self.stats[f].__getattribute__( - stat) for f in enumerate(self.stats)] + for statName in DatabaseFile.RELEVANT_STATS: + # Rounding beause I Syncthing also rounds + features[statName] = [ + int(stat.__getattribute__(statName)) for stat in self.stats] return features def getDiffFeatures(self): @@ -299,18 +321,74 @@ class DatabaseFile(): diffFeatures[key] = vals return diffFeatures - def printInfos(self): - print(os.path.join(self.root, self.name)) + @staticmethod + def shortConflict(conflict): + match = Database.CONFLICT_PATTERN.search(conflict) + if match: + return match[0][15:] + else: + return '-' - # nf = re.sub( '', f) - # F = os.path.join(root, f) - # NF = os.path.join(root, nf) - # if os.path.exists(NF): - # print(f"'{F}' → '{NF}': file already exists") - # else: - # print(f"'{F}' → '{NF}': done") - # # os.rename(F, NF) + def printInfos(self, diff=True): + print(os.path.join(self.root, self.filename)) + if diff: + features = self.getDiffFeatures() + else: + features = self.getFeatures() + features['name'] = [DatabaseFile.shortConflict( + c) for c in self.conflicts] + table = Table(len(features), len(self.conflicts)+1) + for x, featureName in enumerate(features.keys()): + table.set(x, 0, featureName) + for x, featureName in enumerate(features.keys()): + for y in range(len(self.conflicts)): + table.set(x, y+1, features[featureName][y]) + table.print() + def decideAction(self, mostRecent=False): + # TODO More arguments for choosing + reason = "undecided" + self.action = None + if len(self.conflicts) == 1: + self.action = 0 + reason = "only file" + else: + features = self.getDiffFeatures() + if len(features) == 1: + reason = "same files" + self.action = 0 + elif 'st_mtime' in features and mostRecent: + recentTime = features['st_mtime'][0] + recentIndex = 0 + for index, time in enumerate(features['st_mtime']): + if time > recentTime: + recentTime = time + recentIndex = 0 + self.action = recentIndex + reason = "most recent" + + if self.action is None: + log.warning( + f"{self.root}/{self.filename}: skip, cause: {reason}") + else: + log.info( + f"{self.root}/{self.filename}: keep {DatabaseFile.shortConflict(self.conflicts[self.action])}, cause: {reason}") + + def takeAction(self, execute=False): + if self.action is None: + return + actionName = self.conflicts[self.action] + if actionName != self.filename: + log.debug( + f"Rename {self.getPath(actionName)} → {self.getPath(self.filename)}") + if execute: + os.rename(self.getPath(actionName), self.getPath(self.filename)) + for conflict in self.conflicts: + if conflict is actionName: + continue + log.debug(f"Delete {self.getPath(conflict)}") + if execute: + os.unlink(self.getPath(conflict)) if __name__ == "__main__": @@ -318,11 +396,16 @@ if __name__ == "__main__": description="Handle Syncthing's .sync-conflict files ") # Execution flow - parser.add_argument( - '--database', help='Database path for file informations') - parser.add_argument('directory', metavar='DIRECTORY', nargs='?', help='Directory to analyse') + parser.add_argument('-d', '--database', + help='Database path for file informations') + parser.add_argument('-r', '--most-recent', action='store_true', + help='Always keep the most recent version') + parser.add_argument('-e', '--execute', action='store_true', + help='Really apply changes') + parser.add_argument('-p', '--print', action='store_true', + help='Only print differences between files') args = parser.parse_args() @@ -362,4 +445,7 @@ if __name__ == "__main__": database.getChecksums() saveDatabase() - database.act() + if args.print: + database.printDifferences() + else: + database.takeAction(mostRecent=args.most_recent, execute=args.execute) diff --git a/config/vim/pluginconfig b/config/vim/pluginconfig.vim similarity index 54% rename from config/vim/pluginconfig rename to config/vim/pluginconfig.vim index 907872a..e41c738 100644 --- a/config/vim/pluginconfig +++ b/config/vim/pluginconfig.vim @@ -8,7 +8,7 @@ let g:ale_completion_enabled = 1 let g:ale_fixers = ['autopep8', 'shfmt', 'uncrustify', 'remove_trailing_lines', 'trim_whitespace', 'phpcbf'] let g:ale_php_phpcs_standard = '/srv/http/machines/ruleset.xml' -" For PHP, install https://pear.php.net/package/PHP_CodeSniffer +" For PHP, install https://pear.php.net/package/PHP_CodeSniffer """ UNDOTREE """ @@ -24,14 +24,15 @@ nmap :TagbarToggle set noshowmode set laststatus=2 let g:airline_powerline_fonts = 1 -" let g:airline#extensions#syntastic#enabled = 1 let g:airline#extensions#tabline#enabled = 1 let g:airline_section_a = airline#section#create(['mode']) let g:airline_section_b = airline#section#create(['branch', 'hunks']) " let g:airline_section_z = airline#section#create(['%B', '@', '%l', ':', '%c']) let g:airline_theme = 'base16_monokai' -let g:airline#extensions#ale#enabled = 1 + +let airline#extensions#languageclient#error_symbol = '✖ ' +let airline#extensions#languageclient#warning_symbol = '⚠ ' """ FZF """ @@ -51,27 +52,28 @@ let g:fzf_colors = \ 'spinner': ['fg', 'Label'], \ 'header': ['fg', 'Comment'] } -nmap gF :Files -nmap gf :GFiles -nmap gb :Buffers -nmap gL :Lines -nmap gl :BLines -nmap gT :Tags -nmap gt :BTags -nmap gm :Marks -nmap gw :Windows -nmap gh :History -nmap gH :History: -nmap gS :History/ -nmap gs :Snippets +let g:fzf_command_prefix = 'Fzf' +nmap gF :FzfFiles +nmap gf :FzfGFiles +nmap gb :FzfBuffers +nmap gL :FzfLines +nmap gl :FzfBLines +nmap gT :FzfTags +nmap gt :FzfBTags +nmap gm :FzfMarks +nmap gw :FzfWindows +nmap gh :FzfHistory +nmap gH :FzfHistory: +nmap gS :FzfHistory/ +nmap gs :FzfSnippets " TODO `gd` → go to tag matching selected word, or show a list with that " of tags pre-filtered with that word """ SUPERTAB """ -let g:SuperTabDefaultCompletionType = "" " Go down when completing -let g:SuperTabContextDefaultCompletionType = "" +" let g:SuperTabDefaultCompletionType = "" " Go down when completing +" let g:SuperTabContextDefaultCompletionType = "" """ LanguageTool """ @@ -82,3 +84,26 @@ let g:pandoc#modules#disabled = ["folding"] let g:pandoc#spell#enabled = 0 let g:pandoc#syntax#conceal#use = 0 +""" LanguageClient-neovim """ + +let g:LanguageClient_serverCommands = { + \ 'python': ['pyls'], + \ } + + +function LC_maps() + if has_key(g:LanguageClient_serverCommands, &filetype) + nnoremap K :call LanguageClient#textDocument_hover() + nnoremap gd :call LanguageClient#textDocument_definition() + nnoremap gd :call LanguageClient#textDocument_references() + nnoremap :call LanguageClient#textDocument_rename() + nnoremap :call LanguageClient#textDocument_formatting() + set completefunc=LanguageClient#complete + set omnifunc=LanguageClient#complete + endif +endfunction +autocmd FileType * call LC_maps() + +""" deoplete """ + +let g:deoplete#enable_at_startup = 1 diff --git a/config/vim/plugins b/config/vim/pluginlist.vim similarity index 75% rename from config/vim/plugins rename to config/vim/pluginlist.vim index 6b4ca8f..69cc076 100644 --- a/config/vim/plugins +++ b/config/vim/pluginlist.vim @@ -34,15 +34,24 @@ Plug 'tomtom/tcomment_vim' " Plug 'tomlion/vim-solidity' " Plug 'godlygeek/tabular' " Plug 'jrozner/vim-antlr' + +" When in f/F/t/T mode, highlight in red the characters that can be jumped to +Plug 'deris/vim-shot-f' + +" Auto-highlight one character per word for quick f/F movement +Plug 'unblevable/quick-scope' + " " Plug 'maralla/completor.vim' -" if has('nvim') -" Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' } -" else -" Plug 'Shougo/deoplete.nvim' -" Plug 'roxma/nvim-yarp' -" Plug 'roxma/vim-hug-neovim-rpc' -" endif +" +" Auto-completion +if has('nvim') + Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' } +else + Plug 'Shougo/deoplete.nvim' + Plug 'roxma/nvim-yarp' + Plug 'roxma/vim-hug-neovim-rpc' +endif " Plug 'zchee/deoplete-jedi' " Plug 'python-mode/python-mode', { 'branch': 'develop' } @@ -54,7 +63,13 @@ Plug 'terryma/vim-smooth-scroll' Plug 'vim-pandoc/vim-pandoc' Plug 'vim-pandoc/vim-pandoc-syntax' Plug 'idanarye/vim-vebugger' -Plug 'w0rp/ale' + +" Language Server Procotol client +Plug 'autozimu/LanguageClient-neovim', { + \ 'branch': 'next', + \ 'do': 'bash install.sh', + \ } + " Automatically closes brackets, quotes and parentheses Plug 'jiangmiao/auto-pairs' diff --git a/config/vim/vimconfig b/config/vim/vimconfig.vim similarity index 98% rename from config/vim/vimconfig rename to config/vim/vimconfig.vim index 97078df..1ac3679 100644 --- a/config/vim/vimconfig +++ b/config/vim/vimconfig.vim @@ -40,6 +40,7 @@ set splitbelow " Turn off relativenumber only for insert mode. if has('nvim') + set relativenumber augroup every autocmd! au InsertEnter * set norelativenumber diff --git a/config/vim/vimrc b/config/vim/vimrc index 4dcfe5e..ae07883 100644 --- a/config/vim/vimrc +++ b/config/vim/vimrc @@ -7,7 +7,7 @@ filetype on set runtimepath+=~/.config/vim,~/.cache/vim set viminfo+=n~/.cache/vim/viminfo -source ~/.config/vim/plugins -source ~/.config/vim/pluginconfig -source ~/.config/vim/vimconfig +source ~/.config/vim/pluginlist.vim +source ~/.config/vim/pluginconfig.vim +source ~/.config/vim/vimconfig.vim