From 5cff52a9f6deaf2c9b81bb7d0f566bfc675d348d Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Thu, 9 May 2024 22:46:21 +0200 Subject: [PATCH 01/24] Set utility functions in different files --- clir/cli.py | 2 +- clir/command.py | 107 ++++------------------------------------- clir/utils/__init__.py | 0 clir/utils/core.py | 50 +++++++++++++++++++ clir/utils/filters.py | 31 ++++++++++++ clir/utils/system.py | 17 +++++++ 6 files changed, 108 insertions(+), 99 deletions(-) create mode 100644 clir/utils/__init__.py create mode 100644 clir/utils/core.py create mode 100644 clir/utils/filters.py create mode 100644 clir/utils/system.py diff --git a/clir/cli.py b/clir/cli.py index 0ba2df7..6ba811e 100644 --- a/clir/cli.py +++ b/clir/cli.py @@ -37,7 +37,7 @@ def init(): else: print(f'A clir environment already exists in "{dir_path}".') -@cli.command(help="Save new command 💾") +@cli.command(help="Save new commandssss 💾") @click.option('-c', '--command', help="Command to be saved", prompt=True) @click.option('-d', '--description', help="Description of the command", prompt=True) @click.option('-t', '--tag', help="Tag to be associated with the command", prompt=True) diff --git a/clir/command.py b/clir/command.py index 84054f6..3b671db 100644 --- a/clir/command.py +++ b/clir/command.py @@ -9,6 +9,8 @@ from rich.console import Console from rich.table import Table from rich.prompt import Prompt +from clir.utils.system import verify_xclip_installation +from clir.utils.core import get_commands, replace_arguments class Command: def __init__(self, command: str = "", description: str = "", tag: str = ""): @@ -24,7 +26,7 @@ def __repr__(self): return f"{self.command} {self.description} {self.tag}" def save_command(self): - current_commands = _get_commands() + current_commands = get_commands() command = self.command desc = self.description @@ -46,7 +48,7 @@ def save_command(self): #Create class Table class CommandTable: def __init__(self, tag: str = "", grep: str = ""): - self.commands = _get_commands(tag = tag, grep = grep) + self.commands = get_commands(tag = tag, grep = grep) self.tag = tag self.grep = grep @@ -99,7 +101,7 @@ def run_command(self): if current_commands[c]["uid"] == uid: command = c - command = _replace_arguments(command) + command = replace_arguments(command) if uid and command: print(f'[bold green]Running command:[/bold green] {command}') subprocess.Popen(['bash', '-ic', 'set -o history; history -s "$1"', '_', command]) @@ -118,14 +120,14 @@ def copy_command(self): print(f'Copying command: {command}') if platform.system() == "Darwin": # Verify that pbcopy is installed - if _verify_installation(package = "pbcopy"): + if verify_xclip_installation(package = "pbcopy"): os.system(f'echo -n "{command}" | pbcopy') else: print("pbcopy is not installed, this command needs pbcopy to work properly") return elif platform.system() == "Linux": # Verify that xclip is installed - if _verify_installation(package = "xclip"): + if verify_xclip_installation(package = "xclip"): os.system(f'echo -n "{command}" | xclip -selection clipboard') else: print("xclip is not installed, this command needs xclip to work properly") @@ -158,7 +160,7 @@ def remove_command(self): json_file_path = os.path.join(os.path.expanduser('~'), '.clir/commands.json') uid = self.get_command_uid() - all_commands = _get_commands() + all_commands = get_commands() del_command = "" for command in self.commands: @@ -190,95 +192,4 @@ def get_command_uid(self): except ValueError: print("ID must be an integer") - return "" - -def _verify_installation(package: str = ""): - if package == "xclip": - try: - subprocess.run(["xclip", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return True - except: - return False - if package == "pbcopy": - try: - subprocess.run(["pbcopy", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return True - except: - return False - - return "No package specified" - -def _filter_by_tag(commands: dict = {}, tag: str = ""): - if commands: - current_commands = commands - else: - current_commands = _get_commands() - - tag_commands = {} - for command in current_commands: - if current_commands[command]["tag"] == tag: - tag_commands[command] = current_commands[command] - - return tag_commands - -def _filter_by_grep(commands: dict = {}, grep: str = ""): - if commands: - current_commands = commands - else: - current_commands = _get_commands() - - grep_commands = {} - pattern = grep - for command in current_commands: - text = command + " " + current_commands[command]["description"]# + " " + current_commands[command]["tag"] - match = re.findall(pattern, text, re.IGNORECASE) - if match: - grep_commands[command] = current_commands[command] - - return grep_commands - -# Create a function that returns all commands -def _get_commands(tag: str = "", grep: str = ""): - current_commands = "" - json_file_path = os.path.join(os.path.expanduser('~'), '.clir/commands.json') - - try: - with open(json_file_path, 'r') as json_file: - current_commands = json.load(json_file) - except FileNotFoundError: - return [] - - if tag: - current_commands = _filter_by_tag(commands=current_commands, tag=tag) - if grep: - current_commands = _filter_by_grep(commands=current_commands, grep=grep) - - sorted_commands = dict(sorted(current_commands.items(), key=lambda item: item[1]["tag"])) - - return sorted_commands - -def _get_user_input(arg): - return input(f"Enter value for '{arg}': ") - -def _replace_arguments(command): - # Use regex to find all arguments with underscores - matches = re.findall(r'_\w+', command) - - # Check that all arguments are unique - if len(matches) != len(set(matches)): - print("[bold red]Make sure that all arguments are unique[/bold red]") - return None - - # Prompt the user for values for each argument - replacements = {arg: _get_user_input(arg) for arg in matches} - - # Split the command into a list - command_list = command.split(" ") - - # Replace arguments in the command - for arg, value in replacements.items(): - for indx,term in enumerate(command_list): - if arg == term: - command_list[indx] = value - - return " ".join(command_list) \ No newline at end of file + return "" \ No newline at end of file diff --git a/clir/utils/__init__.py b/clir/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clir/utils/core.py b/clir/utils/core.py new file mode 100644 index 0000000..b56370e --- /dev/null +++ b/clir/utils/core.py @@ -0,0 +1,50 @@ +import os +import re +import json +import clir.utils.filters as filters + +# Create a function that returns all commands +def get_commands(tag: str = "", grep: str = ""): + current_commands = "" + json_file_path = os.path.join(os.path.expanduser('~'), '.clir/commands.json') + + try: + with open(json_file_path, 'r') as json_file: + current_commands = json.load(json_file) + except FileNotFoundError: + return [] + + if tag: + current_commands = filters.filter_by_tag(commands=current_commands, tag=tag) + if grep: + current_commands = filters.filter_by_grep(commands=current_commands, grep=grep) + + sorted_commands = dict(sorted(current_commands.items(), key=lambda item: item[1]["tag"])) + + return sorted_commands + +def get_user_input(arg): + return input(f"Enter value for '{arg}': ") + +def replace_arguments(command): + # Use regex to find all arguments with underscores + matches = re.findall(r'_\w+', command) + + # Check that all arguments are unique + if len(matches) != len(set(matches)): + print("[bold red]Make sure that all arguments are unique[/bold red]") + return None + + # Prompt the user for values for each argument + replacements = {arg: get_user_input(arg) for arg in matches} + + # Split the command into a list + command_list = command.split(" ") + + # Replace arguments in the command + for arg, value in replacements.items(): + for indx,term in enumerate(command_list): + if arg == term: + command_list[indx] = value + + return " ".join(command_list) \ No newline at end of file diff --git a/clir/utils/filters.py b/clir/utils/filters.py new file mode 100644 index 0000000..310e11a --- /dev/null +++ b/clir/utils/filters.py @@ -0,0 +1,31 @@ +import re +import clir.utils.core as core + +def filter_by_tag(commands: dict = {}, tag: str = ""): + if commands: + current_commands = commands + else: + current_commands = core.get_commands() + + tag_commands = {} + for command in current_commands: + if current_commands[command]["tag"] == tag: + tag_commands[command] = current_commands[command] + + return tag_commands + +def filter_by_grep(commands: dict = {}, grep: str = ""): + if commands: + current_commands = commands + else: + current_commands = core.get_commands() + + grep_commands = {} + pattern = grep + for command in current_commands: + text = command + " " + current_commands[command]["description"]# + " " + current_commands[command]["tag"] + match = re.findall(pattern, text, re.IGNORECASE) + if match: + grep_commands[command] = current_commands[command] + + return grep_commands \ No newline at end of file diff --git a/clir/utils/system.py b/clir/utils/system.py new file mode 100644 index 0000000..2801b56 --- /dev/null +++ b/clir/utils/system.py @@ -0,0 +1,17 @@ +import subprocess + +def verify_xclip_installation(package: str = ""): + if package == "xclip": + try: + subprocess.run(["xclip", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except: + return False + if package == "pbcopy": + try: + subprocess.run(["pbcopy", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except: + return False + + return "No package specified" \ No newline at end of file From 21c4d4c1efa9cb77ea406504c928850418636c25 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 23 Jun 2024 19:18:15 +0200 Subject: [PATCH 02/24] Add database schema --- clir/config/db/schema.sql | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 clir/config/db/schema.sql diff --git a/clir/config/db/schema.sql b/clir/config/db/schema.sql new file mode 100644 index 0000000..9a83d08 --- /dev/null +++ b/clir/config/db/schema.sql @@ -0,0 +1,32 @@ +PRAGMA user_version = 1; +/*TODO: join date and time in one single field*/ +CREATE TABLE IF NOT EXISTS commands ( + id TEXT PRIMARY KEY, + creation_date DATE NOT NULL, + creation_time TIME NOT NULL, + last_modif_date DATE NOT NULL, + last_modif_time TIME NOT NULL, + note TEXT NOT NULL, + color TEXT +); + +/*TODO: join date and time in one single field*/ +CREATE TABLE IF NOT EXISTS tags ( + id TEXT PRIMARY KEY, + creation_date DATE NOT NULL, + creation_time TIME NOT NULL, + last_modif_date DATE NOT NULL, + last_modif_time TIME NOT NULL, + tag TEXT NOT NULL, + color TEXT NOT NULL +); + +/*TODO: change schema so that one command can have multiple tags*/ +/*TODO: join date and time in one single field*/ +CREATE TABLE IF NOT EXISTS commands_tags ( + command_id TEXT, + tag_id TEXT, + PRIMARY KEY (command_id, tag_id), + FOREIGN KEY (command_id) REFERENCES notes(id), + FOREIGN KEY (tag_id) REFERENCES tags(id) +); From 6b5821c7b3c4c0a1cbfc7f5cae8aedd0518c436a Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 23 Jun 2024 19:18:36 +0200 Subject: [PATCH 03/24] Add json bakckup and db creation functions --- clir/utils/db.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 clir/utils/db.py diff --git a/clir/utils/db.py b/clir/utils/db.py new file mode 100644 index 0000000..8123fba --- /dev/null +++ b/clir/utils/db.py @@ -0,0 +1,50 @@ +import sqlite3 +import uuid +from pathlib import Path +#from forevernotes.utils.objects import Note, Tag, NoteTag +import datetime +import shutil + +# Specify the directory and file name +schema_directory = "clir/config/db/" +schema_file_name = "schema.sql" +db_file_name = "clir.db" +sql_schema_path = Path(schema_directory) / schema_file_name +db_user_version = 1 + +env_path = Path('~').expanduser() / Path(".clir") +db_file = Path(env_path) / db_file_name + +# TODO: Create script to backup json stored commands before migration +def back_json_commands(): + source_file = f"{env_path}/commands.json" + destination_file = f"{env_path}/commands.json.backup" + + shutil.copyfile(source_file, destination_file) + + print(f"Backup of json commands stored in {source_file} to {destination_file} complete.") + +# Creates the DB from the schema file +def create_database(database_name = db_file, schema_file = sql_schema_path): + # Connect to the SQLite database (or create it if it doesn't exist) + conn = sqlite3.connect(database_name) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Read and execute SQL statements from the schema file + with open(schema_file, 'r') as schema_file: + schema_sql = schema_file.read() + #print(schema_sql) + cursor.executescript(schema_sql) + + # Commit the changes + conn.commit() + + # Close the cursor and connection + cursor.close() + conn.close() + +# TODO: Create migration script from json stored commands to database +def migrate_json_to_sqlite(): + pass \ No newline at end of file From 425588272ddb581f536d3b630dcff005d714895c Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 7 Jul 2024 09:29:53 +0000 Subject: [PATCH 04/24] Change system lib file name to config --- clir/command.py | 2 +- clir/utils/config.py | 105 +++++++++++++++++++++++++++++++++++++++++++ clir/utils/system.py | 17 ------- 3 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 clir/utils/config.py delete mode 100644 clir/utils/system.py diff --git a/clir/command.py b/clir/command.py index e556948..74601e3 100644 --- a/clir/command.py +++ b/clir/command.py @@ -9,7 +9,7 @@ from rich.console import Console from rich.table import Table from rich.prompt import Prompt -from clir.utils.system import verify_xclip_installation +from clir.utils.config import verify_xclip_installation from clir.utils.core import get_commands, replace_arguments class Command: diff --git a/clir/utils/config.py b/clir/utils/config.py new file mode 100644 index 0000000..1c1968f --- /dev/null +++ b/clir/utils/config.py @@ -0,0 +1,105 @@ +import os +import shutil +import subprocess +from pathlib import Path +from clir.utils.db import create_database + +config_directory = "clir/config/" +env_path = Path('~').expanduser() / Path(".clir") + +def check_config(): + dir_path = os.path.join(os.path.expanduser('~'), '.clir') + db_file_path = os.path.join(dir_path, 'clir.db') + config_file_path = os.path.join(dir_path, 'clir.conf') + + return os.path.exists(db_file_path) and os.path.exists(config_file_path) + +# TODO: Create script to backup json stored commands before migration +def back_json_commands(): + source_file = f"{env_path}/commands.json" + destination_file = f"{env_path}/commands.json.backup" + + shutil.copyfile(source_file, destination_file) + + print(f"Backup of json commands stored in {source_file} to {destination_file} complete.") + +# TODO: Create migration script from json stored commands to database +def migrate_json_to_sqlite(): + if os.path.exists(f"{env_path}/commands.json"): + print("Migrating json stored commands to sqlite database...") + back_json_commands() + + + +def create_config_files(): + dir_path = os.path.join(os.path.expanduser('~'), '.clir') + os.makedirs(dir_path, exist_ok=True) + + # Define the file path and name + files = ['commands.json'] + + # Check if the file already exists + for file in files: + file_path = os.path.join(dir_path, file) + if not os.path.exists(file_path): + # Create the file + with open(file_path, 'w') as file_object: + file_object.write('{}') + + print(f'File "{file_path}" created successfully.') + else: + print(f'A clir environment already exists in "{dir_path}".') + +def copy_config_files(): + dir_path = os.path.join(os.path.expanduser('~'), '.clir') + os.makedirs(dir_path, exist_ok=True) + + # Define the file path and name + files = ['clir.conf'] + + # Check if the file already exists + for file in files: + + source_file = f"{config_directory}/{file}" + destination_file = f"{env_path}/{file}" + + shutil.copyfile(source_file, destination_file) + print(f"Copying {file} file to {env_path}") + +def init_config(): + if not check_config(): + dir_path = os.path.join(os.path.expanduser('~'), '.clir') + os.makedirs(dir_path, exist_ok=True) + + print("Setting up initial configuration") + print("Creating database...") + create_database() + print("Database created successfully") + + print("Creating config files...") + create_config_files() + + print("Copying other config files...") + copy_config_files() + + + if not check_config(): + print("Could not set the initial configuration Up.") + return + + +def verify_xclip_installation(package: str = ""): + if package == "xclip": + try: + subprocess.run(["xclip", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except: + return False + if package == "pbcopy": + try: + subprocess.run(["pbcopy", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except: + return False + + return "No package specified" \ No newline at end of file diff --git a/clir/utils/system.py b/clir/utils/system.py deleted file mode 100644 index 2801b56..0000000 --- a/clir/utils/system.py +++ /dev/null @@ -1,17 +0,0 @@ -import subprocess - -def verify_xclip_installation(package: str = ""): - if package == "xclip": - try: - subprocess.run(["xclip", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return True - except: - return False - if package == "pbcopy": - try: - subprocess.run(["pbcopy", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return True - except: - return False - - return "No package specified" \ No newline at end of file From 31305fbdd1ac802443516b1cdd6d0d33048eb54d Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 7 Jul 2024 09:30:41 +0000 Subject: [PATCH 05/24] Configure initial configuration as functions --- clir/cli.py | 57 ++++++++++++++++++++++--------------------- clir/config/clir.conf | 1 + 2 files changed, 30 insertions(+), 28 deletions(-) create mode 100644 clir/config/clir.conf diff --git a/clir/cli.py b/clir/cli.py index 5d6cdc2..10667ea 100644 --- a/clir/cli.py +++ b/clir/cli.py @@ -4,53 +4,49 @@ from rich.prompt import Prompt from clir.command import Command from clir.command import CommandTable +from clir.utils.db import create_database +from clir.utils.config import check_config, init_config @click.group() def cli(): pass -def check_config(): - dir_path = os.path.join(os.path.expanduser('~'), '.clir') - file_path = os.path.join(dir_path, 'commands.json') - - return os.path.exists(file_path) +#def check_config(): +# dir_path = os.path.join(os.path.expanduser('~'), '.clir') +# file_path = os.path.join(dir_path, 'commands.json') +# return os.path.exists(file_path) #--------------------------------------- CLI commands ------------------------------------------------------- -@cli.command(help="Clir initial configuration 🛠️") -def init(): - dir_path = os.path.join(os.path.expanduser('~'), '.clir') - os.makedirs(dir_path, exist_ok=True) +#@cli.command(help="Clir initial configuration 🛠️") +#def init(): +# dir_path = os.path.join(os.path.expanduser('~'), '.clir') +# os.makedirs(dir_path, exist_ok=True) # Define the file path and name - files = ['commands.json', 'credentials.json'] +# files = ['commands.json', 'credentials.json'] # Check if the file already exists - for file in files: - file_path = os.path.join(dir_path, file) - if not os.path.exists(file_path): - # Create the file - with open(file_path, 'w') as file_object: - file_object.write('{}') +# for file in files: +# file_path = os.path.join(dir_path, file) +# if not os.path.exists(file_path): +# # Create the file +# with open(file_path, 'w') as file_object: +# file_object.write('{}') + +# print(f'File "{file_path}" created successfully.') +# else: +# print(f'A clir environment already exists in "{dir_path}".') + +# create_database() - print(f'File "{file_path}" created successfully.') - else: - print(f'A clir environment already exists in "{dir_path}".') @cli.command(help="Save new command 💾") @click.option('-c', '--command', help="Command to be saved", prompt=True) @click.option('-d', '--description', help="Description of the command", prompt=True) @click.option('-t', '--tag', help="Tag to be associated with the command", prompt=True) def new(command, description, tag): - if not check_config(): - print("The initial configuration is not set. Executing 'clir init'...") - subprocess.run(["clir", "init"]) - - # Check again after executing 'clir init' - if not check_config(): - print("Could not set the initial configuration. Unable to add the new command.") - return - + init_config() new_command = Command(command = command, description = description, tag = tag) new_command.save_command() @@ -58,6 +54,7 @@ def new(command, description, tag): @click.option('-t', '--tag', help="Search by tag") @click.option('-g', '--grep', help="Search by grep") def rm(tag: str = "", grep: str = ""): + init_config() table = CommandTable(tag=tag, grep=grep) table.remove_command() @@ -65,6 +62,7 @@ def rm(tag: str = "", grep: str = ""): @click.option('-t', '--tag', help="Search by tag") @click.option('-g', '--grep', help="Search by grep") def ls(tag: str = "", grep: str = ""): + init_config() table = CommandTable(tag=tag, grep=grep) table.show_table() @@ -72,6 +70,7 @@ def ls(tag: str = "", grep: str = ""): @click.option('-t', '--tag', help="Search by tag") @click.option('-g', '--grep', help="Search by grep") def run(tag: str = "", grep: str = ""): + init_config() table = CommandTable(tag=tag, grep=grep) table.run_command() @@ -79,11 +78,13 @@ def run(tag: str = "", grep: str = ""): @click.option('-t', '--tag', help="Search by tag") @click.option('-g', '--grep', help="Search by grep") def cp(tag: str = "", grep: str = ""): + init_config() table = CommandTable(tag=tag, grep=grep) table.copy_command() @cli.command(help="Show tags 🏷️") @click.option('-g', '--grep', help="Search by grep") def tags(grep: str = ""): + init_config() table = CommandTable(grep=grep) table.show_tags() \ No newline at end of file diff --git a/clir/config/clir.conf b/clir/config/clir.conf new file mode 100644 index 0000000..0c3c67e --- /dev/null +++ b/clir/config/clir.conf @@ -0,0 +1 @@ +Example \ No newline at end of file From 725dc69b9e4e5fa2791ce0f3936ea4fcbc8c0fa5 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 7 Jul 2024 09:31:05 +0000 Subject: [PATCH 06/24] Move db migration functions to config lib --- clir/utils/db.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/clir/utils/db.py b/clir/utils/db.py index 8123fba..b275297 100644 --- a/clir/utils/db.py +++ b/clir/utils/db.py @@ -15,14 +15,6 @@ env_path = Path('~').expanduser() / Path(".clir") db_file = Path(env_path) / db_file_name -# TODO: Create script to backup json stored commands before migration -def back_json_commands(): - source_file = f"{env_path}/commands.json" - destination_file = f"{env_path}/commands.json.backup" - - shutil.copyfile(source_file, destination_file) - - print(f"Backup of json commands stored in {source_file} to {destination_file} complete.") # Creates the DB from the schema file def create_database(database_name = db_file, schema_file = sql_schema_path): @@ -45,6 +37,3 @@ def create_database(database_name = db_file, schema_file = sql_schema_path): cursor.close() conn.close() -# TODO: Create migration script from json stored commands to database -def migrate_json_to_sqlite(): - pass \ No newline at end of file From beca72afc90381e74d702db11c956d245468342f Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 7 Jul 2024 13:55:54 +0000 Subject: [PATCH 07/24] Update db schema --- clir/config/db/schema.sql | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/clir/config/db/schema.sql b/clir/config/db/schema.sql index 9a83d08..715d4d3 100644 --- a/clir/config/db/schema.sql +++ b/clir/config/db/schema.sql @@ -2,23 +2,18 @@ PRAGMA user_version = 1; /*TODO: join date and time in one single field*/ CREATE TABLE IF NOT EXISTS commands ( id TEXT PRIMARY KEY, - creation_date DATE NOT NULL, - creation_time TIME NOT NULL, - last_modif_date DATE NOT NULL, - last_modif_time TIME NOT NULL, - note TEXT NOT NULL, - color TEXT + creation_date TEXT NOT NULL, + last_modif_date TEST NOT NULL, + command TEXT NOT NULL, + description TEXT NOT NULL ); /*TODO: join date and time in one single field*/ CREATE TABLE IF NOT EXISTS tags ( id TEXT PRIMARY KEY, - creation_date DATE NOT NULL, - creation_time TIME NOT NULL, - last_modif_date DATE NOT NULL, - last_modif_time TIME NOT NULL, - tag TEXT NOT NULL, - color TEXT NOT NULL + creation_date TEXT NOT NULL, + last_modif_date TEXT NOT NULL, + tag TEXT NOT NULL ); /*TODO: change schema so that one command can have multiple tags*/ From d525d7c69f79ade4cddf35a91b27e4f6c957ef1e Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 7 Jul 2024 14:00:45 +0000 Subject: [PATCH 08/24] refactor: Add UUID base64 conversion functions to clir/utils/core.py This refactoring creates shorter uuids in the DB, which is intended to help readability and debugging. --- clir/utils/core.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/clir/utils/core.py b/clir/utils/core.py index b56370e..492e13b 100644 --- a/clir/utils/core.py +++ b/clir/utils/core.py @@ -47,4 +47,14 @@ def replace_arguments(command): if arg == term: command_list[indx] = value - return " ".join(command_list) \ No newline at end of file + return " ".join(command_list) + +def uuid_to_base64(uuid_obj): + uuid_bytes = uuid_obj.bytes + base64_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('ascii') + return base64_uuid + +def base64_to_uuid(base64_uuid): + padding = '=' * (4 - len(base64_uuid) % 4) # Add padding if necessary + uuid_bytes = base64.urlsafe_b64decode(base64_uuid + padding) + return uuid.UUID(bytes=uuid_bytes) From 181002699006dc7f7b9ca8cebd4c7eacea5216ef Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 3 Aug 2024 20:23:36 +0000 Subject: [PATCH 09/24] Import insert_command --- clir/cli.py | 2 +- clir/command.py | 12 ++++-------- clir/utils/config.py | 14 +++++++++++++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/clir/cli.py b/clir/cli.py index 10667ea..7a04445 100644 --- a/clir/cli.py +++ b/clir/cli.py @@ -4,7 +4,7 @@ from rich.prompt import Prompt from clir.command import Command from clir.command import CommandTable -from clir.utils.db import create_database +from clir.utils.db import create_database, insert_command from clir.utils.config import check_config, init_config @click.group() diff --git a/clir/command.py b/clir/command.py index 74601e3..7c61db4 100644 --- a/clir/command.py +++ b/clir/command.py @@ -11,6 +11,7 @@ from rich.prompt import Prompt from clir.utils.config import verify_xclip_installation from clir.utils.core import get_commands, replace_arguments +from clir.utils.db import insert_command class Command: def __init__(self, command: str = "", description: str = "", tag: str = ""): @@ -32,15 +33,10 @@ def save_command(self): desc = self.description tag = self.tag - json_file_path = os.path.join(os.path.expanduser('~'), '.clir/commands.json') - - uid = uuid.uuid4() - - current_commands[str(command)] = {"description": desc, "tag": tag, "uid": str(uid)} + if not tag: + tag = "clir" - # Write updated data to JSON file - with open(json_file_path, 'w') as json_file: - json.dump(current_commands, json_file) + insert_command(command, desc, tag) print(f'Command saved successfuly') diff --git a/clir/utils/config.py b/clir/utils/config.py index 1c1968f..a60e40d 100644 --- a/clir/utils/config.py +++ b/clir/utils/config.py @@ -3,6 +3,8 @@ import subprocess from pathlib import Path from clir.utils.db import create_database +from clir.utils.core import get_commands +from clir.utils.db import insert_command config_directory = "clir/config/" env_path = Path('~').expanduser() / Path(".clir") @@ -29,7 +31,16 @@ def migrate_json_to_sqlite(): print("Migrating json stored commands to sqlite database...") back_json_commands() - + commands = get_commands() + + for command, data in commands.items(): + print(f"Inserting command: {command}") + print(f"Description: {data['description']}") + print(f"Tag: {data['tag']}") + insert_command(command, data['description'], data['tag']) + + print("Migration complete") + def create_config_files(): dir_path = os.path.join(os.path.expanduser('~'), '.clir') @@ -82,6 +93,7 @@ def init_config(): print("Copying other config files...") copy_config_files() + migrate_json_to_sqlite() if not check_config(): print("Could not set the initial configuration Up.") From 77f8e98b60fbf0698ac2f2a03603556b8d154284 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 3 Aug 2024 20:25:00 +0000 Subject: [PATCH 10/24] Add functions for commands manipulation in DB --- clir/utils/db.py | 225 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 1 deletion(-) diff --git a/clir/utils/db.py b/clir/utils/db.py index b275297..bb8a4df 100644 --- a/clir/utils/db.py +++ b/clir/utils/db.py @@ -1,7 +1,7 @@ import sqlite3 import uuid +import base64 from pathlib import Path -#from forevernotes.utils.objects import Note, Tag, NoteTag import datetime import shutil @@ -15,6 +15,63 @@ env_path = Path('~').expanduser() / Path(".clir") db_file = Path(env_path) / db_file_name +def _uuid_to_base64(uuid_obj): + uuid_bytes = uuid_obj.bytes + base64_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('ascii') + return str(base64_uuid) + + +def _base64_to_uuid(base64_uuid): + padding = '=' * (4 - len(base64_uuid) % 4) # Add padding if necessary + uuid_bytes = base64.urlsafe_b64decode(base64_uuid + padding) + return str(uuid.UUID(bytes=uuid_bytes)) + +def _verify_tag_exists(tag: str): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Check if tag exists + cursor.execute("SELECT * FROM tags WHERE tag = ?", (tag,)) + tag_exists = cursor.fetchone() + + # Close the cursor and connection + cursor.close() + conn.close() + + if not tag_exists: + tag_uuid = _uuid_to_base64(uuid.uuid4()) + + # Insert a new tag into the database + insert_tag(tag = tag, tag_uuid = tag_uuid) + else: + tag_uuid = cursor.execute("SELECT id FROM tags WHERE tag = ?", (tag,)) + + return tag_uuid + +def _verify_command_exists(command: str): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Check if command exists + cursor.execute("SELECT * FROM commands WHERE command = ?", (command,)) + command_exists = cursor.fetchone() + + if command_exists: + command_uuid = cursor.execute("SELECT id FROM commands WHERE command = ?", (command,)) + else: + return False + + # Close the cursor and connection + cursor.close() + conn.close() + + return command_uuid # Creates the DB from the schema file def create_database(database_name = db_file, schema_file = sql_schema_path): @@ -37,3 +94,169 @@ def create_database(database_name = db_file, schema_file = sql_schema_path): cursor.close() conn.close() +# Insert a new command into the database +def insert_command(command: str, description: str, tag: str = ""): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + command_uuid = _uuid_to_base64(uuid.uuid4()) + tag_uuid = "" + + creation_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + last_modif_date = creation_date + + tag_uuid = _verify_tag_exists(tag) + + command_exists = _verify_command_exists(command) + + if command_exists: + modify_command(command, description, tag) + else: + # Insert a new command into the database + cursor.execute("INSERT INTO commands (command, description, id, creation_date, last_modif_date) VALUES (?, ?, ?, ?, ?)", (command, description, command_uuid, creation_date, last_modif_date)) + + # Insert a new command into the commands_tags table + cursor.execute("INSERT INTO commands_tags (command_id, tag_id) VALUES (?, ?)", (command_uuid, tag_uuid)) + + # Commit the changes + conn.commit() + + # Close the cursor and connection + cursor.close() + conn.close() + +# Modify a command in the database +def modify_command(command: str, description: str, tag: str = ""): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + command_uuid = _uuid_to_base64(uuid.uuid4()) + tag_uuid = "" + + last_modif_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + + tag_uuid = _verify_tag_exists(tag) + + # Modify the command in the database + cursor.execute("UPDATE commands SET description = ?, last_modif_date = ? WHERE command = ?", (description, last_modif_date, command)) + + # Modify the command in the commands_tags table + cursor.execute("UPDATE commands_tags SET tag_id = ? WHERE command_id = ?", (tag_uuid, command_uuid)) + + # Commit the changes + conn.commit() + + # Close the cursor and connection + cursor.close() + conn.close() + +# Remove a command from the database +def remove_command(command: str): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Remove a command from the database + cursor.execute("DELETE FROM commands WHERE command = ?", (command,)) + + # Commit the changes + conn.commit() + + # Close the cursor and connection + cursor.close() + conn.close() + +# Get all commands from the database +def get_commands(tag: str = "", grep: str = ""): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get all commands from the database + if tag: + cursor.execute("SELECT * FROM commands WHERE tag = ?", (tag,)) + elif grep: + cursor.execute("SELECT * FROM commands WHERE command LIKE ?", (f"%{grep}%",)) + else: + cursor.execute("SELECT * FROM commands") + + commands = cursor.fetchall() + + # Close the cursor and connection + cursor.close() + conn.close() + + return commands + +# Get all tags from the database +def get_tags(grep: str = ""): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get all tags from the database + if grep: + cursor.execute("SELECT * FROM tags WHERE tag LIKE ?", (f"%{grep}%",)) + else: + cursor.execute("SELECT * FROM tags") + + tags = cursor.fetchall() + + # Close the cursor and connection + cursor.close() + conn.close() + + return tags + +# Insert a new tag into the database +def insert_tag(tag: str, tag_uuid: str = _uuid_to_base64(uuid.uuid4())): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + creation_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + last_modif_date = creation_date + + # Insert a new tag into the database + cursor.execute("INSERT INTO tags (tag, id, creation_date, last_modif_date) VALUES (?, ?, ?, ?)", (tag, tag_uuid, creation_date, last_modif_date)) + + # Commit the changes + conn.commit() + + # Close the cursor and connection + cursor.close() + conn.close() + + return tag_uuid + +# Remove a tag from the database +def remove_tag(tag: str): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Remove a tag from the database + cursor.execute("DELETE FROM tags WHERE tag = ?", (tag,)) + + # Commit the changes + conn.commit() + + # Close the cursor and connection + cursor.close() + conn.close() + From 6af41102bf4fa398f48357fe57ba8d6c270e1a3c Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 3 Aug 2024 22:37:23 +0200 Subject: [PATCH 11/24] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 68bc17f..10fffa7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ __pycache__/ *.py[cod] *$py.class +# MacOS +.DS_Store + # C extensions *.so From a74f6a11f5f5634598ecc9572e4124a56f626bd0 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 3 Aug 2024 23:53:22 +0200 Subject: [PATCH 12/24] Remove json commands file after migration --- clir/utils/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/clir/utils/config.py b/clir/utils/config.py index a60e40d..d3c4477 100644 --- a/clir/utils/config.py +++ b/clir/utils/config.py @@ -39,6 +39,7 @@ def migrate_json_to_sqlite(): print(f"Tag: {data['tag']}") insert_command(command, data['description'], data['tag']) + os.remove(f"{env_path}/commands.json") print("Migration complete") From c70899fd444c59f174a70c0b0a4312c002b107ff Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 4 Aug 2024 15:00:25 +0200 Subject: [PATCH 13/24] Transform db fetched data into json format --- clir/utils/core.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clir/utils/core.py b/clir/utils/core.py index 492e13b..e22f1a5 100644 --- a/clir/utils/core.py +++ b/clir/utils/core.py @@ -2,6 +2,7 @@ import re import json import clir.utils.filters as filters +from clir.utils.db import get_tag_id_from_command_id, get_tag_from_tag_id # Create a function that returns all commands def get_commands(tag: str = "", grep: str = ""): @@ -23,6 +24,21 @@ def get_commands(tag: str = "", grep: str = ""): return sorted_commands +def transform_commands_to_json(commands): + commands_json = {} + + for command in commands: + commands_json[command[3]] = { + "description": command[4], + "tag": get_tag_from_tag_id(get_tag_id_from_command_id(command[0])), + "uuid": command[0], + "creation_date": command[1], + "last_modif_date": command[2] + } + + return commands_json + + def get_user_input(arg): return input(f"Enter value for '{arg}': ") From 9ad13c84f9a47b72b75ecdf77eff3d7874348957 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 4 Aug 2024 15:00:38 +0200 Subject: [PATCH 14/24] Fix --- clir/config/db/schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clir/config/db/schema.sql b/clir/config/db/schema.sql index 715d4d3..6266369 100644 --- a/clir/config/db/schema.sql +++ b/clir/config/db/schema.sql @@ -22,6 +22,6 @@ CREATE TABLE IF NOT EXISTS commands_tags ( command_id TEXT, tag_id TEXT, PRIMARY KEY (command_id, tag_id), - FOREIGN KEY (command_id) REFERENCES notes(id), + FOREIGN KEY (command_id) REFERENCES commands(id), FOREIGN KEY (tag_id) REFERENCES tags(id) ); From 6596732eaf0a1ebb3153868b27d4edfca936eb8b Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 4 Aug 2024 15:02:06 +0200 Subject: [PATCH 15/24] Add fonctions to fetch commands from tags and vice versa --- clir/utils/db.py | 98 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 8 deletions(-) diff --git a/clir/utils/db.py b/clir/utils/db.py index bb8a4df..3d20713 100644 --- a/clir/utils/db.py +++ b/clir/utils/db.py @@ -15,6 +15,7 @@ env_path = Path('~').expanduser() / Path(".clir") db_file = Path(env_path) / db_file_name +# TODO: use uuid and remove base64 conversion def _uuid_to_base64(uuid_obj): uuid_bytes = uuid_obj.bytes base64_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('ascii') @@ -37,10 +38,6 @@ def _verify_tag_exists(tag: str): cursor.execute("SELECT * FROM tags WHERE tag = ?", (tag,)) tag_exists = cursor.fetchone() - # Close the cursor and connection - cursor.close() - conn.close() - if not tag_exists: tag_uuid = _uuid_to_base64(uuid.uuid4()) @@ -48,7 +45,12 @@ def _verify_tag_exists(tag: str): insert_tag(tag = tag, tag_uuid = tag_uuid) else: tag_uuid = cursor.execute("SELECT id FROM tags WHERE tag = ?", (tag,)) + tag_uuid = cursor.fetchone()[0] + # Close the cursor and connection + cursor.close() + conn.close() + return tag_uuid def _verify_command_exists(command: str): @@ -175,7 +177,7 @@ def remove_command(command: str): conn.close() # Get all commands from the database -def get_commands(tag: str = "", grep: str = ""): +def get_commands_db(tag: str = "", grep: str = ""): # Connect to the SQLite database conn = sqlite3.connect(db_file) @@ -184,9 +186,11 @@ def get_commands(tag: str = "", grep: str = ""): # Get all commands from the database if tag: - cursor.execute("SELECT * FROM commands WHERE tag = ?", (tag,)) + command_ids = get_commands_from_tag(tag) + query = "SELECT * FROM commands WHERE id IN ({})".format(','.join('?' for _ in command_ids)) + cursor.execute(query, command_ids) elif grep: - cursor.execute("SELECT * FROM commands WHERE command LIKE ?", (f"%{grep}%",)) + cursor.execute("SELECT * FROM commands WHERE command LIKE ? OR description LIKE ?", (f"%{grep}%", f"%{grep}%")) else: cursor.execute("SELECT * FROM commands") @@ -199,7 +203,7 @@ def get_commands(tag: str = "", grep: str = ""): return commands # Get all tags from the database -def get_tags(grep: str = ""): +def get_tags_db(grep: str = ""): # Connect to the SQLite database conn = sqlite3.connect(db_file) @@ -260,3 +264,81 @@ def remove_tag(tag: str): cursor.close() conn.close() + + +def get_tag_id_from_command_id(command_id): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get the tag_id from the commands_tags table + cursor.execute("SELECT * FROM commands_tags WHERE command_id = ?", (command_id,)) + tag_id = cursor.fetchone() + + # Close the cursor and connection + cursor.close() + conn.close() + + return tag_id + +def get_tag_from_tag_id(tag_id): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + query = "SELECT tag FROM tags WHERE id IN ({})".format(','.join('?' for _ in tag_id)) + cursor.execute(query, tag_id) + + tag = cursor.fetchall()[0][0] + #tags = [row[0] for row in tag] + print(tag) + + # Close the cursor and connection + cursor.close() + conn.close() + + return tag + +def get_tag_id_from_tag(tag): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get the tag_id from the tags table + cursor.execute("SELECT id FROM tags WHERE tag = ?", (tag,)) + tag_id = cursor.fetchone()[0] + + # Close the cursor and connection + cursor.close() + conn.close() + + return tag_id + +def get_command_ids_from_tag_id(tag_id): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get the command_id from the commands_tags table + cursor.execute("SELECT * FROM commands_tags WHERE tag_id = ?", (tag_id,)) + command_ids = cursor.fetchall() + + # Close the cursor and connection + cursor.close() + conn.close() + + return [id[0] for id in command_ids] + +def get_commands_from_tag(tag): + tag_id = get_tag_id_from_tag(tag) + command_id = get_command_ids_from_tag_id(tag_id) + + return command_id \ No newline at end of file From d2c4ce894df46c78322738875d53b34ff914a52b Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 4 Aug 2024 15:02:30 +0200 Subject: [PATCH 16/24] Get commands from db instead of json file --- clir/command.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/clir/command.py b/clir/command.py index 7c61db4..1324219 100644 --- a/clir/command.py +++ b/clir/command.py @@ -10,8 +10,8 @@ from rich.table import Table from rich.prompt import Prompt from clir.utils.config import verify_xclip_installation -from clir.utils.core import get_commands, replace_arguments -from clir.utils.db import insert_command +from clir.utils.core import get_commands, replace_arguments, transform_commands_to_json +from clir.utils.db import insert_command, get_commands_db class Command: def __init__(self, command: str = "", description: str = "", tag: str = ""): @@ -27,7 +27,7 @@ def __repr__(self): return f"{self.command} {self.description} {self.tag}" def save_command(self): - current_commands = get_commands() + current_commands = get_commands_db() command = self.command desc = self.description @@ -44,7 +44,7 @@ def save_command(self): #Create class Table class CommandTable: def __init__(self, tag: str = "", grep: str = ""): - self.commands = get_commands(tag = tag, grep = grep) + self.commands = get_commands_db(tag = tag, grep = grep) self.tag = tag self.grep = grep @@ -56,8 +56,8 @@ def __repr__(self): def show_table(self): - commands = self.commands - + commands = transform_commands_to_json(self.commands) + table = Table(show_lines=True, box=box.ROUNDED, style="grey46") table.add_column("ID 📇", style="white bold", no_wrap=True) table.add_column("Command 💻", style="green bold", no_wrap=True) @@ -157,7 +157,7 @@ def remove_command(self): json_file_path = os.path.join(os.path.expanduser('~'), '.clir/commands.json') uid = self.get_command_uid() - all_commands = get_commands() + all_commands = get_commands_db() del_command = "" for command in self.commands: From 181f837235e846f64325c06248ef608cc4068302 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 4 Aug 2024 16:05:55 +0200 Subject: [PATCH 17/24] Remove unused dependencies --- clir/cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clir/cli.py b/clir/cli.py index 7a04445..806e43c 100644 --- a/clir/cli.py +++ b/clir/cli.py @@ -4,8 +4,7 @@ from rich.prompt import Prompt from clir.command import Command from clir.command import CommandTable -from clir.utils.db import create_database, insert_command -from clir.utils.config import check_config, init_config +from clir.utils.config import init_config @click.group() def cli(): From b054a3c9bc71123291cad1865dc4004f49a61699 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 4 Aug 2024 16:07:27 +0200 Subject: [PATCH 18/24] Fix command copy in macos an adapt rm commad to DB usage --- clir/command.py | 31 ++++++++++------------- clir/utils/config.py | 4 +-- clir/utils/db.py | 59 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 24 deletions(-) diff --git a/clir/command.py b/clir/command.py index 1324219..5f1986e 100644 --- a/clir/command.py +++ b/clir/command.py @@ -11,7 +11,7 @@ from rich.prompt import Prompt from clir.utils.config import verify_xclip_installation from clir.utils.core import get_commands, replace_arguments, transform_commands_to_json -from clir.utils.db import insert_command, get_commands_db +from clir.utils.db import insert_command_db, get_commands_db, remove_command_db, verify_command_id_exists, verify_command_id_tag_relation_exists class Command: def __init__(self, command: str = "", description: str = "", tag: str = ""): @@ -36,7 +36,7 @@ def save_command(self): if not tag: tag = "clir" - insert_command(command, desc, tag) + insert_command_db(command, desc, tag) print(f'Command saved successfuly') @@ -44,7 +44,7 @@ def save_command(self): #Create class Table class CommandTable: def __init__(self, tag: str = "", grep: str = ""): - self.commands = get_commands_db(tag = tag, grep = grep) + self.commands = transform_commands_to_json(get_commands_db(tag = tag, grep = grep)) self.tag = tag self.grep = grep @@ -56,7 +56,7 @@ def __repr__(self): def show_table(self): - commands = transform_commands_to_json(self.commands) + commands = self.commands table = Table(show_lines=True, box=box.ROUNDED, style="grey46") table.add_column("ID 📇", style="white bold", no_wrap=True) @@ -118,7 +118,7 @@ def copy_command(self): if platform.system() == "Darwin": # Verify that pbcopy is installed if verify_xclip_installation(package = "pbcopy"): - os.system(f'echo -n "{command}" | pbcopy') + os.system(f'printf "{command}" | pbcopy') else: print("pbcopy is not installed, this command needs pbcopy to work properly") return @@ -154,23 +154,18 @@ def show_tags(self): # Create a function that deletes a command when passing its uid def remove_command(self): - json_file_path = os.path.join(os.path.expanduser('~'), '.clir/commands.json') uid = self.get_command_uid() - all_commands = get_commands_db() - - del_command = "" - for command in self.commands: - if self.commands[command]["uid"] == uid: - del_command = command - - if uid: - all_commands.pop(str(del_command)) - # Write updated data to JSON file - with open(json_file_path, 'w') as json_file: - json.dump(all_commands, json_file) + remove_command_db(uid) + verify_command_id_exists(uid) + verify_command_id_tag_relation_exists(uid) + if verify_command_id_exists(uid): + print(f'Command not removed') + elif not verify_command_id_exists(uid) and verify_command_id_tag_relation_exists(uid): + print(f'Command removed successfuly but relation to tag not removed') + elif not verify_command_id_exists(uid) and not verify_command_id_tag_relation_exists(uid): print(f'Command removed successfuly') diff --git a/clir/utils/config.py b/clir/utils/config.py index d3c4477..46ae9c3 100644 --- a/clir/utils/config.py +++ b/clir/utils/config.py @@ -4,7 +4,7 @@ from pathlib import Path from clir.utils.db import create_database from clir.utils.core import get_commands -from clir.utils.db import insert_command +from clir.utils.db import insert_command_db config_directory = "clir/config/" env_path = Path('~').expanduser() / Path(".clir") @@ -110,7 +110,7 @@ def verify_xclip_installation(package: str = ""): return False if package == "pbcopy": try: - subprocess.run(["pbcopy", "-version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.run(["which", "pbcopy"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return True except: return False diff --git a/clir/utils/db.py b/clir/utils/db.py index 3d20713..3a0c8df 100644 --- a/clir/utils/db.py +++ b/clir/utils/db.py @@ -97,7 +97,7 @@ def create_database(database_name = db_file, schema_file = sql_schema_path): conn.close() # Insert a new command into the database -def insert_command(command: str, description: str, tag: str = ""): +def insert_command_db(command: str, description: str, tag: str = ""): # Connect to the SQLite database conn = sqlite3.connect(db_file) @@ -159,7 +159,7 @@ def modify_command(command: str, description: str, tag: str = ""): conn.close() # Remove a command from the database -def remove_command(command: str): +def remove_command_db(uid: str): # Connect to the SQLite database conn = sqlite3.connect(db_file) @@ -167,7 +167,8 @@ def remove_command(command: str): cursor = conn.cursor() # Remove a command from the database - cursor.execute("DELETE FROM commands WHERE command = ?", (command,)) + cursor.execute("DELETE FROM commands WHERE id = ?", (uid,)) + cursor.execute("DELETE FROM commands_tags WHERE command_id = ?", (uid,)) # Commit the changes conn.commit() @@ -176,6 +177,57 @@ def remove_command(command: str): cursor.close() conn.close() +def verify_command_id_exists(uid: str): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Check if command exists + cursor.execute("SELECT * FROM commands WHERE id = ?", (uid,)) + command_exists = cursor.fetchone() + + # Close the cursor and connection + cursor.close() + conn.close() + + return True if command_exists else False + +def verify_tag_id_exists(uid: str): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Check if tag exists + cursor.execute("SELECT * FROM tags WHERE id = ?", (uid,)) + tag_exists = cursor.fetchone() + + # Close the cursor and connection + cursor.close() + conn.close() + + return True if tag_exists else False + +def verify_command_id_tag_relation_exists(command_id: str): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Check if command_id exists in the commands_tags table + cursor.execute("SELECT * FROM commands_tags WHERE command_id = ?", (command_id,)) + command_id_exists = cursor.fetchone() + + # Close the cursor and connection + cursor.close() + conn.close() + + return True if command_id_exists else False + # Get all commands from the database def get_commands_db(tag: str = "", grep: str = ""): # Connect to the SQLite database @@ -295,7 +347,6 @@ def get_tag_from_tag_id(tag_id): tag = cursor.fetchall()[0][0] #tags = [row[0] for row in tag] - print(tag) # Close the cursor and connection cursor.close() From 837449434b41ccd5e661bdec14c59e4525d60b2a Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sun, 4 Aug 2024 16:07:32 +0200 Subject: [PATCH 19/24] Typo --- clir/utils/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clir/utils/core.py b/clir/utils/core.py index e22f1a5..d7507f5 100644 --- a/clir/utils/core.py +++ b/clir/utils/core.py @@ -31,7 +31,7 @@ def transform_commands_to_json(commands): commands_json[command[3]] = { "description": command[4], "tag": get_tag_from_tag_id(get_tag_id_from_command_id(command[0])), - "uuid": command[0], + "uid": command[0], "creation_date": command[1], "last_modif_date": command[2] } From 34e85a495be625f39c5732bbff5499a96d37028b Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 10 Aug 2024 17:45:54 +0200 Subject: [PATCH 20/24] Delete comments and fix typos --- clir/config/db/schema.sql | 4 ---- clir/utils/config.py | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/clir/config/db/schema.sql b/clir/config/db/schema.sql index 6266369..69a2494 100644 --- a/clir/config/db/schema.sql +++ b/clir/config/db/schema.sql @@ -1,5 +1,4 @@ PRAGMA user_version = 1; -/*TODO: join date and time in one single field*/ CREATE TABLE IF NOT EXISTS commands ( id TEXT PRIMARY KEY, creation_date TEXT NOT NULL, @@ -8,7 +7,6 @@ CREATE TABLE IF NOT EXISTS commands ( description TEXT NOT NULL ); -/*TODO: join date and time in one single field*/ CREATE TABLE IF NOT EXISTS tags ( id TEXT PRIMARY KEY, creation_date TEXT NOT NULL, @@ -16,8 +14,6 @@ CREATE TABLE IF NOT EXISTS tags ( tag TEXT NOT NULL ); -/*TODO: change schema so that one command can have multiple tags*/ -/*TODO: join date and time in one single field*/ CREATE TABLE IF NOT EXISTS commands_tags ( command_id TEXT, tag_id TEXT, diff --git a/clir/utils/config.py b/clir/utils/config.py index 46ae9c3..e9e83c6 100644 --- a/clir/utils/config.py +++ b/clir/utils/config.py @@ -16,7 +16,6 @@ def check_config(): return os.path.exists(db_file_path) and os.path.exists(config_file_path) -# TODO: Create script to backup json stored commands before migration def back_json_commands(): source_file = f"{env_path}/commands.json" destination_file = f"{env_path}/commands.json.backup" @@ -25,7 +24,6 @@ def back_json_commands(): print(f"Backup of json commands stored in {source_file} to {destination_file} complete.") -# TODO: Create migration script from json stored commands to database def migrate_json_to_sqlite(): if os.path.exists(f"{env_path}/commands.json"): print("Migrating json stored commands to sqlite database...") @@ -37,7 +35,7 @@ def migrate_json_to_sqlite(): print(f"Inserting command: {command}") print(f"Description: {data['description']}") print(f"Tag: {data['tag']}") - insert_command(command, data['description'], data['tag']) + insert_command_db(command, data['description'], data['tag']) os.remove(f"{env_path}/commands.json") print("Migration complete") From fa8d47c8db5ec17287edd5d9b0dc4e399ce63215 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 10 Aug 2024 17:46:39 +0200 Subject: [PATCH 21/24] Add necessary functions and delete base64 uuid conversion --- clir/utils/db.py | 173 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 153 insertions(+), 20 deletions(-) diff --git a/clir/utils/db.py b/clir/utils/db.py index 3a0c8df..4ee0b96 100644 --- a/clir/utils/db.py +++ b/clir/utils/db.py @@ -15,18 +15,6 @@ env_path = Path('~').expanduser() / Path(".clir") db_file = Path(env_path) / db_file_name -# TODO: use uuid and remove base64 conversion -def _uuid_to_base64(uuid_obj): - uuid_bytes = uuid_obj.bytes - base64_uuid = base64.urlsafe_b64encode(uuid_bytes).rstrip(b'=').decode('ascii') - return str(base64_uuid) - - -def _base64_to_uuid(base64_uuid): - padding = '=' * (4 - len(base64_uuid) % 4) # Add padding if necessary - uuid_bytes = base64.urlsafe_b64decode(base64_uuid + padding) - return str(uuid.UUID(bytes=uuid_bytes)) - def _verify_tag_exists(tag: str): # Connect to the SQLite database conn = sqlite3.connect(db_file) @@ -39,7 +27,7 @@ def _verify_tag_exists(tag: str): tag_exists = cursor.fetchone() if not tag_exists: - tag_uuid = _uuid_to_base64(uuid.uuid4()) + tag_uuid = str(uuid.uuid4()) # Insert a new tag into the database insert_tag(tag = tag, tag_uuid = tag_uuid) @@ -104,7 +92,7 @@ def insert_command_db(command: str, description: str, tag: str = ""): # Create a cursor object to interact with the database cursor = conn.cursor() - command_uuid = _uuid_to_base64(uuid.uuid4()) + command_uuid = str(uuid.uuid4()) tag_uuid = "" creation_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] @@ -120,8 +108,8 @@ def insert_command_db(command: str, description: str, tag: str = ""): # Insert a new command into the database cursor.execute("INSERT INTO commands (command, description, id, creation_date, last_modif_date) VALUES (?, ?, ?, ?, ?)", (command, description, command_uuid, creation_date, last_modif_date)) - # Insert a new command into the commands_tags table - cursor.execute("INSERT INTO commands_tags (command_id, tag_id) VALUES (?, ?)", (command_uuid, tag_uuid)) + # Insert a new command into the commands_tags table + cursor.execute("INSERT INTO commands_tags (command_id, tag_id) VALUES (?, ?)", (command_uuid, tag_uuid)) # Commit the changes conn.commit() @@ -130,6 +118,8 @@ def insert_command_db(command: str, description: str, tag: str = ""): cursor.close() conn.close() + return 0 + # Modify a command in the database def modify_command(command: str, description: str, tag: str = ""): # Connect to the SQLite database @@ -138,7 +128,8 @@ def modify_command(command: str, description: str, tag: str = ""): # Create a cursor object to interact with the database cursor = conn.cursor() - command_uuid = _uuid_to_base64(uuid.uuid4()) + command_uuid = cursor.execute("SELECT id FROM commands WHERE command = ?", (command,)) + command_uuid = cursor.fetchone()[0] tag_uuid = "" last_modif_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] @@ -158,6 +149,9 @@ def modify_command(command: str, description: str, tag: str = ""): cursor.close() conn.close() + return 0 + + # Remove a command from the database def remove_command_db(uid: str): # Connect to the SQLite database @@ -277,7 +271,7 @@ def get_tags_db(grep: str = ""): return tags # Insert a new tag into the database -def insert_tag(tag: str, tag_uuid: str = _uuid_to_base64(uuid.uuid4())): +def insert_tag(tag: str, tag_uuid: str = str(uuid.uuid4())): # Connect to the SQLite database conn = sqlite3.connect(db_file) @@ -316,7 +310,22 @@ def remove_tag(tag: str): cursor.close() conn.close() +def get_command_id_from_command(command): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get the command_id from the commands table + cursor.execute("SELECT id FROM commands WHERE command = ?", (command,)) + command_id = cursor.fetchone()[0] + + # Close the cursor and connection + cursor.close() + conn.close() + return command_id def get_tag_id_from_command_id(command_id): # Connect to the SQLite database @@ -357,13 +366,17 @@ def get_tag_from_tag_id(tag_id): def get_tag_id_from_tag(tag): # Connect to the SQLite database conn = sqlite3.connect(db_file) + tag_id = "" # Create a cursor object to interact with the database cursor = conn.cursor() # Get the tag_id from the tags table cursor.execute("SELECT id FROM tags WHERE tag = ?", (tag,)) - tag_id = cursor.fetchone()[0] + try : + tag_id = cursor.fetchone()[0] + except: + pass # Close the cursor and connection cursor.close() @@ -392,4 +405,124 @@ def get_commands_from_tag(tag): tag_id = get_tag_id_from_tag(tag) command_id = get_command_ids_from_tag_id(tag_id) - return command_id \ No newline at end of file + return command_id + +# TODO: pass a garbag collector to remove tags that are not used after removing or modifying a command + +class DbIntegrity: + def __init__(self): + self.commands_ids = "" + self.commands = "" + self.tags_ids = "" + self.tags = "" + + def get_commands_ids(self): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get all commands from the database + cursor.execute("SELECT id FROM commands") + + self.commands_ids = [command_id[0] for command_id in cursor.fetchall()] + + # Close the cursor and connection + cursor.close() + conn.close() + + return self.commands_ids + + def get_commands(self): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get all commands from the database + cursor.execute("SELECT command FROM commands") + + self.commands = [command[0] for command in cursor.fetchall()] + + # Close the cursor and connection + cursor.close() + conn.close() + + return self.commands + + def get_tags_ids(self): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get all tags from the database + cursor.execute("SELECT id FROM tags") + + self.tags_ids = [tag_id[0] for tag_id in cursor.fetchall()] + + # Close the cursor and connection + cursor.close() + conn.close() + + return self.tags_ids + + def get_tags(self): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get all tags from the database + cursor.execute("SELECT tag FROM tags") + + self.tags = [tag[0] for tag in cursor.fetchall()] + + # Close the cursor and connection + cursor.close() + conn.close() + + return self.tags + + def get_commands_ids_relation(self): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get all tags from the database + cursor.execute("SELECT command_id FROM commands_tags") + + command_tag_relation = [command_id[0] for command_id in cursor.fetchall()] + + # Close the cursor and connection + cursor.close() + conn.close() + + return command_tag_relation + + def get_tags_ids_relation(self): + # Connect to the SQLite database + conn = sqlite3.connect(db_file) + + # Create a cursor object to interact with the database + cursor = conn.cursor() + + # Get all tags from the database + cursor.execute("SELECT tag_id FROM commands_tags") + + tag_command_relation = [tag_id[0] for tag_id in cursor.fetchall()] + + # Close the cursor and connection + cursor.close() + conn.close() + + return tag_command_relation + + def main(self): + return self.get_commands_ids(), self.get_commands(), self.get_tags_ids(), self.get_tags(), self.get_commands_ids_relation(), self.get_tags_ids_relation() \ No newline at end of file From a12e40a919f042e9a656e1393704d09ee9a48aa5 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 10 Aug 2024 17:47:12 +0200 Subject: [PATCH 22/24] Add tests --- tests/1-init_clir.py | 60 ++++++++++++++++++++++++++++ tests/2-add_command.py | 83 +++++++++++++++++++++++++++++++++++++++ tests/3-copy_command.py | 45 +++++++++++++++++++++ tests/4-run_command.py | 45 +++++++++++++++++++++ tests/5-remove_command.py | 72 +++++++++++++++++++++++++++++++++ tests/6-list_command.py | 24 +++++++++++ tests/add_command.py | 39 ------------------ tests/copy_command.py | 0 tests/files/commands.json | 1 + tests/init_clir.py | 20 ---------- tests/list_command.py | 0 tests/remove_command.py | 29 -------------- tests/run_command.py | 0 13 files changed, 330 insertions(+), 88 deletions(-) create mode 100644 tests/1-init_clir.py create mode 100644 tests/2-add_command.py create mode 100644 tests/3-copy_command.py create mode 100644 tests/4-run_command.py create mode 100644 tests/5-remove_command.py create mode 100644 tests/6-list_command.py delete mode 100644 tests/add_command.py delete mode 100644 tests/copy_command.py create mode 100644 tests/files/commands.json delete mode 100644 tests/init_clir.py delete mode 100644 tests/list_command.py delete mode 100644 tests/remove_command.py delete mode 100644 tests/run_command.py diff --git a/tests/1-init_clir.py b/tests/1-init_clir.py new file mode 100644 index 0000000..387f88a --- /dev/null +++ b/tests/1-init_clir.py @@ -0,0 +1,60 @@ +import os +import subprocess, json +from clir.cli import ls +from click.testing import CliRunner +from clir.utils.db import DbIntegrity + +dir_path = os.path.expanduser('~/.clir') +db_path = os.path.expanduser('~/.clir/clir.db') + +def setup(): + os.makedirs(dir_path, exist_ok=True) + subprocess.run(["cp", "tests/files/commands.json", f"{dir_path}/commands.json"]) + +# Test if the clir folder environment was created +def test_clir_project_folder_created(): + setup() + runner = CliRunner() + runner.invoke(ls) + assert os.path.exists(dir_path) + +# Test if the commands.json file was created +def test_command_db_created(): + assert os.path.exists(db_path) + + +def test_migration_files(): + assert not os.path.exists(os.path.expanduser('~/.clir/commands.json')) + assert os.path.exists(os.path.expanduser('~/.clir/commands.json.backup')) + +def test_migration(): + json_commands_len, json_commands, json_tags, _ = read_commands_json() + cids, commands, tids, tags, crel, trel = db_integrity_check() + assert json_commands_len == len(commands) + assert set(json_commands) == set(commands) + assert set(json_tags) == set(tags) + assert len(crel) == json_commands_len + assert len(trel) == json_commands_len + +def read_commands_json(): + commands_json = {} + commands = [] + tags = [] + description = [] + + # Read commands.json file and load it in a dictionary + with open(f"{dir_path}/commands.json.backup", 'r') as file: + commands_json = json.load(file) + + counter = 1 + for command, data in commands_json.items(): + commands.append(command) + tags.append(data['tag']) + description.append(data['description']) + + return len(commands), commands, tags, description + +def db_integrity_check(): + db_integrity = DbIntegrity() + #commands_ids, commands, tags_ids, tags, cid_relation (commands ids in commands_tags table), tid_relation (commands ids in commands_tags table) + return db_integrity.main() \ No newline at end of file diff --git a/tests/2-add_command.py b/tests/2-add_command.py new file mode 100644 index 0000000..b5ad24c --- /dev/null +++ b/tests/2-add_command.py @@ -0,0 +1,83 @@ +from clir.cli import new, cp +from clir.utils.db import get_command_id_from_command, get_tag_id_from_tag, DbIntegrity +from click.testing import CliRunner +import pyperclip, os + + +# Test add new command using flags +def test_add_new_command(): + cids, _, tids, _, crel, _ = db_integrity_check() + runner = CliRunner() + result = runner.invoke(new, ['-c', "command 1", '-d', "Command 1 description", '-t', "c1"]) + echo_test_result = runner.invoke(new, ['-c', "touch ~/.clir/test-file.txt", '-d', "Create a test file", '-t', "touch-test"]) + cids2, _, tids2, _, crel2, _ = db_integrity_check() + assert result.exit_code == 0 + assert echo_test_result.exit_code == 0 + + cpresult = runner.invoke(cp, ['-t', "c1"], input='1\n') + assert cpresult.exit_code == 0 + assert pyperclip.paste() == "command 1" + + cpechotestresult = runner.invoke(cp, ['-t', "touch-test"], input='1\n') + assert cpechotestresult.exit_code == 0 + assert pyperclip.paste() == "touch ~/.clir/test-file.txt" + assert os.path.exists(os.path.expanduser('~/.clir/test-file.txt')) == False + assert len(cids) + 2 == len(cids2) + assert len(tids) + 2 == len(tids2) + assert len(crel) + 2 == len(crel2) + +def test_add_new_command_with_prompt(): + cids, _, tids, _, crel, _ = db_integrity_check() + runner = CliRunner() + result = runner.invoke(new, input='\n'.join(["command 2", "Command 2 description", "c2"])) + cpresult = runner.invoke(cp, ['-t', "c2"], input='1\n') + cids2, _, tids2, _, crel2, _ = db_integrity_check() + assert result.exit_code == 0 + assert cpresult.exit_code == 0 + assert pyperclip.paste() == "command 2" + assert len(cids) + 1 == len(cids2) + assert len(tids) + 1 == len(tids2) + assert len(crel) + 1 == len(crel2) + +def test_add_fourth_command_with_prompt(): + cids, _, tids, _, crel, _ = db_integrity_check() + runner = CliRunner() + result = runner.invoke(new, input='\n'.join(["command 3", "Command 3 great description", "c3"])) + cpresult = runner.invoke(cp, ['-t', "c3"], input='1\n') + cids2, _, tids2, _, crel2, _ = db_integrity_check() + assert result.exit_code == 0 + assert cpresult.exit_code == 0 + assert pyperclip.paste() == "command 3" + assert len(cids) + 1 == len(cids2) + assert len(tids) + 1 == len(tids2) + assert len(crel) + 1 == len(crel2) + + +# Check 3 things: +# - That the command that already exists still has his original id +# - That the tag of the command that already exists has the same id +# - That there is no a new entry in the commandd_tags table +def test_add_existing_command(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + old_command_id = get_command_id_from_command("command 1") + old_tag_id = get_tag_id_from_tag("c1") + result = runner.invoke(new, ['-c', "command 1", '-d', "Command 1 description", '-t', "c1"]) + new_command_id = get_command_id_from_command("command 1") + new_tag_id = get_tag_id_from_tag("c1") + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert result.exit_code == 0 + assert old_command_id == new_command_id + assert old_tag_id == new_tag_id + assert len(cids) == len(cids2) + assert len(tids) == len(tids2) + assert len(crel) == len(crel2) + assert set(cids) == set(cids2) + assert set(tids) == set(tids2) + assert set(crel) == set(crel2) + assert set(trel) == set(trel2) + +def db_integrity_check(): + db_integrity = DbIntegrity() + #commands_ids, commands, tags_ids, tags, cid_relation (commands ids in commands_tags table), tid_relation (commands ids in commands_tags table) + return db_integrity.main() diff --git a/tests/3-copy_command.py b/tests/3-copy_command.py new file mode 100644 index 0000000..6748c00 --- /dev/null +++ b/tests/3-copy_command.py @@ -0,0 +1,45 @@ +from clir.cli import cp +from clir.utils.db import get_command_id_from_command, get_tag_id_from_tag, DbIntegrity +from click.testing import CliRunner +import pyperclip, os + +# Check: +# - That the command is stored in the clipboard +def test_copy_command(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + cpresult = runner.invoke(cp, ['-t', "touch-test"], input='1\n') + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert cpresult.exit_code == 0 + assert pyperclip.paste() == "touch ~/.clir/test-file.txt" + assert os.path.exists(os.path.expanduser('~/.clir/test-file.txt')) == False + assert len(cids) == len(cids2) + assert len(tids) == len(tids2) + assert len(crel) == len(crel2) + assert set(cids) == set(cids2) + assert set(tids) == set(tids2) + assert set(crel) == set(crel2) + assert set(trel) == set(trel2) + +# Check: +# - That the output corresponds to the one of the command not found message. The message should be: "ID not valid" +def test_copy_command_not_found(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + cpresult = runner.invoke(cp, ['-t', "c1"], input='100\n') + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert cpresult.exit_code == 1 + assert "ID not valid" in cpresult.output + assert len(cids) == len(cids2) + assert len(tids) == len(tids2) + assert len(crel) == len(crel2) + assert set(cids) == set(cids2) + assert set(tids) == set(tids2) + assert set(crel) == set(crel2) + assert set(trel) == set(trel2) + + +def db_integrity_check(): + db_integrity = DbIntegrity() + #commands_ids, commands, tags_ids, tags, cid_relation (commands ids in commands_tags table), tid_relation (commands ids in commands_tags table) + return db_integrity.main() \ No newline at end of file diff --git a/tests/4-run_command.py b/tests/4-run_command.py new file mode 100644 index 0000000..aadad0b --- /dev/null +++ b/tests/4-run_command.py @@ -0,0 +1,45 @@ +from clir.cli import run +from clir.utils.db import get_command_id_from_command, get_tag_id_from_tag, DbIntegrity +from click.testing import CliRunner +import pyperclip, os + +# Check: +# - That the output corresponds to the one of the command +def test_run_command(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + runresult = runner.invoke(run, ['-t', "touch-test"], input='1\n') + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert runresult.exit_code == 0 + assert os.path.exists(os.path.expanduser('~/.clir/test-file.txt')) == True + assert len(cids) == len(cids2) + assert len(tids) == len(tids2) + assert len(crel) == len(crel2) + assert set(cids) == set(cids2) + assert set(tids) == set(tids2) + assert set(crel) == set(crel2) + assert set(trel) == set(trel2) + + +# Check: +# - That the output corresponds to the one of the command not found message +def test_run_command_not_found(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + runresult = runner.invoke(run, ['-t', "touch-test"], input='100\n') + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert runresult.exit_code == 1 + assert "ID not valid" in runresult.output + assert len(cids) == len(cids2) + assert len(tids) == len(tids2) + assert len(crel) == len(crel2) + assert set(cids) == set(cids2) + assert set(tids) == set(tids2) + assert set(crel) == set(crel2) + assert set(trel) == set(trel2) + + +def db_integrity_check(): + db_integrity = DbIntegrity() + #commands_ids, commands, tags_ids, tags, cid_relation (commands ids in commands_tags table), tid_relation (commands ids in commands_tags table) + return db_integrity.main() \ No newline at end of file diff --git a/tests/5-remove_command.py b/tests/5-remove_command.py new file mode 100644 index 0000000..1a7b461 --- /dev/null +++ b/tests/5-remove_command.py @@ -0,0 +1,72 @@ +from clir.cli import rm, cp +from clir.utils.db import get_command_id_from_command, get_tag_id_from_tag, DbIntegrity +from click.testing import CliRunner +import pyperclip, os + + +def test_remove_command(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + result = runner.invoke(rm, input='1\n') + + assert result.exit_code == 0 + + cpresult = runner.invoke(cp, ['-t', "ansible"], input='1\n') + assert cpresult.exit_code == 1 + + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert len(cids) - 1 == len(cids2) + assert len(crel) - 1 == len(crel2) + + +def test_remove_command_by_tag(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + result = runner.invoke(rm, ['-t', "c2"], input='1\n') + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert result.exit_code == 0 + + cpresult = runner.invoke(cp, ['-t', "c2"], input='1\n') + assert cpresult.exit_code == 1 + assert len(cids) - 1 == len(cids2) + assert len(crel) - 1 == len(crel2) + + +def test_remove_command_by_grep(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + result = runner.invoke(rm, ['-g', "command 3"], input='1\n') + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert result.exit_code == 0 + + cpresult = runner.invoke(cp, ['-t', "c3"], input='1\n') + assert cpresult.exit_code == 1 + assert len(cids) - 1 == len(cids2) + assert len(crel) - 1 == len(crel2) + +def test_remove_command_by_tag_and_grep(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + result = runner.invoke(rm, ['-t', "touch-test", '-g', "file"], input='1\n') + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert result.exit_code == 0 + + cpresult = runner.invoke(cp, ['-t', "touch-test"], input='1\n') + assert cpresult.exit_code == 1 + assert len(cids) - 1 == len(cids2) + assert len(crel) - 1 == len(crel2) + + +def test_remove_command_not_found(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + result = runner.invoke(rm, input='100\n') + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert result.exit_code == 1 + assert "ID not valid" in result.output + assert len(cids) == len(cids2) + +def db_integrity_check(): + db_integrity = DbIntegrity() + #commands_ids, commands, tags_ids, tags, cid_relation (commands ids in commands_tags table), tid_relation (commands ids in commands_tags table) + return db_integrity.main() \ No newline at end of file diff --git a/tests/6-list_command.py b/tests/6-list_command.py new file mode 100644 index 0000000..0f6c326 --- /dev/null +++ b/tests/6-list_command.py @@ -0,0 +1,24 @@ +from clir.cli import ls +from clir.utils.db import get_command_id_from_command, get_tag_id_from_tag, DbIntegrity +from click.testing import CliRunner +import pyperclip, os + + +def test_list_command(): + cids, _, tids, _, crel, trel = db_integrity_check() + runner = CliRunner() + lsresult = runner.invoke(ls) + cids2, _, tids2, _, crel2, trel2 = db_integrity_check() + assert lsresult.exit_code == 0 + assert len(cids) == len(cids2) + assert len(tids) == len(tids2) + assert len(crel) == len(crel2) + assert set(cids) == set(cids2) + assert set(tids) == set(tids2) + assert set(crel) == set(crel2) + assert set(trel) == set(trel2) + +def db_integrity_check(): + db_integrity = DbIntegrity() + #commands_ids, commands, tags_ids, tags, cid_relation (commands ids in commands_tags table), tid_relation (commands ids in commands_tags table) + return db_integrity.main() \ No newline at end of file diff --git a/tests/add_command.py b/tests/add_command.py deleted file mode 100644 index c624af1..0000000 --- a/tests/add_command.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -import json -from rich.prompt import Prompt -from clir.cli import new -from click.testing import CliRunner - - -file_path = os.path.expanduser('~/.clir/commands.json') -current_commands = {} - -def get_current_commands(): - with open(file_path, 'r') as file: - current_commands = json.load(file) - for command in current_commands: - current_commands[command]["uid"] = "" - - return current_commands - -# Test add new command using flags -def test_add_new_command(): - expected_commands = {"command 1": {"description": "Command 1 description", "tag": "c1", "uid": ""}} - runner = CliRunner() - result = runner.invoke(new, ['-c', "command 1", '-d', "Command 1 description", '-t', "c1"]) - assert result.exit_code == 0 - assert str(get_current_commands()) == str(expected_commands) - -# Test add new command using prompts -def test_add_new_command_with_prompt(): - expected_commands = {"command 1": - {"description": "Command 1 description", "tag": "c1", "uid": ""}, - "command 2": - {"description": "Command 2 description", "tag": "c2", "uid": ""}} - runner = CliRunner() - result = runner.invoke(new, input='\n'.join(["command 2", "Command 2 description", "c2"])) - assert result.exit_code == 0 - assert str(get_current_commands()) == str(expected_commands) - - - diff --git a/tests/copy_command.py b/tests/copy_command.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/files/commands.json b/tests/files/commands.json new file mode 100644 index 0000000..b2fe21c --- /dev/null +++ b/tests/files/commands.json @@ -0,0 +1 @@ +{"mkdir inventory roles vars files ; touch playbook.yml": {"description": "Create an ansible simple project architecture in the current folder", "tag": "ansible", "uid": "6fd38859-1b34-445a-90d1-b2fc8114403b"}, "docker stack deploy --compose-file ~/gitlab-runners/docker-compose.yml gitlab-runner": {"description": "Deploy gitlab runners stack using docker swarm", "tag": "docker", "uid": "6ac9febd-ad6a-48b5-91be-11405e1c004f"}, "docker stack rm ": {"description": "Remove a docker swarm service", "tag": "docker", "uid": "7775a8ea-eb7a-486e-9236-c2744c3af6e6"}, "kubectl config get-contexts": {"description": "Get Kubernetes contexts", "tag": "kubernetes", "uid": "847215a5-9530-4d25-ad19-7838e6615e07"}, "kubectl config unset contexts.": {"description": "Delete a context", "tag": "kubernetes", "uid": "723af167-f6ff-4c04-8d4e-b3676a31014b"}, "kubectl config use-context ": {"description": "Use a Kubernetes context", "tag": "kubernetes", "uid": "03faf9ae-7a9a-439f-8e0a-b129cc90ebdd"}, "virsh net-dhcp-leases default": {"description": "Show KVM DHCP releases", "tag": "kvm", "uid": "6fb1296b-5296-4b09-bdec-03d887878d0f"}, "virsh list --all": {"description": "List all VMs in KVM", "tag": "kvm", "uid": "183a6e7a-c096-48a7-abb3-873277377949"}, "virt-manager": {"description": "Open virt-manager", "tag": "kvm", "uid": "76ff8351-c53f-41f5-abc7-e4affd831a71"}, "virsh undefine ": {"description": "Undefine a given VM", "tag": "kvm", "uid": "825fa225-a259-47a2-8161-91f80a720869"}, "terraform apply -auto-approve": {"description": "Create resources without the need for approval", "tag": "terraform", "uid": "03ad3ac0-9052-4bc9-ae98-dae1b7f55c3a"}, "terraform destroy -auto-approve": {"description": "Destroy resources without the need for approval", "tag": "terraform", "uid": "4e298622-cd38-4697-a80b-bf052cfae5cd"}, "warp-cli connect": {"description": "Connects to Cloudflare's warp vpn", "tag": "vpn", "uid": "ea163a8e-7781-470b-95af-44af5b05d7cc"}, "warp-cli disconnect": {"description": "Disconnects from Cloudflare's warp vpn", "tag": "vpn", "uid": "3a59c4b3-6ef9-40c4-8014-871c2381da4f"}} \ No newline at end of file diff --git a/tests/init_clir.py b/tests/init_clir.py deleted file mode 100644 index b9e9452..0000000 --- a/tests/init_clir.py +++ /dev/null @@ -1,20 +0,0 @@ -import os -import subprocess - -subprocess.run(['clir', 'init'], capture_output=True, text=True) -dir_path = os.path.expanduser('~/.clir') -file_path = os.path.expanduser('~/.clir/commands.json') -command_file_creation_time = os.path.getctime(file_path) - -# Test if the clir folder environment was created -def test_clir_project_folder_created(): - assert os.path.exists(dir_path) - -# Test if the commands.json file was created -def test_command_file_created(): - assert os.path.exists(file_path) - -# Test if init command doesn't recreate the commands.json file -def test_init_command_doesnt_recreate_commands_json(): - subprocess.run(['clir', 'init'], capture_output=True, text=True) - assert os.path.getctime(file_path) == command_file_creation_time \ No newline at end of file diff --git a/tests/list_command.py b/tests/list_command.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/remove_command.py b/tests/remove_command.py deleted file mode 100644 index 9139f49..0000000 --- a/tests/remove_command.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import json -from rich.prompt import Prompt -from clir.cli import rm -from click.testing import CliRunner - - -file_path = os.path.expanduser('~/.clir/commands.json') -current_commands = {} - -def get_current_commands(): - with open(file_path, 'r') as file: - current_commands = json.load(file) - for command in current_commands: - current_commands[command]["uid"] = "" - - return current_commands - -# Test delete command -def test_delete_command(): - expected_commands = {"command 2": - {"description": "Command 2 description", "tag": "c2", "uid": ""}} - runner = CliRunner() - result = runner.invoke(rm, input='1\n') - assert result.exit_code == 0 - assert str(get_current_commands()) == str(expected_commands) - - - diff --git a/tests/run_command.py b/tests/run_command.py deleted file mode 100644 index e69de29..0000000 From 4084fc7bc88b85c981cc4b24f2af89b7ae5abae1 Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 10 Aug 2024 17:48:03 +0200 Subject: [PATCH 23/24] Delete comments --- clir/cli.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/clir/cli.py b/clir/cli.py index 806e43c..8e894f1 100644 --- a/clir/cli.py +++ b/clir/cli.py @@ -10,34 +10,6 @@ def cli(): pass -#def check_config(): -# dir_path = os.path.join(os.path.expanduser('~'), '.clir') -# file_path = os.path.join(dir_path, 'commands.json') -# return os.path.exists(file_path) - -#--------------------------------------- CLI commands ------------------------------------------------------- - -#@cli.command(help="Clir initial configuration 🛠️") -#def init(): -# dir_path = os.path.join(os.path.expanduser('~'), '.clir') -# os.makedirs(dir_path, exist_ok=True) - - # Define the file path and name -# files = ['commands.json', 'credentials.json'] - - # Check if the file already exists -# for file in files: -# file_path = os.path.join(dir_path, file) -# if not os.path.exists(file_path): -# # Create the file -# with open(file_path, 'w') as file_object: -# file_object.write('{}') - -# print(f'File "{file_path}" created successfully.') -# else: -# print(f'A clir environment already exists in "{dir_path}".') - -# create_database() @cli.command(help="Save new command 💾") From 0dbdf953593de15b079dfb216e00d9217b27140c Mon Sep 17 00:00:00 2001 From: Elkin Aguas Date: Sat, 10 Aug 2024 17:51:40 +0200 Subject: [PATCH 24/24] Fix --- clir/utils/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clir/utils/config.py b/clir/utils/config.py index e9e83c6..8dd7cca 100644 --- a/clir/utils/config.py +++ b/clir/utils/config.py @@ -26,8 +26,8 @@ def back_json_commands(): def migrate_json_to_sqlite(): if os.path.exists(f"{env_path}/commands.json"): - print("Migrating json stored commands to sqlite database...") back_json_commands() + print("Migrating json stored commands to sqlite database...") commands = get_commands() @@ -36,6 +36,7 @@ def migrate_json_to_sqlite(): print(f"Description: {data['description']}") print(f"Tag: {data['tag']}") insert_command_db(command, data['description'], data['tag']) + print("---") os.remove(f"{env_path}/commands.json") print("Migration complete")