kanban-python 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kanban-python-0.3.2 → kanban-python-0.3.4}/CHANGELOG.md +13 -0
- {kanban-python-0.3.2/src/kanban_python.egg-info → kanban-python-0.3.4}/PKG-INFO +12 -12
- {kanban-python-0.3.2 → kanban-python-0.3.4}/README.md +10 -10
- kanban-python-0.3.4/images/image_header.PNG +0 -0
- kanban-python-0.3.4/images/image_kanban.PNG +0 -0
- kanban-python-0.3.4/images/image_kanban_configure.PNG +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/setup.cfg +1 -1
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python/app.py +5 -4
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python/config.py +15 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python/controls.py +128 -45
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python/interface.py +107 -45
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python/utils.py +26 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4/src/kanban_python.egg-info}/PKG-INFO +12 -12
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python.egg-info/requires.txt +1 -1
- {kanban-python-0.3.2 → kanban-python-0.3.4}/tests/test_utils.py +20 -0
- kanban-python-0.3.2/images/image_header.PNG +0 -0
- kanban-python-0.3.2/images/image_kanban.PNG +0 -0
- kanban-python-0.3.2/images/image_kanban_configure.PNG +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/.coveragerc +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/.github/workflows/ci.yml +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/.gitignore +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/.isort.cfg +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/.pre-commit-config.yaml +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/.readthedocs.yml +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/AUTHORS.md +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/CONTRIBUTING.md +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/LICENSE.txt +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/Makefile +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/_static/.gitignore +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/authors.md +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/changelog.md +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/conf.py +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/contributing.md +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/index.md +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/license.md +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/readme.md +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/docs/requirements.txt +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/images/image_config.PNG +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/images/image_kanban_init.PNG +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/images/image_scan_table.PNG +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/images/image_scan_view.PNG +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/images/image_task_example.PNG +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/pyproject.toml +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/setup.py +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python/__init__.py +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python/cli_parser.py +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python/constants.py +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python.egg-info/SOURCES.txt +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python.egg-info/dependency_links.txt +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python.egg-info/entry_points.txt +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python.egg-info/not-zip-safe +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/src/kanban_python.egg-info/top_level.txt +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/tests/conftest.py +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/tests/test_config.py +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/tests/test_interface.py +0 -0
- {kanban-python-0.3.2 → kanban-python-0.3.4}/tox.ini +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Version 0.3.4
|
|
4
|
+
- Bug fix: default separator for `settings.scanner` Pattern setting was space separated not comma separated
|
|
5
|
+
- Fix Image for kanban configure to show right Pattern
|
|
6
|
+
|
|
7
|
+
## Version 0.3.3
|
|
8
|
+
- Push lower bound Version of `platformdirs` dependency to be 3 or higher to include `ensure_exists` argument
|
|
9
|
+
in `user_data_dir` and `user_config_dir`.
|
|
10
|
+
- Update User Action Options with new option `Show Task Details`
|
|
11
|
+
- Change coloring and order of User Actions
|
|
12
|
+
- Added another Menu to configure settings when using `[6] Show Current Settings` or `kanban configure`
|
|
13
|
+
- Update DOCS/README and Images
|
|
14
|
+
- Bugfix for data type of min col width setter
|
|
15
|
+
|
|
3
16
|
## Version 0.3.2
|
|
4
17
|
- Add `^D` besides `^C` as option to close app (on windows pwsh its `^Z`).
|
|
5
18
|
- App closes now on `KeyboardInterrupt` and `EOFError`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kanban-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Terminal Kanban App written in Python
|
|
5
5
|
Home-page: https://github.com/Zaloog/kanban-python
|
|
6
6
|
Author: Zaloog
|
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
|
15
15
|
License-File: LICENSE.txt
|
|
16
16
|
Requires-Dist: importlib-metadata; python_version < "3.8"
|
|
17
17
|
Requires-Dist: rich>=13.7.0
|
|
18
|
-
Requires-Dist: platformdirs<4
|
|
18
|
+
Requires-Dist: platformdirs<4,>=3
|
|
19
19
|
Provides-Extra: testing
|
|
20
20
|
Requires-Dist: setuptools; extra == "testing"
|
|
21
21
|
Requires-Dist: pytest; extra == "testing"
|
|
@@ -45,10 +45,12 @@ Welcome to **kanban-python**, your Terminal Kanban-Board Manager.
|
|
|
45
45
|
|
|
46
46
|

|
|
47
47
|
The [clikan] Kanban App inspired me to write
|
|
48
|
-
my own Terminal Kanban Application
|
|
48
|
+
my own Terminal Kanban Application since I preferred a more simple and guided workflow.
|
|
49
49
|
|
|
50
|
-
**kanban-python** also comes with more features
|
|
51
|
-
|
|
50
|
+
**kanban-python** also comes with more features, like custom column creation,
|
|
51
|
+
automatic scanning and customizable config file to support you being productive.
|
|
52
|
+
|
|
53
|
+
This package was developed with [pyscaffold], which provides awesome project templates
|
|
52
54
|
and takes over much of the boilerplate for python packaging.
|
|
53
55
|
It was a great help for developing my first package and I can highly recommend it.
|
|
54
56
|
|
|
@@ -63,7 +65,7 @@ The config path in the table caption and the path for the task files can be foun
|
|
|
63
65
|
- *automated scanning of files for task creation*: kanban-python can scan files of defined types for specific patterns at start of line.
|
|
64
66
|
Check [Automatic Task Creation](#automatic-task-creation) for more Infos.
|
|
65
67
|
|
|
66
|
-
- *configfile*: A `pykanban.ini` file gets created on first initialization in a
|
|
68
|
+
- *configfile*: A `pykanban.ini` file gets created on first initialization in a `kanban-python` folder in your `user_config_dir`-Directory.
|
|
67
69
|
This can be edited manually or within the kanban-python application. It tracks the location for all your created boards. \
|
|
68
70
|

|
|
69
71
|
* `Active_Board`: current board that is shown when using `kanban`-command
|
|
@@ -77,10 +79,10 @@ This can be edited manually or within the kanban-python application. It tracks t
|
|
|
77
79
|
<br />
|
|
78
80
|
|
|
79
81
|
- *storage-file for each board*: Each created board comes with its own name and `pykanban.json` file,
|
|
80
|
-
which stores all tasks for that board. The files are stored in board specific folders under
|
|
82
|
+
which stores all tasks for that board. The files are stored in board specific folders under `$USER_DATA_DIR/kanban-python/kanban_boards/<BOARDNAME>`
|
|
81
83
|
|
|
82
84
|
- *column customization*: kanban-python comes with 5 pre-defined colored columns: [Ready, Doing, Done, Archived, Deleted]
|
|
83
|
-
More column can be added manually in the `pykanban.ini`, also the visibility can be configured.
|
|
85
|
+
More column can be added manually in the `pykanban.ini`, also the visibility can be configured there.
|
|
84
86
|
|
|
85
87
|
- *time-tracking*: for each task it is tracked, how long it was in the
|
|
86
88
|
<span style="color:yellow">Doing</span> column, based on the moments when you update the task status.
|
|
@@ -102,7 +104,7 @@ After Installation of kanban-python, there are 4 commands available:
|
|
|
102
104
|
kanban init
|
|
103
105
|
```
|
|
104
106
|
Is used to create a new kanban board i.e. it asks for a name and then creates a `pykanban.json` file with a Welcome Task.
|
|
105
|
-
On first use of any command, the `pykanban.ini` configfile and the
|
|
107
|
+
On first use of any command, the `pykanban.ini` configfile and the `kanban-python` folder will be created automatically.
|
|
106
108
|

|
|
107
109
|
|
|
108
110
|
### Interact with Tasks/Boards
|
|
@@ -135,9 +137,7 @@ The filepath were the task was found will be added as description of the task.
|
|
|
135
137
|

|
|
136
138
|
|
|
137
139
|
To create a new custom Columns, you have to edit the `pykanban.ini` manually and add a new column name + visibility status
|
|
138
|
-
under the `settings.columns.visible` section. The
|
|
139
|
-
Keep in mind the specific separators for that section.
|
|
140
|
-
I am working on an option to customize those things in the future without the need to manual edit the file.
|
|
140
|
+
under the `settings.columns.visible` section. The other options are all customizable now via the new settings menu.
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
## Feedback and Issues
|
|
@@ -22,10 +22,12 @@ Welcome to **kanban-python**, your Terminal Kanban-Board Manager.
|
|
|
22
22
|
|
|
23
23
|

|
|
24
24
|
The [clikan] Kanban App inspired me to write
|
|
25
|
-
my own Terminal Kanban Application
|
|
25
|
+
my own Terminal Kanban Application since I preferred a more simple and guided workflow.
|
|
26
26
|
|
|
27
|
-
**kanban-python** also comes with more features
|
|
28
|
-
|
|
27
|
+
**kanban-python** also comes with more features, like custom column creation,
|
|
28
|
+
automatic scanning and customizable config file to support you being productive.
|
|
29
|
+
|
|
30
|
+
This package was developed with [pyscaffold], which provides awesome project templates
|
|
29
31
|
and takes over much of the boilerplate for python packaging.
|
|
30
32
|
It was a great help for developing my first package and I can highly recommend it.
|
|
31
33
|
|
|
@@ -40,7 +42,7 @@ The config path in the table caption and the path for the task files can be foun
|
|
|
40
42
|
- *automated scanning of files for task creation*: kanban-python can scan files of defined types for specific patterns at start of line.
|
|
41
43
|
Check [Automatic Task Creation](#automatic-task-creation) for more Infos.
|
|
42
44
|
|
|
43
|
-
- *configfile*: A `pykanban.ini` file gets created on first initialization in a
|
|
45
|
+
- *configfile*: A `pykanban.ini` file gets created on first initialization in a `kanban-python` folder in your `user_config_dir`-Directory.
|
|
44
46
|
This can be edited manually or within the kanban-python application. It tracks the location for all your created boards. \
|
|
45
47
|

|
|
46
48
|
* `Active_Board`: current board that is shown when using `kanban`-command
|
|
@@ -54,10 +56,10 @@ This can be edited manually or within the kanban-python application. It tracks t
|
|
|
54
56
|
<br />
|
|
55
57
|
|
|
56
58
|
- *storage-file for each board*: Each created board comes with its own name and `pykanban.json` file,
|
|
57
|
-
which stores all tasks for that board. The files are stored in board specific folders under
|
|
59
|
+
which stores all tasks for that board. The files are stored in board specific folders under `$USER_DATA_DIR/kanban-python/kanban_boards/<BOARDNAME>`
|
|
58
60
|
|
|
59
61
|
- *column customization*: kanban-python comes with 5 pre-defined colored columns: [Ready, Doing, Done, Archived, Deleted]
|
|
60
|
-
More column can be added manually in the `pykanban.ini`, also the visibility can be configured.
|
|
62
|
+
More column can be added manually in the `pykanban.ini`, also the visibility can be configured there.
|
|
61
63
|
|
|
62
64
|
- *time-tracking*: for each task it is tracked, how long it was in the
|
|
63
65
|
<span style="color:yellow">Doing</span> column, based on the moments when you update the task status.
|
|
@@ -79,7 +81,7 @@ After Installation of kanban-python, there are 4 commands available:
|
|
|
79
81
|
kanban init
|
|
80
82
|
```
|
|
81
83
|
Is used to create a new kanban board i.e. it asks for a name and then creates a `pykanban.json` file with a Welcome Task.
|
|
82
|
-
On first use of any command, the `pykanban.ini` configfile and the
|
|
84
|
+
On first use of any command, the `pykanban.ini` configfile and the `kanban-python` folder will be created automatically.
|
|
83
85
|

|
|
84
86
|
|
|
85
87
|
### Interact with Tasks/Boards
|
|
@@ -112,9 +114,7 @@ The filepath were the task was found will be added as description of the task.
|
|
|
112
114
|

|
|
113
115
|
|
|
114
116
|
To create a new custom Columns, you have to edit the `pykanban.ini` manually and add a new column name + visibility status
|
|
115
|
-
under the `settings.columns.visible` section. The
|
|
116
|
-
Keep in mind the specific separators for that section.
|
|
117
|
-
I am working on an option to customize those things in the future without the need to manual edit the file.
|
|
117
|
+
under the `settings.columns.visible` section. The other options are all customizable now via the new settings menu.
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
## Feedback and Issues
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -81,8 +81,7 @@ def main(args):
|
|
|
81
81
|
controls.create_new_db()
|
|
82
82
|
|
|
83
83
|
if args.command == "configure":
|
|
84
|
-
controls.
|
|
85
|
-
return
|
|
84
|
+
controls.change_settings()
|
|
86
85
|
|
|
87
86
|
if args.command == "scan":
|
|
88
87
|
controls.add_todos_to_board()
|
|
@@ -98,9 +97,11 @@ def main(args):
|
|
|
98
97
|
elif user_input == 3:
|
|
99
98
|
controls.change_kanban_board()
|
|
100
99
|
elif user_input == 4:
|
|
101
|
-
controls.
|
|
100
|
+
controls.show_tasks()
|
|
102
101
|
elif user_input == 5:
|
|
103
|
-
controls.
|
|
102
|
+
controls.delete_kanban_board()
|
|
103
|
+
elif user_input == 6:
|
|
104
|
+
controls.change_settings()
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
def run():
|
|
@@ -84,6 +84,11 @@ class KanbanConfig:
|
|
|
84
84
|
def col_min_width(self) -> int:
|
|
85
85
|
return int(self.config["settings.general"]["Column_Min_Width"])
|
|
86
86
|
|
|
87
|
+
@col_min_width.setter
|
|
88
|
+
def col_min_width(self, new_width: int) -> None:
|
|
89
|
+
self.config["settings.general"]["Column_Min_Width"] = str(new_width)
|
|
90
|
+
self.save()
|
|
91
|
+
|
|
87
92
|
@property
|
|
88
93
|
def kanban_columns_dict(self) -> dict:
|
|
89
94
|
return self.config["settings.columns.visible"]
|
|
@@ -110,10 +115,20 @@ class KanbanConfig:
|
|
|
110
115
|
def scanned_files(self) -> list:
|
|
111
116
|
return self.config["settings.scanner"]["Files"].split(" ")
|
|
112
117
|
|
|
118
|
+
@scanned_files.setter
|
|
119
|
+
def scanned_files(self, new_files_to_scan: str) -> None:
|
|
120
|
+
self.config["settings.scanner"]["Files"] = new_files_to_scan
|
|
121
|
+
self.save()
|
|
122
|
+
|
|
113
123
|
@property
|
|
114
124
|
def scanned_patterns(self) -> list:
|
|
115
125
|
return self.config["settings.scanner"]["Patterns"].split(",")
|
|
116
126
|
|
|
127
|
+
@scanned_patterns.setter
|
|
128
|
+
def scanned_patterns(self, new_patterns_to_scan: str) -> None:
|
|
129
|
+
self.config["settings.scanner"]["Patterns"] = new_patterns_to_scan
|
|
130
|
+
self.save()
|
|
131
|
+
|
|
117
132
|
|
|
118
133
|
cfg = KanbanConfig(path=CONFIG_FILE_PATH)
|
|
119
134
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from json import dump, load
|
|
2
2
|
|
|
3
|
+
from rich.pretty import pprint
|
|
4
|
+
|
|
3
5
|
from .config import (
|
|
4
6
|
cfg,
|
|
5
7
|
check_if_board_name_exists_in_config,
|
|
@@ -12,13 +14,19 @@ from .interface import (
|
|
|
12
14
|
create_config_table,
|
|
13
15
|
create_table,
|
|
14
16
|
input_ask_for_action,
|
|
17
|
+
input_ask_for_action_settings,
|
|
15
18
|
input_ask_for_change_board,
|
|
16
19
|
input_ask_for_delete_board,
|
|
17
20
|
input_ask_for_new_board_name,
|
|
18
21
|
input_ask_which_task_to_update,
|
|
19
|
-
|
|
22
|
+
input_ask_which_tasks_to_show,
|
|
23
|
+
input_change_column_settings,
|
|
24
|
+
input_change_done_limit_settings,
|
|
25
|
+
input_change_files_to_scan_settings,
|
|
26
|
+
input_change_footer_settings,
|
|
27
|
+
input_change_min_col_width_settings,
|
|
28
|
+
input_change_patterns_to_scan_settings,
|
|
20
29
|
input_confirm_add_todos_to_board,
|
|
21
|
-
input_confirm_change_current_settings,
|
|
22
30
|
input_confirm_delete_board,
|
|
23
31
|
input_confirm_set_board_active,
|
|
24
32
|
input_create_new_task,
|
|
@@ -28,9 +36,12 @@ from .utils import (
|
|
|
28
36
|
check_board_name_valid,
|
|
29
37
|
check_if_done_col_leq_X,
|
|
30
38
|
check_if_there_are_visible_tasks_in_board,
|
|
39
|
+
check_scanner_files_valid,
|
|
40
|
+
check_scanner_patterns_valid,
|
|
31
41
|
console,
|
|
32
42
|
current_time_to_str,
|
|
33
43
|
delete_json_file,
|
|
44
|
+
get_tag_id_choices,
|
|
34
45
|
move_first_done_task_to_archive,
|
|
35
46
|
scan_files,
|
|
36
47
|
scan_for_todos,
|
|
@@ -38,6 +49,8 @@ from .utils import (
|
|
|
38
49
|
)
|
|
39
50
|
|
|
40
51
|
|
|
52
|
+
# DB Controls
|
|
53
|
+
#####################################################################################
|
|
41
54
|
def create_new_db() -> None:
|
|
42
55
|
while True:
|
|
43
56
|
while True:
|
|
@@ -82,11 +95,6 @@ def save_db(data):
|
|
|
82
95
|
dump(data, f, ensure_ascii=False, indent=4)
|
|
83
96
|
|
|
84
97
|
|
|
85
|
-
def add_new_task_to_db():
|
|
86
|
-
new_task = input_create_new_task()
|
|
87
|
-
add_tasks_to_db(tasks=new_task)
|
|
88
|
-
|
|
89
|
-
|
|
90
98
|
def add_tasks_to_db(tasks: dict | list[dict]) -> None:
|
|
91
99
|
db_data = read_db()
|
|
92
100
|
if isinstance(tasks, dict):
|
|
@@ -124,39 +132,20 @@ def read_single_board(path):
|
|
|
124
132
|
return data
|
|
125
133
|
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if not check_if_current_active_board_in_board_list():
|
|
134
|
-
console.print(
|
|
135
|
-
"[yellow]Hmm, Something went wrong.[/] "
|
|
136
|
-
+ f"The active board '{cfg.active_board}' is not in the list of boards."
|
|
137
|
-
)
|
|
138
|
-
change_kanban_board()
|
|
139
|
-
show()
|
|
140
|
-
return
|
|
141
|
-
db_data = read_db()
|
|
142
|
-
table = create_table(data=db_data)
|
|
143
|
-
console.print(table)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def change_kanban_board():
|
|
147
|
-
new_active_board = input_ask_for_change_board()
|
|
148
|
-
cfg.active_board = new_active_board
|
|
135
|
+
# User Action Controls
|
|
136
|
+
#####################################################################################
|
|
137
|
+
# Get User Action
|
|
138
|
+
def get_user_action():
|
|
139
|
+
return input_ask_for_action()
|
|
149
140
|
|
|
150
141
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
delete_json_file(board_to_delete_path)
|
|
157
|
-
delete_board_from_config(board_to_delete)
|
|
142
|
+
# Action 1
|
|
143
|
+
def add_new_task_to_db():
|
|
144
|
+
new_task = input_create_new_task()
|
|
145
|
+
add_tasks_to_db(tasks=new_task)
|
|
158
146
|
|
|
159
147
|
|
|
148
|
+
# Action 2
|
|
160
149
|
def update_task_from_db():
|
|
161
150
|
db_data = read_db()
|
|
162
151
|
if not check_if_there_are_visible_tasks_in_board(db_data, cfg.vis_cols):
|
|
@@ -172,21 +161,64 @@ def update_task_from_db():
|
|
|
172
161
|
save_db(data=db_data)
|
|
173
162
|
|
|
174
163
|
|
|
175
|
-
|
|
176
|
-
|
|
164
|
+
# Action 3
|
|
165
|
+
def change_kanban_board():
|
|
166
|
+
new_active_board = input_ask_for_change_board()
|
|
167
|
+
cfg.active_board = new_active_board
|
|
177
168
|
|
|
178
169
|
|
|
179
|
-
|
|
180
|
-
|
|
170
|
+
# Action 4
|
|
171
|
+
def show_tasks():
|
|
172
|
+
db_data = read_db()
|
|
173
|
+
choices = get_tag_id_choices(db_data, cfg.vis_cols)
|
|
174
|
+
selection_criteria = input_ask_which_tasks_to_show(choices)
|
|
175
|
+
for i, task in db_data.items():
|
|
176
|
+
if selection_criteria in [i, task["Tag"]]:
|
|
177
|
+
console.print(
|
|
178
|
+
20 * "[bold blue]#[/]" + f" Task {i} " + 20 * "[bold blue]#[/]"
|
|
179
|
+
)
|
|
180
|
+
pprint(
|
|
181
|
+
{
|
|
182
|
+
key: val
|
|
183
|
+
for key, val in task.items()
|
|
184
|
+
if key in ["Title", "Description", "Tag", "Status"]
|
|
185
|
+
},
|
|
186
|
+
console=console,
|
|
187
|
+
expand_all=True,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Action 5
|
|
192
|
+
def delete_kanban_board():
|
|
193
|
+
board_to_delete = input_ask_for_delete_board()
|
|
194
|
+
if input_confirm_delete_board(board_to_delete):
|
|
195
|
+
board_to_delete_path = cfg.kanban_boards_dict[board_to_delete]
|
|
181
196
|
|
|
197
|
+
delete_json_file(board_to_delete_path)
|
|
198
|
+
delete_board_from_config(board_to_delete)
|
|
182
199
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
200
|
+
|
|
201
|
+
def show():
|
|
202
|
+
if not cfg.kanban_boards:
|
|
203
|
+
console.print(":warning: [red]No Boards created yet[/]:warning:")
|
|
204
|
+
console.print("Use 'kanban init' to create a new kanban board.")
|
|
205
|
+
raise KeyboardInterrupt
|
|
206
|
+
|
|
207
|
+
if not check_if_current_active_board_in_board_list():
|
|
208
|
+
console.print(
|
|
209
|
+
"[yellow]Hmm, Something went wrong.[/] "
|
|
210
|
+
+ f"The active board '{cfg.active_board}' is not in the list of boards."
|
|
211
|
+
)
|
|
212
|
+
change_kanban_board()
|
|
213
|
+
show()
|
|
214
|
+
return
|
|
215
|
+
db_data = read_db()
|
|
216
|
+
table = create_table(data=db_data)
|
|
217
|
+
console.print(table)
|
|
188
218
|
|
|
189
219
|
|
|
220
|
+
# Scan Functionality
|
|
221
|
+
#####################################################################################
|
|
190
222
|
def add_todos_to_board():
|
|
191
223
|
files = scan_files(endings=cfg.scanned_files)
|
|
192
224
|
todos = scan_for_todos(file_paths=files, patterns=cfg.scanned_patterns)
|
|
@@ -215,3 +247,54 @@ def add_todos_to_board():
|
|
|
215
247
|
|
|
216
248
|
todo_task_list.append(new_task)
|
|
217
249
|
add_tasks_to_db(tasks=todo_task_list)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# Config Settings
|
|
253
|
+
#####################################################################################
|
|
254
|
+
def change_settings():
|
|
255
|
+
while True:
|
|
256
|
+
show_settings()
|
|
257
|
+
settings_selection = input_ask_for_action_settings()
|
|
258
|
+
|
|
259
|
+
if settings_selection == 1:
|
|
260
|
+
change_kanban_board()
|
|
261
|
+
|
|
262
|
+
new_min_col_widths = input_change_min_col_width_settings()
|
|
263
|
+
cfg.col_min_width = new_min_col_widths
|
|
264
|
+
|
|
265
|
+
done_limit = input_change_done_limit_settings()
|
|
266
|
+
cfg.done_limit = done_limit
|
|
267
|
+
|
|
268
|
+
footer_visible = input_change_footer_settings()
|
|
269
|
+
cfg.show_footer = "True" if footer_visible else "False"
|
|
270
|
+
|
|
271
|
+
if settings_selection == 2:
|
|
272
|
+
updated_col_config = input_change_column_settings()
|
|
273
|
+
cfg.kanban_columns_dict = updated_col_config
|
|
274
|
+
|
|
275
|
+
if settings_selection == 3:
|
|
276
|
+
while True:
|
|
277
|
+
new_files_to_scan = input_change_files_to_scan_settings()
|
|
278
|
+
if check_scanner_files_valid(new_files_to_scan):
|
|
279
|
+
cfg.scanned_files = new_files_to_scan
|
|
280
|
+
break
|
|
281
|
+
console.print(
|
|
282
|
+
f":warning: '{new_files_to_scan}' is [red]not[/] a valid."
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
while True:
|
|
286
|
+
new_patterns_to_scan = input_change_patterns_to_scan_settings()
|
|
287
|
+
if check_scanner_patterns_valid(new_patterns_to_scan):
|
|
288
|
+
cfg.scanned_patterns = new_patterns_to_scan
|
|
289
|
+
break
|
|
290
|
+
console.print(
|
|
291
|
+
f":warning: '{new_patterns_to_scan}' is [red]not[/] a valid."
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if settings_selection == 4:
|
|
295
|
+
break
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def show_settings():
|
|
299
|
+
settings_table = create_config_table()
|
|
300
|
+
console.print(settings_table)
|
|
@@ -54,14 +54,18 @@ def input_ask_for_action():
|
|
|
54
54
|
console.print(
|
|
55
55
|
"\t[1] :clipboard: [green]Create new Task[/]"
|
|
56
56
|
+ 2 * "\t"
|
|
57
|
-
+ "[2] :clockwise_vertical_arrows: [bold
|
|
57
|
+
+ "[2] :clockwise_vertical_arrows: [bold cornflower_blue]Update/Check Task[/]"
|
|
58
58
|
)
|
|
59
59
|
console.print(
|
|
60
60
|
"\t[3] :bookmark_tabs: [bold yellow]Change Kanban Board[/]"
|
|
61
61
|
+ "\t"
|
|
62
|
-
+ "[4] :
|
|
62
|
+
+ "[4] :magnifying_glass_tilted_left: [bold blue]Show Task Details[/]"
|
|
63
|
+
)
|
|
64
|
+
console.print(
|
|
65
|
+
"\t[5] :cross_mark: [red]Delete Kanban Board[/]"
|
|
66
|
+
+ "\t"
|
|
67
|
+
+ "[6] :hammer_and_wrench: [grey69]Show Current Settings[/]"
|
|
63
68
|
)
|
|
64
|
-
console.print("\t[5] :hammer_and_wrench: [grey69]Show Current Settings[/]")
|
|
65
69
|
action = IntPrompt.ask(
|
|
66
70
|
prompt="Choose wisely :books:",
|
|
67
71
|
choices=[
|
|
@@ -70,6 +74,7 @@ def input_ask_for_action():
|
|
|
70
74
|
"3",
|
|
71
75
|
"4",
|
|
72
76
|
"5",
|
|
77
|
+
"6",
|
|
73
78
|
],
|
|
74
79
|
show_choices=False,
|
|
75
80
|
)
|
|
@@ -185,7 +190,7 @@ def input_ask_which_task_to_update(data: dict) -> str:
|
|
|
185
190
|
id for id, task in data.items() if task["Status"] in cfg.vis_cols
|
|
186
191
|
]
|
|
187
192
|
task_id_to_update = IntPrompt.ask(
|
|
188
|
-
prompt="Which Task to update?",
|
|
193
|
+
prompt="Which Task to update? Select an [[cyan]Id[/]]",
|
|
189
194
|
choices=choice_task_ids,
|
|
190
195
|
show_choices=False,
|
|
191
196
|
)
|
|
@@ -296,31 +301,76 @@ def input_confirm_add_todos_to_board(todos) -> bool:
|
|
|
296
301
|
)
|
|
297
302
|
|
|
298
303
|
|
|
304
|
+
def input_ask_which_tasks_to_show(choices):
|
|
305
|
+
return Prompt.ask(
|
|
306
|
+
prompt="What Task/s to show? Select an [[cyan]Id[/]] or ([orange3]Tag[/])?",
|
|
307
|
+
default=False,
|
|
308
|
+
show_default=False,
|
|
309
|
+
choices=choices,
|
|
310
|
+
show_choices=False,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
299
314
|
# Config Settings
|
|
300
315
|
#####################################################################################
|
|
301
|
-
def input_change_settings():
|
|
302
|
-
updated_col_config = input_change_column_settings()
|
|
303
|
-
cfg.kanban_columns_dict = updated_col_config
|
|
304
316
|
|
|
305
|
-
footer_visible = input_change_footer_settings()
|
|
306
|
-
done_limit = input_change_done_limit_settings()
|
|
307
|
-
cfg.show_footer = "True" if footer_visible else "False"
|
|
308
|
-
cfg.done_limit = done_limit
|
|
309
317
|
|
|
318
|
+
# Ask for Actions
|
|
319
|
+
def input_ask_for_action_settings():
|
|
320
|
+
console.print(
|
|
321
|
+
"[yellow]Not happy with current settings!?[/],"
|
|
322
|
+
+ "which [blue]Section[/] do you want to change :hammer_and_wrench:?"
|
|
323
|
+
)
|
|
324
|
+
console.print(
|
|
325
|
+
"\t[1] :clipboard: [blue]settings.general[/]"
|
|
326
|
+
+ 2 * "\t"
|
|
327
|
+
+ "[2] :eye: [blue]settings.columns.visibility[/]"
|
|
328
|
+
)
|
|
329
|
+
console.print(
|
|
330
|
+
"\t[3] :magnifying_glass_tilted_left: [blue]settings.scanner[/]"
|
|
331
|
+
+ 2 * "\t"
|
|
332
|
+
+ "[4] :cross_mark: [red]Go back to Kanban Board[/]"
|
|
333
|
+
)
|
|
334
|
+
action = IntPrompt.ask(
|
|
335
|
+
prompt="Choose [blue]Section[/], where you want to change the Current Value",
|
|
336
|
+
choices=[
|
|
337
|
+
"1",
|
|
338
|
+
"2",
|
|
339
|
+
"3",
|
|
340
|
+
"4",
|
|
341
|
+
],
|
|
342
|
+
show_choices=False,
|
|
343
|
+
)
|
|
344
|
+
return action
|
|
310
345
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
346
|
+
|
|
347
|
+
# Show current Config Table
|
|
348
|
+
def create_config_table():
|
|
349
|
+
settings_table = Table(
|
|
350
|
+
title=":hammer_and_wrench: [grey69]Settings Overview[/]:hammer_and_wrench:",
|
|
351
|
+
highlight=True,
|
|
352
|
+
show_header=True,
|
|
353
|
+
caption=f"Your config file is located under [light_green]{CONFIG_FILE_PATH}[/]",
|
|
354
|
+
)
|
|
355
|
+
for col in ["Option", "Current Value"]:
|
|
356
|
+
settings_table.add_column(
|
|
357
|
+
header=col,
|
|
358
|
+
header_style="bold",
|
|
359
|
+
justify="left",
|
|
360
|
+
overflow="fold",
|
|
361
|
+
min_width=30,
|
|
318
362
|
)
|
|
319
|
-
|
|
363
|
+
for section in cfg.config:
|
|
364
|
+
if section:
|
|
365
|
+
settings_table.add_section()
|
|
366
|
+
settings_table.add_row(f"[blue]{section}[/]", "")
|
|
367
|
+
for key, val in cfg.config[section].items():
|
|
368
|
+
settings_table.add_row(key, val)
|
|
320
369
|
|
|
321
|
-
return
|
|
370
|
+
return settings_table
|
|
322
371
|
|
|
323
372
|
|
|
373
|
+
# Change settings.general
|
|
324
374
|
def input_change_footer_settings():
|
|
325
375
|
footer_visible = Confirm.ask(
|
|
326
376
|
prompt="Should Footer be visible?",
|
|
@@ -342,34 +392,46 @@ def input_change_done_limit_settings():
|
|
|
342
392
|
return str(done_limit)
|
|
343
393
|
|
|
344
394
|
|
|
345
|
-
def
|
|
346
|
-
|
|
347
|
-
prompt="
|
|
348
|
-
default=
|
|
395
|
+
def input_change_min_col_width_settings():
|
|
396
|
+
new_min_col_width = IntPrompt.ask(
|
|
397
|
+
prompt="What should the minimum Column Width be?",
|
|
398
|
+
default=cfg.col_min_width,
|
|
349
399
|
show_default=True,
|
|
350
400
|
)
|
|
351
401
|
|
|
402
|
+
return new_min_col_width
|
|
352
403
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
header=col,
|
|
363
|
-
header_style="bold",
|
|
364
|
-
justify="left",
|
|
365
|
-
overflow="fold",
|
|
366
|
-
min_width=30,
|
|
404
|
+
|
|
405
|
+
# Change settings.columns.visible
|
|
406
|
+
def input_change_column_settings():
|
|
407
|
+
updated_column_dict = {}
|
|
408
|
+
for col, vis in cfg.kanban_columns_dict.items():
|
|
409
|
+
new_visible = Confirm.ask(
|
|
410
|
+
prompt=f"Should Column {COLOR_DICT.get(col,col)} be visible?",
|
|
411
|
+
default=True if vis == "True" else False,
|
|
412
|
+
show_default=True,
|
|
367
413
|
)
|
|
368
|
-
|
|
369
|
-
if section:
|
|
370
|
-
settings_table.add_section()
|
|
371
|
-
settings_table.add_row(f"[blue]{section}[/]", "")
|
|
372
|
-
for key, val in cfg.config[section].items():
|
|
373
|
-
settings_table.add_row(key, val)
|
|
414
|
+
updated_column_dict[col] = "True" if new_visible else "False"
|
|
374
415
|
|
|
375
|
-
return
|
|
416
|
+
return updated_column_dict
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# Change settings.scanner
|
|
420
|
+
def input_change_files_to_scan_settings():
|
|
421
|
+
files_to_scan = Prompt.ask(
|
|
422
|
+
prompt="Which Files to scan? Enter [green]' '[/] separated File Endings",
|
|
423
|
+
default=" ".join(cfg.scanned_files),
|
|
424
|
+
show_default=True,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
return files_to_scan
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def input_change_patterns_to_scan_settings():
|
|
431
|
+
files_to_scan = Prompt.ask(
|
|
432
|
+
prompt="Which Patterns to scan? Enter [green]','[/] separated Patterns",
|
|
433
|
+
default=",".join(cfg.scanned_patterns),
|
|
434
|
+
show_default=True,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
return files_to_scan
|
|
@@ -136,3 +136,29 @@ def split_todo_in_tag_and_title(todo: str, patterns: list):
|
|
|
136
136
|
title = title[1:].strip() if title.startswith(":") else title
|
|
137
137
|
|
|
138
138
|
return tag.upper(), title
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_tag_id_choices(data_dict: dict, vis_cols: list) -> list:
|
|
142
|
+
valid_ids = [i for i, task in data_dict.items() if task["Status"] in vis_cols]
|
|
143
|
+
valid_tags = [
|
|
144
|
+
task["Tag"] for task in data_dict.values() if task["Status"] in vis_cols
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
valid_choices = list(set(valid_ids + valid_tags))
|
|
148
|
+
return valid_choices
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def check_scanner_files_valid(files: str) -> bool:
|
|
152
|
+
for file in files.split(" "):
|
|
153
|
+
if not file.startswith("."):
|
|
154
|
+
return False
|
|
155
|
+
if not all(char.isalpha() for char in file[1:]):
|
|
156
|
+
return False
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def check_scanner_patterns_valid(patterns: str) -> bool:
|
|
161
|
+
for pattern in patterns.split(","):
|
|
162
|
+
if not pattern.startswith("#"):
|
|
163
|
+
return False
|
|
164
|
+
return True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kanban-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Terminal Kanban App written in Python
|
|
5
5
|
Home-page: https://github.com/Zaloog/kanban-python
|
|
6
6
|
Author: Zaloog
|
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
|
15
15
|
License-File: LICENSE.txt
|
|
16
16
|
Requires-Dist: importlib-metadata; python_version < "3.8"
|
|
17
17
|
Requires-Dist: rich>=13.7.0
|
|
18
|
-
Requires-Dist: platformdirs<4
|
|
18
|
+
Requires-Dist: platformdirs<4,>=3
|
|
19
19
|
Provides-Extra: testing
|
|
20
20
|
Requires-Dist: setuptools; extra == "testing"
|
|
21
21
|
Requires-Dist: pytest; extra == "testing"
|
|
@@ -45,10 +45,12 @@ Welcome to **kanban-python**, your Terminal Kanban-Board Manager.
|
|
|
45
45
|
|
|
46
46
|

|
|
47
47
|
The [clikan] Kanban App inspired me to write
|
|
48
|
-
my own Terminal Kanban Application
|
|
48
|
+
my own Terminal Kanban Application since I preferred a more simple and guided workflow.
|
|
49
49
|
|
|
50
|
-
**kanban-python** also comes with more features
|
|
51
|
-
|
|
50
|
+
**kanban-python** also comes with more features, like custom column creation,
|
|
51
|
+
automatic scanning and customizable config file to support you being productive.
|
|
52
|
+
|
|
53
|
+
This package was developed with [pyscaffold], which provides awesome project templates
|
|
52
54
|
and takes over much of the boilerplate for python packaging.
|
|
53
55
|
It was a great help for developing my first package and I can highly recommend it.
|
|
54
56
|
|
|
@@ -63,7 +65,7 @@ The config path in the table caption and the path for the task files can be foun
|
|
|
63
65
|
- *automated scanning of files for task creation*: kanban-python can scan files of defined types for specific patterns at start of line.
|
|
64
66
|
Check [Automatic Task Creation](#automatic-task-creation) for more Infos.
|
|
65
67
|
|
|
66
|
-
- *configfile*: A `pykanban.ini` file gets created on first initialization in a
|
|
68
|
+
- *configfile*: A `pykanban.ini` file gets created on first initialization in a `kanban-python` folder in your `user_config_dir`-Directory.
|
|
67
69
|
This can be edited manually or within the kanban-python application. It tracks the location for all your created boards. \
|
|
68
70
|

|
|
69
71
|
* `Active_Board`: current board that is shown when using `kanban`-command
|
|
@@ -77,10 +79,10 @@ This can be edited manually or within the kanban-python application. It tracks t
|
|
|
77
79
|
<br />
|
|
78
80
|
|
|
79
81
|
- *storage-file for each board*: Each created board comes with its own name and `pykanban.json` file,
|
|
80
|
-
which stores all tasks for that board. The files are stored in board specific folders under
|
|
82
|
+
which stores all tasks for that board. The files are stored in board specific folders under `$USER_DATA_DIR/kanban-python/kanban_boards/<BOARDNAME>`
|
|
81
83
|
|
|
82
84
|
- *column customization*: kanban-python comes with 5 pre-defined colored columns: [Ready, Doing, Done, Archived, Deleted]
|
|
83
|
-
More column can be added manually in the `pykanban.ini`, also the visibility can be configured.
|
|
85
|
+
More column can be added manually in the `pykanban.ini`, also the visibility can be configured there.
|
|
84
86
|
|
|
85
87
|
- *time-tracking*: for each task it is tracked, how long it was in the
|
|
86
88
|
<span style="color:yellow">Doing</span> column, based on the moments when you update the task status.
|
|
@@ -102,7 +104,7 @@ After Installation of kanban-python, there are 4 commands available:
|
|
|
102
104
|
kanban init
|
|
103
105
|
```
|
|
104
106
|
Is used to create a new kanban board i.e. it asks for a name and then creates a `pykanban.json` file with a Welcome Task.
|
|
105
|
-
On first use of any command, the `pykanban.ini` configfile and the
|
|
107
|
+
On first use of any command, the `pykanban.ini` configfile and the `kanban-python` folder will be created automatically.
|
|
106
108
|

|
|
107
109
|
|
|
108
110
|
### Interact with Tasks/Boards
|
|
@@ -135,9 +137,7 @@ The filepath were the task was found will be added as description of the task.
|
|
|
135
137
|

|
|
136
138
|
|
|
137
139
|
To create a new custom Columns, you have to edit the `pykanban.ini` manually and add a new column name + visibility status
|
|
138
|
-
under the `settings.columns.visible` section. The
|
|
139
|
-
Keep in mind the specific separators for that section.
|
|
140
|
-
I am working on an option to customize those things in the future without the need to manual edit the file.
|
|
140
|
+
under the `settings.columns.visible` section. The other options are all customizable now via the new settings menu.
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
## Feedback and Issues
|
|
@@ -159,6 +159,26 @@ def test_split_todo_in_tag_and_title(todo, pattern, expected_result):
|
|
|
159
159
|
assert title == expected_result[1]
|
|
160
160
|
|
|
161
161
|
|
|
162
|
+
@pytest.mark.parametrize(
|
|
163
|
+
"files, expected_result",
|
|
164
|
+
[(".md .py", True), (".py md", False), (".py .d3", False)],
|
|
165
|
+
)
|
|
166
|
+
def test_check_scanner_files_valid(files, expected_result):
|
|
167
|
+
result = utils.check_scanner_files_valid(files)
|
|
168
|
+
|
|
169
|
+
assert result is expected_result
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.mark.parametrize(
|
|
173
|
+
"patterns, expected_result",
|
|
174
|
+
[("# TODO,#TODO", True), ("TODO,# BUG", False), ("TODO BUG", False)],
|
|
175
|
+
)
|
|
176
|
+
def test_check_scanner_patterns_valid(patterns, expected_result):
|
|
177
|
+
result = utils.check_scanner_patterns_valid(patterns)
|
|
178
|
+
|
|
179
|
+
assert result is expected_result
|
|
180
|
+
|
|
181
|
+
|
|
162
182
|
# def test_main(capsys):
|
|
163
183
|
# """CLI Tests"""
|
|
164
184
|
# # capsys is a pytest fixture that allows asserts against stdout/stderr
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|