#!/usr/bin/env python3

#Note that `&&&` in a comment by the code means that the section of code is unfinished. `Unimplemented` by a comment means it's unimplemented (but it may or may not be finished). ###### marks the beginning of a significantly different section of the code.

import sys, os, subprocess, re, pickle, platform, shutil, pathlib, tempfile, getpass, shlex, random, hashlib, datetime;

#Variables with capital letters aren't meant to change their values during execution.

PN="Ki"; #Program name, used in the documentation.
FN="ki"; #File name, used in the documentation and other areas.
VER="0.13.2"; #The current program version.
PROTOCOL=5; #This is the protocol for pickling. If not available, the latest one available will be used.

VERSION=f"""
Version {VER}
Each upload constitutes a new version. I only change the version right before the upload, regardless of whatever else I change in the meantime.
This version only applies to this file (not the other files in the project).
""".strip();

LICENSE=f"""
Terms:
* “You” (or “Your”) means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
* “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns the software.

Permissions granted:

Note: The following does not constitute submitting the software to the public domain, but rather describes the sorts of non-exclusive privileges one might be granted to the software via this license:

The software may be used as if it were first published in the 1800s (A.D.), by a reputable commercial publisher, with the software being written by a human (not Kumoshk) who had no heirs (who lived only in the 1800s), such that the software has since passed into the public domain; however it may only be thus used with the rest of the license below in effect, overriding previous statements wherein (and only wherein) they conflict:

This entire license (and each portion thereof) is revokable and changeable by Kumoshk at any time for any reason, without notice.

You are granted no exclusive rights nor any exclusive privileges pertaining to the software.

Disclaimer of Warranty
The software is provided under this license on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the software is with You. Should the software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this license. No use of the software is authorized under this license except under this disclaimer.

Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes the software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
""".strip();

DONATE="";
if datetime.datetime.today().weekday() not in {5, 6, 0}:
    DONATE=f"""
Donations:
I still need to decide what to say about donations, but I do not accept PayPal, nor cryptocurrency, each for ethical reasons.
""".strip();

ABOUT=f"""
Introduction:
{PN} is a tool to assist you in accessing files via the command-line. If you like complex/organized directory structures, but want a faster or easier way to navigate them, {PN} is for you. It was written in Python 3.x. It does not work for Python 2.x.

History:
I made {PN} in order to help me with creative writing projects on my tablet, using Termux. Before I made {PN}, using my tablet to create creative works was a painstaking process. Now it's easy (although it could still be improved).

About the programmer:
Religiously, I'm a member of The Church of Jesus Christ of Latter-day Saints (see <churchofjesuschrist.org> and <comeuntochrist.org>). I have a testimony of it. I know God answers prayers (through my experience and others'). I know God loves us. I have a testimony through the Holy Ghost that Joseph Smith saw God the Father and Jesus Christ.

Politically, I'm independent (as in, I belong to no political party).

I have a lot of interests.

Contact/download:
My website: https://vertex.alwaysdata.net/
Also, see these:
• https://vertex.alwaysdata.net/ki.html
• https://vertex.alwaysdata.net/Termux-tricks.html
• https://vertex.alwaysdata.net/A-desktop-user-s-guide-to-Nano-a-command-line-text-editor.html

{DONATE}
""".strip();

HELP=f"""
Definitions:
• {PN}: The name of our program.
• {FN}: The filename of {PN}. This is what you type to execute it; `.{FN}` is also the starting default file extension.
• key: Any file--with any extension--that you open with {PN}.

Introduction:
Welcome to {PN}, which is designed to make your command-line life easier. It allows you to open a file with a specific name (default) or all the files with that name (searching recursively) within a directory structure, with a program of your choice (nano being the default). For instance, if you are in `~/book/` and you type `{FN} Sam Jones` it will search `/book/` and all its subdirectories recursively for a file named `Sam Jones.{FN}`; when it finds one, it will open it with the nano text editor (without changing the current directory).

Note that you must only provide one file name per use of {PN} (and you do not need to surround it with quotes, nor escape spaces); you do not need to type `.{FN}` when using the default settings. You may do those things, however. `.{FN}` is the default file extension, but you can change it, or make it so no extension is used.

If a file does not exist, {PN} will prompt you to create it (and ask where to do so, giving you some convenient options).

The name {PN} (formerly key, but that was taken by Donkey) is inspired by dictionary key value pairs (or entries in an index, such as a glossary, or an actual lexicon). Because entries in such tend to have unique names, opening only one file by that name is the default.

{PN} begins each feedback entry (except for such as this help page) with a bullet, in order to make them more readable.

Combinable options/flags:
[Note: You must include a hyphen before the flags/options, or else before each flag/option. If you omit the hyphens, the would-be flags/options will be treated as part of the key name.]
• -0: Toggle whether to handle files such as `myFile.{FN}.asc` specially. What this does is decrypt the file in a temporary directory and open it there (opening it with the default app for the left extension); if you edit it, {PN} will offer to re-encrypt it and overwrite the original. In short, this lets you open, edit, and save passphrase-protected files. The default is false.
• -1: Passphrase-protect (symmetrically encrypt) the key instead of opening it (and optionally keep the unencrypted file, too). If the file was named myFile.txt, then it will become myFile.txt.asc; see -0 if you wish to edit and save it (and other encrypted .asc files). Encryption is done with `cat tempPassphraseFilepath|gpg --batch --yes --passphrase-fd 0 --force-mdc --s2k-mode 3 --s2k-count 1000000 --s2k-cipher-algo AES256 --s2k-digest-algo SHA512 --compress-algo='BZIP2' --compress-level 9 --output=myOutputFile.ki.asc -ac myFilepath.ki` (see the warning section regarding the security of this).
• -2: Toggle whether to open the specified extension instead of the default (this won't work for no extension). Note that if this is set, for instance, typing `{FN} test.{FN}` will open `test.{FN}` instead of `test.{FN}.{FN}` even when the default extension is `.{FN}`, but if you type `test.txt` it will open `test.txt` instead of `test.txt.{FN}` (again, when the default extension is `.{FN}`). The default setting is true.
• -4: Toggle whether when specified extensions are opened (see -2) they become the new default or not. The default setting is false.
• -8: Make the current working directory a base directory (you can have more than one).
• -9: Set the default directory: If no base directory is set, make the current working directory the default directory. If there is at least one base directory, make the current or last-accessed base directory the default directory (in that priority order). You may only have one default directory at a time.
• -A: If used, the first word you enter after the flags/options will be the new default app {PN} uses to open files, or to run files if the -x flag is also provided (if you follow it with a file, it will open it in the regular fasion; e.g. `{FN} -A cat test` will open `test.{FN}`, and any subsequent keys opened, with `cat`; assuming python weren't already the default, `{FN} -Ax python test.py` will run test.py with python and set python as the default app for running files with -x).
• -a: Like -A, except it doesn't change the default app, nor does it affect -x. It just opens with the specified app this one time.
• -B: Set the current working directory to be either the default directory or a base directory; or, give the current working directory's path a name to reference it when you use -b. You will be prompted which to do. See also --go.
• -b: Specify a named directory structure to search for a key (or keys) to open. (You name them with -B.) Example: If `books` is the name you've given `~/Documents/e-books` then `{FN} -b books Anne of Green Gables` will search `/Documents/e-books` for a key called Anne of Green Gables.ki as if `books` were the default directory (but it's not). This is not compatible with some other flags/options. However, this is evaluated after -a, so you can combine the two like this: `{FN} -ab appName dirName keyName`.
• -c: Clear the list of base directories, or remove the current base directory from being a base directory (you will be prompted which to do).
• -D: Toggle whether to use the default directory (default is false). This overrides everything else (and uses it as your current base directory no matter where you are). See also -9, -B, -d, -T, and -t.
• -d: Toggle whether to use the default directory without saving the setting change (so it's changed for this execution only). See also -9, -B, -D, -T, and -t.
• -e: This prompts you to set the default file extension.
• -f: (Not implemented yet.) This loads a favorite. A favorite is a word that corresponds to a list of URIs. If they are files, {PN} will open them with the apps that {PN} is configured to open files of their file extensions with. If they are URLs, it will open them in a web browser (probably not a command-line one). Example: `{FN} -f journals` might open several different files, each with a journal in it. This is not compatible with flags/options that involve opening keys (since {PN} searches for no keys when it is used). Also see `--fload` and `--fsave`. Each favorite has its own settings, too; for instance, you should be able to set whether the usual app opens a file, or whether you specify it manually (maybe with a bunch of flags/options).
• -g: Like -b, but it also makes the named directory the default directory, and makes the default directory active (not to be confused with --g and --go, although it has similar uses).
• -h: Print the help.
• -n: List the base directories, any named directories, and the default directory.
• -N: Give the current working directory structure a name (the first argument without a space will be the name). Note that this does not make the directory a base directory (nor does it require it to be one). A single directory may be given multiple names.
• -o: Toggle whether {PN} opens multiple files with the same name (default is false).
• -P: Prompts you to undo a specific customization previously made with -p or -X.
• -p: Prompts you for a specific file extension to open with a specific app, every time; you can also set the default extension here.
• -q: reset the app settings. You will lose any configurations that you have made.
• -R: Remove the current directory (or the current base directory) from being a base directory.
• -r: Disable the default extension this run only: e.g. `{FN} -r myFile` will just open `myFile` (not `myFile.ki`).
• -s: Toggle whether to search for the key (file) to open from the base directory (if you are inside a base directory structure). The default is true; so, if you don't want to open keys starting from the base directory that aren't in/under the current directory you're in within that base directory, then you should set this to false.
• -T: Disable the default directory this time only (no change if it is already disabled). See also -9, -B, -D, -d, and -t.
• -t: Enable the default directory this time only (no change if it is already enabled). See also -9, -B, -D, -d, and -T.
• -u: Use this to update {PN}. If you wish to do a system-wide update, you'll need to use `sudo python3 ki -u` or `sudo ki -u` (you don't need `sudo` on Termux).
• -v: Print the version information (to save you the bother, its version {VER}).
• -X: This is like -p, except it's for the apps that are used when -x is provided. It's basically a second set of the same thing.
• -x: This executes the file instead of opening it. For instance, instead of opening a .py file with nano, it may run it with python. This must be set each time you wish to use it (it doesn't toggle a saved variable).

Non-combinable options/flags:
• --about: About {PN}, contact, etc.
• --aload: Load an app scheme (which apps open which extensions); separate file extensions and apps by semi-colons or new lines (list the file extension first, followed by the new default app to open it, followed by another file extension, followed by the app to open it, and so on; note that all semi-colons in the file are converted to new lines and all whitespace is stripped, so feel free to indent, add double new lines, or whatever; the first line should be a file extension, the second an app, and so on; it doesn't matter what the scheme is called, nor what its file extension is; {PN} searches for the scheme file the same as any key, although it will only load one file even if {PN} is set to open all that it finds); e.g. `{FN} --aload myScheme.sch`, with `myScheme.sch` having such as these contents:
.txt;emacs
.mp3;play
.ini;nano
.ly;lilypond
• --asave: Save your current app scheme.
• --do: This is like --go [named directory], but it also makes it the default directory, and activates the default directory.
• --fload: (Not implemented yet.) This is used to load a scheme of favorites. (See `-f`.)
• --fsave: (Not implemented yet.) This is used to save the current scheme of favorites. (See `-f`.)
• --g or --go: Go to the last key directory. (FYI, this opens a new shell.) You can also open named directory structures with this: e.g. `{FN} --go books` will take you to `~/Documents/ebooks`, if you've named that path `books`.
• --help: Print the help (without the option of doing other stuff at the same time).
• --license: Print the license granted for use of {PN}.
• --load: This will load {PN}'s application data from a backup file saved with `{FN} --save`. Arguments after the option constitute the filename of the backed-up settings. If no arguments are supplied, you will be asked for a filepath.
• --nload: Like `--aload`, but it loads a scheme of named directory structures for use with `{FN} -b`. Preface names with `@` to make their associated paths base directories. The first name prefaced with `$` will be made the default directory (and a base directory). Example:
$book;~/ebooks
docs;~/Documents
bin;~/bin
@pro;~/Desktop/project
@pro2;~/Desktop/project2
• --nsave: Save your current named directory scheme.
• --save: With no arguments, this will prompt you to save a backup of {PN}'s application data. With arguments, it will use the arguments as your filepath for the new backup.
• --settings: (Not implemented, yet.) This will print {PN}'s current settings to the screen.
• --ver: print the version number only (no other information about the version).
• --version: Print the version information. (FYI, it's version {VER}.)
• --xload: Like `--aload`, but it loads an xapp scheme (the scheme of apps for executing files with -x).
• --xsave: Save your current scheme of apps for executing files with -x.

Special options:
• Begin search query with a colon (it won't open a key): Find all the files in the directory structure (or base or default directory structure), regardless of file extension (e.g. `{FN} :my search text` is the same as `grep -rliF --include=\* my\ search\ text /base/directory/**/`, or if it's the current working directory, it's like `grep -rliF --include=\* my\ search\ text ./`). Note that you can even do such as `{FN} -b myDir :mySearchCriteria`.

Warnings:
• See the license: i.e. `{FN} --license`.
• {PN} saves your decisions for future uses of the program. {PN} is designed to be used directly by humans at least as much as it is otherwise (some flags/options prompt for user input and some toggle saved variables; so, you most likely won't be using those in a script). {PN} was made to make things easier for humans (only some flags/options are suitable for scripts, and even then you should be careful, since saved variables that you decide to change might influence what things do).
• Should the program crash, note that it is possible that some data was not saved during that execution of {PN} (since the print statements occur before the saving in many instances, in order to make the code more efficient).
• Not all combinable flags/options were intended to be used together. Some flag/option combinations may result in undesirable effects. Bugs could potentially exist with some combinations.
• Flags/options are evaluated before the key (the file).
• Flags/options should be evaluated in the order that they are listed in the help, but because internally some flags and/or options only trigger future events (like they might change a variable that causes the desired thing to happen further down in the code), this might not seem to be the case for some flags/options.
• Flags/options may change between versions of {PN}. Always check the help after doing an update.
• When doing an update, some things may or may not become incompatible that were associated with previous versions of {PN} (such as your saved backups of {PN}'s settings).
• Encryption passphrases are saved to disk in a temporary file before being piped into gpg with cat on the command-line (the file is deleted when it's no longer necessary, but in theory, it's not secure if someone, or some malware, has access to your system while you're encrypting/decrypting/editing). If I could use gpg's natural passphrase-asking system, I would, but I don't know of a way to do that seemlessly in the desired fashion with symmetric encryption (yet); I could do such as `gpg output myFile.{FN}.asc --symmetric myFile.{FN}`, but then you'd have to enter your passphrase every time you saved an edit, and it would complain at you every time you entered an insecure passphrase; if you're not in Termux, it would launch a GUI-based passphrase entry-box (which could pose some issues, especially if the GUI-based box doesn't stop {PN} from moving on). If it helps, I've at least made the filenames random and extremely difficult to read (I've done the same with the decrypted files themselves). FYI, if you're wondering why I don't just use the GPG Python library to solve these problems, it's because that would necessitate that I license the code under the GNU GPL; also, the GPG Python library wasn't well-documented last I checked when I initially made the decryption software (years ago). I like the GPL, but reading about how to apply it to my work gives me a headache, and I think it was primarily intended for such as C and C++ source code (not pure Python).
• When you encrypt and unencrypt things, it resets the agent so your passphrases won't be remembered. This could be make gpg forget passwords you want it to remember if you're working with gpg and {PN} separately in the same computer session.

Tips:
• Set the default extension to blank (with `{FN} -e`) if you want to have to specify the extension manually every time (e.g. `{FN} myFile` will open a file called `myFile` and `{FN} myFile.ki` will open a file called `myFile.ki`).
• If you need to find the application data, it's at `~/.{FN}_kumoshk`.
• If you're in nano, and want to insert text from a file in a far away place, you can do such as this (assuming you've previously named a directory path `pathName` with `{FN} -B` or `{FN} --nload myScheme`: ctrl+t (in nano) then `{FN} -ab cat pathName myFile`; note that `pathName` should not be confused with an actual path; it's just an abstract name that you invented earlier yourself that represents the path.
• You can't specify flags/options with the new app when using -a and -A (but you can supply them with such as -p and --xload).

Unaffiliated command-line stuff to accompany {PN}:
• Use `tree myDirectory` or `ls -R myDirectory` to show the contents of a directory structure recursively.
""".strip();

NANORC="""
set tabsize 4
set tabstospaces
set nowrap
set softwrap
set autoindent
set atblanks
set afterends
set zap
#set nonewlines
#set multibuffer
set speller "hunspell -x -c"
set rawsequences
bind ^H chopwordleft main
bind F1 scrollup main
bind F2 scrolldown main
""";

######

path=os.getcwd();
execute_it=False; #If changed to True, "xapp" and "xapps" will be used in place of "app" and "apps".
settings={}; #One dictionary for all the settings.


#Make sure the user settings folder exists.
settingsPath=None;
settingsPath=os.path.join(os.path.expanduser("~"), f".{FN}_kumoshk");
if not os.path.isdir(settingsPath):
    os.makedirs(settingsPath);

extension=None; #This is to make it global down below, so the getApp() method can use it.

backupDictPath=os.path.join(settingsPath, "backup_dict.pkl");

def bu(): #Return backup file dictionary
    global backupDictPath;
    with open(backupDictPath, "rb") as FILE:
        result=pickle.load(FILE);
    return result;

def init_settings():
    global settings;
    global backupDictPath;
    global VER;
    global PROTOCOL;
    settings["version"]=VER; #This is used to check to see if you're running a new version.
    if "extension" not in settings:
        settings["extension"]=f".{FN}"; #Unimplemented
    if "scriptExtension" not in settings:
        settings["scriptExtension"]=".ks"; #Unimplemented; Files with this extension, rather than just being opened, can perform various actions (such as specify a list of files to open, open random files, according to a set of likelihoods, etc.).
    if "firstRun" not in settings:
        settings["firstRun"]=True;
    if "gpg_path" not in settings:
        settings["gpg_path"]="gpg";
    if "kicrypt" not in settings:
        settings["kicrypt"]=False; #Toggle how to handle such as myfile.ki.asc when .ki is the default and you type ki myfile
    if "app" not in settings:
        settings["app"]="nano"; #This is the default app for opening files; 'apps' below overrides this, however.
    if "named_dirs" not in settings:
        settings["named_dirs"]={}; #These are your saved directories for you to access by a chosen abstract name, rather than by typing in the path for it.
    if "xapp" not in settings:
        settings["xapp"]="cat"; #This is the default app to execute files with when they're not already set to be executable.
    if "apps" not in settings:
        settings["apps"]={}; #Default apps to open the various file extensions. Keys are file extensions and values are the apps to open them.
    if "xapps" not in settings:
        settings["xapps"]={}; #xapps is to xapp as apps is to app
    if "lastKeyDir" not in settings:
        settings["lastKeyDir"]=None; #Directory of the last key opened.
    if "defaultDir" not in settings:
        settings["defaultDir"]=None; #This is a saved directory for frequent use, which can be changed. You can use it even when you're not in it.
    if "useDefault" not in settings:
        settings["useDefault"]=False; #If this is true then when you're not in a base directory, it will act as if you are in the default directory structure no matter where you are.
    if "baseDirs" not in settings:
        settings["baseDirs"]=set(); #This is a set of directories. If you're within these when you use Ki, it will always search from it even when in its subdirectories. You can't set both a directory and one of its subdirectories to be in baseDirs.
    if "baseDir" not in settings:
        settings["baseDir"]=None; #This is the current base directory (of if there is no current one, it is the last base directory used).
    if "searchBaseDir" not in settings:
        settings["searchBaseDir"]=True; #Whether or not to search for keys from the base dir when in its subdirectories (and so on recursively).
    if "openAll" not in settings:
        settings["openAll"]=False; #Whether or not to open all files found or just one.
    if "setOpenDefault" not in settings:
        settings["setOpenDefault"]=False; #Whether opening a file with a specific extension sets that extension to be the default.
    if "openSpecifiedExt" not in settings:
        settings["openSpecifiedExt"]=True; #Whether you can open extensions that aren't the default just by typing them in (this won't work for blank extensions).
    with open(backupDictPath, "wb") as FILE:
        try:
            pickle.dump(settings, FILE, protocol=PROTOCOL);
        except ValueError:
            pickle.dump(settings, FILE, protocol=-1);

save_it=False; #If set to True, settings will be saved (pickled). Calling saveSettings() sets it back to False.

#Load the settings, if they've been saved.
settingsDictPath=os.path.join(settingsPath, "settings_dict.pkl");
if os.path.exists(settingsDictPath) and os.path.exists(backupDictPath):
    with open(settingsDictPath, "rb") as FILE:
        settings=pickle.load(FILE);
    if "version" in settings:
        if settings["version"]!=VER:
            print(f"• You have installed {PN} {VER}, which does not match the version you used last ({settings['version']}). Updating the settings for compatibility.");
            init_settings();
            save_it=True;
    else:
        print(f"• You have installed {PN} {VER}, which does not match the one you used last. Updating the settings for compatibility.");
        init_settings();
        save_it=True;
else:
    print(f"• Initiating settings for {PN} {VER}.");
    init_settings();
    save_it=True;

oneTimeApp=None;
ddd=False; #Disable the default extension this run.
passphraseProtect=False; #Passphrase-protect the key instead of opening it.

######


useDefault=settings["useDefault"]; #This is so we can do one-time uses of the default-directory, and of not using the default directory.

def saveSettings():
    #Use this wherever you want to save the settings.
    global save_it, settings, PROTOCOL;
    save_it=False; #To make sure we don't do redundant saves.
    with open(settingsDictPath, "wb") as FILE:
        try:
            pickle.dump(settings, FILE, protocol=PROTOCOL);
        except ValueError:
            pickle.dump(settings, FILE, protocol=-1);
    if settings["firstRun"]==True:
        with open(backupDictPath, "wb") as FILE:
            try:
                pickle.dump(settings, FILE, protocol=PROTOCOL);
            except ValueError:
                pickle.dump(settings, FILE, protocol=-1);

baseDir=None;
for x in settings["baseDirs"]:
    if path.startswith(x+os.sep) or x==path:
        baseDir=x;
        if settings["baseDir"]!=baseDir:
            settings["baseDir"]=baseDir;
            save_it=True;
        break;

######

def ask(question, desired=None, pw=False):
    if question.endswith(" ")==False:
        question=question+" ";
    if pw==False:
        askFunc=input;
    else:
        askFunc=getpass.getpass;
    response=None;
    if desired==None:
        return askFunc(question);
    else:
        while response not in desired:
            response=askFunc(question);
        return response;

def askyn(question, pw=False):
    return ask(question=question, desired={"yes", "y", "no", "n"}, pw=pw);

def askdir(question, pw=False, ask_file=False, must_exist=True, must_perm=True, default_dir=None, default_dir2=None, default_dir3=None):
    print("• Enter * to cancel.");
    if default_dir!=None:
        print(f"• Enter ** to use the default: `{default_dir}`.");
    if default_dir2!=None:
        print(f"• Enter *** to use the program directory: `{default_dir2}`");
    if default_dir3!=None:
        print(f"• Enter **** to use the home bin directory: `{default_dir3}`");
    if question.endswith(" ")!=True:
        question=question+" ";
    if pw==False:
        askFunc=input;
    else:
        askFunc=getpass.getpass;
    response=None;
    while 1:
        response=askFunc(question);
        if response=="" or response=="." or response=="./":
            response=os.path.expanduser(path);
        if response=="*":
            return None;
        if response=="**" and default_dir!=None:
            response=default_dir;
        if response=="***" and default_dir2!=None:
            response=default_dir2;
        if response=="****" and default_dir3!=None:
            response=default_dir3;
        response=os.path.expanduser(response);
        response=os.path.abspath(response);
        if os.path.exists(response) and must_exist==True:
            if os.path.isdir(response):
                if ask_file==False:
                    if must_perm==True:
                        if perm(response):
                            return response;
                        else:
                            print(f"• You do not have permission to write in `{response}`. Try another path.");
                    else:
                        return response;
                else:
                    print(f"• The path, `{response}`, is not a file. Please try another path.");
            else:
                if ask_file==False:
                    print(f"• The path, `{response}`, is not a directory. Please try another path.");
                else:
                    return response;
        elif must_exist==False:
            if ask_file==False:
                if must_perm==True:
                    if perm(response):
                        return response;
                    else:
                        print(f"• You do not have permission to write in `{response}`. Please try another path.");
                else:
                    return response;
            else:
                return response;
        else: #must_exist==True and the file exist:
            createyn=askyn(f"• The path, `{response}` does not exist. Would you like to create it?");
            if createyn in {"yes", "y"}:
                os.makedirs(response);
                if not os.path.isdir(response):
                    print(f"• `{response}` not created. You likely don't have permission. Try another path.");
                else:
                    print(f"• `{response}` created.");
                    return response;
            else:
                print(f"• Please try another path.");
    return response;

def askfile(question, pw=False, ask_dir=False):
    #Permissions are not tested for files.
    return askdir(question=question, pw=pw, ask_file=True);

######

#First run stuff
if settings["firstRun"]==True:
    settings["firstRun"]=False;
    save_it=True;
    nanorc_path="~/.nanorc";
    nanorc_path=os.path.expanduser(nanorc_path);
    if os.path.exists(nanorc_path)==False:
        yn=askyn(f"""• Would you like to install some convenient nano settings in `{os.path.join(os.path.expanduser("~/"), ".nanorc")}`? These settings don't work on some versions of nano before 5.8. If it gives you errors when you run nano, either update nano, or delete the `.nanorc` file.""");
        if yn in {"yes", "y"}:
            print("• For tips on how to use nano, see\n• <http://radishrain.321.s1.nabble.com/A-desktop-user-s-guide-to-Nano-a-command-line-text-editor-td3471.html>.\n• If you want the spellcheck to work, don't forget to do `sudo apt-get install hunspell`.");
            with open(nanorc_path, "w") as FILE:
                FILE.write(NANORC);
            print("• `.nanorc settings written.`");
        else:
            print("• `.nanorc` settings not written.");
    if os.path.isdir("/data/data/com.termux/files/"):
        if shutil.which("gpg")==None:
            subprocess.run("pkg update", shell=True);
            subprocess.run("pkg install gnupg", shell=True);
    else:
        if shutil.which("gpg")==None:
            print("• GNU Privacy Guard is not installed. Installing it now.");
            subprocess.run("sudo apt-get update", shell=True);
            subprocess.run("sudo apt-get install gnupg", shell=True);

######

def user_group(p):
    stat=os.stat(p);
    return {"user":stat.st_uid, "group":stat.st_gid};
def getPerm(p): #Gets the important part of the permission mask.
    return oct(os.stat(p).st_mode)[-3:];
def perm(dp): #Test to see if you have permission to write in the directory (or else the directory of a file).
    global path;
    dp=os.path.dirname(dp);
    if dp=="":
        dp=path;
    #print(os.access(dp, os.W_OK), os.access(dp, os.X_OK));
    #print(getPerm(dp));
    return os.access(dp, os.W_OK | os.X_OK);
def setPerm(p, permissions): #Sets the permissions (e.g. 755). p is the file or directory path (its permissions will be set). This function requires sudo privileges.
    subprocess.run(["chmod", "0"+permissions, p]);
def is_exec(fp):
    #Tells if the file is executable or not.
    return os.access(fp, os.X_OK) #test to see if the file is executable
def set_exec(fp, set_it=True): #Make the file executable or not executable
    dn=os.path.dirname(fp);
    isdir=os.path.isdir(dn);
    if perm(dn)==True and isdir==True:
        if set_it==True:
            subprocess.run(["chmod", "+x", fp]); #make the file executable
        else:
            subprocess.run(["chmod", "-x", fp]); #make the file not executable
    else:
        if isdir==True:
            raise PermissionError("• You don't have permission to change the executable status of `"+os.path.basename(fp)+"`.");
        else:
            raise FileNotFoundError("• `"+fp+"` does not exist.");

def programPath(): #Get the file path of Ki.
    #pp=os.path.join(sys.path[0], sys.argv[0]);
    pp=os.path.realpath(__file__);
    if os.path.isfile(pp):
        return pp;
        #pp=str(pathlib.Path(pp).resolve().absolute()); #If the path is such as /usr/bin/ki, then it's a symlink that points somewhere else, and we need to get where it points.
    else:
        return None;

def directoryPath(): #Get the directory path of Ki.
    return os.path.dirname(programPath());

multipleFiles=None;

chars="\\ !\"'#$&()*,;<>?[]{}^`|";
def escapePath(p):
    global chars;
    for x in chars:
        p=p.strip().replace(x, "\\"+x);
    return p;

def unescapePath(p):
    global chars;
    for x in chars:
        p=p.strip().replace("\\"+x, x);
    return p;

def eglob(p1, p2, recursive=False): #escaping variant of glob
    #escapedPath=glob.escape(p1)+"/**/"+glob.escape(p2).replace("[*]", "*"); #&&&Research any others you might not want to be escaped (the asterisk allows you to do wildcard searches for files: e.g. ki myPartial*
    #to_return=glob.glob(escapedPath, recursive=recursive);
    to_return=[];
    if recursive==True:
        to_return=eglob(p1, p2, recursive=False);
        if len(to_return)>0 and settings["openAll"]==False:
            return [to_return[0]]; #This ensures we don't have to search everything if what we want is right in front of our nose.
        for x in pathlib.Path(p1).rglob(p2):
            to_return.append(x);
    else:
        for x in pathlib.Path(p1).glob(p2):
            to_return.append(x);
    if settings["kicrypt"]==True:
        if p2.endswith(".asc")==False:
            #Treat such as .ki.asc files as if they were such as .ki files.
            #ascReturn=glob.glob(escapedPath+".asc", recursive=recursive);
            ascReturn=[];
            if recursive==True:
                for x in pathlib.Path(p1).rglob(p2+".asc"):
                    ascReturn.append(x);
            else:
                for x in pathlib.Path(p1).glob(p2+".asc"):
                    ascReturn.append(x);
            to_return.extend(ascReturn);
    if settings["openAll"]==False:
        i=0;
        while i<len(to_return):
            if os.path.isdir(to_return[i])==False:
                return [to_return[i]];
            if i==len(to_return)-1:
                return [];
            i+=1;
    else:
        for x in to_return.copy():
            if os.path.isdir(x):
                to_return.remove(x);
    return to_return;

def getApp(): #Returns a list containing the app to be used to open the key.
    global extension, oneTimeApp;
    if oneTimeApp!=None:
        return [oneTimeApp];
    elif execute_it==True:
        if extension in settings["xapps"]:
            return shlex.split(settings["xapps"][extension]);
        else:
            return shlex.split(settings["xapp"]);
    else:
        if extension in settings["apps"]:
            return shlex.split(settings["apps"][extension]);
        else:
            return shlex.split(settings["app"]);

def addCurrentBaseDir(thePath=None): #Make the current working directory a base directory.
    global settings, path, save_it;
    whichDir="The current working directory";
    if thePath==None:
        thePath=path;
    else:
        whichDir=f"`{thePath}`";
    for x in settings["baseDirs"]:
        if thePath==x:
            print(f"• {whichDir} is already a base directory. It cannot be added.");
            return False;
        elif thePath.startswith(x+os.sep):
            print(f"• {whichDir} is already within the directory structure of a base directory. It cannot be added. Here is the base directory:\n"+x);
            return False;
        elif x==thePath:
            print(f"• {whichDir} is already a base directory. It cannot be added.");
            return False;
        elif x.startswith(thePath+os.sep):
            print(f"• {whichDir} has a base directory within its recursive subdirectories. It cannot be added. Here is the base directory:\n"+x);
            return False;
    settings["baseDirs"].add(thePath);
    print(f"• Adding {whichDir.lower()} to your list of base directories. Here is the new list of all your base directories:\n"+"\n".join(settings["baseDirs"])+"\n");
    save_it=True;
    return True;

def setDefaultDir(name=None, activate=False):
    #Make the current or last-accessed base directory the default directory, or if there is none, make the current working directory the default directory (and a base directory).
    global path;
    global settings;
    global baseDir;
    global save_it;
    global useDefault;
    if name!=None:
        if name in settings["named_dirs"]:
            setfp=settings["named_dirs"][name];
            if setfp in settings["baseDirs"]:
                settings["defaultDir"]=setfp;
                print(f"• `{setfp}` is now the default directory.");
                if activate==True:
                    settings["useDefault"]=True;
                    useDefault=True;
                    print("• The default directory is now active.");
                    path=settings["named_dirs"][name];
                    baseDir=path;
                    os.chdir(path);
                save_it=True;    
            else:
                print(f"• `{setfp}` is not a base directory, and cannot be made the default directory.");
        else:
            print(f"• `{name}` is not the name of a base directory.");
    else:
        if settings["baseDir"]==None:
            yn=askyn("• No base directories exist. Would you like to make the current working directory a base directory (and the default directory)? (y/n) ");
            if yn in {"y", "yes"}:
                addCurrentBaseDir();
                #settings["baseDirs"].add(path);
                settings["defaultDir"]=path;
                baseDir=path;
                settings["baseDir"]=path;
                print("• The current working directory is now a base directory and the default directory.");
                if activate==True:
                    settings["useDefault"]=True;
                    useDefault=True;
                    print("• The default directory is now active.");
                save_it=True;
            else:
                print("• New default directory not set.");
        else:
            if baseDir==None:
                settings["defaultDir"]=path;
                itWorked=addCurrentBaseDir();
                #settings["baseDirs"].add(path);
                if itWorked==True:
                    print("• The current working directory is now set to be the default directory.");
                    if activate==True:
                        settings["useDefault"]=True;
                        useDefault=True;
                        print("• The default directory is now active.");
                else:
                    print("• The current working directory has not been set to be the default directory.");
            else:
                settings["defaultDir"]=settings["baseDir"];
                if settings["defaultDir"]==baseDir:
                    print("• The current base directory is now set to be the default directory:\n"+settings["defaultDir"]);
                else:
                    print("• The last-accessed base directory is now set to be the default directory:\n"+settings["defaultDir"]);
                if activate==True:
                    settings["useDefault"]=True;
                    useDefault=True;
                    print("• The default directory is now active.");
            save_it=True;

def nameDir(name=None): #Name a directory.
    global path;
    global save_it;
    global settings;
    if name==None:
        name=ask(f"• What would you like to name the current working directory (`{path}`)? ");
    if " " in name:
        print(f"• No spaces are allowed in abstract directory names. `{path}` not named.");
    elif name!=None and name!="":
        ow="0"; #0 is just a value that won't get chosen (since we need to test for None in case of a canceled value).
        if name in settings["named_dirs"]:
            ow=askyn(f"• `{settings['named_dirs'][name]}` is already named `{name}`. Would you like to overwrite it?");
        if ow in {"y", "yes", "0"}:
            settings["named_dirs"][name]=path;
            save_it=True;
            print(f"• `{path}` is now named `{name}`.");
        else:
            if ow==None:
                print(f"• `{path}` not named.");
            else:
                print(f"• `{path}` not re-named.");
    else:
        print(f"• `{path}` not named.");

def setDir(): #Name a directory, make it a default directory, or make it a base directory.
    desired={"b", "d", "a", "c"};
    choice=ask(f"• What would you like to do to the current working directory?\n(d=make it the default directory; a=name the directory; b=make it a base directory; c=nothing): ", desired=desired);
    if choice=="b":
        addCurrentBaseDir();
    elif choice=="d":
        setDefaultDir();
    elif choice=="a":
        nameDir();
    elif choice=="c":
        print("• Current working directory not named/renamed, nor made a default, nor made a base directory.");
    else:
        assert choice in desired;

def updateProgram(): #Update the program
    global path;
    with tempfile.TemporaryDirectory() as DIR:
        print("• Downloading the update to a temporary directory.");
        tempDownload=os.path.join(DIR, FN);
        subprocess.run(["wget", f"https://vertex.alwaysdata.net/downloads/{FN}", "-P", DIR]);
        if os.path.exists(tempDownload)==False:
            print(f"• Downloading the update failed. {PN} not updated.");
        else:
            proDir=directoryPath();
            pp=programPath();
            if pp!=None:
                if not perm(pp):
                    pp=None
            if proDir!=None:
                if not perm(proDir):
                    proDir=None;
            yn=None;
            updatePath=None;
            def updateLocal():
                recommendedPath="/opt/bin";
                if os.path.isdir("/data/data/com.termux/files/"):
                    recommendedPath="/data/data/com.termux/files/usr/opt/bin";
                upDir=askdir(f"• Please enter a path in which to install {PN}:", default_dir=recommendedPath, default_dir2=os.path.dirname(pp), default_dir3="~/bin");
                if upDir==None:
                    print("• Update canceled");
                    return False;
                updatePath=os.path.join(os.path.expanduser(upDir), FN);
                if os.path.exists(updatePath):
                    if os.path.isdir(updatePath):
                        print(f"• A directory exists in that location. {PN} not updated.");
                        return False;
                    else:
                        overwrite=askyn(f"• `{updatePath}` already exists. Would you like to overwrite? ");
                        if overwrite in {"yes", "y"}:
                            return True, updatePath;
                        else:
                            print(f"• File not overwritten. {PN} not updated.");
                            return False;
                else:
                    #updatePath doesn't exist; make it.
                    return True, updatePath;
                #End updateLocal()
            if proDir!=None and pp!=None:
                ulresult=updateLocal();
                if ulresult==False:
                    return False;
                else:
                    updatePath=ulresult[1];
                print("• We have permission.");
                if os.path.exists(updatePath):
                    os.remove(updatePath);
                #print(tempDownload, updatePath);
                shutil.copyfile(tempDownload, updatePath);
                fileUserGroup=None;
                if os.path.exists(updatePath)==True:
                    fileUserGroup=user_group(updatePath);
                    if fileUserGroup["user"]==0 or fileUserGroup["group"]==0: #0 is root
                        setPerm(updatePath, getPerm(os.path.dirname(updatePath)));
                        permDict=user_group(os.path.dirname(updatePath));
                        shutil.chown(updatePath, user=permDict["user"], group=permDict["group"]);
                    else:
                        set_exec(updatePath);
                print(f"• {PN} successfully updated.");
                inPath=False;
                for x in sys.path:
                    x=os.path.expanduser(x);
                    x=os.path.abspath(x);
                    if x==updatePath:
                        inPath=True;
                        break;
                    elif os.path.isfile(os.path.join(x, FN))==True:
                        inPath=True;
                        break;
                putInPath=None;
                putInPath=askyn(f"• Would you like to put {PN} in your path? ");
                if putInPath in {"y", "yes"}:
                    if os.path.exists(updatePath)==True and "SUDO_UID" in os.environ.keys():
                        #Add to path for all users
                        if inPath==True:
                            yn=None;
                            yn=askyn(f"• There are already one or more programs called `{FN}` in your path. Remove it/them and update? ");
                            if yn in {"yes", "y"}:
                                subprocess.run(["update-alternatives", "--remove-all", FN]);
                        subprocess.run(["update-alternatives", "--install", f"/usr/bin/{FN}", FN, updatePath, "0"]);
                    else:
                        #Add to path for current user
                        bashrc=os.path.expanduser("~/.bashrc");
                        FN2path=f"export PATH=$PATH:{os.path.expanduser(updatePath)}";
                        bin2path=f"export PATH=$PATH:{os.path.expanduser('~/bin')}";
                        opt2path=f"export PATH=$PATH:{os.path.expanduser('/opt/bin')}";
                        if os.path.isdir("/data/data/com.termux/files/"):
                            opt2path=f"export PATH=$PATH:/data/data/com.termux/files/usr/opt/bin";
                        if os.path.isdir(bashrc):
                            print(f"• Error. `{bashrc} is a directory when it should be a file.` Not added to path.");
                            return None;
                        elif os.path.isfile(bashrc):
                            #Appending it to ~/.bashrc
                            with open(bashrc, "r") as FILE:
                                bashrcContents=FILE.read();
                                bashrcSplit=bashrcContents.split("\n");
                                if opt2path not in bashrcSplit:
                                    with open(bashrc, "a") as FILE2:
                                        FILE2.write(f"\n{opt2path}");
                                if bin2path not in bashrcSplit:
                                    with open(bashrc, "a") as FILE2:
                                        FILE2.write(f"\n{bin2path}");
                                if FN2path not in bashrcSplit and os.path.dirname(FN2path) not in bashrcSplit:
                                    with open(bashrc, "a") as FILE2:
                                        FILE2.write(f"\n{FN2path}");
                            subprocess.run(f"bash {bashrc}", shell=True);
                            print(f"Restarted `{bashrc}`.");
                        else:
                            #Creating ~/.bashrc and writing it to the path.
                            with open(bashrc, "w") as FILE:
                                if os.path.dirname(FN2path) not in {bin2path, opt2path}:
                                    FILE.write(f"{bin2path}\n{opt2path}\n{FN2path}");
                                else:
                                    #print(os.path.dirname(FN2path), {bin2path, opt2path});
                                    FILE.write(f"{bin2path}\n{opt2path}");
                            subprocess.run(f"bash {bashrc}", shell=True);
                            print(f"• Restarted `{bashrc}`.");
                else:
                    print(f"• {PN} was not put in the path (but if it was already in the path before the update, it should still be there).");
                    return None;

def loadScheme(filepath, kind="apps"): #Loads a scheme, such as for which apps ki uses to open which kinds of files, or a named directory scheme.
    global save_it;
    global settings;
    assert kind in {"apps", "xapps", "named_dirs"};
    f=None;
    with open(filepath, "r") as FILE:
        f=FILE.read();
    f=f.replace(";", "\n");
    f=re.sub("\n+", "\n", f).strip();
    lines=f.split("\n");
    if len(lines)%2!=0:
        if kind in {"apps", "xapps"}:
            print(f"• Your chosen file, `{filepath}`, has an odd number of entries. It must be even. An entry is defined as something that is not whitespace that is delimited by new lines and/or semi-colons. Semi-colons are converted to new lines, the first line is a file extension, the second is the app that opens it, the third line is a file extension, the fourth is the app that opens it, etc.");
        elif kind=="named_dirs":
            print(f"• Your chosen file, `{filepath}`, has an odd number of entries. It must be even. An entry is defined as something that is not whitespace that is delimited by new lines and/or semi-colons. Semi-colons are converted to new lines, the first line is a directory name, the second is the directory path that it corresponds to, the third line is another name, the fourth is its path, etc.");
        sys.exit(1);
    i=0;
    foundDefaultDir=False;
    while i<len(lines):
        fe=lines[i].strip(); #fe=file extension
        pr=lines[i+1].strip(); #pr=program to open the file extension by default.
        isDefaultDir=False;
        isBaseDir=False;
        if kind=="named_dirs":
            if fe.startswith("$")==True and foundDefaultDir==False:
                isDefaultDir=True;
                foundDefaultDir=True;
                fe=fe[1:];
            elif fe.startswith("@")==True:
                isBaseDir=True;
                fe=fe[1:];
            pr=os.path.expanduser(pr);
            if not os.path.isdir(pr):
                print(f"• Warning: `{pr}` is not a directory. Not naming it `{fe}`.");
                i+=2;
                continue;
            else:
                if isBaseDir==True or isDefaultDir==True:
                    addCurrentBaseDir(thePath=pr)
                if isDefaultDir==True:
                    if pr in settings["baseDirs"]:
                        settings["defaultDir"]=pr;
                        print(f"• `{pr}` is now the default directory.");
                    else:
                        print(f"• `{pr}` not made the default directory.");
        settings[kind][fe]=pr;
        if kind=="apps":
            print(f"• Making `{pr}` the default app to open `{fe}` files.");
        elif kind=="xapps":
            print(f"• Making `{pr}` the default app to run `{fe}` files.");
        elif kind=="named_dirs":
            print(f"• Making `{fe}` the name for `{pr}`.");
        save_it=True;
        i+=2;

whichScheme=None; #Values are None (to not load), "apps", "xapps", or "named_dirs".

######

#Encryption and decryption methods

def temp_uname(ext=None): #Do not delete this method; it isn't temporary
    #Create a filename that will hopefully crash some hacker programs that try to access it.
    tmp_filename="";
    for x in range(random.randint(4,10)):
        tmp_filename+=chr(random.randint(1113000, 1114111));
    tmp_filename+=".";
    for x in range(random.randint(1,10)):
        tmp_filename+=chr(random.randint(1113000, 1114111));
    if ext!=None:
        if ext[0]!=".":
            ext="."+ext;
        tmp_filename+=ext;
    return tmp_filename;
def hash(filepath, kind="sha3_512"):
    hash=None;
    text=None;
    with open(filepath, "rb") as FILE:
        text=FILE.read();
    try:
        hash=hashlib.new(kind);
    except AttributeError:
        #The specified hash doesn't exist.
        hash=hashlib.new("sha1");
    try:
        hash.update(text.encode());
    except AttributeError:
        hash.update(text);
    if kind.startswith("shake_"):
        return hash.hexdigest(16);
    else:
        return hash.hexdigest();
def gpg_encrypt(filepath, passphrase=None, output=None, alg="AES256", hash="SHA512", compress_alg="BZIP2", compress_lvl="9", iterations="1000000", armor=True):
    #This is for symmetric encryption.
    global settings;
    filepath=os.path.expanduser(filepath);
    if output!=None:
        output=os.path.expanduser(output);
    else:
        output=os.path.join(os.getcwd(), os.path.basename(filepath)+".asc");
    if armor==True:
        armor="a";
    else:
        armor="";
    iterations=str(iterations);
    compress_level="--compress-level "+compress_lvl;
    if compress_alg.upper()=="BZIP2":
        compress_level="--bzip2-compress-level "+compress_lvl;
    result=None;
    if passphrase==None:
        passphrase=getpass.getpass("• Please enter your passphrase: ");
        repassphrase=getpass.getpass("• Please re-enter your passphrase: ");
        if passphrase!=repassphrase:
            print("• Your passphrases do not match. File not encrypted.");
            return None;
        elif passphrase==None:
            print("• File not encrypted.");
            return None;
    tmp_filename=temp_uname();
    with tempfile.TemporaryDirectory() as DIR:
        tmpfilepath=os.path.join(DIR, tmp_filename);
        with open(tmpfilepath, "w") as FILE:
            FILE.write(passphrase);
        if output:
            result=subprocess.Popen("cat "+tmpfilepath+"|"+settings["gpg_path"]+" --batch --yes --passphrase-fd 0 --force-mdc --s2k-mode 3 --s2k-count "+iterations+" --s2k-cipher-algo "+alg+" --s2k-digest-algo "+hash+" --compress-algo='"+compress_alg+"' "+compress_level+" --output="+shlex.quote(output)+" -"+armor+"c "+shlex.quote(filepath), stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True).communicate()[0];
        else:
            result=subprocess.Popen("cat "+tmpfilepath+"|"+settings["gpg_path"]+" --batch --yes --passphrase-fd 0 --force-mdc --s2k-mode 3 --s2k-count "+iterations+" --s2k-cipher-algo "+alg+" --s2k-digest-algo "+hash+" --compress-algo='"+compress_alg+"' --compress-level "+compress_lvl+" -"+armor+"c "+shlex.quote(filepath), stdin=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True).communicate()[0];
    if result.strip()==b"":
        print("• File encrypted.");
    return result.strip();
def encrypt_file(filepath):
    if os.path.basename(filepath).endswith(".asc")==False:
        encrypt=False;
        passphrase=ask("• Please enter a new passphrase: ", pw=True);
        repassphrase=ask("• Please re-enter it: ", pw=True);
        if passphrase==None:
            print(f"• No changes made to `{filepath}`.");
        elif passphrase==repassphrase:
            encrypt=True;
        else:
            print(f"• The passphrase doesn't match. No changes made to `{filepath}`.");
        if encrypt==True:
            subprocess.run("echo RELOADAGENT|gpg-connect-agent", shell=True);
            gpg_encrypt(filepath=filepath, passphrase=passphrase, output=filepath+".asc");
            subprocess.run("echo RELOADAGENT|gpg-connect-agent", shell=True);
        else:
            print("• File not encrypted.");
    else:
        print("• According to the file extension, it is already encrypted.");
def crypt(filepath, app):
    #Decrypt an .x.asc file (where x is any file extension), open the decrypted file, after it closes, check to see if the file has changed, and if it has, encrypt it and write over the old encrypted file.
    global settings;
    subprocess.run("echo RELOADAGENT|gpg-connect-agent", shell=True);
    decryptedfp=temp_uname(); #Filepath for the decrypted file.
    reEncrypted=None;
    result=None;
    with tempfile.TemporaryDirectory() as DIR:
        decryptedfp=os.path.join(DIR, decryptedfp);
        passphrase=getpass.getpass("• Please enter the passphrase: ");
        if passphrase!=None:
            tmp_filename=temp_uname();
            with tempfile.TemporaryDirectory() as DIR2:
                tmpfilepath=os.path.join(DIR2, tmp_filename);
                with open(tmpfilepath, "w") as FILE:
                    FILE.write(passphrase);
                result=subprocess.Popen("cat "+shlex.quote(tmpfilepath)+"|"+settings["gpg_path"]+" -d --batch --yes --passphrase-fd 0 --output="+shlex.quote(decryptedfp)+" "+shlex.quote(filepath), stdin=subprocess.PIPE, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0];
        result=not "decryption failed:" in str(result), str(result); #If it worked, return True and the result. If not, return False and the result.
        if result[0]==True:
            print(f"• `{filepath}` decrypted and saved to `{decryptedfp}`.");
            #Hash the decrypted file.
            theHash=hash(decryptedfp);
            #Open with app
            subprocess.run(app+[decryptedfp]);
            #Take a new hash of the decrypted file.
            newHash=hash(decryptedfp);
            #Check the hashes to see if changes were saved.
            if theHash!=newHash:
                reEncrypt=ask(f"• Would you like to re-encrypt and overwrite `{filepath}`? (y=yes, same passphrase, n=no, r=yes, new passphrase) ", desired={"y", "n", "r"});
                if reEncrypt=="r":
                    passphrase=ask("• Please enter a new passphrase: ", pw=True);
                    repassphrase=ask("• Please re-enter it: ", pw=True);
                    if passphrase==None:
                        print(f"• No changes made to `{filepath}`.");
                    elif passphrase==repassphrase:
                        reEncrypt="y";
                    else:
                        print(f"• The passphrase doesn't match. No changes made to `{filepath}`.");
                if reEncrypt=="y":
                    #Ask whether to re-encrypt and overwrite.
                    print(f"• Encrypting saved changes to `{decryptedfp}` and overwriting `{filepath}` with the new encrypted file.");
                    #Re-encrypt
                    reEncrypted=gpg_encrypt(filepath=decryptedfp, passphrase=passphrase, output=filepath);
                else:
                    print(f"• No changes made to `{filepath}`.");
            else:
                print(f"• No changes made to `{filepath}`.");
        else:
            print(f"• `{filepath}` not decrypted.");
            #print("result", result);
        subprocess.run("echo RELOADAGENT|gpg-connect-agent", shell=True);
        return result, reEncrypted; #decryptionResult, re-encryptionResult

######

if __name__=="__main__":
    if len(sys.argv)>=2:
        args=sys.argv[1:];
        pre=" ".join(args).strip(); #string args to test for flags and such before doing any manipulations.
        if pre.startswith("-")==True and pre.startswith("--")==False:
            flags=pre.replace(" -", "");
            flagSplit=flags.split(" ");
            args=flagSplit[1:];
            flags=flagSplit[0];
            if flags.replace("-","") in {"aload", "nload", "xload", "help", "about", "ver", "version", "go", "license", "asave", "nsave", "xsave", "load", "save", "settings", "do"}:
                print("• Your chosen flags/options are arranged like a non-cominable option name. For safety reasons, in case this was an accident, the program will exit here without doing anything else. Use two hyphens for the non-combinable option. You may try reordering your flags.");
                sys.exit(1);
            if "0" in flags: #Toggle whether to open/decrypt passphrase-protected files and offer to encrypt any saved changes to the temporary file and overwrite the original.
                settings["kicrypt"]=not settings["kicrypt"];
                save_it=True;
                if settings["kicrypt"]==True:
                    print(f"• `.{FN}.asc` files are now handled specially (for encryption/decryption).");
                else:
                    print(f"• `.{FN}.asc` files are now handled as any other file.");
            if "1" in flags: #Passphrase-protect the key instead of opening it.
                passphraseProtect=True;
                print("• Attempting to passphrase-protect the key.");
            if "2" in flags: #Toggle whether to open the specified extension if one is specified (instead of the default), and whether to set that specified extension as the default.
                settings["openSpecifiedExt"]=not settings["openSpecifiedExt"];
                save_it=True;
                print("• Open specified extension set to "+str(settings["openSpecifiedExt"])+".");
            if "4" in flags: #Toggle non-default extension specified as the default.
                settings["setOpenDefault"]=not settings["setOpenDefault"];
                save_it=True;
                print("• The setting whether to make a specified extension the default has been set to "+str(settings["setOpenDefault"])+".");
            if "8" in flags: #Make the current working directory a base directory
                addCurrentBaseDir();
            if "9" in flags: #Make the current or last-accessed base directory the default directory, or if there is none, make the current working directory the default directory (and a base directory).
                setDefaultDir();
            if "A" in flags: #-p This means the next non-flag group of characters is the new default app to use instead of nano, or whatever.
                if "x" in flags:
                    settings["xapp"]=args[0];
                    print("• Files will now be run with "+settings["xapp"]+" by default.\n");
                else:
                    settings["app"]=args[0];
                    print("• Files will now be opened with "+settings["app"]+" by default.\n");
                args=args[1:];
                save_it=True;
            if "a" in flags: #Like -A without changing the default app. No point using it with -x, either.
                oneTimeApp=args[0]; #&&&
                print("• Your file will open with "+oneTimeApp+", this time.\n");
                args=args[1:];
            if "B" in flags: #Set the current working directory (to be a base or the default; or give it an abstract name for use later).
                #addCurrentBaseDir();
                setDir();
            if "b" in flags: #Search from a specified directory (by the abstract name you've given it).
                incflags=set("BdDgNt89");
                badCombo=False;
                badList="";
                for x in incflags:
                    if x in flags:
                        badCombo=True;
                        badList+=x;
                if badCombo==True:
                    print(f"• The flag(s)/option(s) `-{badList}` are not combinable with -b; therefore, -b is not being done. Not opening/creating a key.");
                    args=[];
                else:
                    abname=args[0];
                    args=args[1:];
                    if abname in settings["named_dirs"]:
                        path=settings["named_dirs"][abname];
                        baseDir=path;
                        useDefault=False;
                        os.chdir(path);
                    else:
                        print(f"• `{abname}` is not an abstract directory name. Because of this, if you supplied a key it will not be accessed, nor created this time.");
                        args=[];
            if "c" in flags: #Clear the list of base directories, or remove the current base directory from being a base directory.
                if len(settings["baseDirs"])>0:
                    yn=ask("• Are you sure you want to clear the following base directories?\n"+"\n".join(settings["baseDirs"])+"\n(y/n; press c to remove only the current base directory.) ", desired={"yes", "y", "no", "n", "c"});
                    if yn in {"y", "yes"}:
                        settings["baseDirs"].clear();
                        save_it=True;
                        print("• Base directories cleared.");
                        baseDir=None;
                        if settings["defaultDir"]!=None:
                            settings["defaultDir"]=None;
                            print("• The default directory has been removed.");
                    elif yn=="c":
                        if baseDir==None:
                            print("• You are not in a base directory. It cannot be removed.");
                        else:
                            settings["baseDirs"].remove(baseDir);
                            save_it=True;
                            print("• The following directory is no longer a base directory:\n"+baseDir);
                            baseDir=None;
                    else:
                        print("• Base directories not cleared.");
                else:
                    print("• You don't have any base directories set to clear.");
            if "D" in flags: #Toggle whether to use the default directory when you aren't in a base directory.
                settings["useDefault"]=not settings["useDefault"];
                print("• Default directory: "+settings["defaultDir"]);
                print("• Use default directory (when not in a base directory structure) set to "+str(settings["useDefault"])+".");
                save_it=True;
            if "d" in flags: #Make it so the default directory is used (this time only). Or, if the default directory is enabled, this makes it so it isn't used (this time only).
                useDefault=not useDefault; #Toggle it, without saving settings["useDefault"];
                if useDefault==True:
                    print("• Default directory: "+settings["defaultDir"]);
                    print("• Default directory enabled (this time only).");
                else:
                    print("• Default directory: "+settings["defaultDir"]);
                    print("• Default directory disabled (this time only).");
            if "e" in flags: #Set the default extension.
                ext_set=input("• Please enter a file extension to be the default (blank for no file extension; `.` for cancel; current default=`"+settings["extension"]+"`): ");
                if ext_set!=None:
                    if ext_set==".":
                        print("• The default file extension has not been changed.");
                    elif ext_set=="":
                        settings["extension"]=ext_set;
                        print("• The default file extension is now no extension.\n");
                        save_it=True;
                    else:
                        if ext_set[0]!=".":
                            ext_set="."+ext_set;
                        settings["extension"]=ext_set;
                        print("• The default file extension is now `"+settings["extension"]+"`.\n");
                        save_it=True;
                else:
                    print("• The default file extension has not been changed.");
            if "g" in flags: #Specify a named directory (by name) and turn it into the default directory.
                incflags=set("BbdDNt89");
                badCombo=False;
                badList="";
                for x in incflags:
                    if x in flags:
                        badCombo=True;
                        badList+=x;
                if badCombo==True:
                    print(f"• The flag(s)/option(s) `-{badList}` are not combinable with -g; therefore, -g is not being done. Not opening/creating a key.");
                    args=[];
                else:
                    abname=args[0];
                    args=args[1:];
                    if abname in settings["named_dirs"]:
                        setDefaultDir(name=abname, activate=True);
                    else:
                        print(f"• `{abname}` is not an abstract directory name. Because of this, if you supplied a key it will not be accessed, nor created this time.");
                        args=[];
            if "h" in flags: #Print the help string.
                print("\n\n"+HELP+"\n\n");
            if "n" in flags: #List the base directories and the default directory.
                print("• Default directory:\n"+str(settings["defaultDir"]));
                if len(settings["baseDirs"])>0:
                    print("• Base directories:\n"+"\n".join(settings["baseDirs"]));
                else:
                    print("• Base directories:\nNone");
                if len(settings["named_dirs"])==0:
                    print("• There are no named directories.");
                else:
                    print("• Named directories:")
                    for k,v in settings["named_dirs"].items():
                        print(f"`{k}`: `{v}`");
            if "N" in flags: #Give the current working directory structure a name.
                incflags=set("AaBbdDgt89");
                badCombo=False;
                badList="";
                for x in incflags:
                    if x in flags:
                        badCombo=True;
                        badList+=x;
                if badCombo==True:
                    print(f"• The flag(s)/option(s) `-{badList}` are not combinable with -N; therefore, -N is not being done. Not opening/creating a key.");
                    args=[];
                else:
                    try:
                        nameDir(name=args[0]);
                        args=args[1:];
                    except IndexError:
                        print("You did not supply a name to give the current working directory");
                        nameDir();
                        args=[];
            if "o" in flags: #Toggle whether to open all files with the name at once (instead of just one). Default is False.
                settings["openAll"]=not settings["openAll"];
                print("• Open all files at once set to "+str(settings["openAll"])+".");
                save_it=True
            if "P" in flags: #Undo an action performed with -p.
                whichapps=ask("• Do you wish to reset regular apps or -x apps? (r/x) ", desired={"r", "x"});
                if whichapps not in {"r","x"}:
                    print("• No opening/running apps reset.");
                else:
                    if whichapps=="r":
                        whichapps=["app", "apps"];
                    else:
                        whichapps=["xapp","xapps"];
                    removeEH=input("• Please enter the extension you wish to reset (press * to reset the default extension): ");
                    if removeEH!=None:
                        if removeEH=="*":
                            settings[whichapps[0]]=bu()[whichapps[0]];
                            save_it=True;
                            print("• The default app has been reset to `"+settings[whichapps[0]]+"`.");
                        else:
                            removeEH=re.sub(r"^\.*(\w+)", r".\1", removeEH).strip();
                            if removeEH in settings[whichapps[1]]:
                                try:
                                    settings[whichapps[1]][removeEH]=bu()[whichapps[1]][removeEH];
                                    print("• Extension `"+removeEH+"` reset to `"+settings[whichapps[1]][removeEH]+"`.");
                                except KeyError:
                                    del settings[whichapps[1]][removeEH];
                                    print("• Extension `"+removeEH+"` reset to the default, `"+settings[whichapps[0]]+"`.");
                                save_it=True;
                            else:
                                print("• That file extension has not been set (so, it cannot be reset).");
                    else:
                        print("• No changes have been made to which apps open which extensions.");
            if "p" in flags: #Set a specific extension to open with a specific app.
                whichExtension=None;
                whichApp=None;
                print("• Here you can specify which app will correspond to which file extension. (This overrides the default program.)");
                whichExtension=input("• Please enter a file extension (keep blank for no file extension; type * to set the default app for all unassigned extensions): ");
                if whichExtension!=None:
                    whichApp=input("• Please enter the name of an app to open it: ");
                if whichApp!=None:
                    if whichApp.strip()!="":
                        if whichExtension=="*":
                            settings["app"]=whichApp;
                            save_it=True;
                            print("• All unhandled extensions will now be opened with `"+whichApp+"`.")
                        else:
                            whichExtension=re.sub(r"^\.*(\w+)", r".\1", whichExtension).strip();
                            settings["apps"][whichExtension]=whichApp;
                            save_it=True;
                            print("• The extension `"+whichExtension+"` is now handled by `"+whichApp+"`");
                    else:
                        print("• Your app name must be more than whitespace.");
                else:
                    print("• Default app for file extension unchanged.");
            if "q" in flags: #Reset app data
                yn=askyn(f"• Are you sure you want to reset {PN}'s application data (if you have unevaluated flags/options, {PN} will quit before evaluating them)? (y/n) ");
                if yn in {"y", "yes"}:
                    #settings=backup.copy();
                    #os.remove(settingsDictPath);
                    #os.remove(backupDictPath);
                    shutil.rmtree(settingsPath)
                    baseDir=None;
                    #save_it=True;
                    print(f"• {PN}'s application data has been reset.");
                    sys.exit(1);
                else:
                    print(f"• {PN}'s application data has not been reset.");
            if "R" in flags: #Remove the current directory or current base directory from being a base directory.
                if baseDir==None:
                    print("• You are not in a base directory structure and so cannot remove it from being a base directory (because it isn't one). No base directory removed from being a base directory.");
                else:
                    yn=askyn("• Are you sure you want to remove the current base directory from being a base directory? FYI, if so, if it's the default directory, it will no longer be the default. (y/n) ");
                    if yn in {"y", "yes"}:
                        settings["baseDirs"].remove(baseDir);
                        if settings["baseDir"]==baseDir:
                            settings["baseDir"]=None;
                        if settings["defaultDir"]==baseDir:
                            settings["defaultDir"]=None;
                            print("• The default directory is no longer set.");
                        save_it=True;
                        print("• The current base directory has been removed from being a base directory:\n"+baseDir);
                        baseDir=None;
                    else:
                        print("• The current base directory is unchanged (still a base directory).");
            if "r" in flags: #Disable the default file extension for this run only.
                ddd=True;
                print("• Ignoring the default extension this run.");
            if "s" in flags: #Toggle whether to search for the key to open from the base directory (default is True).
                settings["searchBaseDir"]=not settings["searchBaseDir"];
                print("• Searching for keys from the base directory set to "+str(settings["searchBaseDir"])+".");
                save_it=True;
            if "t" in flags: #Enable the default directory this time only if it is disabled.
                if useDefault==False:
                    useDefault=True;
                    print("• Default directory enabled (this time only).");
                else:
                    print("• The default directory is already enabled.");
            if "T" in flags: #Disable the default directory this time only if it is enabled.
                if useDefault==True:
                    useDefault=False;
                    print("• Default directory disabled (this time only).");
                else:
                    print("• The default directory is already disabled.");
            if "u" in flags: #Prompt to install Ki to your path
                updateProgram();
            if "v" in flags: #Print the version string.
                print("\n\n"+VERSION+"\n\n");
            if "X" in flags:
                whichExtension=None;
                whichApp=None;
                print("• Here you can specify which app will correspond to which file extension when using the -x flag. (Settings for specific extensions override the default.)");
                whichExtension=input("• Please enter a file extension (keep blank for no file extension; type * to set the default app for all unassigned extensions): ");
                if whichExtension!=None:
                    whichApp=input("• Please enter the name of an app to open it: ");
                if whichApp!=None:
                    if whichApp.strip()!="":
                        if whichExtension=="*":
                            settings["xapp"]=whichApp;
                            save_it=True;
                            print("• All unhandled extensions will now be run with `"+whichApp+"` when the -x flag is given.");
                        else:
                            whichExtension=re.sub(r"^\.*(\w+)", r".\1", whichExtension).strip();
                            settings["xapps"][whichExtension]=whichApp;
                            save_it=True;
                            print("• The extension `"+whichExtension+"` is now handled by `"+whichApp+"`");
                    else:
                        print("• Your app name must be more than whitespace.");
                else:
                    print("• Default app for file extension unchanged.");
            if "x" in flags: #Toggle the option to execute a file instead of opening it.
                execute_it=True;
                print("• The file will be executed instead of opened, where applicable, this time.");
        elif pre.startswith("--")==True:
            if pre=="--about":
                args=[]; #Clear the args so it won't do anything else.
                print(ABOUT);
            elif pre=="--aload" or pre.startswith("--aload "): #Loads an app scheme (which apps open which files)
                args=args[1:];
                whichScheme="apps";
                if pre=="--aload":
                    schemefp=askfile("• Please enter the filepath for your app scheme: ");
                    if schemefp==None:
                        sys.exit(1);
                    else:
                        loadScheme(schemefp);
                else:
                    print("• Attempting to load specified app scheme.");
            elif pre=="--asave" or pre.startswith("--asave "):
                args=args[1:];
                appsString="";
                for x in settings["apps"]:
                    appsString+=x+"; "+settings["apps"][x]+"\n";
                if pre=="--asave":
                    schemefp=ask(f"• Please enter the filepath in which to save the current app scheme (or just the filename, for the current working directory): ");
                    if schemefp==None:
                        sys.exit(1);
                    else:
                        obackup=None;
                        if os.path.isfile(schemefp)==True:
                            obackup=ask(f"• `{schemefp}` already exists. Overwrite? ");
                        if os.path.isdir(schemefp)==True:
                            print(f"• `{schemefp}` is a directory; app scheme not saved.");
                        else:
                            if obackup==None or obackup in {"y", "yes"}:
                                with open(schemefp, "w") as FILE:
                                    FILE.write(appsString);
                                print("• Backup app scheme saved.");
                else:
                    obackup=None;
                    if os.path.isfile(args[0])==True:
                        obackup=ask(f"• `{args[0]}` already exists. Overwrite? ");
                    if os.path.isdir(args[0])==True:
                        print(f"• `{args[0]}` is a directory. App scheme not saved.");
                    else:
                        if obackup==None or obackup in {"y", "yes"}:
                            #subprocess.run(f"cp {settingsDictPath} {args[0]}", shell=True);
                            with open(args[0], "w") as FILE:
                                FILE.write(appsString);
                            print(f"• Current app scheme saved at `{args[0]}`.");
                        else:
                            print("• Current app scheme not saved.");
                    args=[];
            elif pre.startswith("--do "): #Like --go [name], but it makes it the default directory and activates the default directory, too.
                args=args[1:];
                if args[0] in settings["named_dirs"]:
                    print(f"• Opening a new shell in `{args[0]}`: `{settings['named_dirs'][args[0]]}`. Type `exit` to return to where you were, when you are ready.");
                    setDefaultDir(name=args[0], activate=True);
                    subprocess.run(["bash"]);
                    print("• Welcome back to your old shell!");
                else:
                    print(f"• You have not named a directory `{args[0]}`. You cannot go to it.");
                args=[];
            elif pre=="--f": #&&&Open multiple filepaths (files with different names) instead of just one name; requires escaped spaces (no unescaped qutoes to handle spaces).
                #multipleFiles=re.split(r"(?<!\\) ", args);
                pass;
            elif pre in {"--g", "--go"} or pre.startswith("--g ") or pre.startswith("--go "):
                args=args[1:];
                if pre in {"--g", "--go"}:
                    if settings["lastKeyDir"]!=None:
                        print(f"""• Opening a new shell in the last key directory: `{settings["lastKeyDir"]}`. Type `exit` to return to where you were, when you are ready.""");
                        os.chdir(settings["lastKeyDir"]);
                        #subprocess.run(["cd", settings["lastKeyDir"]]);
                        subprocess.run(["bash"]);
                        print("• Welcome back to your old shell!");
                    else:
                        print("• There is no last key directory, yet.");
                else:
                    if args[0] in settings["named_dirs"]:
                        print(f"• Opening a new shell in `{args[0]}`: `{settings['named_dirs'][args[0]]}`. Type `exit` to return to where you were, when you are ready.");
                        os.chdir(settings["named_dirs"][args[0]]);
                        subprocess.run(["bash"]);
                        print("• Welcome back to your old shell!");
                    else:
                        print(f"• You have not named a directory `{args[0]}`. You cannot go to it.");
                args=[];
            elif pre=="--help":
                args=[]; #Clear the args so it won't do anything else.
                print(HELP);
            elif pre=="--license":
                args=[];
                print(LICENSE);
            elif pre=="--load" or pre.startswith("--load "):
                args=args[1:];
                if pre=="--load":
                    schemefp=ask(f"• Please enter the filepath for your backup of {PN}'s data (or just the filename, for the current working directory): ");
                    if schemefp==None:
                        sys.exit(1);
                    else:
                        if os.path.isfile(schemefp)==True:
                            subprocess.run(f"cp {schemefp} {settingsDictPath}", shell=True);
                            print("• Backup restored.");
                        else:
                            print(f"• `{schemefp}` is not a file; backup not restored.");
                            sys.exit(1);
                else:
                    if os.path.isdir(args[0])==True:
                        print("• `{args[0]}` is a directory. Settings not restored.");
                    else:
                        subprocess.run(f"cp {args[0]} {settingsDictPath}", shell=True);
                        print(f"• Restored the settings with `{args[0]}`.");
                    args=[];
                sys.exit(1); #This prevents the restored settings from being overwritten with the old ones later if this is the first run.
            elif pre=="--nload" or pre.startswith("--nload "):
                args=args[1:];
                whichScheme="named_dirs";
                if pre=="--nload":
                    schemefp=askfile("• Please enter the filepath for your named directory scheme: ");
                    if schemefp==None:
                        sys.exit(1);
                    else:
                        loadScheme(schemefp, kind="named_dirs");
                else:
                    print("• Attempting to load specified named directory scheme.");
            elif pre=="--nsave" or pre.startswith("--nsave "):
                args=args[1:];
                nschemeString="";
                checkDefault=True;
                for x in settings["named_dirs"]:
                    if checkDefault==True and settings["defaultDir"]==settings["named_dirs"][x]:
                        nschemeString+="$";
                        checkDefault=False;
                    elif settings["named_dirs"][x] in settings["baseDirs"]:
                        nschemeString+="@";
                    nschemeString+=x+"; "+settings["named_dirs"][x]+"\n";
                if pre=="--nsave":
                    schemefp=ask(f"• Please enter the filepath in which to save the current directory name scheme (or just the filename, for the current working directory): ");
                    if schemefp==None:
                        sys.exit(1);
                    else:
                        obackup=None;
                        if os.path.isfile(schemefp)==True:
                            obackup=ask(f"• `{schemefp}` already exists. Overwrite? ");
                        if os.path.isdir(schemefp)==True:
                            print(f"• `{schemefp}` is a directory; xapp scheme not saved.");
                        else:
                            if obackup==None or obackup in {"y", "yes"}:
                                with open(schemefp, "w") as FILE:
                                    FILE.write(nschemeString);
                                print("• Backup directory name scheme saved.");
                else:
                    obackup=None;
                    if os.path.isfile(args[0])==True:
                        obackup=ask(f"• `{args[0]}` already exists. Overwrite? ");
                    if os.path.isdir(args[0])==True:
                        print(f"• `{args[0]}` is a directory; directory name scheme not saved.");
                    else:
                        if obackup==None or obackup in {"y", "yes"}:
                            with open(args[0], "w") as FILE:
                                FILE.write(nschemeString);
                            print(f"• Current directory name scheme saved at `{args[0]}`.");
                        else:
                            print("• Current directory name scheme not saved.");
                    args=[];
            elif pre=="--save" or pre.startswith("--save "):
                args=args[1:];
                if pre=="--save":
                    schemefp=ask(f"• Please enter the filepath for your backup of {PN}'s data (or just the filename, for the current working directory): ");
                    if schemefp==None:
                        sys.exit(1);
                    else:
                        subprocess.run(f"cp {settingsDictPath} {schemefp}", shell=True);
                        print("• Backup saved.");
                else:
                    obackup=None;
                    if os.path.isfile(args[0])==True:
                        obackup=ask(f"• `{args[0]}` already exists. Overwrite? ");
                    if os.path.isdir(args[0])==True:
                        print(f"• `{args[0]}` is a directory. Settings not backed up.");
                    else:
                        if obackup==None or obackup in {"y", "yes"}:
                            subprocess.run(f"cp {settingsDictPath} {args[0]}", shell=True);
                            print(f"• Backed up the settings at `{args[0]}`.");
                        else:
                            print("• Settings not backed up.");
                    args=[];
            elif pre=="--settings":
                pass; #&&& This is to print the current settings/configuration.
            elif pre=="--ver": #Print the version number only.
                args=[];
                print(VER);
            elif pre=="--version":
                args=[]; #Clear the args so it won't do anything else.
                print(VERSION);
            elif pre=="--xload" or pre.startswith("--xload "): #Loads a xapp scheme (which xapps run which files)
                args=args[1:];
                whichScheme="xapps";
                if pre=="--xload":
                    schemefp=askfile("• Please enter the filepath for your xapps scheme: ");
                    if schemefp==None:
                        sys.exit(1);
                    else:
                        loadScheme(schemefp, kind="xapps");
                else:
                    print("• Attempting to load specified xapp scheme.");
            elif pre=="--xsave" or pre.startswith("--xsave "):
                args=args[1:];
                appsString="";
                for x in settings["xapps"]:
                    appsString+=x+"; "+settings["xapps"][x]+"\n";
                if pre=="--xsave":
                    schemefp=ask(f"• Please enter the filepath in which to save the current xapp scheme (or just the filename, for the current working directory): ");
                    if schemefp==None:
                        sys.exit(1);
                    else:
                        obackup=None;
                        if os.path.isfile(schemefp)==True:
                            obackup=ask(f"`{schemefp}` already exists. Overwrite? ");
                        if os.path.isdir(schemefp)==True:
                            print(f"• `{schemefp}` is a directory; xapp scheme not saved.");
                        else:
                            if obackup==None or obackup in {"y", "yes"}:
                                with open(schemefp, "w") as FILE:
                                    FILE.write(appsString);
                                print("• Backup xapp scheme saved.");
                else:
                    obackup=None;
                    if os.path.isfile(args[0])==True:
                        obackup=ask(f"• `{args[0]}` already exists. Overwrite? ");
                    if os.path.isdir(args[0])==True:
                        print(f"• `{args[0]}` is a directory; xapp scheme not saved.");
                    else:
                        if obackup==None or obackup in {"y", "yes"}:
                            with open(args[0], "w") as FILE:
                                FILE.write(appsString);
                            print(f"• Current xapp scheme saved at `{args[0]}`.");
                        else:
                            print("• Current xapp scheme not saved.");
                    args=[];
        if len(args)!=0 and args[0].startswith(":")==True:
            args=" ".join(args).strip();
            args=args[1:];
            #args=re.sub(r"\\* +", r"\\ ", args);
            args=escapePath(args);
            if baseDir!=None and settings["searchBaseDir"]==True:
                #rdir=re.sub(r"\\* +", r"\\ ", baseDir);
                rdir=escapePath(baseDir);
                subprocess.run("grep -rliF --include=\\* "+args+" "+rdir+"/**/", shell=True);
            elif settings["defaultDir"]!=None and useDefault==True:
                rfile=os.path.join(settings["defaultDir"], args);
                #rdir=re.sub(r"\\* +", r"\\ ", settings["defaultDir"]);
                rdir=escapePath(settings["defaultDir"]);
                subprocess.run("grep -rliF --include=\\* "+args+" "+rdir+"/**/", shell=True);
            else:
                subprocess.run("grep -rliF --include=\\* "+args+" ./", shell=True);
        elif len(args)!=0:
            ######
            #print(args);
            extension=settings["extension"]; #We need to use this in case we don't save to save it.
            if "." in args[-1] and settings["openSpecifiedExt"]==True: #If an extension is specified, make it the new default extension (it goes from the final period to the end of the filename).
                extension="."+args[-1].split(".")[-1];
                if extension!=settings["extension"] and settings["setOpenDefault"]==True:
                    settings["extension"]=extension;
                    print("• Default extension changed to `"+settings["extension"]+"`.");
                    #saveSettings();
                    save_it=True;
            if ddd==True: #Disable the default extension this run.
                extension="";
            if len(args)>1:
                args=" ".join(args);
                args=args.strip();
                if not args.endswith(extension):
                    args=args+extension;
                elif settings["openSpecifiedExt"]==False: #Add the extension even if you specify it.
                    args=args+extension;
                if "\\" in args: #&&& Make it so it does args=unescapePath(args) if and only all the characters that need escaping are escaped (make a method that checks to see if they're escaped).
                    args=args.replace("\\", "");
            else:
                args=args[0];
                args=os.path.basename(args);
                if not args.endswith(extension):
                    args=args+extension;
                elif settings["openSpecifiedExt"]==False: #Add the extension even if you specify it.
                    args=args+extension;

            g=None;
            if settings["searchBaseDir"]==True and baseDir!=None:
                if useDefault==True and settings["defaultDir"]!=None:
                    g=eglob(settings["defaultDir"], args, recursive=True);
                else:
                    g=eglob(baseDir, args, recursive=True);
            else:
                if settings["searchBaseDir"]==True and useDefault==False:
                    print("• There is no base directory here; using the current working directory.");
                    g=eglob(path, args, recursive=True);
                elif settings["searchBaseDir"]==True and useDefault==True and settings["defaultDir"]!=None:
                    print("• Using the default directory:\n"+settings["defaultDir"]);
                    g=eglob(settings["defaultDir"], args, recursive=True);
                elif useDefault==True:
                    if settings["defaultDir"]!=None:
                        g=eglob(settings["defaultDir"], args, recursive=True);
                    else:
                        print("• The default directory is not set. (Acting as if it is disabled.)");
                        if baseDir==None:
                            print("• There is no base directory here; using the current working directory.");
                        g=eglob(path, args, recursive=True);
                else:
                    g=eglob(path, args, recursive=True);
            try:
                allFound=[];
                firstFile=True;
                for f in g:
                    dn=os.path.dirname(f);
                    bn=os.path.basename(f);
                    f=dn+os.sep+bn;
                    #f=re.sub(r"\\* ", r"\\ ", f);
                    #f=escapePath(f);
                    allFound.append(f);
                    if firstFile==True:
                        if settings["lastKeyDir"]!=dn:
                            settings["lastKeyDir"]=dn;
                            print("• Last key directory set to `"+settings["lastKeyDir"]+"`.");
                            #saveSettings();
                            save_it=True;
                        firstFile=False;
                if whichScheme!=None: #If we're loading a scheme, do this
                    loadScheme(allFound[0], kind=whichScheme);
                else:
                    if settings["openAll"]==False:
                        if passphraseProtect==False or allFound[0].endswith(".asc")==True:
                            if settings["kicrypt"]==True and allFound[0].endswith(".asc"):
                                crypt(allFound[0], getApp());
                            else:
                                #print("getApp", getApp(), "[allFound[0]]", [allFound[0]]);
                                subprocess.run(getApp()+[allFound[0]]); #This is what is usually run when a file is opened with the default settings.
                        else:
                            #If it's not encrypted, encrypt it instead of opening it.
                            ascFound=os.path.isfile(allFound[0]+".asc");
                            ynOverwrite="unassigned";
                            if ascFound==True:
                                ynOverwrite=askyn("""• A file called `{allFound[0]+".asc"}` already exists. Overwrite? (y/n)""");
                            if ynOverwrite in {"y", "yes", "unassigned"}:
                                encrypt_file(allFound[0]);
                                if os.path.isfile(allFound[0]+".asc")==True and allFound[0].endswith(".asc")==False:
                                    #Delete allFound[0]
                                    delOldKey=askyn("• Would you like to keep the unencrypted file?");
                                    if delOldKey in {"no", "n"}:
                                        os.remove(allFound[0]);
                                        print("• Unencrypted file deleted.");
                                    else:
                                        print("• Unencrypted file kept.");
                            else:
                                print("• File not encrypted. File not overwritten.");
                    else:
                        subprocess.run(getApp()+allFound);
            except IndexError:
                ######
                yn=None;
                def chunk():
                    global settings, path;
                    if settings["defaultDir"]==None:
                        return False;
                    else:
                        #return settings["defaultDir"].startswith(path+os.sep);
                        return path.startswith(settings["defaultDir"]+os.sep);
                if useDefault==True and (chunk() or settings["defaultDir"]==path):
                    yn=ask("• "+args+" does not exist.\n• Do you wish to create and open it (n = no; blank = no; y = current working directory; b = base directory; k = directory of the last key opened?) ", desired={"n", "no", "", "y", "yes", "b", "k"});
                elif useDefault==True and settings["defaultDir"]!=None:
                    yn=ask("• "+args+" does not exist.\n• Do you wish to create and open it (n = no; blank = no; b = base directory; k = directory of the last key opened?) ", desired={"no", "", "n", "b", "k"});
                    if yn in {"y", "yes"}:
                        yn="n";
                else:
                    yn=ask("• "+args+" does not exist.\n• Do you wish to create and open it (n = no; blank = no; y = current working directory; b = base directory; k = directory of the last key opened?) ", desired={"y", "yes", "n", "no", "", "b", "k"});
                if yn in {"y", "yes"}:
                    if settings["lastKeyDir"]!=path:
                        settings["lastKeyDir"]=path;
                        print("• Last key directory set to `"+settings["lastKeyDir"]+"`.");
                        #saveSettings();
                        save_it=True;
                    if perm(args)==True:
                        subprocess.run(["touch", args]);
                        if os.path.isdir(args)==True:
                            print("• Sorry, a directory named `"+args+"` already exists. Key not created.");
                        else:
                            print("• "+args+" created.");
                            subprocess.run(getApp()+[args]);
                            if os.path.isdir(args):
                                print("• ERROR: `"+args+"` is a directory???");
                            else:
                                pass;
                                #print("HERE"); #Working normally.
                    else:
                        print("• You don't have permission to create `"+args+"` in `"+path+"`.");
                elif yn=="b":
                    if useDefault==True and settings["defaultDir"]!=None:

                        file=os.path.join(settings["defaultDir"], os.path.basename(args));
                        if settings["lastKeyDir"]!=settings["defaultDir"]:
                            settings["lastKeyDir"]=settings["defaultDir"];
                            print("• Last key directory set to `"+settings["lastKeyDir"]+"`.");
                            save_it=True;
                        if perm(file)==True:
                            subprocess.run(["touch", file]);
                            print("• "+args+" created in the default directory:\n"+file);
                            subprocess.run(getApp()+[file]);
                        else:
                            print("• You do not have permission to create `"+file+"`.");
                    else:
                        if useDefault==True and settings["defaultDir"]==None:
                            print("• No default directory has been set. Acting as if the default directory is disabled.");
                        if baseDir==None:
                            print("• You are not within a base directory structure.");
                            if settings["baseDir"]==None:
                                print("• "+args+" not created.");
                            else:
                                yn2=askyn("• Would you like to create it within the last-accessed base directory? (y/n) ");
                                if yn2 in {"y", "yes"}:
                                    file=os.path.join(settings["baseDir"], os.path.basename(args));
                                    if settings["lastKeyDir"]!=settings["baseDir"]:
                                        settings["lastKeyDir"]=settings["baseDir"];
                                        print("• Last key directory set to `"+settings["lastKeyDir"]+"`.");
                                        save_it=True;
                                    if perm(file)==True:
                                        subprocess.run(["touch", file]);
                                        print("• "+args+" created in the last-accessed base directory:\n"+file);
                                        subprocess.run(getApp()+[file]);
                                    else:
                                        print("• You do not have permission to create `"+file+"`.");
                                else:
                                    print("• "+args+" not created.");
                        else:
                            file=os.path.join(baseDir, os.path.basename(args));
                            if settings["lastKeyDir"]!=baseDir:
                                settings["lastKeyDir"]=baseDir;
                                print("• Last key directory set to `"+settings["lastKeyDir"]+"`.");
                                save_it=True;
                            if perm(file)==True:
                                subprocess.run(["touch", file]);
                                print("• "+args+" created in the base directory:\n"+file);
                                subprocess.run(getApp()+[file]);
                            else:
                                print("• You do not have permission to create `"+file+"`.");
                elif yn=="k":
                    if useDefault==True and settings["defaultDir"]!=None and settings["lastKeyDir"].startswith(settings["defaultDir"]+os.sep)==False:
                        print("• The last key directory is not within the default directory structure. No file created.");
                    else:
                        if useDefault==True and settings["defaultDir"]==None:
                            print("• The default directory is not set. Acting as if the default directory is disabled.");
                        file=os.path.join(settings["lastKeyDir"], os.path.basename(args));
                        if perm(file)==True:
                            subprocess.run(["touch", file]);
                            print("• "+args+" created in the last key directory:\n"+file);
                            subprocess.run(getApp()+[file]);
                        else:
                            print("• You do not have permission to create `"+file+"`.");
                else:
                    print("• "+args+" not created.");
    if save_it==True:
        try:
            saveSettings();
        except:
            print(f"• Exception: {PN}'s settings changes did not save properly. Did you your launching app shut down properly?");
