mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 08:38:32 +02:00
Add --config-file argument to use alternate config file at runtime (#1290)
* added new CLI argument option --config-file * pass argument and fetch alt config file if specified * argparse argument setting update * argument alias --cf added * documentation update - usage of CLI argument * fixed name-clash + unit tests * feature test added * #1170-alternate-config-file: Auto stash before rebase of "refs/heads/#1170-alternate-config-file" * Update docs/advanced.md Co-authored-by: Jonathan Wren <jonathan@nowandwren.com> * BDD tests added * Begin migrating/rewording --cf tests in pytest-bdd. Uses current directory instead of deep directory structure, but requires a given for each config file referenced * Fix issue where specifying a config-file that needs to be upgraded ended up upgrading the user config file instead * Uncomment and rework remaining tests for pytest-bdd instead of behave * Fix copytree for Python 3.7 (which doesn't support dirs_exist_ok) * Minor fixes to alternative config examples * Remove behave tests (behave is no longer in use) * Move config file unit test to unit test dir and use pytext path fixture instead of current directory to find test data * Use explicit "given the config exists" for copying config files instead of shoehorning in "given we use the config" twice * Change when/when to when/and * Clarify scenarios and fix indentation * Confirm primary config file isn't modified when encrypting/decrypting a journal in an alternate config file * Remove try/except on copytree since I'm no longer using the same Co-authored-by: Jonathan Wren <jonathan@nowandwren.com> Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
This commit is contained in:
parent
5057c290c1
commit
ae009099ed
10 changed files with 209 additions and 15 deletions
|
@ -85,6 +85,25 @@ jrnl --config-override display_format fancy --config-override linewrap 20 \
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using an alternate config
|
||||||
|
|
||||||
|
You can specify an alternate configuration file for the current instance of `jrnl` using `--config-file CONFIG_FILE_PATH` where
|
||||||
|
`CONFIG_FILE_PATH` is a path to an alternate `jrnl` configuration file.
|
||||||
|
|
||||||
|
#### Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Use personalised configuration file for personal journal entries
|
||||||
|
jrnl --config-file ~/foo/jrnl/personal-config.yaml
|
||||||
|
|
||||||
|
# Use alternate configuration file for work-related entries
|
||||||
|
jrnl --config-file ~/foo/jrnl/work-config.yaml
|
||||||
|
|
||||||
|
# Use default configuration file (created on installation)
|
||||||
|
jrnl
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Multiple journal files
|
## Multiple journal files
|
||||||
|
|
||||||
You can configure `jrnl`to use with multiple journals (eg.
|
You can configure `jrnl`to use with multiple journals (eg.
|
||||||
|
|
25
jrnl/args.py
25
jrnl/args.py
|
@ -337,6 +337,31 @@ def parse_args(args=[]):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
alternate_config = parser.add_argument_group(
|
||||||
|
"Specifies alternate config to be used",
|
||||||
|
textwrap.dedent("Applies alternate config for current session"),
|
||||||
|
)
|
||||||
|
|
||||||
|
alternate_config.add_argument(
|
||||||
|
"--config-file",
|
||||||
|
dest="config_file_path",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
help="""
|
||||||
|
Overrides default (created when first installed) config file for this command only.
|
||||||
|
|
||||||
|
Examples: \n
|
||||||
|
\t - Use a work config file for this jrnl entry, call: \n
|
||||||
|
\t jrnl --config-file /home/user1/work_config.yaml
|
||||||
|
\t - Use a personal config file stored on a thumb drive: \n
|
||||||
|
\t jrnl --config-file /media/user1/my-thumb-drive/personal_config.yaml
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
alternate_config.add_argument(
|
||||||
|
"--cf", dest="config_file_path", type=str, default="", help=argparse.SUPPRESS
|
||||||
|
)
|
||||||
|
|
||||||
# Handle '-123' as a shortcut for '-n 123'
|
# Handle '-123' as a shortcut for '-n 123'
|
||||||
num = re.compile(r"^-(\d+)$")
|
num = re.compile(r"^-(\d+)$")
|
||||||
args = [num.sub(r"-n \1", arg) for arg in args]
|
args = [num.sub(r"-n \1", arg) for arg in args]
|
||||||
|
|
|
@ -47,9 +47,14 @@ def make_yaml_valid_dict(input: list) -> dict:
|
||||||
return runtime_modifications
|
return runtime_modifications
|
||||||
|
|
||||||
|
|
||||||
def save_config(config):
|
def save_config(config, alt_config_path=None):
|
||||||
|
"""Supply alt_config_path if using an alternate config through --config-file."""
|
||||||
config["version"] = __version__
|
config["version"] = __version__
|
||||||
with open(get_config_path(), "w", encoding=YAML_FILE_ENCODING) as f:
|
with open(
|
||||||
|
alt_config_path if alt_config_path else get_config_path(),
|
||||||
|
"w",
|
||||||
|
encoding=YAML_FILE_ENCODING,
|
||||||
|
) as f:
|
||||||
yaml.safe_dump(
|
yaml.safe_dump(
|
||||||
config,
|
config,
|
||||||
f,
|
f,
|
||||||
|
|
|
@ -19,32 +19,53 @@ from .prompt import yesno
|
||||||
from .upgrade import is_old_version
|
from .upgrade import is_old_version
|
||||||
|
|
||||||
|
|
||||||
def upgrade_config(config):
|
def upgrade_config(config_data, alt_config_path=None):
|
||||||
"""Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly.
|
"""Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly.
|
||||||
This essentially automatically ports jrnl installations if new config parameters are introduced in later
|
This essentially automatically ports jrnl installations if new config parameters are introduced in later
|
||||||
versions."""
|
versions.
|
||||||
|
Supply alt_config_path if using an alternate config through --config-file."""
|
||||||
default_config = get_default_config()
|
default_config = get_default_config()
|
||||||
missing_keys = set(default_config).difference(config)
|
missing_keys = set(default_config).difference(config_data)
|
||||||
if missing_keys:
|
if missing_keys:
|
||||||
for key in missing_keys:
|
for key in missing_keys:
|
||||||
config[key] = default_config[key]
|
config_data[key] = default_config[key]
|
||||||
save_config(config)
|
save_config(config_data, alt_config_path)
|
||||||
|
config_path = alt_config_path if alt_config_path else get_config_path()
|
||||||
print(
|
print(
|
||||||
f"[Configuration updated to newest version at {get_config_path()}]",
|
f"[Configuration updated to newest version at {config_path}]",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_or_install_jrnl():
|
def find_default_config():
|
||||||
"""
|
|
||||||
If jrnl is already installed, loads and returns a config object.
|
|
||||||
Else, perform various prompts to install jrnl.
|
|
||||||
"""
|
|
||||||
config_path = (
|
config_path = (
|
||||||
get_config_path()
|
get_config_path()
|
||||||
if os.path.exists(get_config_path())
|
if os.path.exists(get_config_path())
|
||||||
else os.path.join(os.path.expanduser("~"), ".jrnl_config")
|
else os.path.join(os.path.expanduser("~"), ".jrnl_config")
|
||||||
)
|
)
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
|
def find_alt_config(alt_config):
|
||||||
|
if os.path.exists(alt_config):
|
||||||
|
return alt_config
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Alternate configuration file not found at path specified.", file=sys.stderr
|
||||||
|
)
|
||||||
|
print("Exiting.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def load_or_install_jrnl(alt_config_path):
|
||||||
|
"""
|
||||||
|
If jrnl is already installed, loads and returns a default config object.
|
||||||
|
If alternate config is specified via --config-file flag, it will be used.
|
||||||
|
Else, perform various prompts to install jrnl.
|
||||||
|
"""
|
||||||
|
config_path = (
|
||||||
|
find_alt_config(alt_config_path) if alt_config_path else find_default_config()
|
||||||
|
)
|
||||||
|
|
||||||
if os.path.exists(config_path):
|
if os.path.exists(config_path):
|
||||||
logging.debug("Reading configuration from file %s", config_path)
|
logging.debug("Reading configuration from file %s", config_path)
|
||||||
|
@ -68,7 +89,7 @@ def load_or_install_jrnl():
|
||||||
print("Exiting.", file=sys.stderr)
|
print("Exiting.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
upgrade_config(config)
|
upgrade_config(config, alt_config_path)
|
||||||
verify_config_colors(config)
|
verify_config_colors(config)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -36,7 +36,7 @@ def run(args):
|
||||||
|
|
||||||
# Load the config, and extract journal name
|
# Load the config, and extract journal name
|
||||||
try:
|
try:
|
||||||
config = install.load_or_install_jrnl()
|
config = install.load_or_install_jrnl(args.config_file_path)
|
||||||
original_config = config.copy()
|
original_config = config.copy()
|
||||||
|
|
||||||
# Apply config overrides
|
# Apply config overrides
|
||||||
|
|
92
tests/bdd/features/config_file.feature
Normal file
92
tests/bdd/features/config_file.feature
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
Feature: Multiple journals
|
||||||
|
|
||||||
|
Scenario: Read a journal from an alternate config
|
||||||
|
Given the config "basic_onefile.yaml" exists
|
||||||
|
And we use the config "multiple.yaml"
|
||||||
|
When we run "jrnl --cf basic_onefile.yaml -999"
|
||||||
|
Then the output should not contain "My first entry" # from multiple.yaml
|
||||||
|
And the output should contain "Lorem ipsum" # from basic_onefile.yaml
|
||||||
|
|
||||||
|
Scenario: Write to default journal by default using an alternate config
|
||||||
|
Given the config "multiple.yaml" exists
|
||||||
|
And we use the config "basic_onefile.yaml"
|
||||||
|
When we run "jrnl --cf multiple.yaml this goes to default"
|
||||||
|
And we run "jrnl -1"
|
||||||
|
Then the output should not contain "this goes to default"
|
||||||
|
When we run "jrnl --cf multiple.yaml -1"
|
||||||
|
Then the output should contain "this goes to default"
|
||||||
|
|
||||||
|
Scenario: Write to specified journal using an alternate config
|
||||||
|
Given the config "multiple.yaml" exists
|
||||||
|
And we use the config "basic_onefile.yaml"
|
||||||
|
When we run "jrnl work --cf multiple.yaml a long day in the office"
|
||||||
|
And we run "jrnl default --cf multiple.yaml -1"
|
||||||
|
Then the output should contain "But I'm better"
|
||||||
|
When we run "jrnl work --cf multiple.yaml -1"
|
||||||
|
Then the output should contain "a long day in the office"
|
||||||
|
|
||||||
|
Scenario: Tell user which journal was used while using an alternate config
|
||||||
|
Given the config "multiple.yaml" exists
|
||||||
|
And we use the config "basic_onefile.yaml"
|
||||||
|
When we run "jrnl --cf multiple.yaml work a long day in the office"
|
||||||
|
Then we should see the message "Entry added to work journal"
|
||||||
|
|
||||||
|
Scenario: Write to specified journal with a timestamp using an alternate config
|
||||||
|
Given the config "multiple.yaml" exists
|
||||||
|
And we use the config "basic_onefile.yaml"
|
||||||
|
When we run "jrnl work --cf multiple.yaml 23 july 2012: a long day in the office"
|
||||||
|
And we run "jrnl --cf multiple.yaml -1"
|
||||||
|
Then the output should contain "But I'm better"
|
||||||
|
When we run "jrnl --cf multiple.yaml work -1"
|
||||||
|
Then the output should contain "a long day in the office"
|
||||||
|
And the output should contain "2012-07-23"
|
||||||
|
|
||||||
|
Scenario: Write to specified journal without a timestamp but with colon using an alternate config
|
||||||
|
Given the config "multiple.yaml" exists
|
||||||
|
And we use the config "basic_onefile.yaml"
|
||||||
|
When we run "jrnl work --cf multiple.yaml : a long day in the office"
|
||||||
|
And we run "jrnl --cf multiple.yaml -1"
|
||||||
|
Then the output should contain "But I'm better"
|
||||||
|
When we run "jrnl --cf multiple.yaml work -1"
|
||||||
|
Then the output should contain "a long day in the office"
|
||||||
|
|
||||||
|
Scenario: Create new journals as required using an alternate config
|
||||||
|
Given the config "multiple.yaml" exists
|
||||||
|
And we use the config "basic_onefile.yaml"
|
||||||
|
When we run "jrnl ideas -1"
|
||||||
|
Then the output should be empty
|
||||||
|
When we run "jrnl ideas --cf multiple.yaml 23 july 2012: sell my junk on ebay and make lots of money"
|
||||||
|
Then the output should contain "Journal 'ideas' created"
|
||||||
|
When we run "jrnl ideas --cf multiple.yaml -1"
|
||||||
|
Then the output should contain "sell my junk on ebay and make lots of money"
|
||||||
|
|
||||||
|
Scenario: Don't crash if no default journal is specified using an alternate config
|
||||||
|
Given the config "bug343.yaml" exists
|
||||||
|
And we use the config "basic_onefile.yaml"
|
||||||
|
When we run "jrnl --cf bug343.yaml a long day in the office"
|
||||||
|
Then we should see the message "No default journal configured"
|
||||||
|
|
||||||
|
Scenario: Don't crash if no file exists for a configured encrypted journal using an alternate config
|
||||||
|
Given the config "multiple.yaml" exists
|
||||||
|
And we use the config "basic_onefile.yaml"
|
||||||
|
When we run "jrnl new_encrypted --cf multiple.yaml Adding first entry" and enter
|
||||||
|
these three eyes
|
||||||
|
these three eyes
|
||||||
|
n
|
||||||
|
Then we should see the message "Encrypted journal 'new_encrypted' created"
|
||||||
|
|
||||||
|
Scenario: Don't overwrite main config when encrypting a journal in an alternate config
|
||||||
|
Given the config "basic_onefile.yaml" exists
|
||||||
|
And we use the config "multiple.yaml"
|
||||||
|
When we run "jrnl --cf basic_onefile.yaml --encrypt" and enter
|
||||||
|
these three eyes
|
||||||
|
these three eyes
|
||||||
|
n
|
||||||
|
Then we should see the message "Journal encrypted to features/journals/basic_onefile.journal"
|
||||||
|
And the config should contain "encrypt: false" # multiple.yaml remains unchanged
|
||||||
|
|
||||||
|
Scenario: Don't overwrite main config when decrypting a journal in an alternate config
|
||||||
|
Given the config "editor_encrypted.yaml" exists
|
||||||
|
And we use the config "basic_encrypted.yaml"
|
||||||
|
When we run "jrnl --cf editor_encrypted.yaml --decrypt"
|
||||||
|
Then the config should contain "encrypt: true" # basic_encrypted remains unchanged
|
|
@ -1,6 +1,7 @@
|
||||||
from pytest_bdd import scenarios
|
from pytest_bdd import scenarios
|
||||||
|
|
||||||
scenarios("features/build.feature")
|
scenarios("features/build.feature")
|
||||||
|
scenarios("features/config_file.feature")
|
||||||
scenarios("features/core.feature")
|
scenarios("features/core.feature")
|
||||||
scenarios("features/datetime.feature")
|
scenarios("features/datetime.feature")
|
||||||
scenarios("features/delete.feature")
|
scenarios("features/delete.feature")
|
||||||
|
|
|
@ -105,6 +105,14 @@ def we_use_the_config(config_file, temp_dir, working_dir):
|
||||||
return config_dest
|
return config_dest
|
||||||
|
|
||||||
|
|
||||||
|
@given(parse('the config "{config_file}" exists'), target_fixture="config_path")
|
||||||
|
@given('the config "<config_file>" exists', target_fixture="config_path")
|
||||||
|
def config_exists(config_file, temp_dir, working_dir):
|
||||||
|
config_source = os.path.join(working_dir, "data", "configs", config_file)
|
||||||
|
config_dest = os.path.join(temp_dir.name, config_file)
|
||||||
|
shutil.copy2(config_source, config_dest)
|
||||||
|
|
||||||
|
|
||||||
@given(parse('we use the password "{pw}" if prompted'), target_fixture="password")
|
@given(parse('we use the password "{pw}" if prompted'), target_fixture="password")
|
||||||
def use_password_forever(pw):
|
def use_password_forever(pw):
|
||||||
return pw
|
return pw
|
||||||
|
|
22
tests/unit/test_config_file.py
Normal file
22
tests/unit/test_config_file.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
|
||||||
|
from jrnl.install import find_alt_config
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_alt_config(request):
|
||||||
|
work_config_path = os.path.join(
|
||||||
|
request.fspath.dirname, "..", "data", "configs", "basic_onefile.yaml"
|
||||||
|
)
|
||||||
|
found_alt_config = find_alt_config(work_config_path)
|
||||||
|
assert found_alt_config == work_config_path
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_alt_config_not_exist(request):
|
||||||
|
bad_config_path = os.path.join(
|
||||||
|
request.fspath.dirname, "..", "data", "configs", "not-existing-config.yaml"
|
||||||
|
)
|
||||||
|
with pytest.raises(SystemExit) as ex:
|
||||||
|
found_alt_config = find_alt_config(bad_config_path)
|
||||||
|
assert found_alt_config is not None
|
||||||
|
assert isinstance(ex.value, SystemExit)
|
|
@ -37,6 +37,7 @@ def expected_args(**kwargs):
|
||||||
"tags": False,
|
"tags": False,
|
||||||
"text": [],
|
"text": [],
|
||||||
"config_override": [],
|
"config_override": [],
|
||||||
|
"config_file_path": "",
|
||||||
}
|
}
|
||||||
return {**default_args, **kwargs}
|
return {**default_args, **kwargs}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue