Show More
@@ -16,10 +16,10 b' Contains writer for writing nbconvert output to filesystem.' | |||||
16 |
|
16 | |||
17 | import io |
|
17 | import io | |
18 | import os |
|
18 | import os | |
19 | import shutil |
|
|||
20 | import glob |
|
19 | import glob | |
21 |
|
20 | |||
22 | from IPython.utils.traitlets import Unicode |
|
21 | from IPython.utils.traitlets import Unicode | |
|
22 | from IPython.utils.path import link_or_copy | |||
23 |
|
23 | |||
24 | from .base import WriterBase |
|
24 | from .base import WriterBase | |
25 |
|
25 | |||
@@ -90,7 +90,7 b' class FilesWriter(WriterBase):' | |||||
90 |
|
90 | |||
91 | # Copy if destination is different. |
|
91 | # Copy if destination is different. | |
92 | if not os.path.normpath(dest) == os.path.normpath(matching_filename): |
|
92 | if not os.path.normpath(dest) == os.path.normpath(matching_filename): | |
93 |
|
|
93 | link_or_copy(matching_filename, dest) | |
94 |
|
94 | |||
95 | # Determine where to write conversion results. |
|
95 | # Determine where to write conversion results. | |
96 | dest = notebook_name + '.' + output_extension |
|
96 | dest = notebook_name + '.' + output_extension |
@@ -16,6 +16,9 b' Utilities for path handling.' | |||||
16 |
|
16 | |||
17 | import os |
|
17 | import os | |
18 | import sys |
|
18 | import sys | |
|
19 | import errno | |||
|
20 | import shutil | |||
|
21 | import random | |||
19 | import tempfile |
|
22 | import tempfile | |
20 | import warnings |
|
23 | import warnings | |
21 | from hashlib import md5 |
|
24 | from hashlib import md5 | |
@@ -510,3 +513,52 b" def get_security_file(filename, profile='default'):" | |||||
510 | raise IOError("Profile %r not found") |
|
513 | raise IOError("Profile %r not found") | |
511 | return filefind(filename, ['.', pd.security_dir]) |
|
514 | return filefind(filename, ['.', pd.security_dir]) | |
512 |
|
515 | |||
|
516 | ||||
|
517 | ENOLINK = 1998 | |||
|
518 | ||||
|
519 | def link(src, dst): | |||
|
520 | """Hard links ``src`` to ``dst``, returning 0 or errno. | |||
|
521 | ||||
|
522 | Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't | |||
|
523 | supported by the operating system. | |||
|
524 | """ | |||
|
525 | ||||
|
526 | if not hasattr(os, "link"): | |||
|
527 | return ENOLINK | |||
|
528 | link_errno = 0 | |||
|
529 | try: | |||
|
530 | os.link(src, dst) | |||
|
531 | except OSError as e: | |||
|
532 | link_errno = e.errno | |||
|
533 | return link_errno | |||
|
534 | ||||
|
535 | ||||
|
536 | def link_or_copy(src, dst): | |||
|
537 | """Attempts to hardlink ``src`` to ``dst``, copying if the link fails. | |||
|
538 | ||||
|
539 | Attempts to maintain the semantics of ``shutil.copy``. | |||
|
540 | ||||
|
541 | Because ``os.link`` does not overwrite files, a unique temporary file | |||
|
542 | will be used if the target already exists, then that file will be moved | |||
|
543 | into place. | |||
|
544 | """ | |||
|
545 | ||||
|
546 | if os.path.isdir(dst): | |||
|
547 | dst = os.path.join(dst, os.path.basename(src)) | |||
|
548 | ||||
|
549 | link_errno = link(src, dst) | |||
|
550 | if link_errno == errno.EEXIST: | |||
|
551 | new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), ) | |||
|
552 | try: | |||
|
553 | link_or_copy(src, new_dst) | |||
|
554 | except: | |||
|
555 | try: | |||
|
556 | os.remove(new_dst) | |||
|
557 | except OSError: | |||
|
558 | pass | |||
|
559 | raise | |||
|
560 | os.rename(new_dst, dst) | |||
|
561 | elif link_errno != 0: | |||
|
562 | # Either link isn't supported, or the filesystem doesn't support | |||
|
563 | # linking, or 'src' and 'dst' are on different filesystems. | |||
|
564 | shutil.copy(src, dst) |
@@ -559,3 +559,69 b' def test_unescape_glob():' | |||||
559 | nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*') |
|
559 | nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*') | |
560 | nt.assert_equals(path.unescape_glob(r'\\a'), r'\a') |
|
560 | nt.assert_equals(path.unescape_glob(r'\\a'), r'\a') | |
561 | nt.assert_equals(path.unescape_glob(r'\a'), r'\a') |
|
561 | nt.assert_equals(path.unescape_glob(r'\a'), r'\a') | |
|
562 | ||||
|
563 | ||||
|
564 | class TestLinkOrCopy(object): | |||
|
565 | def setUp(self): | |||
|
566 | self.tempdir = TemporaryDirectory() | |||
|
567 | self.src = self.dst("src") | |||
|
568 | with open(self.src, "w") as f: | |||
|
569 | f.write("Hello, world!") | |||
|
570 | ||||
|
571 | def tearDown(self): | |||
|
572 | self.tempdir.cleanup() | |||
|
573 | ||||
|
574 | def dst(self, *args): | |||
|
575 | return os.path.join(self.tempdir.name, *args) | |||
|
576 | ||||
|
577 | def assert_inode_not_equal(self, a, b): | |||
|
578 | nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino, | |||
|
579 | "%r and %r do reference the same indoes" %(a, b)) | |||
|
580 | ||||
|
581 | def assert_inode_equal(self, a, b): | |||
|
582 | nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino, | |||
|
583 | "%r and %r do not reference the same indoes" %(a, b)) | |||
|
584 | ||||
|
585 | def assert_content_equal(self, a, b): | |||
|
586 | with open(a) as a_f: | |||
|
587 | with open(b) as b_f: | |||
|
588 | nt.assert_equals(a_f.read(), b_f.read()) | |||
|
589 | ||||
|
590 | @skip_win32 | |||
|
591 | def test_link_successful(self): | |||
|
592 | dst = self.dst("target") | |||
|
593 | path.link_or_copy(self.src, dst) | |||
|
594 | self.assert_inode_equal(self.src, dst) | |||
|
595 | ||||
|
596 | @skip_win32 | |||
|
597 | def test_link_into_dir(self): | |||
|
598 | dst = self.dst("some_dir") | |||
|
599 | os.mkdir(dst) | |||
|
600 | path.link_or_copy(self.src, dst) | |||
|
601 | expected_dst = self.dst("some_dir", os.path.basename(self.src)) | |||
|
602 | self.assert_inode_equal(self.src, expected_dst) | |||
|
603 | ||||
|
604 | @skip_win32 | |||
|
605 | def test_target_exists(self): | |||
|
606 | dst = self.dst("target") | |||
|
607 | open(dst, "w").close() | |||
|
608 | path.link_or_copy(self.src, dst) | |||
|
609 | self.assert_inode_equal(self.src, dst) | |||
|
610 | ||||
|
611 | @skip_win32 | |||
|
612 | def test_no_link(self): | |||
|
613 | real_link = os.link | |||
|
614 | try: | |||
|
615 | del os.link | |||
|
616 | dst = self.dst("target") | |||
|
617 | path.link_or_copy(self.src, dst) | |||
|
618 | self.assert_content_equal(self.src, dst) | |||
|
619 | self.assert_inode_not_equal(self.src, dst) | |||
|
620 | finally: | |||
|
621 | os.link = real_link | |||
|
622 | ||||
|
623 | @skip_if_not_win32 | |||
|
624 | def test_windows(self): | |||
|
625 | dst = self.dst("target") | |||
|
626 | path.link_or_copy(self.src, dst) | |||
|
627 | self.assert_content_equal(self.src, dst) |
General Comments 0
You need to be logged in to leave comments.
Login now