1414from pprint import pformat
1515from collections import defaultdict
1616from mimetypes import guess_type
17+ from pathlib import Path
1718import click
1819
1920
3334
3435# From https://github.com/rstudio/rsconnect/blob/485e05a26041ab8183a220da7a506c9d3a41f1ff/R/bundle.R#L85-L88
3536# noinspection SpellCheckingInspection
36- directories_to_ignore = [
37+ directories_ignore_list = [
3738 ".Rproj.user/" ,
3839 ".env/" ,
3940 ".git/" ,
4748 "rsconnect/" ,
4849 "venv/" ,
4950]
51+ directories_to_ignore = {Path (d ) for d in directories_ignore_list }
5052
5153
5254# noinspection SpellCheckingInspection
@@ -423,17 +425,20 @@ def make_notebook_html_bundle(
423425 return bundle_file
424426
425427
426- def keep_manifest_specified_file (relative_path ):
428+ def keep_manifest_specified_file (relative_path , ignore_path_set = directories_to_ignore ):
427429 """
428430 A helper to see if the relative path given, which is assumed to have come
429431 from a manifest.json file, should be kept or ignored.
430432
431433 :param relative_path: the relative path name to check.
432434 :return: True, if the path should kept or False, if it should be ignored.
433435 """
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 :
436439 return False
440+ if p in ignore_path_set :
441+ return False
437442 return True
438443
439444
@@ -550,56 +555,6 @@ def list_environment_dirs(directory):
550555 return envs
551556
552557
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-
603558def make_api_manifest (
604559 directory : str ,
605560 entry_point : str ,
@@ -624,7 +579,17 @@ def make_api_manifest(
624579 if is_environment_dir (directory ):
625580 excludes = list (excludes or []) + ["bin/" , "lib/" ]
626581
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 )
628593 manifest = make_source_manifest (app_mode , environment , entry_point , None , image )
629594
630595 manifest_add_buffer (manifest , environment .filename , environment .contents )
@@ -667,37 +632,15 @@ def make_html_bundle_content(
667632
668633 extra_files = extra_files or []
669634 skip = ["manifest.json" ]
670- extra_files = sorted (list ( set (extra_files ) - set (skip ) ))
635+ extra_files = sorted (set (extra_files ) - set (skip ))
671636
672637 # Don't include these top-level files.
673638 excludes = list (excludes ) if excludes else []
674639 excludes .append ("manifest.json" )
675640 if not isfile (path ):
676641 excludes .extend (list_environment_dirs (path ))
677- glob_set = create_glob_set (path , excludes )
678-
679- file_list = []
680642
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 )
643+ relevant_files = create_file_list (path , extra_files , excludes )
701644 manifest = make_html_manifest (entrypoint , image )
702645
703646 for rel_path in relevant_files :
@@ -706,6 +649,48 @@ def make_html_bundle_content(
706649 return manifest , relevant_files
707650
708651
652+ def create_file_list (
653+ path : str ,
654+ extra_files : typing .List [str ] = None ,
655+ excludes : typing .List [str ] = None ,
656+ ) -> typing .List [str ]:
657+ """
658+ Builds a full list of files under the given path that should be included
659+ in a manifest or bundle. Extra files and excludes are relative to the given
660+ directory and work as you'd expect.
661+
662+ :param path: a file, or a directory to walk for files.
663+ :param extra_files: a sequence of any extra files to include in the bundle.
664+ :param excludes: a sequence of glob patterns that will exclude matched files.
665+ :return: the list of relevant files, relative to the given directory.
666+ """
667+ extra_files = extra_files or []
668+ excludes = excludes if excludes else []
669+ glob_set = create_glob_set (path , excludes )
670+ exclude_paths = {Path (p ) for p in excludes }
671+ file_set = set () # type: typing.Set[str]
672+ file_set .union (extra_files )
673+
674+ if isfile (path ):
675+ file_set .add (path )
676+ return sorted (file_set )
677+
678+ for subdir , dirs , files in os .walk (path ):
679+ if Path (subdir ) in exclude_paths :
680+ continue
681+ for file in files :
682+ abs_path = os .path .join (subdir , file )
683+ rel_path = os .path .relpath (abs_path , path )
684+
685+ if Path (abs_path ) in exclude_paths :
686+ continue
687+ if keep_manifest_specified_file (rel_path , exclude_paths | directories_to_ignore ) and (
688+ rel_path in extra_files or not glob_set .matches (abs_path )
689+ ):
690+ file_set .add (rel_path )
691+ return sorted (file_set )
692+
693+
709694def infer_entrypoint (path , mimetype ):
710695 if os .path .isfile (path ):
711696 return path
@@ -823,25 +808,9 @@ def _create_quarto_file_list(
823808 excludes = list (excludes ) if excludes else []
824809 excludes .append ("manifest.json" )
825810 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 )
843811
844- return sorted (file_list )
812+ file_list = create_file_list (directory , extra_files , excludes )
813+ return file_list
845814
846815
847816def make_quarto_manifest (
0 commit comments