Skip to content

Commit

Permalink
Changed the default regex search to non-regex.
Browse files Browse the repository at this point in the history
Fixes #59

- Changed the flags to --regex/--no-regex
- updated tests and docs
  • Loading branch information
coordt committed Nov 4, 2023
1 parent fda71b0 commit 0034716
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 53 deletions.
24 changes: 12 additions & 12 deletions bumpversion/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,10 @@ def cli(ctx: Context) -> None:
help="Template for complete string to replace",
)
@click.option(
"--no-regex",
is_flag=True,
envvar="BUMPVERSION_NO_REGEX",
help="Do not treat the search parameter as a regular expression",
"--regex/--no-regex",
default=False,
envvar="BUMPVERSION_REGEX",
help="Treat the search parameter as a regular expression or explicitly do not treat it as a regular expression.",
)
@click.option(
"--no-configured-files",
Expand Down Expand Up @@ -233,7 +233,7 @@ def bump(
serialize: Optional[List[str]],
search: Optional[str],
replace: Optional[str],
no_regex: bool,
regex: bool,
no_configured_files: bool,
ignore_missing_version: bool,
dry_run: bool,
Expand Down Expand Up @@ -277,7 +277,7 @@ def bump(
message=message,
commit_args=commit_args,
ignore_missing_version=ignore_missing_version,
no_regex=no_regex,
regex=regex,
)

found_config_file = find_config_file(config_file)
Expand Down Expand Up @@ -418,10 +418,10 @@ def show(args: List[str], config_file: Optional[str], format_: str, increment: O
help="Template for complete string to replace",
)
@click.option(
"--no-regex",
is_flag=True,
envvar="BUMPVERSION_NO_REGEX",
help="Do not treat the search parameter as a regular expression",
"--regex/--no-regex",
default=False,
envvar="BUMPVERSION_REGEX",
help="Treat the search parameter as a regular expression or explicitly do not treat it as a regular expression.",
)
@click.option(
"--no-configured-files",
Expand Down Expand Up @@ -456,7 +456,7 @@ def replace(
serialize: Optional[List[str]],
search: Optional[str],
replace: Optional[str],
no_regex: bool,
regex: bool,
no_configured_files: bool,
ignore_missing_version: bool,
dry_run: bool,
Expand Down Expand Up @@ -486,7 +486,7 @@ def replace(
message=None,
commit_args=None,
ignore_missing_version=ignore_missing_version,
no_regex=no_regex,
regex=regex,
)

found_config_file = find_config_file(config_file)
Expand Down
10 changes: 5 additions & 5 deletions bumpversion/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class FileConfig(BaseModel):
serialize: Optional[List[str]] = None # If different from outer scope
search: Optional[str] = None # If different from outer scope
replace: Optional[str] = None # If different from outer scope
no_regex: Optional[bool] = None # If different from outer scope
regex: Optional[bool] = None # If different from outer scope
ignore_missing_version: Optional[bool] = None


Expand All @@ -55,7 +55,7 @@ class Config(BaseSettings):
serialize: List[str] = Field(min_length=1)
search: str
replace: str
no_regex: bool
regex: bool
ignore_missing_version: bool
tag: bool
sign_tags: bool
Expand Down Expand Up @@ -86,7 +86,7 @@ def add_files(self, filename: Union[str, List[str]]) -> None:
serialize=self.serialize,
search=self.search,
replace=self.replace,
no_regex=self.no_regex,
regex=self.regex,
ignore_missing_version=self.ignore_missing_version,
)
)
Expand Down Expand Up @@ -128,7 +128,7 @@ def version_config(self) -> "VersionConfig":
"serialize": ["{major}.{minor}.{patch}"],
"search": "{current_version}",
"replace": "{new_version}",
"no_regex": False,
"regex": False,
"ignore_missing_version": False,
"tag": False,
"sign_tags": False,
Expand Down Expand Up @@ -159,7 +159,7 @@ def get_all_file_configs(config_dict: dict) -> List[FileConfig]:
"search": config_dict["search"],
"replace": config_dict["replace"],
"ignore_missing_version": config_dict["ignore_missing_version"],
"no_regex": config_dict["no_regex"],
"regex": config_dict["regex"],
}
files = [{k: v for k, v in filecfg.items() if v is not None} for filecfg in config_dict["files"]]
for f in files:
Expand Down
27 changes: 14 additions & 13 deletions bumpversion/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from copy import deepcopy
from difflib import context_diff
from typing import List, MutableMapping, Optional
from typing import List, MutableMapping, Optional, Tuple

from bumpversion.config import FileConfig
from bumpversion.exceptions import VersionNotFoundError
Expand All @@ -27,7 +27,7 @@ def __init__(
self.serialize = file_cfg.serialize or version_config.serialize_formats
self.search = search or file_cfg.search or version_config.search
self.replace = replace or file_cfg.replace or version_config.replace
self.no_regex = file_cfg.no_regex or False
self.regex = file_cfg.regex or False
self.ignore_missing_version = file_cfg.ignore_missing_version or False
self.version_config = VersionConfig(
self.parse, self.serialize, self.search, self.replace, version_config.part_configs
Expand Down Expand Up @@ -63,7 +63,7 @@ def contains_version(self, version: Version, context: MutableMapping) -> bool:
Returns:
True if the version number is in fact present.
"""
search_expression = self.get_search_pattern(context)
search_expression, raw_search_expression = self.get_search_pattern(context)

if self.contains(search_expression):
return True
Expand All @@ -84,7 +84,7 @@ def contains_version(self, version: Version, context: MutableMapping) -> bool:
# version not found
if self.ignore_missing_version:
return False
raise VersionNotFoundError(f"Did not find '{search_expression.pattern}' in file: '{self.path}'")
raise VersionNotFoundError(f"Did not find '{raw_search_expression}' in file: '{self.path}'")

def contains(self, search: re.Pattern) -> bool:
"""Does the work of the contains_version method."""
Expand Down Expand Up @@ -115,15 +115,15 @@ def replace_version(
if new_version:
context["new_version"] = self.version_config.serialize(new_version, context)

search_for = self.get_search_pattern(context)
search_for, raw_search_pattern = self.get_search_pattern(context)
replace_with = self.version_config.replace.format(**context)

file_content_after = search_for.sub(replace_with, file_content_before)

if file_content_before == file_content_after and current_version.original:
og_context = deepcopy(context)
og_context["current_version"] = current_version.original
search_for_og = self.get_search_pattern(og_context)
search_for_og, og_raw_search_pattern = self.get_search_pattern(og_context)
file_content_after = search_for_og.sub(replace_with, file_content_before)

if file_content_before != file_content_after:
Expand All @@ -147,25 +147,26 @@ def replace_version(
if not dry_run: # pragma: no-coverage
self.write_file_contents(file_content_after)

def get_search_pattern(self, context: MutableMapping) -> re.Pattern:
def get_search_pattern(self, context: MutableMapping) -> Tuple[re.Pattern, str]:
"""Compile and return the regex if it is valid, otherwise return the string."""
# the default search pattern is escaped, so we can still use it in a regex
default = re.compile(re.escape(self.version_config.search.format(**context)), re.MULTILINE | re.DOTALL)
if self.no_regex:
raw_pattern = self.version_config.search.format(**context)
default = re.compile(re.escape(raw_pattern), re.MULTILINE | re.DOTALL)
if not self.regex:
logger.debug("No RegEx flag detected. Searching for the default pattern: '%s'", default.pattern)
return default
return default, raw_pattern

re_context = {key: re.escape(str(value)) for key, value in context.items()}
regex_pattern = self.version_config.search.format(**re_context)
try:
search_for_re = re.compile(regex_pattern, re.MULTILINE | re.DOTALL)
logger.debug("Searching for the regex: '%s'", search_for_re.pattern)
return search_for_re
return search_for_re, raw_pattern
except re.error as e:
logger.error("Invalid regex '%s' for file %s: %s.", default, self.path, e)

logger.debug("Searching for the default pattern: '%s'", default.pattern)
return default
logger.debug("Searching for the default pattern: '%s'", raw_pattern)
return default, raw_pattern

def __str__(self) -> str: # pragma: no-coverage
return self.path
Expand Down
2 changes: 1 addition & 1 deletion docsrc/reference/search-and-replace-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Bump-my-version uses a combination of [template strings](https://docs.python.org/3/library/string.html#format-string-syntax) using a [formatting context](formatting-context.md) and regular expressions to search the configured files for the old or current version and replace the text with the new version.

Bump My Version falls back to using a simple string search if the search template is not a valid regular expression or if the `no-regex` flag is `True`. The search template is always rendered using the formatting context. The basic logic is:
Bump My Version defaults to using a simple string search. If the search template is not a valid regular expression or if the `no-regex` flag is `True`. The search template is always rendered using the formatting context. The basic logic is:

1. Escape the formatting context for use in a regular expression.
2. Render the search string using the escaped formatting context.
Expand Down
8 changes: 4 additions & 4 deletions tests/fixtures/basic_cfg_expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,33 @@
'files': [{'filename': 'setup.py',
'glob': None,
'ignore_missing_version': False,
'no_regex': False,
'parse': '(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?',
'regex': False,
'replace': '{new_version}',
'search': '{current_version}',
'serialize': ['{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}']},
{'filename': 'bumpversion/__init__.py',
'glob': None,
'ignore_missing_version': False,
'no_regex': False,
'parse': '(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?',
'regex': False,
'replace': '{new_version}',
'search': '{current_version}',
'serialize': ['{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}']},
{'filename': 'CHANGELOG.md',
'glob': None,
'ignore_missing_version': False,
'no_regex': False,
'parse': '(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?',
'regex': False,
'replace': '**unreleased**\n**v{new_version}**',
'search': '**unreleased**',
'serialize': ['{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}']}],
'ignore_missing_version': False,
'included_paths': [],
'message': 'Bump version: {current_version} → {new_version}',
'no_regex': False,
'parse': '(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?',
'parts': {'major': {'first_value': None,
'independent': False,
Expand All @@ -51,6 +50,7 @@
'independent': False,
'optional_value': 'gamma',
'values': ['dev', 'gamma']}},
'regex': False,
'replace': '{new_version}',
'scm_info': {'branch_name': None,
'commit_sha': None,
Expand Down
8 changes: 4 additions & 4 deletions tests/fixtures/basic_cfg_expected.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ files:
- filename: "setup.py"
glob: null
ignore_missing_version: false
no_regex: false
parse: "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?"
regex: false
replace: "{new_version}"
search: "{current_version}"
serialize:
Expand All @@ -18,8 +18,8 @@ files:
- filename: "bumpversion/__init__.py"
glob: null
ignore_missing_version: false
no_regex: false
parse: "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?"
regex: false
replace: "{new_version}"
search: "{current_version}"
serialize:
Expand All @@ -28,8 +28,8 @@ files:
- filename: "CHANGELOG.md"
glob: null
ignore_missing_version: false
no_regex: false
parse: "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?"
regex: false
replace: "**unreleased**\n**v{new_version}**"
search: "**unreleased**"
serialize:
Expand All @@ -39,7 +39,6 @@ ignore_missing_version: false
included_paths:

message: "Bump version: {current_version} → {new_version}"
no_regex: false
parse: "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?"
parts:
major:
Expand All @@ -64,6 +63,7 @@ parts:
values:
- "dev"
- "gamma"
regex: false
replace: "{new_version}"
scm_info:
branch_name: null
Expand Down
8 changes: 4 additions & 4 deletions tests/fixtures/basic_cfg_expected_full.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"filename": "setup.py",
"glob": null,
"ignore_missing_version": false,
"no_regex": false,
"parse": "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
"regex": false,
"replace": "{new_version}",
"search": "{current_version}",
"serialize": [
Expand All @@ -22,8 +22,8 @@
"filename": "bumpversion/__init__.py",
"glob": null,
"ignore_missing_version": false,
"no_regex": false,
"parse": "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
"regex": false,
"replace": "{new_version}",
"search": "{current_version}",
"serialize": [
Expand All @@ -35,8 +35,8 @@
"filename": "CHANGELOG.md",
"glob": null,
"ignore_missing_version": false,
"no_regex": false,
"parse": "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
"regex": false,
"replace": "**unreleased**\n**v{new_version}**",
"search": "**unreleased**",
"serialize": [
Expand All @@ -48,7 +48,6 @@
"ignore_missing_version": false,
"included_paths": [],
"message": "Bump version: {current_version} \u2192 {new_version}",
"no_regex": false,
"parse": "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
"parts": {
"major": {
Expand Down Expand Up @@ -79,6 +78,7 @@
]
}
},
"regex": false,
"replace": "{new_version}",
"scm_info": {
"branch_name": null,
Expand Down
Loading

0 comments on commit 0034716

Please sign in to comment.