From 9f509cbfb0c2aac603dae2c2133573dfbee76156 Mon Sep 17 00:00:00 2001 From: Kevin Guillaumond Date: Thu, 17 Nov 2022 01:41:22 -0800 Subject: [PATCH] fix: Use relative path to create GitHub links, fix regex (#37) When checking whether a file has an existing Confluence page, and when linking to GitHub, use the file path relative to the repo root and not relative to the root of the machine that is running the wiki sync script. Also update the regex that finds links, so that multiple links on the same line are parsed properly. --- tests/test_relative_links.py | 94 +++++++++++++++++++++++++++++------- wiki_sync.py | 20 +++++--- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/tests/test_relative_links.py b/tests/test_relative_links.py index 95f5e26..95af178 100644 --- a/tests/test_relative_links.py +++ b/tests/test_relative_links.py @@ -41,12 +41,12 @@ def test_http_link(wiki_mock, get_repo_root_mock): print('Check out this [link](https://example.org)', file=doc_file) output = wiki_sync.get_formatted_file_content( - wiki_mock, doc_path, GH_ROOT, REPO_NAME) + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) assert output == 'Check out this [link|https://example.org]\n' -def test_link_to_file_in_same_folder(wiki_mock, get_repo_root_mock): +def test_link_to_file_both_in_root(wiki_mock, get_repo_root_mock): with tempfile.TemporaryDirectory() as repo_root: get_repo_root_mock.return_value = repo_root @@ -62,10 +62,34 @@ def test_link_to_file_in_same_folder(wiki_mock, get_repo_root_mock): print(contents, file=doc_file) output = wiki_sync.get_formatted_file_content( - wiki_mock, doc_path, GH_ROOT, REPO_NAME) + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) - expected_output = (f'Check out this' - f' [other file|{GH_ROOT}{linked_doc_path}]\n') + expected_gh_link = f'{GH_ROOT}{linked_file_name}' + expected_output = f'Check out this [other file|{expected_gh_link}]\n' + assert output == expected_output + + +def test_link_to_file_in_same_non_root_folder(wiki_mock, get_repo_root_mock): + with tempfile.TemporaryDirectory() as repo_root: + get_repo_root_mock.return_value = repo_root + + os.makedirs(os.path.join(repo_root, 'foo')) + # Create a file that the doc will link to + linked_file_name = 'linked_file.py' + linked_doc_path = os.path.join(repo_root, 'foo', linked_file_name) + write_something_to_file(linked_doc_path) + + # Create the doc file with a link to the other one + doc_path = os.path.join(repo_root, 'foo', 'new_doc.md') + with open(doc_path, mode='w', encoding='utf-8') as doc_file: + contents = f'Check out this [other file]({linked_file_name})' + print(contents, file=doc_file) + + output = wiki_sync.get_formatted_file_content( + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) + + expected_gh_link = f'{GH_ROOT}foo/{linked_file_name}' + expected_output = f'Check out this [other file|{expected_gh_link}]\n' assert output == expected_output @@ -86,10 +110,10 @@ def test_link_to_file_in_child_folder(wiki_mock, get_repo_root_mock): print(contents, file=doc_file) output = wiki_sync.get_formatted_file_content( - wiki_mock, doc_path, GH_ROOT, REPO_NAME) + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) - expected_output = (f'Check out this' - f' [other file|{GH_ROOT}{linked_doc_path}]\n') + expected_gh_link = f'{GH_ROOT}foo/bar/linked_file.py' + expected_output = f'Check out this [other file|{expected_gh_link}]\n' assert output == expected_output @@ -110,10 +134,10 @@ def test_link_to_file_in_parent_folder(wiki_mock, get_repo_root_mock): print(contents, file=doc_file) output = wiki_sync.get_formatted_file_content( - wiki_mock, doc_path, GH_ROOT, REPO_NAME) + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) - expected_output = (f'Check out this' - f' [other file|{GH_ROOT}{linked_doc_path}]\n') + expected_gh_link = f'{GH_ROOT}linked_file.py' + expected_output = f'Check out this [other file|{expected_gh_link}]\n' assert output == expected_output @@ -135,9 +159,9 @@ def test_simplified_link(wiki_mock, get_repo_root_mock): print(contents, file=doc_file) output = wiki_sync.get_formatted_file_content( - wiki_mock, doc_path, GH_ROOT, REPO_NAME) + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) - expected_link = f'[{linked_file_name}|{GH_ROOT}{linked_doc_path}' + expected_link = f'[{linked_file_name}|{GH_ROOT}linked_file.py' assert output == f'Check out {expected_link}\n' @@ -152,13 +176,17 @@ def test_link_to_non_existing_file(wiki_mock, get_repo_root_mock): print(contents, file=doc_file) output = wiki_sync.get_formatted_file_content( - wiki_mock, doc_path, GH_ROOT, REPO_NAME) + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) + # Output is the same assert output == 'Check out this [other file|non_existing.py]\n' def test_link_to_file_that_exists_on_confluence(wiki_mock, get_repo_root_mock): - os.environ['INPUT_WIKI-BASE-URL'] = 'http://mywiki.atlassian.net' + space = 'WikiSpace' + os.environ['INPUT_SPACE-NAME'] = space + wiki_url = 'http://mywiki.atlassian.net' + os.environ['INPUT_WIKI-BASE-URL'] = wiki_url with tempfile.TemporaryDirectory() as repo_root: get_repo_root_mock.return_value = repo_root @@ -172,7 +200,7 @@ def test_link_to_file_that_exists_on_confluence(wiki_mock, get_repo_root_mock): # existing Confluence page, say yes wiki_mock.get_page_by_title.return_value = { '_links': { - 'webui': '/spaces/SPACE/pages/123' + 'webui': f'/spaces/{space}/pages/123' } } @@ -183,12 +211,42 @@ def test_link_to_file_that_exists_on_confluence(wiki_mock, get_repo_root_mock): print(contents, file=doc_file) output = wiki_sync.get_formatted_file_content( - wiki_mock, doc_path, GH_ROOT, REPO_NAME) + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) - wiki_link = 'http://mywiki.atlassian.net/wiki/spaces/SPACE/pages/123' + wiki_link = f'{wiki_url}/wiki/spaces/{space}/pages/123' expected_output = (f'Check out this [other file|{wiki_link}]\n') assert output == expected_output + wiki_mock.get_page_by_title.assert_called_once_with( + space, f'{REPO_NAME}/linked_file.py') + + +def test_several_links_on_same_line(wiki_mock, get_repo_root_mock): + with tempfile.TemporaryDirectory() as repo_root: + get_repo_root_mock.return_value = repo_root + + # Create file that the doc will link to + linked_file_name = 'linked_file.py' + write_something_to_file(os.path.join(repo_root, linked_file_name)) + linked_file_name_2 = 'linked_file_2.go' + write_something_to_file(os.path.join(repo_root, linked_file_name_2)) + + # Create the doc file with a link to the other one + doc_path = os.path.join(repo_root, 'new_doc.md') + with open(doc_path, mode='w', encoding='utf-8') as doc_file: + contents = (f'Check out this [file]({linked_file_name})' + f' and also [that one]({linked_file_name_2})') + print(contents, file=doc_file) + + output = wiki_sync.get_formatted_file_content( + wiki_mock, repo_root, doc_path, GH_ROOT, REPO_NAME) + + expected_gh_links = [f'{GH_ROOT}{linked_file_name}', + f'{GH_ROOT}{linked_file_name_2}'] + expected_output = (f'Check out this [file|{expected_gh_links[0]}] and' + f' also [that one|{expected_gh_links[1]}]\n') + assert output == expected_output + def write_something_to_file(file_path: str) -> None: with open(file_path, mode='w', encoding='utf-8') as doc_file: diff --git a/wiki_sync.py b/wiki_sync.py index 2285a71..7eb98c5 100644 --- a/wiki_sync.py +++ b/wiki_sync.py @@ -18,7 +18,7 @@ # We only need a capture group for the link itself # TODO handle links like [link], which happen when the link name is the same as # the link itself -JIRA_LINK_PATTERN = re.compile(r'\[.*\|(.*)\]') +JIRA_LINK_PATTERN = re.compile(r'\[[^|\n]+\|([^|\n]+)\]') def get_files_to_sync(changed_files: str) -> List[str]: @@ -91,7 +91,7 @@ def sync_files(files: List[str]) -> bool: try: formatted_content = get_formatted_file_content( - wiki_client, absolute_file_path, url_root_for_file, + wiki_client, repo_root, file_path, url_root_for_file, repo_name) content = read_only_warning + formatted_content except Exception: @@ -111,8 +111,8 @@ def sync_files(files: List[str]) -> bool: def get_formatted_file_content(wiki_client: atlassian.Confluence, - file_path: str, gh_root: str, repo_name: str - ) -> str: + repo_root: str, file_path: str, gh_root: str, + repo_name: str) -> str: """ Takes the absolute path of a file and returns its contents formatted as JIRA markdown. @@ -123,20 +123,24 @@ def get_formatted_file_content(wiki_client: atlassian.Confluence, # keys are relative links; values are what they should be replaced with links_to_replace: Dict[str, str] = {} - formated_file_contents = pypandoc.convert_file(file_path, 'jira') + absolute_file_path = os.path.join(repo_root, file_path) + formated_file_contents = pypandoc.convert_file(absolute_file_path, 'jira') for link in re.findall(JIRA_LINK_PATTERN, formated_file_contents): # Most links are HTTP - don't waste time with them if link.startswith('http'): continue - target_path = os.path.join(os.path.split(file_path)[0], link) + target_path = os.path.join(os.path.split(absolute_file_path)[0], link) target_path = os.path.normpath(target_path) if not os.path.exists(target_path): # Not actually a relative link continue + target_from_root = os.path.relpath(target_path, start=repo_root) + wiki_page_info = wiki_client.get_page_by_title( - os.environ['INPUT_SPACE-NAME'], f'{repo_name}/{target_path}') + os.environ['INPUT_SPACE-NAME'], + f'{repo_name}/{target_from_root}') if wiki_page_info: # The link is to a file that has a Confluence page # Let's link to the page directly @@ -145,7 +149,7 @@ def get_formatted_file_content(wiki_client: atlassian.Confluence, links_to_replace[link] = target_page_url else: # No existing Confluence page - link to GitHub - links_to_replace[link] = gh_root + target_path + links_to_replace[link] = gh_root + target_from_root # Replace relative links for relative_link, new_link in links_to_replace.items():