def test_change(fs): doc = """--- title: An Examination of the Navel --- #personal #book-draft # Preface: Reasons for #journaling As I have explained at length in [another note](../Another%20Note.md) and also published about online (see [this article](http://example.com/blahblah) and [this one](http://example.com/blah) among many others), ... """ expected = """--- created: 2019-06-04 10:12:13-08:00 title: A Close Examination of the Navel --- #personal #book-draft # Preface: Reasons for #journaling As I have explained at length in [another note](moved/another-note.md) and also published about online (see [this article](http://example.com/blahblah) and [this one](https://example.com/meh) among many others), ... """ path = '/fakenotes/test.md' fs.create_file(path, contents=doc) acc = MarkdownAccessor(path) acc.edit(ReplaceHrefCmd(path, '../Another%20Note.md', 'moved/another-note.md')) acc.edit(ReplaceHrefCmd(path, 'http://example.com/blah', 'https://example.com/meh')) acc.edit(SetTitleCmd(path, 'A Close Examination of the Navel')) acc.edit(SetCreatedCmd(path, datetime(2019, 6, 4, 10, 12, 13, 0, timezone(timedelta(hours=-8))))) assert acc.save() assert Path(path).read_text() == expected
def test_change(fs): fs.cwd = '/notes' path1 = '/notes/one.md' path2 = '/notes/two.md' path3 = '/notes/moved.md' fs.create_file(path1, contents='[1](old)') fs.create_file(path2, contents='[2](foo)') edits = [SetTitleCmd(path1, 'New Title'), ReplaceHrefCmd(path1, 'old', 'new'), MoveCmd(path1, path3), ReplaceHrefCmd(path2, 'foo', 'bar')] repo = config().instantiate() repo.change(edits) assert not Path(path1).exists() assert Path(path3).read_text() == '---\ntitle: New Title\n...\n[1](new)' assert Path(path2).read_text() == '[2](bar)' assert repo.info(path1, FileInfoReq.full()) == FileInfo(path1) assert repo.info(path3, FileInfoReq.full()) == FileInfo(path3, title='New Title', links=[LinkInfo(path3, 'new')]) assert repo.info(path2, FileInfoReq.full()) == FileInfo(path2, links=[LinkInfo(path2, 'bar')]) assert repo.info('old', FileInfoReq.full()) == FileInfo('/notes/old') assert repo.info('foo', FileInfoReq.full()) == FileInfo('/notes/foo') assert repo.info('new', FileInfoReq.full()) == FileInfo('/notes/new', backlinks=[LinkInfo(path3, 'new')]) assert repo.info('bar', FileInfoReq.full()) == FileInfo('/notes/bar', backlinks=[LinkInfo(path2, 'bar')]) # regression test for bug where invalidate removed entries for files that were referred to # only by files that had not been changed repo.invalidate() assert repo.info('new', FileInfoReq.full()) == FileInfo('/notes/new', backlinks=[LinkInfo(path3, 'new')]) assert repo.info('bar', FileInfoReq.full()) == FileInfo('/notes/bar', backlinks=[LinkInfo(path2, 'bar')])
def test_ignore_fenced_code_blocks(fs): doc = """#tag1 [link](link1.md) ```foo #tag2 #tag3 text [link](link1.md) ``` #tag3 [link](link2.md) ``` [link](link3.md) ```""" path = '/fakenotes/text.md' fs.create_file(path, contents=doc) acc = MarkdownAccessor(path) info = acc.info() assert info.tags == {'tag1', 'tag3'} assert info.links == [ LinkInfo(path, 'link1.md'), LinkInfo(path, 'link2.md') ] acc.edit(ReplaceHrefCmd(path, 'link1.md', 'CHANGED1')) acc.edit(ReplaceHrefCmd(path, 'link2.md', 'CHANGED2')) acc.edit(ReplaceHrefCmd(path, 'link3.md', 'CHANGED3')) acc.edit(DelTagCmd(path, 'tag3')) acc.edit(DelTagCmd(path, 'tag2')) acc.save() assert Path(path).read_text() == """#tag1 [link](CHANGED1)
def test_change(fs): doc = """<html> <head> <title>fdsalkhflsdakjsdhfaslkjdhfalkj</title> <meta name="created" content="2001-01-01 02:02:02 +0000" /> </head> <body> <p>Hi! Here's a <a href="../Mediocre%20Note.md">link</a> and a <img src="http://example.com/foo.png" title="picture"/>.</p> <video controls src="media/something.weird">a video element</video> <audio controls src="media/something.weird">an audio element</audio> <audio controls><source src="media/something.weird">another audio element</audio> <p>Here's an <a href="../Mediocre%20Note.html">unaffected link</a>.</p> </body> </html>""" expected = """<html> <head> <title>A Delightful Note</title> <meta name="created" content="2019-06-04 10:12:13 -0800" /> </head> <body> <p>Hi! Here's a <a href="../archive/Mediocre%20Note.md">link</a> and a <img src="http://example.com/bar.png" title="picture"/>.</p> <video controls src="content/something.cool">a video element</video> <audio controls src="content/something.cool">an audio element</audio> <audio controls><source src="content/something.cool">another audio element</audio> <p>Here's an <a href="../Mediocre%20Note.html">unaffected link</a>.</p> </body> </html>""" path = Path('/fakenotes/test.html') fs.create_file(path, contents=doc) acc = HTMLAccessor(str(path)) acc.edit(SetTitleCmd(str(path), 'A Delightful Note')) acc.edit( SetCreatedCmd( str(path), datetime(2019, 6, 4, 10, 12, 13, 0, timezone(timedelta(hours=-8))))) acc.edit( ReplaceHrefCmd(str(path), '../Mediocre%20Note.md', '../archive/Mediocre%20Note.md')) acc.edit( ReplaceHrefCmd(str(path), 'http://example.com/foo.png', 'http://example.com/bar.png')) acc.edit( ReplaceHrefCmd(str(path), 'media/something.weird', 'content/something.cool')) assert acc.save() assert BeautifulSoup( path.read_text(), 'lxml', ) == BeautifulSoup(expected, 'lxml')
def test_change(fs): fs.create_file('/notes/one.md', contents='[1](old)') fs.create_file('/notes/two.md', contents='[2](foo)') edits = [SetTitleCmd('/notes/one.md', 'New Title'), ReplaceHrefCmd('/notes/one.md', 'old', 'new'), MoveCmd('/notes/one.md', '/notes/moved.md'), ReplaceHrefCmd('/notes/two.md', 'foo', 'bar')] repo = DirectRepoConf(root_paths={'/notes'}).instantiate() repo.change(edits) assert not Path('/notes/one.md').exists() assert Path('/notes/moved.md').read_text() == '---\ntitle: New Title\n...\n[1](new)' assert Path('/notes/two.md').read_text() == '[2](bar)'
def edits_for_path_replacement(referrer: str, hrefs: Set[str], replacement: str) -> Iterator[ReplaceHrefCmd]: """Yields commands to replace a file's links to a path with links to another path.""" for href in hrefs: url = urlparse(href) newref = path_as_href(href_path(referrer, replacement), url) yield ReplaceHrefCmd(referrer, href, newref)
def edits_for_rearrange(store: Repo, renames: Dict[str, str]) -> Iterator[FileEditCmd]: """Yields commands that will rename files and update links accordingly. The keys of the dictionary are the paths to be renamed, and the values are what they should be renamed to. (If a path appears as both a key and as a value, it will be moved to a temporary file as an intermediate step.) The given store is used to search for files that link to any of the paths that are keys in the dictionary, so that ReplaceHrefEditCmd instances can be generated for them. The files that are being renamed will also be checked for outbound links, and ReplaceRef edits will be generated for those too. Source paths may be directories; the directory as a whole will be moved, and links to/from all files/folders within it will be updated too. """ to_move = { os.path.realpath(s): os.path.realpath(d) for s, d in renames.items() } all_moves = {} for src, dest in to_move.items(): all_moves[src] = dest if os.path.isdir(src): for path in glob(os.path.join(src, '**', '*'), recursive=True): all_moves[path] = os.path.join(dest, os.path.relpath(path, src)) for src, dest in all_moves.items(): info = store.info(src, FileInfoReq(path=True, links=True, backlinks=True)) if info: for link in info.links: referent = link.referent() if not referent: continue url = urlparse(link.href) if referent == src and url.path == '': continue if referent in all_moves: referent = all_moves[referent] elif os.path.isabs(url.path): # Don't try to rewrite absolute paths, unless they refer to a file we're moving. continue newhref = path_as_href(href_path(dest, referent), url) if not link.href == newhref: yield ReplaceHrefCmd(src, link.href, newhref) for link in info.backlinks: if link.referrer in all_moves: continue # TODO either pass in all the hrefs at once, or change method to not take in a set yield from edits_for_path_replacement(link.referrer, {link.href}, dest) yield from edits_for_raw_moves(to_move)
def test_relink(fs, capsys): nd_setup(fs) path1 = Path('/notes/foo.md') fs.create_file(path1, contents='I link to [bar](/notes/subdir1/bar.md#section)') assert cli.main(['relink', '-p', '/notes/subdir1/bar.md', '/blah/baz.md']) == 0 out, err = capsys.readouterr() assert out == str( ReplaceHrefCmd(str(path1), '/notes/subdir1/bar.md#section', '../blah/baz.md#section')) + '\n' assert cli.main(['relink', '/notes/subdir1/bar.md', '/blah/baz.md']) == 0 assert path1.read_text() == 'I link to [bar](../blah/baz.md#section)'
def test_org_simple(fs, capsys, mocker): mocker.patch('shortuuid.uuid', side_effect=(f'uuid{i}' for i in itertools.count(1))) nd_setup(fs, extra_conf=""" conf.path_organizer = lambda info: info.path.replace('hi', 'hello') """) path1 = Path('/notes/cwd/one.md') path2 = Path('/notes/cwd/two.md') fs.create_file(path1) fs.create_file(path2, contents='I link to [hi](hi.md).') assert cli.main(['organize', '-j']) == 0 assert path1.is_file() assert path2.is_file() out, err = capsys.readouterr() assert json.loads(out) == {} path3 = Path('/notes/cwd/hi.md') path4 = Path('/notes/cwd/hello.md') fs.create_file(path3, contents='I link to [one](one.md).') assert cli.main(['organize', '-p']) == 0 assert path3.exists() assert not path4.exists() out, err = capsys.readouterr() assert out == (str(ReplaceHrefCmd(str(path2), 'hi.md', 'hello.md')) + '\n' + str( MoveCmd(str(path3), str(path4), create_parents=True, delete_empty_parents=True)) + '\n') assert cli.main(['organize', '-j']) == 0 assert path1.is_file() assert path2.read_text() == 'I link to [hi](hello.md).' assert not path3.exists() assert path4.read_text() == 'I link to [one](one.md).' capsys.readouterr() path5 = Path('/notes/cwd/hello_uuid1.md') fs.create_file(path3, contents='I am a duplicate name') assert cli.main(['organize', '-j']) == 0 assert not path3.exists() assert path4.read_text() == 'I link to [one](one.md).' assert path5.read_text() == 'I am a duplicate name' out, err = capsys.readouterr() assert json.loads(out) == {'/notes/cwd/hi.md': '/notes/cwd/hello_uuid1.md'}
def test_mv_file(fs, capsys): nd_setup(fs) fs.create_file('/notes/cwd/subdir/old.md') fs.create_file('/notes/dir/referrer.md', contents='I have a [link](../cwd/subdir/old.md).') assert cli.main(['mv', '-p', 'subdir/old.md', '../dir/new.md']) == 0 assert Path('/notes/cwd/subdir/old.md').exists() assert Path('/notes/dir/referrer.md').read_text( ) == 'I have a [link](../cwd/subdir/old.md).' out, err = capsys.readouterr() assert out == ( str( ReplaceHrefCmd('/notes/dir/referrer.md', '../cwd/subdir/old.md', 'new.md')) + '\n' + str(MoveCmd('/notes/cwd/subdir/old.md', '/notes/dir/new.md')) + '\n') assert cli.main(['mv', 'subdir/old.md', '../dir/new.md']) == 0 assert not Path('/notes/cwd/subdir/old.md').exists() assert Path('/notes/dir/new.md').exists() assert Path( '/notes/dir/referrer.md').read_text() == 'I have a [link](new.md).' out, err = capsys.readouterr() assert not out
def replace_href(self, path: str, original: str, replacement: str) -> None: """Convenience method equivalent to calling change with one :class:`notesdir.models.ReplaceRefCmd`""" self.change([ReplaceHrefCmd(path, original, replacement)])