Browse Source

apps: added tlogg to the monorepo

pull/384/head
Parodi, Eugenio 🌶 12 months ago
parent
commit
18c0f7efa0
  1. 11
      .vscode/launch.json
  2. 35
      apps/tlogg/Makefile
  3. 56
      apps/tlogg/README.md
  4. 46
      apps/tlogg/pyproject.toml
  5. 27
      apps/tlogg/tlogg/__init__.py
  6. 28
      apps/tlogg/tlogg/__main__.py
  7. 28
      apps/tlogg/tlogg/app/__init__.py
  8. 64
      apps/tlogg/tlogg/app/about.py
  9. 81
      apps/tlogg/tlogg/app/cfg.py
  10. 123
      apps/tlogg/tlogg/app/filetree.py
  11. 307
      apps/tlogg/tlogg/app/fileviewer.py
  12. 36
      apps/tlogg/tlogg/app/glbl.py
  13. 164
      apps/tlogg/tlogg/app/highlighters.py
  14. 106
      apps/tlogg/tlogg/app/loggwidget.py
  15. 206
      apps/tlogg/tlogg/app/main.py
  16. 177
      apps/tlogg/tlogg/app/notepad.py
  17. 76
      apps/tlogg/tlogg/app/options.py
  18. 180
      apps/tlogg/tlogg/app/predefinedfilters.py
  19. 82
      apps/tlogg/tlogg/helper.py
  20. 41
      apps/tlogg/tlogg/plugin.py
  21. 127
      apps/tlogg/tlogg/plugins/fileexplorer.py
  22. 56
      apps/tlogg/tlogg/plugins/jsonnviewer.py
  23. 33
      apps/tlogg/tlogg/plugins/testplugin.py
  24. 36
      apps/tlogg/tlogg/plugins/testplugin1.py
  25. 50
      apps/tlogg/tlogg/proxy.py
  26. 70
      apps/tlogg/tools/create_log.py
  27. 39
      apps/tlogg/tools/import.filters.from.klogg.py
  28. 30
      apps/tlogg/tools/test.config/colors.yaml
  29. 3
      apps/tlogg/tools/test.config/options.yaml
  30. 2
      apps/ttkode/pyproject.toml
  31. 6
      apps/ttkode/ttkode/app/cfg.py

11
.vscode/launch.json vendored

@ -116,6 +116,17 @@
"PYTHONPATH": "./apps/ttkode"
}
},
{
"name": "py Debug: Module tlogg",
"type": "debugpy",
"request": "launch",
"module": "tlogg",
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "./apps/tlogg"
}
},
{
"name": "Python: Demo",
"type": "debugpy",

35
apps/tlogg/Makefile

@ -0,0 +1,35 @@
.PHONY: doc runGittk runDemo build deploy buildTest deployTest
.venv:
python3 -m venv .venv
. .venv/bin/activate ; \
pip install -r docs/requirements.txt
# Regen requirements;
# pip freeze > docs/requirements.txt
build: .venv
. .venv/bin/activate ; \
rm -rf dist ; \
tools/prepareBuild.sh release ; \
cd tmp ; \
python3 -m build
buildTest: .venv
. .venv/bin/activate ; \
rm -rf dist ; \
tools/prepareBuild.sh test ; \
cd tmp ; \
python3 -m build ;
deployTest: .venv
. .venv/bin/activate ; \
python3 -m twine upload --repository testpypi tmp/dist/* --verbose
deploy: .venv
. .venv/bin/activate ; \
python3 -m twine upload tmp/dist/* --repository tlogg --verbose
#test: .venv
# . .venv/bin/activate ; \
# flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude .venv,build,tmp ; \
# pytest demo/demo.py

56
apps/tlogg/README.md

@ -0,0 +1,56 @@
![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux)
![Usage](https://img.shields.io/badge/Usage-Terminal%20User%20Interface-yellow)
![Python](https://img.shields.io/badge/Python-v3.8%5E-green?logo=python)
![tlogg_version](https://img.shields.io/github/v/tag/ceccopierangiolieugenio/tlogg?label=version)
[![pypi_version](https://img.shields.io/pypi/v/tlogg?label=pypi)](https://pypi.org/project/tlogg)
[![pypi_version](https://img.shields.io/twitter/follow/Pier95886803?style=social&logo=twitter)](https://twitter.com/hashtag/pyTermTk?src=hashtag_click&f=live)
# tlogg
A fast, advanced [text-based](https://en.wikipedia.org/wiki/Text-based_user_interface) log explorer written in [pyTermTk](https://github.com/ceccopierangiolieugenio/pyTermTk), inspired by [glogg - the fast, smart log explorer](https://github.com/nickbnf/glogg) and [klogg - Faster log explorer](https://klogg.filimonov.dev)(fork of glogg)
[![screenshot](https://raw.githubusercontent.com/ceccopierangiolieugenio/binaryRepo/master/tlogg/screenshot.003.png)](https://pypi.org/project/tlogg)
## Features
- Search Panel
- Highlight
- Bookmarks
- Shiny ASCII Red Peppers
[![screenshot](https://raw.githubusercontent.com/ceccopierangiolieugenio/binaryRepo/master/tlogg/demo.001.gif)](https://pypi.org/project/tlogg)
- _Draggable_ **Tiling tabs**
[screenshot](https://github.com/ceccopierangiolieugenio/tlogg/assets/8876552/b3db13d9-48b4-485e-bc19-d655021479b6)
# Install from [pypi](https://pypi.org/project/tlogg)
```bash
pip install tlogg
```
## Enable the system Clipboard
[pyTermTk](https://github.com/ceccopierangiolieugenio/pyTermTk) automatically support the system clipboard through [pyperclip](https://pypi.org/project/pyperclip/)
```bash
pip install pyperclip
```
# QuickRun
```bash
$ tlogg -h
usage: tlogg [-h] [-c C] filename [filename ...]
positional arguments:
filename the filename/s
optional arguments:
-h, --help show this help message and exit
-c C config folder (default: "/home/user/.config/tlogg")
```
# Test
### Clone
```bash
git clone https://github.com/ceccopierangiolieugenio/tlogg.git
cd tlogg
```
### Run
```
python3 -m tlogg <File/s>
```

46
apps/tlogg/pyproject.toml

@ -0,0 +1,46 @@
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "tlogg"
dynamic = ["version"]
readme = {file = "README.md", content-type = "text/markdown"}
authors = [
{name = "Eugenio Parodi", email = "ceccopierangiolieugenio@googlemail.com"},
]
description = "A fast, advanced log explorer"
requires-python = ">=3.9"
license = {text = "MIT"}
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Topic :: Terminals",
"Topic :: Software Development :: User Interfaces",
]
dependencies = [
'pyTermTk>=0.41.17-a.0',
'appdirs',
'copykitten',
'pyyaml'
]
[project.urls]
Homepage = "https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/tlogg"
Repository = "https://github.com/ceccopierangiolieugenio/pyTermTk.git"
Issues = "https://github.com/ceccopierangiolieugenio/pyTermTk/issues"
Changelog = "https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/apps/tlogg/CHANGELOG.md"
[project.scripts]
tlogg = "tlogg.__main__:main"
[tool.setuptools]
packages = ["tlogg", "tlogg.app", "tlogg.plugins"]
[tool.setuptools.dynamic]
version = {attr = "tlogg.__version__"}

27
apps/tlogg/tlogg/__init__.py

@ -0,0 +1,27 @@
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__version__:str = '0.2.7-a.3'
from .plugin import TloggPlugin
from .proxy import tloggProxy, TloggViewerProxy
from .helper import *

28
apps/tlogg/tlogg/__main__.py

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .app import main
if __name__ == '__main__':
main()

28
apps/tlogg/tlogg/app/__init__.py

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .cfg import *
from .glbl import *
from .main import *

64
apps/tlogg/tlogg/app/about.py

@ -0,0 +1,64 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['About']
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.color import TTkColor
from TermTk import TTkAbout, TTkWindow
from .cfg import TloggCfg
class About(TTkAbout):
tlogg = [
" __ ___ ",
"/\ \__/\_ \ ",
"\ \ ,_\//\ \ ___ __ __ ",
" \ \ \/ \ \ \ / __`\ /'_ `\ /'_ `\ ",
" \ \ \_ \_\ \_/\ \L\ \/\ \L\ \/\ \L\ \ ",
" \ \__\/\____\ \____/\ \____ \ \____ \ ",
" \/__/\/____/\/___/ \/___L\ \/___L\ \\",
" /\____/ /\____/",
" \_/__/ \_/__/ "]
__slots__=('_image')
def __init__(self, *args, **kwargs):
TTkAbout.__init__(self, *args, **kwargs)
self._name = kwargs.get('name' , 'About' )
self.setTitle('[PierCecco Cecco] Eugenio Parodi proudly presents...')
self.resize(56,16)
def paintEvent(self, canvas):
c = [0xFF,0xFF,0xFF]
for y, line in enumerate(About.tlogg):
canvas.drawText(pos=(13,3+y),text=line, color=TTkColor.fg(f'#{c[0]:02X}{c[1]:02X}{c[2]:02X}'))
c[2]-=0x18
c[0]-=0x08
canvas.drawText(pos=(26,4),text=f" Version: {TloggCfg.version}", color=TTkColor.fg('#AAAAFF'))
canvas.drawText(pos=(14,11),text=f"Powered By, pyTermTk")
canvas.drawText(pos=(2,13),text=f"https://github.com/ceccopierangiolieugenio/tlogg", color=TTkColor.fg('#44FFFF'))
canvas.drawText(pos=(2,14),text=f"https://github.com/ceccopierangiolieugenio/pyTermTk", color=TTkColor.fg('#44FFFF'))
TTkWindow.paintEvent(self, canvas)

81
apps/tlogg/tlogg/app/cfg.py

@ -0,0 +1,81 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TloggCfg']
import os
import yaml
from .. import __version__
class TloggCfg:
version=__version__
name="tlogg"
cfgVersion = '1.0'
pathCfg="."
colors=[]
filters=[]
options={}
searches=[]
maxsearches=200
@staticmethod
def save(searches=True, filters=True, colors=True, options=True):
os.makedirs(TloggCfg.pathCfg, exist_ok=True)
colorsPath = os.path.join(TloggCfg.pathCfg,'colors.yaml')
filtersPath = os.path.join(TloggCfg.pathCfg,'filters.yaml')
optionsPath = os.path.join(TloggCfg.pathCfg,'options.yaml')
searchesPath = os.path.join(TloggCfg.pathCfg,'searches.yaml')
def writeCfg(path, cfg):
fullCfg = {
'version':TloggCfg.cfgVersion,
'cfg':cfg }
with open(path, 'w') as f:
yaml.dump(fullCfg, f, sort_keys=False, default_flow_style=False)
if colors: writeCfg(colorsPath, TloggCfg.colors)
if filters: writeCfg(filtersPath, TloggCfg.filters)
if options: writeCfg(optionsPath, TloggCfg.options)
if searches: writeCfg(searchesPath, TloggCfg.searches)
@staticmethod
def load():
colorsPath = os.path.join(TloggCfg.pathCfg,'colors.yaml')
filtersPath = os.path.join(TloggCfg.pathCfg,'filters.yaml')
optionsPath = os.path.join(TloggCfg.pathCfg,'options.yaml')
searchesPath = os.path.join(TloggCfg.pathCfg,'searches.yaml')
if os.path.exists(colorsPath):
with open(colorsPath) as f:
TloggCfg.colors = yaml.load(f, Loader=yaml.SafeLoader)['cfg']
if os.path.exists(filtersPath):
with open(filtersPath) as f:
TloggCfg.filters = yaml.load(f, Loader=yaml.SafeLoader)['cfg']
if os.path.exists(optionsPath):
with open(optionsPath) as f:
TloggCfg.options = yaml.load(f, Loader=yaml.SafeLoader)['cfg']
if os.path.exists(searchesPath):
with open(searchesPath) as f:
TloggCfg.searches = yaml.load(f, Loader=yaml.SafeLoader)['cfg']

123
apps/tlogg/tlogg/app/filetree.py

@ -0,0 +1,123 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2022 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this soself._ftware and associated documentation files (the "Soself._ftware"), to deal
# in the Soself._ftware without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Soself._ftware, and to permit persons to whom the Soself._ftware is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Soself._ftware.
#
# THE SOself._FTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOself._FTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOself._FTWARE.
import os
from TermTk import TTkK
from TermTk import TTkWidget, TTkCheckbox, TTkButton, TTkSpacer, TTkFileTree, TTkFileTreeWidgetItem
from TermTk import TTkVBoxLayout, TTkHBoxLayout
from TermTk import pyTTkSlot, pyTTkSignal
from . import TloggCfg
class FileTree(TTkWidget):
__slots__ = (
'_recentPath', '_recentPathId',
'_controlsLayout',
'_ftCbFollow', '_ftBtnPrev', '_ftBtnNext', '_ftBtnUp', '_fileTree',
# Forwarded Methods
'fileActivated')
def __init__(self, *args, **kwargs):
TTkWidget.__init__(self, *args, **kwargs)
self._name = kwargs.get('name' , 'FileTree' )
self.setLayout(TTkVBoxLayout())
self._controlsLayout = TTkHBoxLayout()
self.layout().addItem(self._controlsLayout)
self._ftCbFollow = TTkCheckbox(text="Follow", maxWidth=9, checked=False)
self._ftBtnPrev = TTkButton(text="<",maxWidth=3, enabled=False)
self._ftBtnNext = TTkButton(text=">",maxWidth=3, enabled=False)
self._ftBtnUp = TTkButton(text="^",maxWidth=3, enabled=True)
self._controlsLayout.addWidget(self._ftCbFollow)
self._controlsLayout.addWidget(TTkSpacer())
self._controlsLayout.addWidget(self._ftBtnPrev)
self._controlsLayout.addWidget(self._ftBtnNext)
self._controlsLayout.addWidget(self._ftBtnUp)
self._fileTree = TTkFileTree(parent=self, path=".")
self._recentPath = []
self._recentPathId = -1
self._addFolder(os.path.abspath('.'))
self._ftBtnPrev.clicked.connect(self._openPrev)
self._ftBtnNext.clicked.connect(self._openNext)
self._ftBtnUp.clicked.connect(self._openUp)
self._fileTree.folderActivated.connect(self._openFolder)
# Forward Functions
self.fileActivated = self._fileTree.fileActivated
def follow(self, path):
if self._ftCbFollow.checkState() == TTkK.Checked:
if os.path.isfile(path):
path, _ = os.path.split(path)
self._fileTree.openPath(path)
def _addFolder(self, dir):
if not self._recentPath or dir != self._recentPath[-1]:
self._recentPath.append(dir)
self._recentPathId = len(self._recentPath)-1
if self._recentPathId:
self._ftBtnPrev.setEnabled()
self._ftBtnNext.setDisabled()
@pyTTkSlot(TTkFileTreeWidgetItem)
def _openFolder(self, dir):
self._fileTree.openPath(dir.path())
self._addFolder(dir.path())
@pyTTkSlot()
def _openPrev(self):
if self._recentPathId<=0 or self._recentPathId>=len(self._recentPath):
self._ftBtnPrev.setDisabled()
return
self._recentPathId -= 1
self._fileTree.openPath(self._recentPath[self._recentPathId])
if self._recentPathId<=0:
self._ftBtnPrev.setDisabled()
self._ftBtnNext.setEnabled()
@pyTTkSlot()
def _openNext(self):
if self._recentPathId<0 or self._recentPathId>=len(self._recentPath)-1:
self._ftBtnNext.setDisabled()
return
self._recentPathId += 1
self._fileTree.openPath(self._recentPath[self._recentPathId])
if self._recentPathId>=len(self._recentPath)-1:
self._ftBtnNext.setDisabled()
self._ftBtnPrev.setEnabled()
@pyTTkSlot()
def _openUp(self):
path = os.path.abspath(self._fileTree.getOpenPath())
path, e = os.path.split(path)
if e:
self._addFolder(path)
self._fileTree.openPath(path)

307
apps/tlogg/tlogg/app/fileviewer.py

@ -0,0 +1,307 @@
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['FileViewer','FileViewerSearch','FileViewerArea']
import TermTk as ttk
from tlogg import TloggHelper, tloggProxy
from . import TloggCfg
class FileViewer(ttk.TTkAbstractScrollView):
__slots__ = (
'_fileBuffer', '_indexesMark', '_indexesSearched',
'_selected', '_indexing', '_searchRe',
'_selection', '_pressed'
# Signals
'selected', 'marked')
def __init__(self, *args, **kwargs):
self._indexesMark = []
self._indexesSearched = []
self._indexing = None
self._selected = -1
self._selection = None
self._pressed = False
self._searchRe = ""
# Signals
self.selected = ttk.pyTTkSignal(int)
self.marked = ttk.pyTTkSignal(list)
self._fileBuffer = kwargs.get('filebuffer')
super().__init__(*args, **kwargs)
self.viewChanged.connect(self._viewChangedHandler)
self.setFocusPolicy(ttk.TTkK.ClickFocus)
@ttk.pyTTkSlot()
def _viewChangedHandler(self):
self.update()
def _copy(self):
clipboard = ttk.TTkClipboard()
lines = ttk.TTkString().join([
self.getLine(i) for i in range(min(self._selection),max(self._selection)+1) ])
clipboard.setText(lines)
@ttk.pyTTkSlot(float)
def fileIndexing(self, percentage):
self._indexing = percentage
self.viewChanged.emit()
@ttk.pyTTkSlot()
def fileIndexed(self):
self._indexing = None
self.viewChanged.emit()
def markIndexes(self, indexes):
self._indexesMark = indexes
self.viewChanged.emit()
def searchedIndexes(self, indexes):
self._indexesSearched = indexes
self.viewChanged.emit()
def searchRe(self, searchRe):
self._searchRe = searchRe
self.update()
def viewFullAreaSize(self) -> (int, int):
w = 10+self._fileBuffer.getWidth()
h = self._fileBuffer.getLen()
return ( w , h )
def viewDisplayedSize(self) -> (int, int):
return self.size()
def keyEvent(self, evt):
# Enable CTRL+C = Copy
if ( evt.type == ttk.TTkK.SpecialKey and
evt.mod == ttk.TTkK.ControlModifier and
evt.key == ttk.TTkK.Key_C ):
self._copy()
def mousePressEvent(self, evt):
x,y = evt.x, evt.y
ox,oy = self.getViewOffsets()
index = oy+y
self._selection = [index,index]
self._pressed = True
if 0 <= index < self._fileBuffer.getLen():
if x<3:
if index in self._indexesMark:
self._indexesMark.pop(self._indexesMark.index(index))
else:
self._indexesMark.append(index)
self.marked.emit(self._indexesMark)
else:
self._selected = index
self.selected.emit(self._selected)
tloggProxy.lineSelected.emit(self.getLine(self._selected))
self.update()
return True
return super().mousePressEvent(evt)
def mouseDragEvent(self, evt) -> bool:
if self._pressed and self._selection:
w,h = self.size()
x,y = evt.x, evt.y
lines=self._fileBuffer.getLen()
ox,oy = self.getViewOffsets()
index = oy+y
self._selection[1] = min(max(0,index),lines-1)
if y < 0:
self.viewMoveTo(ox, max(0,min(oy+min(y,3),lines-h)))
elif y >= h:
self.viewMoveTo(ox, max(0,min(oy+min(y-h,3),lines-h)))
else:
self.update()
return super().mouseDragEvent(evt)
def mouseReleaseEvent(self, evt) -> bool:
self._pressed = False
if self._selection and self._selection[0] != self._selection[1]:
self._copy()
self.update()
return super().mouseReleaseEvent(evt)
@ttk.pyTTkSlot(int)
def selectAndMove(self, line):
self._selected = line
self._selection = [line,line]
ox,_ = self.getViewOffsets()
self.viewMoveTo(ox, max(0,line-self.height()//2))
self.update()
def getLen(self):
return self._fileBuffer.getLen()
def getLine(self, num) -> str:
return self._fileBuffer.getLine(num)
def getLineNum(self, num) -> int:
return num
def paintEvent(self, canvas):
ox,oy = self.getViewOffsets()
bufferLen = self.getLen()
for i in range(min(self.height(),bufferLen-oy)):
line = ttk.TTkString(self.getLine(i+oy).replace('\n','')).tab2spaces()
lineNum = self.getLineNum(i+oy)
if lineNum in self._indexesMark:
symbolcolor = ttk.TTkColor.fg("#00ffff")
numberColor = ttk.TTkColor.bg("#444444")
symbol=''
elif lineNum in self._indexesSearched:
symbolcolor = ttk.TTkColor.fg("#ff0000")
numberColor = ttk.TTkColor.bg("#444444")
symbol=''
else:
symbolcolor = ttk.TTkColor.fg("#0000ff")
numberColor = ttk.TTkColor.bg("#444444")
symbol=''
if i+oy == self._selected:
selectedColor = ttk.TTkColor.bg("#008844")
searchedColor = ttk.TTkColor.fg("#FFFF00")+ttk.TTkColor.bg("#004400")
line = line.setColor(selectedColor)
elif self._selection and min(self._selection) <= i+oy <= max(self._selection):
selectedColor = ttk.TTkColor.bg("#008888")
searchedColor = ttk.TTkColor.fg("#FFFF00")+ttk.TTkColor.bg("#004400")
line = line.setColor(selectedColor)
else:
selectedColor = ttk.TTkColor.RST
searchedColor = ttk.TTkColor.fg("#000000")+ttk.TTkColor.bg("#AAAAAA")
# Check in the filters a matching color
for color in TloggCfg.colors:
#TTkLog.debug(f"{color['pattern']} - {line}")
if m := line.findall(regexp=color['pattern'], ignoreCase=color['ignorecase']):
selectedColor = ttk.TTkColor.fg(color['fg'])+ttk.TTkColor.bg(color['bg'])
searchedColor = ttk.TTkColor.fg(color['bg'])+ttk.TTkColor.bg(color['fg'])
line = line.setColor(selectedColor)
break
if self._searchRe:
if m := line.findall(regexp=self._searchRe, ignoreCase=True):
for match in m:
line = line.setColor(searchedColor, match=match)
# Add Line Number
lenLineNumber = len(str(self.getLineNum(bufferLen-1)))
lineNumber = ttk.TTkString() + numberColor + str(lineNum).rjust(lenLineNumber) + ttk.TTkColor.RST + ' '
# Compose print line
printLine = ttk.TTkString() + symbolcolor + symbol + ttk.TTkColor.RST + ' ' + lineNumber + line.substring(ox)
# stupid scramble
# printLine._text = ''.join([chr(121-(ord(l)-65)) if (65<=ord(l)<=121) else l for l in printLine._text])
canvas.drawText(pos=(0,i), text=printLine, color=selectedColor, width=self.width(), )
# Draw the loading banner
if self._indexing is not None:
canvas.drawText(pos=(0,0), text=f" [ Indexed: {int(100*self._indexing)}% ] ")
class FileViewerSearch(FileViewer):
__slots__ = ('_indexes')
def __init__(self, *args, **kwargs):
self._indexes = []
FileViewer.__init__(self, *args, **kwargs)
self._name = kwargs.get('name' , 'FileViewerSearch' )
def markIndexes(self, indexes):
self._indexesMark = indexes
self._indexes = [i for i in sorted(set(self._indexesSearched+self._indexesMark))]
ox,oy = self.getViewOffsets()
self.viewMoveTo(ox,oy)
self.update()
def searchedIndexes(self, indexes):
# Get the current lineSelected to be used to scroll
# the search viewer to the similar to previous position
lineSelected = -1
if self._selected > -1:
lineSelected = self._indexes[self._selected]
self._indexesSearched = indexes
self._indexes = [i for i in sorted(set(self._indexesSearched+self._indexesMark))]
ox,_ = self.getViewOffsets()
lineToMove = 0
if lineSelected > -1:
for i, lineNum in enumerate(self._indexes):
if lineNum >= lineSelected:
if lineNum == lineSelected:
self._selected = i
else:
self._selected = -1
# Try to keep the selected area at the center of the widget
lineToMove = i if i <= self.height()/2 else int(i-self.height()/2)
break
self.viewMoveTo(ox, lineToMove)
self.update()
def viewFullAreaSize(self) -> (int, int):
if self._indexes is None:
w = 2+self._fileBuffer.getWidth()
h = self._fileBuffer.getLen()
else:
w = 2+self._fileBuffer.getWidth(self._indexes)
h = len(self._indexes)
return w , h
def mousePressEvent(self, evt):
x,y = evt.x, evt.y
ox,oy = self.getViewOffsets()
index = oy+y
self._selection = [index,index]
self._pressed = True
if 0 <= index < len(self._indexes):
if x<3:
index = self._indexes[oy+y]
if index in self._indexesMark:
self._indexesMark.pop(self._indexesMark.index(index))
else:
self._indexesMark.append(index)
self.markIndexes(self._indexesMark)
self.marked.emit(self._indexesMark)
else:
self._selected = index
self.selected.emit(self._indexes[self._selected])
tloggProxy.lineSelected.emit(self.getLine(self._selected))
self.update()
return True
return False
def getLen(self):
return len(self._indexes)
def getLine(self, num) -> str:
return self._fileBuffer.getLine(self._indexes[num])
def getLineNum(self, num) -> int:
return self._indexes[num]
class FileViewerArea(ttk.TTkAbstractScrollArea):
__slots__ = ('_fileView')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._name = kwargs.get('name' , 'FileViewer' )
if 'parent' in kwargs: kwargs.pop('parent')
self._fileView = kwargs.get('fileView')
self.setFocusPolicy(ttk.TTkK.ClickFocus)
self.setViewport(self._fileView)

36
apps/tlogg/tlogg/app/glbl.py

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TloggGlbl']
class TloggGlbl:
_refViews = []
@staticmethod
def addRefView(view):
TloggGlbl._refViews.append(view)
@staticmethod
def refreshViews():
for view in TloggGlbl._refViews:
view.update()

164
apps/tlogg/tlogg/app/highlighters.py

@ -0,0 +1,164 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['highlightersFormLayout']
import copy
from . import TloggCfg, TloggGlbl
from TermTk import *
def highlightersFormLayout(win):
'''
This form is inspired by glogg "Filters..." form
'''
colors = copy.deepcopy(TloggCfg.colors)
leftRightLayout = TTkHBoxLayout()
leftLayout = TTkGridLayout()
rightLayout = TTkGridLayout()
bottomRightLayout = TTkGridLayout()
leftRightLayout.addItem(leftLayout)
leftRightLayout.addItem(rightLayout)
frameColors = TTkFrame(border=True, layout=TTkGridLayout())
leftLayout.addWidget(frameColors,0,0,1,5)
listColors = TTkList(parent=frameColors, dragDropMode=TTkK.DragDropMode.AllowDragDrop)
addButton = TTkButton(text="+",maxWidth=3)
removeButton = TTkButton(text="-",maxWidth=3)
upButton = TTkButton(text="",maxWidth=3)
downButton = TTkButton(text="",maxWidth=3)
leftLayout.addWidget(addButton ,1,0)
leftLayout.addWidget(removeButton ,1,1)
leftLayout.addWidget(TTkSpacer() ,1,2)
leftLayout.addWidget(upButton ,1,3)
leftLayout.addWidget(downButton ,1,4)
rightLayout.addWidget(TTkLabel(text="Pattern:"),0,0)
rightLayout.addWidget(pattern:=TTkLineEdit(text="-----"),0,1)
rightLayout.addWidget(TTkLabel(text="Ignore Case:"),1,0)
rightLayout.addWidget(ignoreCase:=TTkCheckbox(),1,1)
rightLayout.addWidget(TTkLabel(text="FG Color:"),2,0)
rightLayout.addWidget(fgColor := TTkColorButtonPicker(border=False, color=TTkColor.fg('#eeeeee') ),2,1)
rightLayout.addWidget(TTkLabel(text="BG Color:"),3,0)
rightLayout.addWidget(bgColor := TTkColorButtonPicker(border=False, color=TTkColor.bg('#333333') ),3,1)
rightLayout.addWidget(TTkSpacer() ,4,0,1,2)
rightLayout.addItem(bottomRightLayout ,5,0,1,2)
bottomRightLayout.addWidget(applyBtn := TTkButton(text="Apply", border=True, maxHeight=3),0,1)
bottomRightLayout.addWidget(cancelBtn := TTkButton(text="Cancel", border=True, maxHeight=3),0,2)
bottomRightLayout.addWidget(okBtn := TTkButton(text="OK", border=True, maxHeight=3),0,3)
def _move(offset):
def _moveUpDown():
if items := listColors.selectedItems():
item = items[0]
index = listColors.indexOf(item)
listColors.moveItem(index,index+offset)
return _moveUpDown
upButton.clicked.connect(_move(-1))
downButton.clicked.connect(_move(1))
def _addCallback():
color = {'pattern':"<PATTERN>", 'ignorecase':True, 'fg':"#FFFFFF", 'bg':"#000000" }
colors.append(color)
listColors.addItem(item=color['pattern'],data=color)
addButton.clicked.connect(_addCallback)
def _removeCallback():
# Clear all the signals
pattern.textEdited.clear()
ignoreCase.clicked.clear()
fgColor.colorSelected.clear()
bgColor.colorSelected.clear()
if items := listColors.selectedItems():
colors.remove(items[0].data)
listColors.removeItem(items[0])
removeButton.clicked.connect(_removeCallback)
def _saveColors():
colors = []
for item in listColors.items():
colors.append(item.data())
TloggCfg.colors = colors
TloggCfg.save(searches=False, filters=False, colors=True, options=False)
TloggGlbl.refreshViews()
# TTkHelper.updateAll()
applyBtn.clicked.connect(_saveColors)
okBtn.clicked.connect(_saveColors)
okBtn.clicked.connect(win.close)
cancelBtn.clicked.connect(win.close)
@ttk.pyTTkSlot(TTkAbstractListItem)
def _listCallback(item):
if color:=item.data():
# Clear all the signals
pattern.textEdited.clear()
ignoreCase.clicked.clear()
fgColor.colorSelected.clear()
bgColor.colorSelected.clear()
# Config the color widgets
pattern.setText(color['pattern'])
ignoreCase.setCheckState(TTkK.Checked if color['ignorecase'] else TTkK.Unchecked)
fgColor.setColor(TTkColor.bg(color['fg']))
bgColor.setColor(TTkColor.bg(color['bg']))
# Connect the actions
## Pattern Line Edit
@pyTTkSlot(str)
def _setPattern(p:TTkString):
item.setText(str(p))
color['pattern'] = str(p)
pattern.textEdited.connect(_setPattern)
## Case Sensitivity checkbox
def _setCase(c):color['ignorecase'] = c
ignoreCase.clicked.connect(_setCase)
## Color Button
def _setFg(c):color['fg'] = c.getHex(TTkK.Background)
def _setBg(c):color['bg'] = c.getHex(TTkK.Background)
fgColor.colorSelected.connect(_setFg)
bgColor.colorSelected.connect(_setBg)
listColors.itemClicked.connect(_listCallback)
for i,color in enumerate(colors):
# ali = TTkAbstractListItem(text=color['pattern'],data=color)
listColors.addItem(item=color['pattern'],data=color)
return leftRightLayout
def highlightersForm(root=None):
preferencesLayout = TTkGridLayout(columnMinWidth=1)
frame = TTkFrame(parent=root, layout=preferencesLayout, border=0)
frameColors = TTkFrame(border=True, title="Highlighters...", layout=highlightersFormLayout())
preferencesLayout.addWidget(frameColors,0,0)
return frame

106
apps/tlogg/tlogg/app/loggwidget.py

@ -0,0 +1,106 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['LoggWidget']
import TermTk as ttk
from .cfg import TloggCfg
from .glbl import TloggGlbl
from .fileviewer import FileViewer, FileViewerArea, FileViewerSearch
from .predefinedfilters import PredefinedFilters
class LoggWidget(ttk.TTkSplitter):
__slots__ = ('_btn_filters', '_bls_label_1', '_bls_cb_icase', '_bls_search', '_bls_searchbox',
'_topViewport', '_bottomViewport',
'_fileBuffer')
def __init__(self, filename, *args, **kwargs):
super().__init__(*args, **kwargs|{'orientation':ttk.TTkK.VERTICAL})
topFrame = ttk.TTkFrame(parent=self, border=False, layout=ttk.TTkVBoxLayout())
bottomFrame = ttk.TTkFrame(parent=self, border=False, layout=ttk.TTkVBoxLayout())
# Define the bottom layout widgets
bottomLayoutSearch = ttk.TTkHBoxLayout()
self._btn_filters = ttk.TTkButton(text="Filters ^", maxWidth=11)
self._bls_label_1 = ttk.TTkLabel(text=" Txt:", maxWidth=5)
self._bls_cb_icase = ttk.TTkCheckbox(text="Aa", maxWidth=5, checked=True)
self._bls_search = ttk.TTkButton(text="Search", maxWidth=10)
self._bls_searchbox = ttk.TTkComboBox(editable=True)
self._bls_searchbox.addItems(TloggCfg.searches)
self._bls_searchbox.setCurrentIndex(0)
bottomLayoutSearch.addWidget(self._btn_filters)
bottomLayoutSearch.addWidget(self._bls_label_1)
bottomLayoutSearch.addWidget(self._bls_searchbox)
bottomLayoutSearch.addWidget(self._bls_cb_icase)
bottomLayoutSearch.addWidget(self._bls_search)
bottomFrame.layout().addItem(bottomLayoutSearch)
# Define the main file Viewer
self._fileBuffer = ttk.TTkFileBuffer(filename, 0x100, 0x1000)
self._topViewport = FileViewer(filebuffer=self._fileBuffer)
topViewer = FileViewerArea(parent=topFrame, fileView=self._topViewport)
self._fileBuffer.indexUpdated.connect(self._topViewport.fileIndexing)
self._fileBuffer.indexed.connect(self._topViewport.fileIndexed)
# Define the Search Viewer
self._bottomViewport = FileViewerSearch(filebuffer=self._fileBuffer)
bottomViewer = FileViewerArea(parent=bottomFrame, fileView=self._bottomViewport)
self._bottomViewport.selected.connect(self._topViewport.selectAndMove)
self._bottomViewport.marked.connect(self._topViewport.markIndexes)
self._topViewport.marked.connect(self._bottomViewport.markIndexes)
# Add those viewpoers to the global list to allow dynamic refresh
# TODO: Try to get rid of this
TloggGlbl.addRefView(self._topViewport)
TloggGlbl.addRefView(self._bottomViewport)
self._bls_search.clicked.connect(self._search)
self._bls_searchbox.editTextChanged.connect(self._search)
def _openPredefinedFilters():
ttk.TTkHelper.overlay(self._btn_filters, PredefinedFilters(self._bls_searchbox), -2, 1)
self._btn_filters.clicked.connect(_openPredefinedFilters)
@ttk.pyTTkSlot()
def _search(self):
searchtext = str(self._bls_searchbox.currentText())
ttk.TTkLog.debug(f"{searchtext=}")
indexes = self._fileBuffer.searchRe(searchtext, ignoreCase=self._bls_cb_icase.checkState() == ttk.TTkK.Checked)
self._bottomViewport.searchedIndexes(indexes)
self._bottomViewport.searchRe(searchtext)
self._topViewport.searchedIndexes(indexes)
self._topViewport.searchRe(searchtext)
if TloggCfg.searches:
x = set(TloggCfg.searches)
ttk.TTkLog.debug(f"{x}")
TloggCfg.searches = list(x)
if searchtext in TloggCfg.searches:
TloggCfg.searches.remove(searchtext)
TloggCfg.searches.insert(0, searchtext)
TloggCfg.searches = TloggCfg.searches[:TloggCfg.maxsearches]
TloggCfg.save(searches=True,filters=False)
self._bls_searchbox.clear()
self._bls_searchbox.addItems(TloggCfg.searches)
self._bls_searchbox.setCurrentIndex(0)

206
apps/tlogg/tlogg/app/main.py

@ -0,0 +1,206 @@
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import re
import sys
import argparse
import appdirs
from TermTk import *
from tlogg import TloggHelper, tloggProxy
from .cfg import *
from .glbl import *
from .about import *
from .loggwidget import LoggWidget
from .options import optionsFormLayout, optionsLoadTheme
from .highlighters import highlightersFormLayout
from .predefinedfilters import PredefinedFiltersFormWindow
from .notepad import NotePad
class TLOGG(TTkGridLayout):
'''
[Main Splitter]
[File Tab Splitter]
F.Tree Tab
Controls
File File Viewer
Tree
Search Widget
Logger,Debug View
'''
__slots__ = ('_kodeTab', '_tloggProxy','_notepad')
def __init__(self, tloggProxy, *args, **kwargs) -> None:
self._tloggProxy = tloggProxy
self._notepad = NotePad()
super().__init__(*args, **kwargs)
self._tloggProxy.setOpenFile(self.openFile)
self.addWidget(appTemplate:=TTkAppTemplate(border=False))
self._kodeTab = TTkKodeTab(border=False, closable=True)
appTemplate.setMenuBar(appMenuBar:=TTkMenuBarLayout(), TTkAppTemplate.LEFT)
fileMenu = appMenuBar.addMenu("&File")
buttonOpen = fileMenu.addMenu("&Open")
buttonClose = fileMenu.addMenu("&Close")
fileMenu.addSpacer()
buttonColors = fileMenu.addMenu("C&olors...")
buttonFilters = fileMenu.addMenu("&Filters...")
buttonOptions = fileMenu.addMenu("O&ptions...")
fileMenu.addSpacer()
buttonExit = fileMenu.addMenu("E&xit")
buttonExit.menuButtonClicked.connect(TTkHelper.quit)
extraMenu = appMenuBar.addMenu("E&xtra")
extraMenu.addMenu("Scratchpad").menuButtonClicked.connect(self.scratchpad)
extraMenu.addSpacer()
helpMenu = appMenuBar.addMenu("&Help", alignment=TTkK.RIGHT_ALIGN)
helpMenu.addMenu("About ...").menuButtonClicked.connect(self.showAbout)
helpMenu.addMenu("About tlogg").menuButtonClicked.connect(self.showAboutTlogg)
def _tabChanged(_,__,widget,data):
tloggProxy.tloggFocussed.emit(None, data)
self._kodeTab.currentChanged.connect(_tabChanged)
# fileTree.fileActivated.connect(lambda x: self.openFile(x.path()))
buttonOpen.menuButtonClicked.connect(self.openFileCallback)
buttonColors.menuButtonClicked.connect(self.showColors)
buttonFilters.menuButtonClicked.connect(self.showFilters)
buttonOptions.menuButtonClicked.connect(self.showOptions )
for mod in TloggHelper._getPlugins():
if mod.position:
appTemplate.setWidget(mod.widget, mod.position, 30)
if mod.menu:
_menu = extraMenu.addMenu(mod.name, checkable=True, checked=mod.visible)
mod.widget.setVisible(mod.visible)
_menu.toggled.connect(mod.widget.setVisible)
appTemplate.setWidget(self._kodeTab, TTkAppTemplate.MAIN)
appTemplate.setWidget(TTkLogViewer(),TTkAppTemplate.BOTTOM,size=1,title="Logs")
@pyTTkSlot()
def scratchpad(self):
win = TTkWindow(
title="Mr Scratchpad",
size=(80,30),
layout=self._notepad,
flags=TTkK.WindowFlag.WindowMaximizeButtonHint|TTkK.WindowFlag.WindowCloseButtonHint)
TTkHelper.overlay(None, win, 2, 2, toolWindow=True)
@pyTTkSlot(TTkMenuButton)
def openFileCallback(self, btn):
filePicker = TTkFileDialogPicker(
pos = (3,3), size=(90,30),
caption="Open a File", path=".",
filter="All Files (*);;Text Files (*.txt);;Log Files (*.log)")
filePicker.filePicked.connect(self.openFile)
TTkHelper.overlay(btn, filePicker, 10, 2, modal=True)
@pyTTkSlot(TTkMenuButton)
def showColors(self, btn):
win = TTkWindow(title="Highlighters...", size=(70,20), border=True)
win.setLayout(highlightersFormLayout(win))
TTkHelper.overlay(btn, win, 10,2, modal=True)
@pyTTkSlot(TTkMenuButton)
def showFilters(self, btn):
win = PredefinedFiltersFormWindow(title="Predefined Filters...", size=(70,20), border=True)
TTkHelper.overlay(btn, win, 10,2, modal=True)
@pyTTkSlot(TTkMenuButton)
def showOptions(self, btn):
win = TTkWindow(title="Options...", size=(70,20), border=True)
win.setLayout(optionsFormLayout(win))
TTkHelper.overlay(btn, win, 10,2, modal=True)
# tab.addTab(h
# @pyTTkSlot()ighlightersForm(), "-Setup-")
@pyTTkSlot(TTkMenuButton)
def showAbout(self, btn):
TTkHelper.overlay(btn, TTkAbout(), 20,5)
@pyTTkSlot(TTkMenuButton)
def showAboutTlogg(self, btn):
TTkHelper.overlay(btn, About(), 20,5)
def openFile(self, file):
# openedFiles.append(file)
loggWidget = LoggWidget(file)
self._kodeTab.addTab(widget=loggWidget, label=os.path.basename(file), data=file)
self._kodeTab.setCurrentWidget(loggWidget)
def main():
TloggCfg.pathCfg = appdirs.user_config_dir("tlogg")
parser = argparse.ArgumentParser()
# parser.add_argument('-f', help='Full Screen', action='store_true')
parser.add_argument('-c', help=f'config folder (default: "{TloggCfg.pathCfg}")', default=TloggCfg.pathCfg)
parser.add_argument('filename', type=str, nargs='*',
help='the filename/s')
args = parser.parse_args()
# TTkLog.use_default_file_logging()
TloggCfg.pathCfg = args.c
TTkLog.debug(f"Config Path: {TloggCfg.pathCfg}")
TloggCfg.load()
if 'theme' not in TloggCfg.options:
TloggCfg.options['theme'] = 'UTF8'
optionsLoadTheme(TloggCfg.options['theme'])
TloggHelper._loadPlugins()
root = TTk(
title="tlogg",
layout=(tlogg:=TLOGG(tloggProxy=tloggProxy)),
sigmask=(
TTkTerm.Sigmask.CTRL_C |
TTkTerm.Sigmask.CTRL_Q |
TTkTerm.Sigmask.CTRL_S |
TTkTerm.Sigmask.CTRL_Z ))
TloggHelper._runPlugins()
for file in args.filename:
tlogg.openFile(file)
root.mainloop()

177
apps/tlogg/tlogg/app/notepad.py

@ -0,0 +1,177 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['NotePad']
import TermTk as ttk
class SuperSimpleHorizontalLine(ttk.TTkWidget):
def paintEvent(self, canvas):
w,h = self.size()
canvas.drawText(pos=(0,h-1), text=''+(''*(w-2))+'',color=ttk.TTkColor.fg("#888888"))
class NotePad(ttk.TTkGridLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
te = ttk.TTkTextEdit(readOnly=False, lineNumber=True)
self.addItem(wrapLayout := ttk.TTkGridLayout(), 0,0)
self.addItem(fontLayout := ttk.TTkGridLayout(columnMinWidth=1), 1,0)
self.addWidget(te,2,0)
wrapLayout.addWidget(ttk.TTkLabel(text="Wrap: ", maxWidth=6),0,0)
wrapLayout.addWidget(lineWrap := ttk.TTkComboBox(list=['NoWrap','WidgetWidth','FixedWidth'], maxWidth=20),0,1)
wrapLayout.addWidget(ttk.TTkLabel(text=" Type: ",maxWidth=7),0,2)
wrapLayout.addWidget(wordWrap := ttk.TTkComboBox(list=['WordWrap','WrapAnywhere'], maxWidth=20, enabled=False),0,3)
wrapLayout.addWidget(ttk.TTkLabel(text=" FixW: ",maxWidth=7),0,4)
wrapLayout.addWidget(fixWidth := ttk.TTkSpinBox(value=te.wrapWidth(), maxWidth=5, maximum=500, minimum=10, enabled=False),0,5)
wrapLayout.addWidget(ttk.TTkSpacer(),0,10)
# Empty columns/cells are 1 char wide due to "columnMinWidth=1" parameter in the GridLayout
# 1 3 8 11
# 0 2 4 5 6 7 9 10 12
# 0 [ ] FG [ ] BG [ ] LineNumber
# 1 ┌─────┐ ┌─────┐ ╒═══╕╒═══╕╒═══╕╒═══╕ ┌──────┐┌──────┐
# 2 │ │ │ │ │ a ││ a ││ a ││ a │ │ UNDO ││ REDO │
# 3 └─────┘ └─────┘ └───┘└───┘└───┘└───┘ ╘══════╛└──────┘ ┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┙
# Char Fg/Bg buttons
fontLayout.addWidget(cb_fg := ttk.TTkCheckbox(text=" FG"),0,0)
fontLayout.addWidget(btn_fgColor := ttk.TTkColorButtonPicker(border=True, enabled=False, maxSize=(7,3)),1,0)
fontLayout.addWidget(cb_bg := ttk.TTkCheckbox(text=" BG"),0,2)
fontLayout.addWidget(btn_bgColor := ttk.TTkColorButtonPicker(border=True, enabled=False, maxSize=(7 ,3)),1,2)
fontLayout.addWidget(cb_linenumber := ttk.TTkCheckbox(text=" LineNumber", checked=True),0,4,1,3)
# Char style buttons
fontLayout.addWidget(btn_bold := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString( 'a' , ttk.TTkColor.BOLD) ),1,4)
fontLayout.addWidget(btn_italic := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString( 'a' , ttk.TTkColor.ITALIC) ),1,5)
fontLayout.addWidget(btn_underline := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString(' a ', ttk.TTkColor.UNDERLINE) ),1,6)
fontLayout.addWidget(btn_strikethrough := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString(' a ', ttk.TTkColor.STRIKETROUGH)),1,7)
# Undo/Redo buttons
fontLayout.addWidget(btn_undo := ttk.TTkButton(border=True, maxSize=(8,3), enabled=te.isUndoAvailable(), text=' UNDO '),1,9 )
fontLayout.addWidget(btn_redo := ttk.TTkButton(border=True, maxSize=(8,3), enabled=te.isRedoAvailable(), text=' REDO '),1,10)
# Undo/Redo events
te.undoAvailable.connect(btn_undo.setEnabled)
te.redoAvailable.connect(btn_redo.setEnabled)
btn_undo.clicked.connect(te.undo)
btn_redo.clicked.connect(te.redo)
# Useless custom horizontal bar for aestetic reason
fontLayout.addWidget(SuperSimpleHorizontalLine(),0,12,2,1)
@ttk.pyTTkSlot(ttk.TTkColor)
def _currentColorChangedCB(format):
if fg := format.foreground():
cb_fg.setCheckState(ttk.TTkK.Checked)
btn_fgColor.setEnabled()
btn_fgColor.setColor(fg.invertFgBg())
else:
cb_fg.setCheckState(ttk.TTkK.Unchecked)
btn_fgColor.setDisabled()
if bg := format.background():
cb_bg.setCheckState(ttk.TTkK.Checked)
btn_bgColor.setEnabled()
btn_bgColor.setColor(bg)
else:
cb_bg.setCheckState(ttk.TTkK.Unchecked)
btn_bgColor.setDisabled()
btn_bold.setChecked(format.bold())
btn_italic.setChecked(format.italic())
btn_underline.setChecked(format.underline())
btn_strikethrough.setChecked(format.strikethrough())
# ttk.TTkLog.debug(f"{fg=} {bg=} {bold=} {italic=} {underline=} {strikethrough= }")
te.currentColorChanged.connect(_currentColorChangedCB)
def _setStyle():
color = ttk.TTkColor()
if cb_fg.checkState() == ttk.TTkK.Checked:
color += btn_fgColor.color().invertFgBg()
if cb_bg.checkState() == ttk.TTkK.Checked:
color += btn_bgColor.color()
if btn_bold.isChecked():
color += ttk.TTkColor.BOLD
if btn_italic.isChecked():
color += ttk.TTkColor.ITALIC
if btn_underline.isChecked():
color += ttk.TTkColor.UNDERLINE
if btn_strikethrough.isChecked():
color += ttk.TTkColor.STRIKETROUGH
cursor = te.textCursor()
cursor.applyColor(color)
cursor.setColor(color)
te.setFocus()
cb_fg.stateChanged.connect(lambda x: btn_fgColor.setEnabled(x==ttk.TTkK.Checked))
cb_bg.stateChanged.connect(lambda x: btn_bgColor.setEnabled(x==ttk.TTkK.Checked))
cb_fg.clicked.connect(lambda _: _setStyle())
cb_bg.clicked.connect(lambda _: _setStyle())
cb_linenumber.stateChanged.connect(lambda x: te.setLineNumber(x==ttk.TTkK.Checked))
btn_fgColor.colorSelected.connect(lambda _: _setStyle())
btn_bgColor.colorSelected.connect(lambda _: _setStyle())
btn_bold.clicked.connect(_setStyle)
btn_italic.clicked.connect(_setStyle)
btn_underline.clicked.connect(_setStyle)
btn_strikethrough.clicked.connect(_setStyle)
lineWrap.setCurrentIndex(0)
wordWrap.setCurrentIndex(1)
fixWidth.valueChanged.connect(te.setWrapWidth)
@ttk.pyTTkSlot(int)
def _lineWrapCallback(index):
if index == 0:
te.setLineWrapMode(ttk.TTkK.NoWrap)
wordWrap.setDisabled()
fixWidth.setDisabled()
elif index == 1:
te.setLineWrapMode(ttk.TTkK.WidgetWidth)
wordWrap.setEnabled()
fixWidth.setDisabled()
else:
te.setLineWrapMode(ttk.TTkK.FixedWidth)
te.setWrapWidth(fixWidth.value())
wordWrap.setEnabled()
fixWidth.setEnabled()
lineWrap.currentIndexChanged.connect(_lineWrapCallback)
@ttk.pyTTkSlot(int)
def _wordWrapCallback(index):
if index == 0:
te.setWordWrapMode(ttk.TTkK.WordWrap)
else:
te.setWordWrapMode(ttk.TTkK.WrapAnywhere)
wordWrap.currentIndexChanged.connect(_wordWrapCallback)

76
apps/tlogg/tlogg/app/options.py

@ -0,0 +1,76 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2022 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['optionsLoadTheme', 'optionsFormLayout']
import copy
from . import TloggCfg, TloggGlbl
from TermTk import *
def optionsLoadTheme(theme):
if theme == 'ASCII':
TTkTheme.loadTheme(TTkTheme.ASCII)
elif theme == 'UTF8':
TTkTheme.loadTheme(TTkTheme.UTF8)
elif theme == 'NERD':
TTkTheme.loadTheme(TTkTheme.NERD)
def optionsFormLayout(win):
options = copy.deepcopy(TloggCfg.options)
retLayout = TTkGridLayout()
bottomLayout = TTkGridLayout()
themesFrame = TTkFrame(title="Theme", border=True, layout=TTkVBoxLayout(), maxHeight=5, minHeight=5)
# Themes
themesFrame.layout().addWidget(r1 := TTkRadioButton(text="ASCII", name="theme", checked=options['theme'] == 'ASCII'))
themesFrame.layout().addWidget(r2 := TTkRadioButton(text="UTF-8", name="theme", checked=options['theme'] == 'UTF8'))
themesFrame.layout().addWidget(r3 := TTkRadioButton(text="Nerd", name="theme", checked=options['theme'] == 'NERD'))
retLayout.addWidget(themesFrame,0,0)
retLayout.addWidget(TTkSpacer() ,1,0,1,2)
retLayout.addItem(bottomLayout ,2,0,1,2)
bottomLayout.addWidget(applyBtn := TTkButton(text="Apply", border=True, maxHeight=3),0,1)
bottomLayout.addWidget(cancelBtn := TTkButton(text="Cancel", border=True, maxHeight=3),0,2)
bottomLayout.addWidget(okBtn := TTkButton(text="OK", border=True, maxHeight=3),0,3)
def _saveOptions():
if r1.checkState() == TTkK.Checked: options['theme'] = 'ASCII'
if r2.checkState() == TTkK.Checked: options['theme'] = 'UTF8'
if r3.checkState() == TTkK.Checked: options['theme'] = 'NERD'
TloggCfg.options = options
TloggCfg.save(searches=False, filters=False, colors=False, options=True)
optionsLoadTheme(options['theme'])
TloggGlbl.refreshViews()
TTkHelper.updateAll()
applyBtn.clicked.connect(_saveOptions)
okBtn.clicked.connect(_saveOptions)
okBtn.clicked.connect(win.close)
cancelBtn.clicked.connect(win.close)
return retLayout

180
apps/tlogg/tlogg/app/predefinedfilters.py

@ -0,0 +1,180 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['PredefinedFilters','PredefinedFiltersFormWindow']
import copy
from readline import insert_text
from . import TloggCfg, TloggGlbl
from TermTk import *
class PredefinedFiltersFormWindow(TTkWindow):
'''
This form is inspired by klogg "PredefinedFilters..." form
'''
def __init__(self, *args, **kwargs):
TTkWindow.__init__(self, *args, **kwargs)
self._name = kwargs.get('name' , 'PredefinedFiltersFormWindow' )
self._filters = copy.deepcopy(TloggCfg.filters)
self._selected = -1
self._retLayout = TTkVBoxLayout() # Full Layout
self._controlLayout = TTkGridLayout() # Layout including the add/remove/move buttons
self._filtersLayout = TTkGridLayout() # Layout having the filters form
self._bottomLayout = TTkGridLayout() # Layout including the OK,Cancel,Apply Buttons
self.setLayout(self._retLayout)
self._retLayout.addItem(self._controlLayout)
self._retLayout.addItem(self._filtersLayout)
self._retLayout.addItem(self._bottomLayout)
self._addButton = TTkButton(text="+",maxWidth=3)
self._removeButton = TTkButton(text="-",maxWidth=3)
self._upButton = TTkButton(text="",maxWidth=3)
self._downButton = TTkButton(text="",maxWidth=3)
self._controlLayout.addWidget(self._addButton ,1,0)
self._controlLayout.addWidget(self._removeButton ,1,1)
self._controlLayout.addWidget(TTkSpacer(maxHeight=1) ,1,2)
self._controlLayout.addWidget(self._upButton ,1,3)
self._controlLayout.addWidget(self._downButton ,1,4)
self._bottomLayout.addWidget(applyBtn := TTkButton(text="Apply", border=True, maxHeight=3),0,1)
self._bottomLayout.addWidget(cancelBtn := TTkButton(text="Cancel", border=True, maxHeight=3),0,2)
self._bottomLayout.addWidget(okBtn := TTkButton(text="OK", border=True, maxHeight=3),0,3)
self._refreshFilters()
def _move(offset):
def _moveUpDown():
if self._selected < 0: return
if self._selected + offset < 0: return
if self._selected + offset >= len(self._filters): return
item = self._filters.pop(self._selected)
self._filters = self._filters[:self._selected+offset] + [item] + self._filters[self._selected+offset:]
self._refreshFilters()
self._selected += offset
return _moveUpDown
self._upButton.clicked.connect(_move(-1))
self._downButton.clicked.connect(_move(1))
def _addCallback():
filter = {'name':f'n:{len(self._filters)+1}', 'pattern':'' }
self._filters.append(filter)
self._selected = -1
self._refreshFilters()
self._addButton.clicked.connect(_addCallback)
def _removeCallback():
if self._selected > -1:
self._filters.pop(self._selected)
self.selected = -1
self._refreshFilters()
self._removeButton.clicked.connect(_removeCallback)
def _saveFilters():
TloggCfg.filters = self._filters
TloggCfg.save(searches=False, filters=True, colors=False, options=False)
applyBtn.clicked.connect(_saveFilters)
okBtn.clicked.connect(_saveFilters)
okBtn.clicked.connect(self.close)
cancelBtn.clicked.connect(self.close)
def _refreshFilters(self):
for item in self._filtersLayout.children():
self._filtersLayout.removeItem(item)
splitter = TTkSplitter(border=True)
names = TTkVBoxLayout()
patterns = TTkVBoxLayout()
splitter.addWidget(TTkFrame(border=False, layout=names))
splitter.addWidget(TTkFrame(border=False, layout=patterns))
splitter.setSizes([20,100])
for i, filter in enumerate(self._filters):
names.addWidget( nl := TTkLineEdit(text=filter['name']))
patterns.addWidget(pl := TTkLineEdit(text=filter['pattern']))
# Stupid way to attach the textEdited signal directly to the array modification
# It could have been made much better but Lazyness is an Issue
def _setName(pos):
def _set(txt): self._filters[pos]['name'] = txt
return _set
def _setPattern(pos):
def _set(txt): self._filters[pos]['pattern'] = txt
return _set
def _setSelected(pos):
def _set(focus):
if focus:
self._selected = pos
return _set
nl.textEdited.connect(_setName(i))
pl.textEdited.connect(_setPattern(i))
nl.focusChanged.connect(_setSelected(i))
pl.focusChanged.connect(_setSelected(i))
names.addWidget( TTkSpacer())
patterns.addWidget(TTkSpacer())
self._filtersLayout.addWidget(splitter)
class PredefinedFilters(TTkResizableFrame):
__slots__ = ('checked', 'unchecked', '_searchbox')
def __init__(self, searchbox):
layout=TTkVBoxLayout()
TTkResizableFrame.__init__(self, layout=layout)
self._name = 'PredefinedFilters'
self._searchbox = searchbox
filters = copy.deepcopy(TloggCfg.filters)
for filter in filters:
def _cb(p, sb):
def _ret(state):
txt = sb.currentText()
if state == TTkK.Checked:
if not txt:
txt = p
elif txt.find(p) == -1:
txt += '|'+p
else:
p1 = f"|{p}"
p2 = f"{p}|"
if txt.find(p1) != -1:
txt = txt.replace(p1,'')
elif txt.find(p2) != -1:
txt = txt.replace(p2,'')
else:
txt = txt.replace(p,'')
sb.setCurrentText(txt)
return _ret
layout.addWidget(cb := TTkCheckbox(text=filter['name'],checked=searchbox.currentText().find(filter['pattern'])!=-1))
cb.stateChanged.connect(_cb(filter['pattern'], searchbox))
w,h = self.minimumSize()
self.resize(w,h)

82
apps/tlogg/tlogg/helper.py

@ -0,0 +1,82 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import importlib, pkgutil
import runpy
import sys, os
import TermTk as ttk
from tlogg import TloggPlugin
class TloggHelper():
@staticmethod
def _loadPlugins():
# Check for the plugin folder
pluginFolder = os.path.join(os.path.dirname(os.path.abspath(__file__)),'plugins')
if not os.path.exists(pluginFolder):
ttk.TTkLog.error("No 'plugins' folder found in the 'tlogg' main directory")
else:
for fn in os.listdir(pluginFolder):
filePath = os.path.join(pluginFolder,fn)
if not os.path.isfile(filePath): continue
absolute_name = importlib.util.resolve_name(filePath, None)
ttk.TTkLog.debug(absolute_name)
loader = importlib.machinery.SourceFileLoader(fn, filePath)
spec = importlib.util.spec_from_loader(loader.name, loader)
mod = importlib.util.module_from_spec(spec)
loader.exec_module(mod)
# Check installed plugins
for finder, name, ispkg in pkgutil.iter_modules():
if name.startswith("tlogg_"):
loader = importlib.find_loader(name)
spec = importlib.util.find_spec(name)
mod = importlib.util.module_from_spec(spec)
# runpy.run_module(name)
loader.exec_module(mod)
# check the plugin folder
for mod in TloggPlugin.instances:
if mod.init is not None:
mod.init()
@staticmethod
def _runPlugins():
for mod in TloggPlugin.instances:
if mod.apply is not None:
mod.apply()
@staticmethod
def _getPlugins():
return TloggPlugin.instances
@staticmethod
def _getPluginPlacements():
ret = ttk.TTkK.NONE
for mod in TloggPlugin.instances:
ret |= mod.position
return ret
@staticmethod
def _getPlacedPlugins(placement):
return [mod for mod in TloggPlugin.instances if mod.position & placement]

41
apps/tlogg/tlogg/plugin.py

@ -0,0 +1,41 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from dataclasses import dataclass
from typing import Callable
import TermTk as ttk
@dataclass
class TloggPlugin:
instances = []
name : str
init : Callable[[],None] = None
apply : Callable[[],None] = None
run : Callable[[],None] = None
position : int = ttk.TTkK.NONE # Accepted Values are ; NONE, LEFT, RIGHT
widget : ttk.TTkWidget = None # Required if a position is defined
menu : bool = False
visible: bool = False
def __post_init__(self):
TloggPlugin.instances.append(self)

127
apps/tlogg/tlogg/plugins/fileexplorer.py

@ -0,0 +1,127 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import TermTk as ttk
import tlogg
class FileTree(ttk.TTkContainer):
__slots__ = (
'_recentPath', '_recentPathId',
'_controlsLayout',
'_ftCbFollow', '_ftBtnPrev', '_ftBtnNext', '_ftBtnUp', '_fileTree',
# Forwarded Methods
'fileActivated')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._name = kwargs.get('name' , 'FileTree' )
self.setLayout(ttk.TTkVBoxLayout())
self._controlsLayout = ttk.TTkHBoxLayout()
self.layout().addItem(self._controlsLayout)
self._ftCbFollow = ttk.TTkCheckbox(text="Follow", maxWidth=9, checked=False)
self._ftBtnPrev = ttk.TTkButton(text="<",maxWidth=3, enabled=False)
self._ftBtnNext = ttk.TTkButton(text=">",maxWidth=3, enabled=False)
self._ftBtnUp = ttk.TTkButton(text="^",maxWidth=3, enabled=True)
self._controlsLayout.addWidget(self._ftCbFollow)
self._controlsLayout.addWidget(ttk.TTkSpacer())
self._controlsLayout.addWidget(self._ftBtnPrev)
self._controlsLayout.addWidget(self._ftBtnNext)
self._controlsLayout.addWidget(self._ftBtnUp)
self._fileTree = ttk.TTkFileTree(parent=self, path=".")
self._recentPath = []
self._recentPathId = -1
self._addFolder(os.path.abspath('.'))
self._ftBtnPrev.clicked.connect(self._openPrev)
self._ftBtnNext.clicked.connect(self._openNext)
self._ftBtnUp.clicked.connect(self._openUp)
self._fileTree.folderActivated.connect(self._openFolder)
# Forward Functions
self._fileTree.fileActivated.connect(lambda x: tlogg.tloggProxy.openFile(x.path()))
tlogg.tloggProxy.tloggFocussed.connect(self.follow)
@ttk.pyTTkSlot(tlogg.TloggViewerProxy, str)
def follow(self, _, path):
if self._ftCbFollow.checkState() == ttk.TTkK.Checked:
if os.path.isfile(path):
path, _ = os.path.split(path)
self._fileTree.openPath(path)
def _addFolder(self, dir):
if not self._recentPath or dir != self._recentPath[-1]:
self._recentPath.append(dir)
self._recentPathId = len(self._recentPath)-1
if self._recentPathId:
self._ftBtnPrev.setEnabled()
self._ftBtnNext.setDisabled()
@ttk.pyTTkSlot(ttk.TTkFileTreeWidgetItem)
def _openFolder(self, dir):
self._fileTree.openPath(dir.path())
self._addFolder(dir.path())
@ttk.pyTTkSlot()
def _openPrev(self):
if self._recentPathId<=0 or self._recentPathId>=len(self._recentPath):
self._ftBtnPrev.setDisabled()
return
self._recentPathId -= 1
self._fileTree.openPath(self._recentPath[self._recentPathId])
if self._recentPathId<=0:
self._ftBtnPrev.setDisabled()
self._ftBtnNext.setEnabled()
@ttk.pyTTkSlot()
def _openNext(self):
if self._recentPathId<0 or self._recentPathId>=len(self._recentPath)-1:
self._ftBtnNext.setDisabled()
return
self._recentPathId += 1
self._fileTree.openPath(self._recentPath[self._recentPathId])
if self._recentPathId>=len(self._recentPath)-1:
self._ftBtnNext.setDisabled()
self._ftBtnPrev.setEnabled()
@ttk.pyTTkSlot()
def _openUp(self):
path = os.path.abspath(self._fileTree.getOpenPath())
path, e = os.path.split(path)
if e:
self._addFolder(path)
self._fileTree.openPath(path)
tlogg.TloggPlugin(
name="File Explorer",
position=ttk.TTkK.LEFT,
visible=True,
widget=FileTree())

56
apps/tlogg/tlogg/plugins/jsonnviewer.py

@ -0,0 +1,56 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import json
from pygments import highlight
from pygments.lexers import PythonLexer, JavascriptLexer
from pygments.formatters import TerminalFormatter, Terminal256Formatter, TerminalTrueColorFormatter
import TermTk as ttk
import tlogg
class JsonViewer(ttk.TTkTextEdit):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setLineWrapMode(ttk.TTkK.WidgetWidth)
self.setWordWrapMode(ttk.TTkK.WordWrap)
tlogg.tloggProxy.lineSelected.connect(self._showLine)
ttk.pyTTkSlot(str)
def _showLine(self, text):
try:
text = json.loads(text)
text = json.dumps(text, indent=4)
text = highlight(text, JavascriptLexer(), TerminalTrueColorFormatter(style='material'))
except ValueError as e:
pass
self.setText(text)
tlogg.TloggPlugin(
name="Json Viewer",
position=ttk.TTkK.RIGHT,
menu=True,
visible=False,
widget=JsonViewer(lineNumber=True, readOnly=False, ))

33
apps/tlogg/tlogg/plugins/testplugin.py

@ -0,0 +1,33 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import TermTk as ttk
import tlogg
def init():
ttk.TTkLog.debug("Test Plugin Init")
def apply():
ttk.TTkLog.debug("Test Plugin Apply")
tlogg.TloggPlugin(name="Test Plugin", init=init, apply=apply)

36
apps/tlogg/tlogg/plugins/testplugin1.py

@ -0,0 +1,36 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import TermTk as ttk
import tlogg
def init():
ttk.TTkLog.debug("Test Plugin1 Init")
def apply():
ttk.TTkLog.debug("Test Plugin1 Apply")
def run():
ttk.TTkLog.debug("Test Plugin1 Run")
tlogg.TloggPlugin(name="Test Plugin 1", init=init, apply=apply, run=run)

50
apps/tlogg/tlogg/proxy.py

@ -0,0 +1,50 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TloggViewerProxy', 'tloggProxy']
import TermTk as ttk
class TloggViewerProxy():
__slots__ = ('_fileName')
def __init__(self, fileName) -> None:
self._fileName = fileName
def fileName(self):
return self._fileName
class TloggProxy():
__slots__ = ('_openFileCb',
# Signals
'tloggFocussed', 'lineSelected')
def __init__(self) -> None:
self._openFileCb = lambda _ : None
self.tloggFocussed = ttk.pyTTkSignal(TloggViewerProxy, str)
self.lineSelected = ttk.pyTTkSignal(str)
def setOpenFile(self, cb):
self._openFileCb = cb
def openFile(self, fileName):
return self._openFileCb(fileName)
tloggProxy = TloggProxy()

70
apps/tlogg/tools/create_log.py

@ -0,0 +1,70 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys
import random, json
words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."]
def getWord():
return random.choice(words)
def getSentence(a,b):
return " ".join([getWord() for i in range(0,random.randint(a,b))])
if len(sys.argv) != 3 :
print ("Missing filename")
print ("use %s <FILENAME> <LINES>" % sys.argv[0])
exit(1)
filename = sys.argv[1]
lines = int(sys.argv[2])
print ("Lines=%d" % lines)
with open(filename, 'a') as out:
for i in range(0,lines):
seconds = 1000 + i
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
if ((rnd:=random.random()) < 0.6):
out.write( "TEST;%d:%02d:%02d;COL1\tCOL2 COL3 c:COL4;LIN=%05X\tRND=%f %s %s\n" % (h, m, s, i, random.random(), getSentence(3,20), " Fill" * random.randint(1,5)) )
elif (rnd < 0.9):
jsonLine = {
'time':f"{h:02d}:{m:02d}:{s:02d}",
'line':i,
'data':{
'rnd':random.random(),
'sentence1':getSentence(3,6),
'sentence2':getSentence(3,6),
'sentence3':getSentence(3,6),
'sentence4':getSentence(3,6)},
'extra': {
'text':getSentence(3,10),
'list':[getSentence(1,2) for _ in range(random.randint(2,7))],
'list2':[random.randint(2,128) for _ in range(random.randint(2,7))],
'fill':" Fill" * random.randint(1,5)
} }
out.write( json.dumps(jsonLine) + '\n')
else:
out.write( "TEST;%d:%02d:%02d;COL1 --- (BROKEN LINE) --- LIN=%05X\tRND=%f %s %s\n" % (h, m, s, i, random.random(), getSentence(3,20), " Fill" * random.randint(1,5)) )
out.write( " END LINE (No Newline)" )

39
apps/tlogg/tools/import.filters.from.klogg.py

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# This is just a helper, don't rely on this script too much
# Usage:
# cat ~/.config/klogg/klogg.conf | tools/import.filters.from.klogg.py
import re
import fileinput
# I am trying o match things like:
# sets\1\HighlighterSet\highlighters\1\fore_colour=#ff000000
# sets\1\HighlighterSet\highlighters\1\ignore_case=false
# sets\1\HighlighterSet\highlighters\1\match_only=false
rr = re.compile('^sets\\\(\d*)\\\HighlighterSet\\\highlighters\\\(\d*)\\\([^=]*)=(.*)$')
sets = {}
for line in fileinput.input():
# print(line.rstrip())
if m:=rr.match(line.rstrip()):
set = m.group(1)
num = m.group(2)
name = m.group(3)
value = m.group(4)
if set not in sets:
sets[set] = {}
if num not in sets[set]:
sets[set][num] = {}
sets[set][num][name] = value
for s in sets:
print(f"\nset {s}:")
for n in sets[s]:
f = sets[s][n]
# print (f)
print (f"- pattern: '{f['regexp']}'")
print (f" ignorecase: {f['ignore_case']}")
print (f" fg: '#{f['fore_colour'][3:]}'")
print (f" bg: '#{f['back_colour'][3:]}'")

30
apps/tlogg/tools/test.config/colors.yaml

@ -0,0 +1,30 @@
version: '1.0'
cfg:
- pattern: ipsum dolore
ignorecase: true
fg: '#FFFFFF'
bg: '#0000ff'
- pattern: cillum sit
ignorecase: true
fg: '#101d46'
bg: '#ff00ff'
- pattern: excepteur
ignorecase: true
fg: '#ffff00'
bg: '#000000'
- pattern: dolore
ignorecase: true
fg: '#00ff00'
bg: '#002338'
- pattern: cupida
ignorecase: true
fg: '#00ffff'
bg: '#0000ff'
- pattern: consequa
ignorecase: true
fg: '#ffff00'
bg: '#0000ff'
- pattern: sunt ad
ignorecase: true
fg: '#ffff00'
bg: '#ff0000'

3
apps/tlogg/tools/test.config/options.yaml

@ -0,0 +1,3 @@
version: '1.0'
cfg:
theme: NERD

2
apps/ttkode/pyproject.toml

@ -34,7 +34,7 @@ dependencies = [
Homepage = "https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/ttkode"
Repository = "https://github.com/ceccopierangiolieugenio/pyTermTk.git"
Issues = "https://github.com/ceccopierangiolieugenio/pyTermTk/issues"
Changelog = "https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/apps/ttkDesigner/CHANGELOG.md"
Changelog = "https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/apps/ttkode/CHANGELOG.md"
[project.scripts]
ttkode = "ttkode:main"

6
apps/ttkode/ttkode/app/cfg.py

@ -25,9 +25,11 @@
import os
import json
from .. import __version__
class TTKodeCfg:
version="__VERSION__"
name="__NAME__"
version=__version__
name="ttkode"
cfgVersion = '1.0'
pathCfg="."
options={}

Loading…
Cancel
Save