|
24 | 24 | from collections import defaultdict |
25 | 25 | from contextlib import suppress |
26 | 26 | from pathlib import Path |
| 27 | +from re import match as regex_match |
27 | 28 |
|
28 | 29 | from django.contrib.postgres.aggregates.general import ArrayAgg |
29 | 30 | from django.core.exceptions import MultipleObjectsReturned |
|
43 | 44 | from scanpipe import pipes |
44 | 45 | from scanpipe.models import CodebaseRelation |
45 | 46 | from scanpipe.models import CodebaseResource |
| 47 | +from scanpipe.models import posix_regex_to_django_regex_lookup |
46 | 48 | from scanpipe.pipes import LoopProgress |
47 | 49 | from scanpipe.pipes import flag |
48 | 50 | from scanpipe.pipes import get_resource_diff_ratio |
@@ -774,87 +776,129 @@ def _map_javascript_resource( |
774 | 776 | resource.update(status=flag.MAPPED) |
775 | 777 |
|
776 | 778 |
|
777 | | -def _map_about_file_resource(project, about_file_resource, to_resources): |
778 | | - about_file_location = str(about_file_resource.location_path) |
779 | | - package_data = resolve.resolve_about_package(about_file_location) |
780 | | - |
781 | | - error_message_details = { |
782 | | - "path": about_file_resource.path, |
783 | | - "package_data": package_data, |
784 | | - } |
785 | | - if not package_data: |
786 | | - project.add_error( |
787 | | - description="Cannot create package from ABOUT file", |
788 | | - model="map_about_files", |
789 | | - details=error_message_details, |
790 | | - ) |
| 779 | +def map_about_files(project, logger=None): |
| 780 | + """Map ``from/`` .ABOUT files to their related ``to/`` resources.""" |
| 781 | + project_resources = project.codebaseresources |
| 782 | + from_about_files = ( |
| 783 | + project_resources.files().from_codebase().filter(extension=".ABOUT") |
| 784 | + ) |
| 785 | + if not from_about_files.exists(): |
791 | 786 | return |
792 | 787 |
|
793 | | - files_pattern = package_data.get("filename") |
794 | | - if not files_pattern: |
795 | | - # Cannot map anything without the about_resource value. |
796 | | - project.add_error( |
797 | | - description="ABOUT file does not have about_resource", |
798 | | - model="map_about_files", |
799 | | - details=error_message_details, |
800 | | - ) |
801 | | - return |
| 788 | + to_resources = project_resources.to_codebase().no_status() |
802 | 789 |
|
803 | | - ignored_resource_patterns = [] |
804 | | - codebase_resources = to_resources.path_pattern(pattern=files_pattern) |
805 | | - if extra_data := package_data.get("extra_data"): |
806 | | - ignored_resource_patterns = extra_data.get("ignored_resources") |
807 | | - |
808 | | - # Fetch all resources that are covered by the .ABOUT file. |
809 | | - if not codebase_resources: |
810 | | - # If there's nothing to map on the ``to/`` do not create the package. |
811 | | - project.add_warning( |
812 | | - description=( |
813 | | - "Resource paths listed at about_resource is not found" |
814 | | - " in the to/ codebase" |
815 | | - ), |
816 | | - model="map_about_files", |
817 | | - details=error_message_details, |
| 790 | + if logger: |
| 791 | + logger( |
| 792 | + f"Mapping {from_about_files.count():,d} .ABOUT files found in the from/ " |
| 793 | + f"codebase." |
818 | 794 | ) |
819 | | - return |
820 | 795 |
|
821 | | - # Ignore resources for paths in `ignored_resources` attribute |
822 | | - if ignored_resource_patterns: |
823 | | - codebase_resources = codebase_resources.path_patterns( |
824 | | - patterns=ignored_resource_patterns |
| 796 | + regex_by_about_path = {} |
| 797 | + ignore_regex_by_about_path = {} |
| 798 | + about_resources_by_path = {} |
| 799 | + about_pkgdata_by_path = {} |
| 800 | + mapped_resources_by_aboutpath = {} |
| 801 | + |
| 802 | + for about_file_resource in from_about_files: |
| 803 | + package_data = resolve.resolve_about_package( |
| 804 | + input_location=str(about_file_resource.location_path) |
825 | 805 | ) |
| 806 | + error_message_details = { |
| 807 | + "path": about_file_resource.path, |
| 808 | + "package_data": package_data, |
| 809 | + } |
| 810 | + if not package_data: |
| 811 | + project.add_error( |
| 812 | + description="Cannot create package from ABOUT file", |
| 813 | + model="map_about_files", |
| 814 | + details=error_message_details, |
| 815 | + ) |
| 816 | + continue |
826 | 817 |
|
827 | | - # Create the Package using .ABOUT data and assigned related codebase_resources |
828 | | - pipes.update_or_create_package(project, package_data, codebase_resources) |
| 818 | + about_pkgdata_by_path[about_file_resource.path] = package_data |
| 819 | + files_pattern = package_data.get("filename") |
| 820 | + if not files_pattern: |
| 821 | + # Cannot map anything without the about_resource value. |
| 822 | + project.add_error( |
| 823 | + description="ABOUT file does not have about_resource", |
| 824 | + model="map_about_files", |
| 825 | + details=error_message_details, |
| 826 | + ) |
| 827 | + continue |
| 828 | + else: |
| 829 | + regex = posix_regex_to_django_regex_lookup(files_pattern) |
| 830 | + regex_by_about_path[about_file_resource.path] = regex |
| 831 | + |
| 832 | + if extra_data := package_data.get("extra_data"): |
| 833 | + ignore_regex = [] |
| 834 | + for pattern in extra_data.get("ignored_resources", []): |
| 835 | + ignore_regex.append(posix_regex_to_django_regex_lookup(pattern)) |
| 836 | + if ignore_regex: |
| 837 | + ignore_regex_by_about_path[about_file_resource.path] = ignore_regex |
| 838 | + |
| 839 | + about_resources_by_path[about_file_resource.path] = about_file_resource |
| 840 | + mapped_resources_by_aboutpath[about_file_resource.path] = [] |
| 841 | + |
| 842 | + for to_resource in to_resources: |
| 843 | + resource_matched = False |
| 844 | + for about_path, regex_pattern in regex_by_about_path.items(): |
| 845 | + if regex_match(pattern=regex_pattern, string=to_resource.path): |
| 846 | + resource_matched = True |
| 847 | + break |
| 848 | + |
| 849 | + if not resource_matched: |
| 850 | + continue |
829 | 851 |
|
830 | | - # Map the .ABOUT file resource to all related resources in the ``to/`` side. |
831 | | - for to_resource in codebase_resources: |
832 | | - pipes.make_relation( |
833 | | - from_resource=about_file_resource, |
834 | | - to_resource=to_resource, |
835 | | - map_type="about_file", |
836 | | - ) |
| 852 | + ignore_regex_patterns = ignore_regex_by_about_path.get(about_path, []) |
| 853 | + ignore_resource = False |
| 854 | + for ignore_regex_pattern in ignore_regex_patterns: |
| 855 | + if regex_match(pattern=ignore_regex_pattern, string=to_resource.path): |
| 856 | + ignore_resource = True |
| 857 | + break |
837 | 858 |
|
838 | | - codebase_resources.update(status=flag.ABOUT_MAPPED) |
839 | | - about_file_resource.update(status=flag.ABOUT_MAPPED) |
| 859 | + if ignore_resource: |
| 860 | + continue |
840 | 861 |
|
| 862 | + mapped_resources_about = mapped_resources_by_aboutpath.get(about_path) |
| 863 | + if mapped_resources_about: |
| 864 | + mapped_resources_about.append(to_resource) |
| 865 | + else: |
| 866 | + mapped_resources_by_aboutpath[about_path] = [to_resource] |
| 867 | + to_resource.update(status=flag.ABOUT_MAPPED) |
| 868 | + |
| 869 | + for about_path, mapped_resources in mapped_resources_by_aboutpath.items(): |
| 870 | + about_file_resource = about_resources_by_path[about_path] |
| 871 | + package_data = about_pkgdata_by_path[about_file_resource.path] |
| 872 | + |
| 873 | + if not mapped_resources: |
| 874 | + error_message_details = { |
| 875 | + "path": about_file_resource.path, |
| 876 | + "package_data": package_data, |
| 877 | + } |
| 878 | + project.add_warning( |
| 879 | + description=( |
| 880 | + "Resource paths listed at about_resource is not found" |
| 881 | + " in the to/ codebase" |
| 882 | + ), |
| 883 | + model="map_about_files", |
| 884 | + details=error_message_details, |
| 885 | + ) |
| 886 | + continue |
841 | 887 |
|
842 | | -def map_about_files(project, logger=None): |
843 | | - """Map ``from/`` .ABOUT files to their related ``to/`` resources.""" |
844 | | - project_resources = project.codebaseresources |
845 | | - from_files = project_resources.files().from_codebase() |
846 | | - from_about_files = from_files.filter(extension=".ABOUT") |
847 | | - to_resources = project_resources.to_codebase() |
| 888 | + # Create the Package using .ABOUT data and assigned related codebase_resources |
| 889 | + pipes.update_or_create_package(project, package_data, mapped_resources) |
848 | 890 |
|
849 | | - if logger: |
850 | | - logger( |
851 | | - f"Mapping {from_about_files.count():,d} .ABOUT files found in the from/ " |
852 | | - f"codebase." |
853 | | - ) |
| 891 | + # Map the .ABOUT file resource to all related resources in the ``to/`` side. |
| 892 | + for mapped_resource in mapped_resources: |
| 893 | + pipes.make_relation( |
| 894 | + from_resource=about_file_resource, |
| 895 | + to_resource=mapped_resource, |
| 896 | + map_type="about_file", |
| 897 | + ) |
854 | 898 |
|
855 | | - for about_file_resource in from_about_files: |
856 | | - _map_about_file_resource(project, about_file_resource, to_resources) |
| 899 | + about_file_resource.update(status=flag.ABOUT_MAPPED) |
857 | 900 |
|
| 901 | + for about_file_resource in about_resources_by_path.values(): |
858 | 902 | about_file_companions = ( |
859 | 903 | about_file_resource.siblings() |
860 | 904 | .filter(name__startswith=about_file_resource.name_without_extension) |
|
0 commit comments