14
14
from pprint import pformat
15
15
from collections import defaultdict
16
16
from mimetypes import guess_type
17
+ from pathlib import Path
17
18
import click
18
19
19
20
33
34
34
35
# From https://github.com/rstudio/rsconnect/blob/485e05a26041ab8183a220da7a506c9d3a41f1ff/R/bundle.R#L85-L88
35
36
# noinspection SpellCheckingInspection
36
- directories_to_ignore = [
37
+ directories_ignore_list = [
37
38
".Rproj.user/" ,
38
39
".env/" ,
39
40
".git/" ,
47
48
"rsconnect/" ,
48
49
"venv/" ,
49
50
]
51
+ directories_to_ignore = {Path (d ) for d in directories_ignore_list }
50
52
51
53
52
54
# noinspection SpellCheckingInspection
@@ -423,17 +425,20 @@ def make_notebook_html_bundle(
423
425
return bundle_file
424
426
425
427
426
- def keep_manifest_specified_file (relative_path ):
428
+ def keep_manifest_specified_file (relative_path , ignore_path_set = directories_to_ignore ):
427
429
"""
428
430
A helper to see if the relative path given, which is assumed to have come
429
431
from a manifest.json file, should be kept or ignored.
430
432
431
433
:param relative_path: the relative path name to check.
432
434
:return: True, if the path should kept or False, if it should be ignored.
433
435
"""
434
- for ignore_me in directories_to_ignore :
435
- if relative_path .startswith (ignore_me ):
436
+ p = Path (relative_path )
437
+ for parent in p .parents :
438
+ if parent in ignore_path_set :
436
439
return False
440
+ if p in ignore_path_set :
441
+ return False
437
442
return True
438
443
439
444
@@ -550,56 +555,6 @@ def list_environment_dirs(directory):
550
555
return envs
551
556
552
557
553
- def _create_api_file_list (
554
- directory , # type: str
555
- requirements_file_name , # type: str
556
- extra_files = None , # type: typing.Optional[typing.List[str]]
557
- excludes = None , # type: typing.Optional[typing.List[str]]
558
- ):
559
- # type: (...) -> typing.List[str]
560
- """
561
- Builds a full list of files under the given directory that should be included
562
- in a manifest or bundle. Extra files and excludes are relative to the given
563
- directory and work as you'd expect.
564
-
565
- :param directory: the directory to walk for files.
566
- :param requirements_file_name: the name of the requirements file for the current
567
- Python environment.
568
- :param extra_files: a sequence of any extra files to include in the bundle.
569
- :param excludes: a sequence of glob patterns that will exclude matched files.
570
- :return: the list of relevant files, relative to the given directory.
571
- """
572
- # Don't let these top-level files be added via the extra files list.
573
- extra_files = extra_files or []
574
- skip = [requirements_file_name , "manifest.json" ]
575
- extra_files = sorted (list (set (extra_files ) - set (skip )))
576
-
577
- # Don't include these top-level files.
578
- excludes = list (excludes ) if excludes else []
579
- excludes .append ("manifest.json" )
580
- excludes .append (requirements_file_name )
581
- excludes .extend (list_environment_dirs (directory ))
582
- glob_set = create_glob_set (directory , excludes )
583
-
584
- file_list = []
585
-
586
- for subdir , dirs , files in os .walk (directory ):
587
- for file in files :
588
- abs_path = os .path .join (subdir , file )
589
- rel_path = os .path .relpath (abs_path , directory )
590
-
591
- if keep_manifest_specified_file (rel_path ) and (rel_path in extra_files or not glob_set .matches (abs_path )):
592
- file_list .append (rel_path )
593
- # Don't add extra files more than once.
594
- if rel_path in extra_files :
595
- extra_files .remove (rel_path )
596
-
597
- for rel_path in extra_files :
598
- file_list .append (rel_path )
599
-
600
- return sorted (file_list )
601
-
602
-
603
558
def make_api_manifest (
604
559
directory : str ,
605
560
entry_point : str ,
@@ -624,7 +579,17 @@ def make_api_manifest(
624
579
if is_environment_dir (directory ):
625
580
excludes = list (excludes or []) + ["bin/" , "lib/" ]
626
581
627
- relevant_files = _create_api_file_list (directory , environment .filename , extra_files , excludes )
582
+ extra_files = extra_files or []
583
+ skip = [environment .filename , "manifest.json" ]
584
+ extra_files = sorted (list (set (extra_files ) - set (skip )))
585
+
586
+ # Don't include these top-level files.
587
+ excludes = list (excludes ) if excludes else []
588
+ excludes .append ("manifest.json" )
589
+ excludes .append (environment .filename )
590
+ excludes .extend (list_environment_dirs (directory ))
591
+
592
+ relevant_files = create_file_list (directory , extra_files , excludes )
628
593
manifest = make_source_manifest (app_mode , environment , entry_point , None , image )
629
594
630
595
manifest_add_buffer (manifest , environment .filename , environment .contents )
@@ -655,6 +620,8 @@ def make_html_bundle_content(
655
620
"""
656
621
extra_files = list (extra_files ) if extra_files else []
657
622
entrypoint = entrypoint or infer_entrypoint (path = path , mimetype = "text/html" )
623
+ if not entrypoint :
624
+ raise RSConnectException ("Unable to find a valid html entry point." )
658
625
659
626
if path .startswith (os .curdir ):
660
627
path = relpath (path )
@@ -667,37 +634,15 @@ def make_html_bundle_content(
667
634
668
635
extra_files = extra_files or []
669
636
skip = ["manifest.json" ]
670
- extra_files = sorted (list ( set (extra_files ) - set (skip ) ))
637
+ extra_files = sorted (set (extra_files ) - set (skip ))
671
638
672
639
# Don't include these top-level files.
673
640
excludes = list (excludes ) if excludes else []
674
641
excludes .append ("manifest.json" )
675
642
if not isfile (path ):
676
643
excludes .extend (list_environment_dirs (path ))
677
- glob_set = create_glob_set (path , excludes )
678
-
679
- file_list = []
680
644
681
- for rel_path in extra_files :
682
- file_list .append (rel_path )
683
-
684
- if isfile (path ):
685
- file_list .append (path )
686
- else :
687
- for subdir , dirs , files in os .walk (path ):
688
- for file in files :
689
- abs_path = os .path .join (subdir , file )
690
- rel_path = os .path .relpath (abs_path , path )
691
-
692
- if keep_manifest_specified_file (rel_path ) and (
693
- rel_path in extra_files or not glob_set .matches (abs_path )
694
- ):
695
- file_list .append (rel_path )
696
- # Don't add extra files more than once.
697
- if rel_path in extra_files :
698
- extra_files .remove (rel_path )
699
-
700
- relevant_files = sorted (file_list )
645
+ relevant_files = create_file_list (path , extra_files , excludes )
701
646
manifest = make_html_manifest (entrypoint , image )
702
647
703
648
for rel_path in relevant_files :
@@ -706,6 +651,48 @@ def make_html_bundle_content(
706
651
return manifest , relevant_files
707
652
708
653
654
+ def create_file_list (
655
+ path : str ,
656
+ extra_files : typing .List [str ] = None ,
657
+ excludes : typing .List [str ] = None ,
658
+ ) -> typing .List [str ]:
659
+ """
660
+ Builds a full list of files under the given path that should be included
661
+ in a manifest or bundle. Extra files and excludes are relative to the given
662
+ directory and work as you'd expect.
663
+
664
+ :param path: a file, or a directory to walk for files.
665
+ :param extra_files: a sequence of any extra files to include in the bundle.
666
+ :param excludes: a sequence of glob patterns that will exclude matched files.
667
+ :return: the list of relevant files, relative to the given directory.
668
+ """
669
+ extra_files = extra_files or []
670
+ excludes = excludes if excludes else []
671
+ glob_set = create_glob_set (path , excludes )
672
+ exclude_paths = {Path (p ) for p in excludes }
673
+ file_set = set () # type: typing.Set[str]
674
+ file_set .union (extra_files )
675
+
676
+ if isfile (path ):
677
+ file_set .add (path )
678
+ return sorted (file_set )
679
+
680
+ for subdir , dirs , files in os .walk (path ):
681
+ if Path (subdir ) in exclude_paths :
682
+ continue
683
+ for file in files :
684
+ abs_path = os .path .join (subdir , file )
685
+ rel_path = os .path .relpath (abs_path , path )
686
+
687
+ if Path (abs_path ) in exclude_paths :
688
+ continue
689
+ if keep_manifest_specified_file (rel_path , exclude_paths | directories_to_ignore ) and (
690
+ rel_path in extra_files or not glob_set .matches (abs_path )
691
+ ):
692
+ file_set .add (rel_path )
693
+ return sorted (file_set )
694
+
695
+
709
696
def infer_entrypoint (path , mimetype ):
710
697
if os .path .isfile (path ):
711
698
return path
@@ -823,25 +810,9 @@ def _create_quarto_file_list(
823
810
excludes = list (excludes ) if excludes else []
824
811
excludes .append ("manifest.json" )
825
812
excludes .extend (list_environment_dirs (directory ))
826
- glob_set = create_glob_set (directory , excludes )
827
-
828
- file_list = []
829
-
830
- for subdir , dirs , files in os .walk (directory ):
831
- for file in files :
832
- abs_path = os .path .join (subdir , file )
833
- rel_path = os .path .relpath (abs_path , directory )
834
-
835
- if keep_manifest_specified_file (rel_path ) and (rel_path in extra_files or not glob_set .matches (abs_path )):
836
- file_list .append (rel_path )
837
- # Don't add extra files more than once.
838
- if rel_path in extra_files :
839
- extra_files .remove (rel_path )
840
-
841
- for rel_path in extra_files :
842
- file_list .append (rel_path )
843
813
844
- return sorted (file_list )
814
+ file_list = create_file_list (directory , extra_files , excludes )
815
+ return file_list
845
816
846
817
847
818
def make_quarto_manifest (
0 commit comments