From 2d60994d843f02367fc04d187a5adc74f071c934 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Mon, 9 May 2022 12:51:54 -0700 Subject: [PATCH 01/13] Make initial updates to earlier forked code #2945 Signed-off-by: John M. Horan --- setup.cfg | 4 +- src/summarycode/file_cat.py | 771 ++++++++++++++++++++++++++++++++++++ 2 files changed, 772 insertions(+), 3 deletions(-) create mode 100644 src/summarycode/file_cat.py diff --git a/setup.cfg b/setup.cfg index ed308cfc2f2..c123b89c106 100644 --- a/setup.cfg +++ b/setup.cfg @@ -195,9 +195,7 @@ scancode_post_scan = mark-source = scancode.plugin_mark_source:MarkSource filter-clues = cluecode.plugin_filter_clues:RedundantCluesFilter consolidate = summarycode.plugin_consolidate:Consolidator - license-references = licensedcode.licenses_reference:LicenseReference - todo = summarycode.todo:AmbiguousDetectionsToDoPlugin - classify = summarycode.classify_plugin:FileClassifier + # scancode_output_filter is the entry point for filter plugins executed after diff --git a/src/summarycode/file_cat.py b/src/summarycode/file_cat.py new file mode 100644 index 00000000000..1c24e508959 --- /dev/null +++ b/src/summarycode/file_cat.py @@ -0,0 +1,771 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/scancode-toolkit for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + + + +from commoncode.datautils import String +from plugincode.post_scan import PostScanPlugin +from plugincode.post_scan import post_scan_impl +from commoncode.cliutils import PluggableCommandLineOption +from commoncode.cliutils import POST_SCAN_GROUP + +""" +Categorize files. +""" + +# Tracing flag +TRACE = False + +def logger_debug(*args): + pass + + +if TRACE: + import logging + import click + + class ClickHandler(logging.Handler): + _use_stderr = True + + def emit(self, record): + try: + msg = self.format(record) + click.echo(msg, err=self._use_stderr) + except Exception: + self.handleError(record) + + + logger = logging.getLogger(__name__) + logger.handlers = [ClickHandler()] + logger.propagate = False + logger.setLevel(logging.DEBUG) + + + def logger_debug(*args): + return logger.debug(' '.join(isinstance(a, str) and a or repr(a) for a in args)) + + +@post_scan_impl +class FileCategorizer(PostScanPlugin): + """ + Categorize a file. + """ + # resource_attributes = dict([ + # ('category', + # String(help='Category.')), + + # ('subcategory', + # String(help='Subcategory.')), + # ]) + resource_attributes = dict([ + ('file_category', + String(help='File category.')), + + ('file_subcategory', + String(help='File subcategory.')), + ]) + + sort_order = 50 + + options = [ + # PluggableCommandLineOption(('--categorize',), + PluggableCommandLineOption(('--file-cat',), + is_flag=True, default=False, + help='Categorize files.', + help_group=POST_SCAN_GROUP, + sort_order=50, + ) + ] + + # def is_enabled(self, categorize, **kwargs): + # return categorize + def is_enabled(self, file_cat, **kwargs): + return file_cat + + # def process_codebase(self, codebase, categorize, **kwargs): + # if not categorize: + # return + def process_codebase(self, codebase, file_cat, **kwargs): + if not file_cat: + return + + for resource in codebase.walk(topdown=True): + if resource.is_file: + category = categorize_resource(resource) + if not category: + continue + # resource.category = category.file_category + # resource.subcategory = category.file_subcategory + resource.file_category = category.file_category + resource.file_subcategory = category.file_subcategory + resource.save(codebase) + + +class Categorizer: + order = 0 + rule_applied = None + analysis_priority = None + file_category = None + file_subcategory = None + notes = None + + @classmethod + def categorize(cls, resource): + """ + Return True if this Categorizer applies to this resource or False or None + otherwise. + """ + raise NotImplementedError + + +class archive_debian(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'archive' + file_subcategory = 'debian' + notes = 'special type of archive' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".deb"] + ) + + +class archive_general(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'archive' + file_subcategory = 'general' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".7zip", ".bz", ".bz2", ".bzip", ".gz", ".gzi", ".tar", ".tgz", ".xz", ".zip"] + ) + + +class archive_rpm(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'archive' + file_subcategory = 'rpm' + notes = 'special type of archive' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".rpm"] + ) + + +class binary_ar(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'binary' + file_subcategory = 'ar' + + @classmethod + def categorize(cls, resource): + return ( + resource.mime_type in ["application/x-archive"] + ) + + +class binary_elf_exec(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'binary' + file_subcategory = 'elf-exec' + + @classmethod + def categorize(cls, resource): + return ( + resource.mime_type in ["application/x-executable"] + ) + + +class binary_elf_ko(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'binary' + file_subcategory = 'elf-ko' + + @classmethod + def categorize(cls, resource): + return ( + resource.mime_type in ["application/x-object"] + and + resource.extension.lower().startswith('.ko') + ) + + +class binary_elf_o(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'binary' + file_subcategory = 'elf-o' + + @classmethod + def categorize(cls, resource): + return ( + resource.mime_type in ["application/x-object"] + and + not resource.extension.lower().startswith('.ko') + ) + + +class binary_elf_so(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'binary' + file_subcategory = 'elf-so' + + @classmethod + def categorize(cls, resource): + return ( + resource.mime_type in ["application/x-sharedlib"] + ) + + +class binary_java(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'binary' + file_subcategory = 'java' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".class", ".jar"] + ) + + +class binary_windows(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'binary' + file_subcategory = 'windows' + notes = 'For DLL and EXE binaries in Windows' + + @classmethod + def categorize(cls, resource): + return ( + resource.mime_type in ["application/x-dosexec"] + ) + + +class config_general(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'config' + file_subcategory = 'general' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".bat", ".cfg", ".conf", ".config", ".sh", ".yaml", ".yml"] + ) + + +class config_xml(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'config' + file_subcategory = 'xml' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".dtd", ".xml", ".xsd", ".xsl", ".xslt"] + ) + + +class data_json(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'data' + file_subcategory = 'json' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".json"] + ) + + +class doc_general(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'doc' + file_subcategory = 'general' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".csv", ".doc", ".docx", ".md", ".odp", ".ods", ".odt", ".pdf", ".ppt", ".pptx", ".rtf", ".tex", ".txt", ".xls", ".xlsm", ".xlsx"] + ) + + +class doc_readme(Categorizer): + order = 0 + analysis_priority = '3' + file_category = 'doc' + file_subcategory = 'readme' + + @classmethod + def categorize(cls, resource): + return ( + "readme" in resource.name.lower() + ) + + +class font(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'font' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".fnt", ".otf", ".ttf", ".woff", ".woff2", ".eot"] + ) + + +class manifest_npm(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'npm' + + @classmethod + def categorize(cls, resource): + return ( + resource.name.lower() in ["package.json", "package-lock.json"] + ) + + +class media_audio(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'media' + file_subcategory = 'audio' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".mp3", ".mpa", ".ogg", ".wav", ".wma"] + ) + + +class media_image(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'media' + file_subcategory = 'image' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".bmp", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".svg"] + ) + + +class media_video(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'media' + file_subcategory = 'video' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".avi", ".h264", ".mp4", ".mpg", ".mpeg", ".swf", ".wmv"] + ) + + +class script_bash(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'script' + file_subcategory = 'bash' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".bash"] + ) + + +class script_build(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'build' + file_subcategory = 'script' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".cmake", ".cmakelist"] + ) + + +class script_perl(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'script' + file_subcategory = 'perl' + notes = 'We will treat all Perl as script' + + @classmethod + def categorize(cls, resource): + return ( + resource.mime_type in ["text/x-perl"] + ) + + +class script_data(Categorizer): + order = 1 + analysis_priority = '3' + file_category = 'data' + file_subcategory = 'script' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".sql", ".psql"] + ) + + +class source_c(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'c' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".c"] + or + ( + resource.extension.lower() in [".h"] + and + resource.mime_type in ["text/x-c"] + ) + ) + + +class source_cpp(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'c++' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".cpp", ".hpp", ".cc"] + or + ( + resource.extension.lower() in [".h"] + and + resource.mime_type in ["text/x-c++"] + ) + ) + + +class source_csharp(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'c#' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".cs"] + ) + + +class source_go(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'go' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".go"] + ) + + +class source_haskell(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'haskell' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".hs", ".lhs"] + ) + + +class source_java(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'java' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".java"] + ) + + +class source_javascript(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'javascript' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".js"] + ) + + +class source_javaserverpage(Categorizer): + order = 1 + analysis_priority = '2' + file_category = 'source' + file_subcategory = 'javaserverpage' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".jsp"] + ) + + +class source_kotlin(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'kotlin' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".kt"] + ) + + +class source_objectivec(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'objectivec' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".m", ".mm"] + or + ( + resource.extension.lower() in [".h"] + and + resource.mime_type in ["text/x-objective-c"] + ) + ) + + +class source_php(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'php' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".php", ".php3", ".php4", ".php5"] + ) + + +class source_python(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'python' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".py"] + ) + + +class source_ruby(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'ruby' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".rb", ".rake"] + ) + + +class source_rust(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'rust' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".rs"] + ) + + +class source_scala(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'scala' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".scala"] + ) + + +class source_swift(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'swift' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".swift"] + ) + + +class source_typescript(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'typescript' + notes = '.ts extension is not definitive' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".ts"] + and + resource.programming_language in ["TypeScript"] + ) + + +class web_css(Categorizer): + order = 1 + analysis_priority = '1' + file_category = 'web' + file_subcategory = 'css' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".css"] + ) + + +class web_html(Categorizer): + order = 1 + analysis_priority = '2' + file_category = 'web' + file_subcategory = 'html' + + @classmethod + def categorize(cls, resource): + return ( + resource.extension.lower() in [".htm", ".html"] + ) + + +categories = [ + archive_debian, + archive_general, + archive_rpm, + binary_ar, + binary_elf_exec, + binary_elf_ko, + binary_elf_o, + binary_elf_so, + binary_java, + binary_windows, + config_general, + config_xml, + data_json, + doc_general, + doc_readme, + font, + manifest_npm, + media_audio, + media_image, + media_video, + script_bash, + script_build, + script_data, + script_perl, + source_c, + source_cpp, + source_csharp, + source_go, + source_haskell, + source_java, + source_javascript, + source_javaserverpage, + source_kotlin, + source_objectivec, + source_php, + source_python, + source_ruby, + source_rust, + source_scala, + source_swift, + source_typescript, + web_css, + web_html +] + + +def category_key(category): + return category.order + + +def categorize_resource(resource): + """ + Return a Categorizer for this ``resource`` Resource object, or None. + """ + for category in sorted(categories, key=category_key): + if category.categorize(resource): + return category From 3e67d38ccb1d9c912d6edd84c9061b53c9c112c4 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Mon, 9 May 2022 14:13:38 -0700 Subject: [PATCH 02/13] Update rules #2945 Signed-off-by: John M. Horan --- src/summarycode/file_cat.py | 1059 ++++++++++++++++++++++++++++------- 1 file changed, 850 insertions(+), 209 deletions(-) diff --git a/src/summarycode/file_cat.py b/src/summarycode/file_cat.py index 1c24e508959..26d792c4468 100644 --- a/src/summarycode/file_cat.py +++ b/src/summarycode/file_cat.py @@ -7,8 +7,6 @@ # See https://aboutcode.org for more information about nexB OSS projects. # - - from commoncode.datautils import String from plugincode.post_scan import PostScanPlugin from plugincode.post_scan import post_scan_impl @@ -56,14 +54,10 @@ class FileCategorizer(PostScanPlugin): """ Categorize a file. """ - # resource_attributes = dict([ - # ('category', - # String(help='Category.')), - - # ('subcategory', - # String(help='Subcategory.')), - # ]) resource_attributes = dict([ + ('analysis_priority', + String(help='Analysis priority.')), + ('file_category', String(help='File category.')), @@ -74,7 +68,6 @@ class FileCategorizer(PostScanPlugin): sort_order = 50 options = [ - # PluggableCommandLineOption(('--categorize',), PluggableCommandLineOption(('--file-cat',), is_flag=True, default=False, help='Categorize files.', @@ -83,14 +76,9 @@ class FileCategorizer(PostScanPlugin): ) ] - # def is_enabled(self, categorize, **kwargs): - # return categorize def is_enabled(self, file_cat, **kwargs): return file_cat - # def process_codebase(self, codebase, categorize, **kwargs): - # if not categorize: - # return def process_codebase(self, codebase, file_cat, **kwargs): if not file_cat: return @@ -100,8 +88,7 @@ def process_codebase(self, codebase, file_cat, **kwargs): category = categorize_resource(resource) if not category: continue - # resource.category = category.file_category - # resource.subcategory = category.file_subcategory + resource.analysis_priority = category.analysis_priority resource.file_category = category.file_category resource.file_subcategory = category.file_subcategory resource.save(codebase) @@ -109,11 +96,11 @@ def process_codebase(self, codebase, file_cat, **kwargs): class Categorizer: order = 0 - rule_applied = None analysis_priority = None file_category = None file_subcategory = None - notes = None + category_notes = None + rule_applied = None @classmethod def categorize(cls, resource): @@ -124,637 +111,1266 @@ def categorize(cls, resource): raise NotImplementedError -class archive_debian(Categorizer): - order = 1 +class ArchiveAndroid(Categorizer): + order = 10 + analysis_priority = '1' + file_category = 'archive' + file_subcategory = 'Android' + rule_applied = 'ArchiveAndroid' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.apk', '.aar']) + ) + + +class ArchiveDebian(Categorizer): + order = 10 analysis_priority = '1' file_category = 'archive' file_subcategory = 'debian' - notes = 'special type of archive' + category_notes = 'special type of archive' + rule_applied = 'ArchiveDebian' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".deb"] + extension_in(resource.extension, ['.deb']) ) -class archive_general(Categorizer): - order = 1 +class ArchiveGeneral(Categorizer): + order = 10 analysis_priority = '1' file_category = 'archive' file_subcategory = 'general' + rule_applied = 'ArchiveGeneral' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, [ + '.7zip', '.bz', '.bz2', '.bzip', '.gz', '.gzi', '.tar', '.tgz', '.xz', '.zip' + ]) + ) + + +class ArchiveIos(Categorizer): + order = 10 + analysis_priority = '1' + file_category = 'archive' + file_subcategory = 'iOS' + rule_applied = 'ArchiveIos' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".7zip", ".bz", ".bz2", ".bzip", ".gz", ".gzi", ".tar", ".tgz", ".xz", ".zip"] + extension_in(resource.extension, ['.ipa']) ) -class archive_rpm(Categorizer): - order = 1 +class ArchiveRpm(Categorizer): + order = 10 analysis_priority = '1' file_category = 'archive' file_subcategory = 'rpm' - notes = 'special type of archive' + category_notes = 'special type of archive' + rule_applied = 'ArchiveRpm' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".rpm"] + extension_in(resource.extension, ['.rpm']) ) -class binary_ar(Categorizer): - order = 1 +class BinaryAr(Categorizer): + order = 10 analysis_priority = '1' file_category = 'binary' file_subcategory = 'ar' + rule_applied = 'BinaryAr' @classmethod def categorize(cls, resource): return ( - resource.mime_type in ["application/x-archive"] + resource.mime_type in ['application/x-archive'] ) -class binary_elf_exec(Categorizer): - order = 1 +class BinaryElfExec(Categorizer): + order = 10 analysis_priority = '1' file_category = 'binary' file_subcategory = 'elf-exec' + rule_applied = 'BinaryElfExec' @classmethod def categorize(cls, resource): return ( - resource.mime_type in ["application/x-executable"] + resource.mime_type in ['application/x-executable'] ) -class binary_elf_ko(Categorizer): - order = 1 +class BinaryElfKo(Categorizer): + order = 10 analysis_priority = '1' file_category = 'binary' file_subcategory = 'elf-ko' + rule_applied = 'BinaryElfKo' @classmethod def categorize(cls, resource): return ( - resource.mime_type in ["application/x-object"] + resource.mime_type in ['application/x-object'] and - resource.extension.lower().startswith('.ko') + extension_startswith(resource.extension, ('.ko')) ) -class binary_elf_o(Categorizer): - order = 1 +class BinaryElfO(Categorizer): + order = 10 analysis_priority = '1' file_category = 'binary' file_subcategory = 'elf-o' + rule_applied = 'BinaryElfO' @classmethod def categorize(cls, resource): return ( - resource.mime_type in ["application/x-object"] + resource.mime_type in ['application/x-object'] and - not resource.extension.lower().startswith('.ko') + not extension_startswith(resource.extension, ('.ko')) ) -class binary_elf_so(Categorizer): - order = 1 +class BinaryElfSo(Categorizer): + order = 10 analysis_priority = '1' file_category = 'binary' file_subcategory = 'elf-so' + rule_applied = 'BinaryElfSo' @classmethod def categorize(cls, resource): return ( - resource.mime_type in ["application/x-sharedlib"] + resource.mime_type in ['application/x-sharedlib'] ) -class binary_java(Categorizer): - order = 1 +class BinaryJava(Categorizer): + order = 10 analysis_priority = '1' file_category = 'binary' file_subcategory = 'java' + rule_applied = 'BinaryJava' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".class", ".jar"] + extension_in(resource.extension, ['.class', '.jar', '.ear', '.sar', '.war']) ) -class binary_windows(Categorizer): - order = 1 +class BinaryPython(Categorizer): + order = 10 + analysis_priority = '1' + file_category = 'binary' + file_subcategory = 'Python' + rule_applied = 'BinaryPython' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.pyc', '.pyo']) + ) + + +class BinaryWindows(Categorizer): + order = 10 analysis_priority = '1' file_category = 'binary' file_subcategory = 'windows' - notes = 'For DLL and EXE binaries in Windows' + category_notes = 'For DLL and EXE binaries in Windows' + rule_applied = 'BinaryWindows' + + @classmethod + def categorize(cls, resource): + return ( + resource.mime_type in ['application/x-dosexec'] + ) + + +class BuildBazel(Categorizer): + order = 0 + analysis_priority = '3' + file_category = 'build' + file_subcategory = 'Bazel' + rule_applied = 'BuildBazel' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.bzl']) + or + ( + name_in(resource.name, ['build.bazel']) + and + resource.type == 'file' + ) + ) + + +class BuildBuck(Categorizer): + order = 0 + analysis_priority = '3' + file_category = 'build' + file_subcategory = 'BUCK' + rule_applied = 'BuildBuck' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['buck']) + and + resource.type == 'file' + ) + + +class BuildDocker(Categorizer): + order = 0 + analysis_priority = '2' + file_category = 'build' + file_subcategory = 'Docker' + category_notes = 'May have an "arbitrary" extension' + rule_applied = 'BuildDocker' + + @classmethod + def categorize(cls, resource): + return ( + name_substring(resource.name, ['dockerfile']) + and + resource.type == 'file' + ) + + +class BuildMake(Categorizer): + order = 0 + analysis_priority = '3' + file_category = 'build' + file_subcategory = 'make' + category_notes = '"Makefile" may have an "arbitrary" extension' + rule_applied = 'BuildMake' + + @classmethod + def categorize(cls, resource): + return ( + ( + name_substring(resource.name, ['makefile']) + and + resource.type == 'file' + ) + or + extension_in(resource.extension, ['.mk', '.make']) + ) + + +class BuildQt(Categorizer): + order = 10 + analysis_priority = '3' + file_category = 'build' + file_subcategory = 'Qt' + rule_applied = 'BuildQt' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.pri', '.pro']) + ) + + +class Certificate(Categorizer): + order = 10 + analysis_priority = '3' + file_category = 'certificate' + file_subcategory = '' + rule_applied = 'Certificate' @classmethod def categorize(cls, resource): return ( - resource.mime_type in ["application/x-dosexec"] + extension_in(resource.extension, ['.crt', '.der', '.pem']) ) -class config_general(Categorizer): - order = 1 +class ConfigGeneral(Categorizer): + order = 10 analysis_priority = '3' file_category = 'config' file_subcategory = 'general' + rule_applied = 'ConfigGeneral' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, [ + '.cfg', '.conf', '.config', '.jxs', '.properties', '.yaml', '.yml' + ]) + ) + + +class ConfigInitialPeriod(Categorizer): + order = 0 + analysis_priority = '3' + file_category = 'config' + file_subcategory = 'initial_period' + rule_applied = 'ConfigInitialPeriod' + + @classmethod + def categorize(cls, resource): + return ( + name_startswith(resource.name, ('.')) + and + resource.type == 'file' + ) + + +class ConfigMacro(Categorizer): + order = 10 + analysis_priority = '3' + file_category = 'config' + file_subcategory = 'macro' + rule_applied = 'ConfigMacro' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.m4']) + ) + + +class ConfigPython(Categorizer): + order = 0 + analysis_priority = '3' + file_category = 'config' + file_subcategory = 'Python' + rule_applied = 'ConfigPython' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['__init__.py']) + and + resource.type == 'file' + ) + + +class ConfigTemplate(Categorizer): + order = 10 + analysis_priority = '3' + file_category = 'config' + file_subcategory = 'template' + rule_applied = 'ConfigTemplate' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.tmpl']) + ) + + +class ConfigVisualCpp(Categorizer): + order = 10 + analysis_priority = '3' + file_category = 'config' + file_subcategory = 'Visual-CPP' + category_notes = 'vcproj is older version' + rule_applied = 'ConfigVisualCpp' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.vcxproj', '.vcproj']) + ) + + +class ConfigXcode(Categorizer): + order = 0 + analysis_priority = '3' + file_category = 'config' + file_subcategory = 'xcode' + rule_applied = 'ConfigXcode' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".bat", ".cfg", ".conf", ".config", ".sh", ".yaml", ".yml"] + name_in(resource.name, ['info.plist']) + and + resource.type == 'file' ) -class config_xml(Categorizer): - order = 1 +class ConfigXml(Categorizer): + order = 10 analysis_priority = '3' file_category = 'config' file_subcategory = 'xml' + rule_applied = 'ConfigXml' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".dtd", ".xml", ".xsd", ".xsl", ".xslt"] + extension_in(resource.extension, ['.dtd', '.xml', '.xsd', '.xsl', '.xslt']) ) -class data_json(Categorizer): - order = 1 +class DataJson(Categorizer): + order = 10 analysis_priority = '3' file_category = 'data' file_subcategory = 'json' + rule_applied = 'DataJson' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".json"] + extension_in(resource.extension, ['.json']) ) -class doc_general(Categorizer): - order = 1 +class DataProtoBuf(Categorizer): + order = 10 + analysis_priority = '2' + file_category = 'data' + file_subcategory = 'ProtoBuf' + rule_applied = 'DataProtoBuf' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.proto']) + ) + + +class Directory(Categorizer): + order = 10 + analysis_priority = '4' + file_category = 'directory' + file_subcategory = '' + rule_applied = 'Directory' + + @classmethod + def categorize(cls, resource): + return ( + resource.type == 'directory' + ) + + +class DocGeneral(Categorizer): + order = 10 analysis_priority = '3' file_category = 'doc' file_subcategory = 'general' + rule_applied = 'DocGeneral' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, [ + '.csv', '.doc', '.docx', '.man', '.md', '.odp', '.ods', '.odt', '.pdf', '.ppt', '.pptx', + '.rtf', '.tex', '.txt', '.xls', '.xlsm', '.xlsx' + ]) + or + ( + name_in(resource.name, ['changelog', 'changes']) + and + resource.type == 'file' + ) + ) + + +class DocLicense(Categorizer): + order = 0 + analysis_priority = '3' + file_category = 'doc' + file_subcategory = 'license' + rule_applied = 'DocLicense' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".csv", ".doc", ".docx", ".md", ".odp", ".ods", ".odt", ".pdf", ".ppt", ".pptx", ".rtf", ".tex", ".txt", ".xls", ".xlsm", ".xlsx"] + name_substring(resource.name, ['copying', 'copyright', 'license', 'notice']) + and + resource.type == 'file' ) -class doc_readme(Categorizer): +class DocReadme(Categorizer): order = 0 analysis_priority = '3' file_category = 'doc' file_subcategory = 'readme' + rule_applied = 'DocReadme' @classmethod def categorize(cls, resource): return ( - "readme" in resource.name.lower() + name_substring(resource.name, ['readme']) + and + resource.type == 'file' ) -class font(Categorizer): - order = 1 +class Font(Categorizer): + order = 10 analysis_priority = '1' file_category = 'font' + rule_applied = 'Font' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.fnt', '.otf', '.ttf', '.woff', '.woff2', '.eot']) + ) + + +class ManifestBower(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'Bower' + rule_applied = 'ManifestBower' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['bower.json']) + and + resource.type == 'file' + ) + + +class ManifestCargo(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'Cargo' + category_notes = 'For Rust, Not sure about Cargo.toml ?' + rule_applied = 'ManifestCargo' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['cargo.toml', 'cargo.lock']) + and + resource.type == 'file' + ) + + +class ManifestCocoaPod(Categorizer): + order = 10 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'CocoaPod' + rule_applied = 'ManifestCocoaPod' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.podspec']) + ) + + +class ManifestComposer(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'Composer' + category_notes = 'For PHP' + rule_applied = 'ManifestComposer' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['composer.json', 'composer.lock']) + and + resource.type == 'file' + ) + + +class ManifestGolang(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'Golang' + rule_applied = 'ManifestGolang' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['go.mod', 'go.sum']) + and + resource.type == 'file' + ) + + +class ManifestGradle(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'Gradle' + rule_applied = 'ManifestGradle' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['build.gradle']) + and + resource.type == 'file' + ) + + +class ManifestHaxe(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'Haxe' + rule_applied = 'ManifestHaxe' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['haxelib.json']) + and + resource.type == 'file' + ) + + +class ManifestIvy(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'Ivy' + rule_applied = 'ManifestIvy' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['ivy.xml']) + and + resource.type == 'file' + ) + + +class ManifestMaven(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'maven' + rule_applied = 'ManifestMaven' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".fnt", ".otf", ".ttf", ".woff", ".woff2", ".eot"] + name_in(resource.name, ['pom.xml']) + and + resource.type == 'file' ) -class manifest_npm(Categorizer): +class ManifestNpm(Categorizer): order = 0 analysis_priority = '1' file_category = 'manifest' file_subcategory = 'npm' + rule_applied = 'ManifestNpm' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['package.json', 'package-lock.json', 'yarn.lock']) + and + resource.type == 'file' + ) + + +class ManifestNuGet(Categorizer): + order = 10 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'NuGet' + rule_applied = 'ManifestNuGet' @classmethod def categorize(cls, resource): return ( - resource.name.lower() in ["package.json", "package-lock.json"] + extension_in(resource.extension, ['.nuspec']) ) -class media_audio(Categorizer): - order = 1 +class ManifestPyPi(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'PyPi' + rule_applied = 'ManifestPyPi' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['requirements.txt']) + and + resource.type == 'file' + ) + + +class ManifestRubyGem(Categorizer): + order = 0 + analysis_priority = '1' + file_category = 'manifest' + file_subcategory = 'RubyGem' + rule_applied = 'ManifestRubyGem' + + @classmethod + def categorize(cls, resource): + return ( + name_in(resource.name, ['gemfile', 'gemfile.lock']) + and + resource.type == 'file' + ) + + +class MediaAudio(Categorizer): + order = 10 analysis_priority = '3' file_category = 'media' file_subcategory = 'audio' + rule_applied = 'MediaAudio' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".mp3", ".mpa", ".ogg", ".wav", ".wma"] + extension_in(resource.extension, [ + '.3pg', '.aac', '.amr', '.awb', '.m4a', '.mp3', + '.mpa', '.ogg', '.opus', '.wav', '.wma' + ]) ) -class media_image(Categorizer): - order = 1 +class MediaImage(Categorizer): + order = 10 analysis_priority = '3' file_category = 'media' file_subcategory = 'image' + rule_applied = 'MediaImage' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".bmp", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".svg"] + extension_in(resource.extension, [ + '.bmp', '.gif', '.ico', '.jpg', '.jpeg', '.png', '.svg', '.webp' + ]) ) -class media_video(Categorizer): - order = 1 +class MediaVideo(Categorizer): + order = 10 analysis_priority = '3' file_category = 'media' file_subcategory = 'video' + rule_applied = 'MediaVideo' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".avi", ".h264", ".mp4", ".mpg", ".mpeg", ".swf", ".wmv"] + extension_in(resource.extension, [ + '.avi', '.h264', '.mp4', '.mpg', '.mpeg', '.swf', '.wmv' + ]) ) -class script_bash(Categorizer): - order = 1 +class ScriptBash(Categorizer): + order = 10 analysis_priority = '3' file_category = 'script' file_subcategory = 'bash' + rule_applied = 'ScriptBash' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".bash"] + extension_in(resource.extension, ['.bash']) ) -class script_build(Categorizer): - order = 1 +class ScriptBatSh(Categorizer): + order = 10 analysis_priority = '3' - file_category = 'build' - file_subcategory = 'script' + file_category = 'script' + file_subcategory = 'bat_sh' + rule_applied = 'ScriptBatSh' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".cmake", ".cmakelist"] + extension_in(resource.extension, ['.bat', '.sh']) ) -class script_perl(Categorizer): - order = 1 +class ScriptBuild(Categorizer): + order = 10 analysis_priority = '3' file_category = 'script' - file_subcategory = 'perl' - notes = 'We will treat all Perl as script' + file_subcategory = 'build' + rule_applied = 'ScriptBuild' @classmethod def categorize(cls, resource): return ( - resource.mime_type in ["text/x-perl"] + extension_in(resource.extension, ['.cmake', '.cmakelist']) + or + resource.mime_type in ['text/x-makefile'] ) -class script_data(Categorizer): - order = 1 +class ScriptData(Categorizer): + order = 10 analysis_priority = '3' - file_category = 'data' - file_subcategory = 'script' + file_category = 'script' + file_subcategory = 'data' + rule_applied = 'ScriptData' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.sql', '.psql']) + ) + + +class SourceAssembler(Categorizer): + order = 10 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'assembler' + category_notes = 'upper case only' + rule_applied = 'SourceAssembler' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".sql", ".psql"] + extension_uppercase_in(resource.extension, ['.S']) ) -class source_c(Categorizer): - order = 1 +class SourceC(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'c' + rule_applied = 'SourceC' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".c"] + extension_in(resource.extension, ['.c']) or ( - resource.extension.lower() in [".h"] + extension_in(resource.extension, ['.h']) and - resource.mime_type in ["text/x-c"] + resource.mime_type in ['text/x-c'] ) ) -class source_cpp(Categorizer): - order = 1 +class SourceCoffeeScript(Categorizer): + order = 10 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'CoffeeScript' + category_notes = 'transcompiles to JS' + rule_applied = 'SourceCoffeeScript' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.coffee']) + ) + + +class SourceCpp(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'c++' + rule_applied = 'SourceCpp' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".cpp", ".hpp", ".cc"] + extension_in(resource.extension, ['.cpp', '.hpp', '.cc']) or ( - resource.extension.lower() in [".h"] + extension_in(resource.extension, ['.h']) and - resource.mime_type in ["text/x-c++"] + resource.mime_type in ['text/x-c++'] ) ) -class source_csharp(Categorizer): - order = 1 +class SourceCsharp(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'c#' + rule_applied = 'SourceCsharp' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".cs"] + extension_in(resource.extension, ['.cs']) ) -class source_go(Categorizer): - order = 1 +class SourceGo(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'go' + rule_applied = 'SourceGo' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".go"] + extension_in(resource.extension, ['.go']) ) -class source_haskell(Categorizer): - order = 1 +class SourceHaskell(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'haskell' + rule_applied = 'SourceHaskell' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".hs", ".lhs"] + extension_in(resource.extension, ['.hs', '.lhs']) ) -class source_java(Categorizer): - order = 1 +class SourceJava(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'java' + rule_applied = 'SourceJava' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".java"] + extension_in(resource.extension, ['.java']) ) -class source_javascript(Categorizer): - order = 1 +class SourceJavascript(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'javascript' + rule_applied = 'SourceJavascript' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".js"] + extension_in(resource.extension, ['.js']) ) -class source_javaserverpage(Categorizer): - order = 1 +class SourceJavaserverpage(Categorizer): + order = 10 analysis_priority = '2' file_category = 'source' file_subcategory = 'javaserverpage' + rule_applied = 'SourceJavaserverpage' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".jsp"] + extension_in(resource.extension, ['.jsp']) ) -class source_kotlin(Categorizer): - order = 1 +class SourceKotlin(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'kotlin' + rule_applied = 'SourceKotlin' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".kt"] + extension_in(resource.extension, ['.kt']) ) -class source_objectivec(Categorizer): - order = 1 +class SourceObjectivec(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'objectivec' + rule_applied = 'SourceObjectivec' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".m", ".mm"] + extension_in(resource.extension, ['.m', '.mm']) or ( - resource.extension.lower() in [".h"] + extension_in(resource.extension, ['.h']) and - resource.mime_type in ["text/x-objective-c"] + resource.mime_type in ['text/x-objective-c'] ) ) -class source_php(Categorizer): - order = 1 +class SourcePerl(Categorizer): + order = 10 + analysis_priority = '1' + file_category = 'source' + file_subcategory = 'perl' + rule_applied = 'SourcePerl' + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.pl', '.pm']) + ) + + +class SourcePhp(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'php' + rule_applied = 'SourcePhp' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".php", ".php3", ".php4", ".php5"] + extension_in(resource.extension, ['.php', '.php3', '.php4', '.php5']) ) -class source_python(Categorizer): - order = 1 +class SourcePython(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'python' + rule_applied = 'SourcePython' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".py"] + extension_in(resource.extension, ['.py']) ) -class source_ruby(Categorizer): - order = 1 +class SourceRuby(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'ruby' + rule_applied = 'SourceRuby' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".rb", ".rake"] + extension_in(resource.extension, ['.rb', '.rake']) ) -class source_rust(Categorizer): - order = 1 +class SourceRust(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'rust' + rule_applied = 'SourceRust' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".rs"] + extension_in(resource.extension, ['.rs']) ) -class source_scala(Categorizer): - order = 1 +class SourceScala(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'scala' + rule_applied = 'SourceScala' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".scala"] + extension_in(resource.extension, ['.scala']) ) -class source_swift(Categorizer): - order = 1 +class SourceSwift(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'swift' + rule_applied = 'SourceSwift' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".swift"] + extension_in(resource.extension, ['.swift']) ) -class source_typescript(Categorizer): - order = 1 +class SourceTypescript(Categorizer): + order = 10 analysis_priority = '1' file_category = 'source' file_subcategory = 'typescript' - notes = '.ts extension is not definitive' + category_notes = '.ts extension is not definitive' + rule_applied = 'SourceTypescript' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".ts"] + extension_in(resource.extension, ['.ts']) and - resource.programming_language in ["TypeScript"] + resource.programming_language in ['TypeScript'] ) -class web_css(Categorizer): - order = 1 +class WebCss(Categorizer): + order = 10 analysis_priority = '1' file_category = 'web' file_subcategory = 'css' + rule_applied = 'WebCss' @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".css"] + extension_in(resource.extension, ['.css', '.less', '.scss']) ) -class web_html(Categorizer): - order = 1 +class WebHtml(Categorizer): + order = 10 analysis_priority = '2' file_category = 'web' file_subcategory = 'html' + rule_applied = "WebHtml" + + @classmethod + def categorize(cls, resource): + return ( + extension_in(resource.extension, ['.htm', '.html']) + ) + + +class WebRuby(Categorizer): + order = 10 + analysis_priority = '2' + file_category = 'web' + file_subcategory = 'Ruby' + rule_applied = "WebRuby" @classmethod def categorize(cls, resource): return ( - resource.extension.lower() in [".htm", ".html"] + extension_in(resource.extension, ['.erb']) ) categories = [ - archive_debian, - archive_general, - archive_rpm, - binary_ar, - binary_elf_exec, - binary_elf_ko, - binary_elf_o, - binary_elf_so, - binary_java, - binary_windows, - config_general, - config_xml, - data_json, - doc_general, - doc_readme, - font, - manifest_npm, - media_audio, - media_image, - media_video, - script_bash, - script_build, - script_data, - script_perl, - source_c, - source_cpp, - source_csharp, - source_go, - source_haskell, - source_java, - source_javascript, - source_javaserverpage, - source_kotlin, - source_objectivec, - source_php, - source_python, - source_ruby, - source_rust, - source_scala, - source_swift, - source_typescript, - web_css, - web_html + ArchiveAndroid, + ArchiveDebian, + ArchiveGeneral, + ArchiveIos, + ArchiveRpm, + BinaryAr, + BinaryElfExec, + BinaryElfKo, + BinaryElfO, + BinaryElfSo, + BinaryJava, + BinaryPython, + BinaryWindows, + BuildBazel, + BuildBuck, + BuildDocker, + BuildMake, + BuildQt, + Certificate, + ConfigGeneral, + ConfigInitialPeriod, + ConfigMacro, + ConfigPython, + ConfigTemplate, + ConfigVisualCpp, + ConfigXcode, + ConfigXml, + DataJson, + DataProtoBuf, + Directory, + DocGeneral, + DocLicense, + DocReadme, + Font, + ManifestBower, + ManifestCargo, + ManifestCocoaPod, + ManifestComposer, + ManifestGolang, + ManifestGradle, + ManifestHaxe, + ManifestIvy, + ManifestMaven, + ManifestNpm, + ManifestNuGet, + ManifestPyPi, + ManifestRubyGem, + MediaAudio, + MediaImage, + MediaVideo, + ScriptBash, + ScriptBatSh, + ScriptBuild, + ScriptData, + SourceAssembler, + SourceC, + SourceCoffeeScript, + SourceCpp, + SourceCsharp, + SourceGo, + SourceHaskell, + SourceJava, + SourceJavascript, + SourceJavaserverpage, + SourceKotlin, + SourceObjectivec, + SourcePerl, + SourcePhp, + SourcePython, + SourceRuby, + SourceRust, + SourceScala, + SourceSwift, + SourceTypescript, + WebCss, + WebHtml, + WebRuby ] @@ -766,6 +1382,31 @@ def categorize_resource(resource): """ Return a Categorizer for this ``resource`` Resource object, or None. """ - for category in sorted(categories, key=category_key): + sorted_categories = sorted(categories, key=category_key) + for category in sorted_categories: if category.categorize(resource): return category + + +def extension_in(ext, exts): + return str(ext).lower() in exts + + +def extension_uppercase_in(ext, exts): + return ext in exts + + +def extension_startswith(ext, exts): + return str(ext).lower().startswith(exts) + + +def name_in(name, names): + return str(name).lower() in names + + +def name_substring(name, names): + return any(string in str(name).lower() for string in names) + + +def name_startswith(name, names): + return str(name).lower().startswith(names) From 8b9c35791bd890124ab4804e21a83fe5d382a1fa Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Fri, 13 May 2022 15:07:00 -0700 Subject: [PATCH 03/13] Add initial (failing) test #2945 Signed-off-by: John M. Horan --- tests/summarycode/test_file_cat.py | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/summarycode/test_file_cat.py diff --git a/tests/summarycode/test_file_cat.py b/tests/summarycode/test_file_cat.py new file mode 100644 index 00000000000..6bc307e51c1 --- /dev/null +++ b/tests/summarycode/test_file_cat.py @@ -0,0 +1,54 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/scancode-toolkit for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import io +import os + +from commoncode.testcase import FileBasedTesting +from commoncode.testcase import FileDrivenTesting +from commoncode.resource import Resource + +from scancode.cli_test_utils import run_scan_click +from scancode.cli_test_utils import check_json_scan +from scancode_config import REGEN_TEST_FIXTURES +from summarycode import file_cat + + +class TestFileCat(FileBasedTesting): + + test_data_dir = os.path.join(os.path.dirname(__file__), 'data') + + def test_ArchiveAndroid(self): + test_resource = Resource( + path='', + is_file='file', + name='baloney.apk', + mime_type='', + file_type='', + programming_language='' + ) + assert file_cat.ArchiveAndroid.categorize(test_resource) + +# Original test from SPATS file-cat: + +# class TestFileCat(FileBasedTesting): + +# test_data_dir = os.path.join(os.path.dirname(__file__), 'data') + +# def test_ArchiveAndroid(self): +# test_resource = Resource( +# path='', +# type='file', +# name='', +# extension='.apk', +# mime_type='', +# file_type='', +# programming_language='' +# ) +# assert file_cat_rules.ArchiveAndroid.categorize(test_resource) From dd93bd7d0a2fb87ccdee31047bb42dc0227d7652 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Mon, 16 May 2022 17:59:22 -0700 Subject: [PATCH 04/13] Save test code comments #2945 Signed-off-by: John M. Horan --- tests/summarycode/test_file_cat.py | 63 ++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/tests/summarycode/test_file_cat.py b/tests/summarycode/test_file_cat.py index 6bc307e51c1..8b140e772ba 100644 --- a/tests/summarycode/test_file_cat.py +++ b/tests/summarycode/test_file_cat.py @@ -10,6 +10,8 @@ import io import os +import attr + from commoncode.testcase import FileBasedTesting from commoncode.testcase import FileDrivenTesting from commoncode.resource import Resource @@ -17,23 +19,78 @@ from scancode.cli_test_utils import run_scan_click from scancode.cli_test_utils import check_json_scan from scancode_config import REGEN_TEST_FIXTURES +from scancode.plugin_info import InfoScanner from summarycode import file_cat +resource_class = attr.make_class( + name='TestResource', + attrs=InfoScanner.resource_attributes, + # attrs={ + # "mime_type": attr.ib(default=None, repr=False), + # "file_type": attr.ib(default=None, repr=False), + # "programming_language": attr.ib(default=None, repr=False) + # }, + slots=True, + bases=(Resource,) + ) + + class TestFileCat(FileBasedTesting): test_data_dir = os.path.join(os.path.dirname(__file__), 'data') def test_ArchiveAndroid(self): - test_resource = Resource( + # def test_ArchiveAndroid(self, resource_class): + test_resource_01 = resource_class( + name='baloney.apk', + location='', path='', + rid='', + pid='', is_file='file', - name='baloney.apk', mime_type='', file_type='', programming_language='' ) - assert file_cat.ArchiveAndroid.categorize(test_resource) + assert file_cat.ArchiveAndroid.categorize(test_resource_01) + + + # Test with commented-out efforts: + + # def test_ArchiveAndroid(self): + # # def test_ArchiveAndroid(self, test_resource_class): + # test_resource_class = attr.make_class( + # name='TestResource', + # # attrs=self.resource_attributes or {}, + # # attrs=Codebase.resource_attributes or {}, + # # attrs=Codebase.resource_attributes, + # # attrs=Codebase._populate(self), + # attrs={ + # "mime_type": attr.ib(default=None, repr=False), + # "file_type": attr.ib(default=None, repr=False), + # "programming_language": attr.ib(default=None, repr=False) + # }, + # slots=True, + # # frozen=True, + # bases=(Resource,) + # ) + + # test_resource = test_resource_class( + # # test_resource = self.test_resource_class( + # name='baloney.apk', + # location='', + # path='', + # rid='', + # pid='', + # is_file='file', + # mime_type='', + # file_type='', + # programming_language='' + # ) + # assert file_cat.ArchiveAndroid.categorize(test_resource) + + # Original test from SPATS file-cat: From 7068b2517487f0225a40e65ae5f56a3ce8636baa Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Tue, 17 May 2022 09:39:12 -0700 Subject: [PATCH 05/13] Create structure for testing file-cat plugin rules #2945 Signed-off-by: John M. Horan --- tests/summarycode/test_file_cat.py | 113 ++++++++++++++--------------- 1 file changed, 55 insertions(+), 58 deletions(-) diff --git a/tests/summarycode/test_file_cat.py b/tests/summarycode/test_file_cat.py index 8b140e772ba..2c601390eea 100644 --- a/tests/summarycode/test_file_cat.py +++ b/tests/summarycode/test_file_cat.py @@ -26,11 +26,6 @@ resource_class = attr.make_class( name='TestResource', attrs=InfoScanner.resource_attributes, - # attrs={ - # "mime_type": attr.ib(default=None, repr=False), - # "file_type": attr.ib(default=None, repr=False), - # "programming_language": attr.ib(default=None, repr=False) - # }, slots=True, bases=(Resource,) ) @@ -41,9 +36,8 @@ class TestFileCat(FileBasedTesting): test_data_dir = os.path.join(os.path.dirname(__file__), 'data') def test_ArchiveAndroid(self): - # def test_ArchiveAndroid(self, resource_class): test_resource_01 = resource_class( - name='baloney.apk', + name='foo.apk', location='', path='', rid='', @@ -54,58 +48,61 @@ def test_ArchiveAndroid(self): programming_language='' ) assert file_cat.ArchiveAndroid.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == 'archive' + test_resource_02 = resource_class( + name='foo.aar', + location='', + path='', + rid='', + pid='', + is_file='file', + mime_type='', + file_type='', + programming_language='' + ) + assert file_cat.ArchiveAndroid.categorize(test_resource_02) + assert file_cat.categorize_resource(test_resource_02).file_category == 'archive' - # Test with commented-out efforts: - - # def test_ArchiveAndroid(self): - # # def test_ArchiveAndroid(self, test_resource_class): - # test_resource_class = attr.make_class( - # name='TestResource', - # # attrs=self.resource_attributes or {}, - # # attrs=Codebase.resource_attributes or {}, - # # attrs=Codebase.resource_attributes, - # # attrs=Codebase._populate(self), - # attrs={ - # "mime_type": attr.ib(default=None, repr=False), - # "file_type": attr.ib(default=None, repr=False), - # "programming_language": attr.ib(default=None, repr=False) - # }, - # slots=True, - # # frozen=True, - # bases=(Resource,) - # ) - - # test_resource = test_resource_class( - # # test_resource = self.test_resource_class( - # name='baloney.apk', - # location='', - # path='', - # rid='', - # pid='', - # is_file='file', - # mime_type='', - # file_type='', - # programming_language='' - # ) - # assert file_cat.ArchiveAndroid.categorize(test_resource) - - - -# Original test from SPATS file-cat: - -# class TestFileCat(FileBasedTesting): + def test_ArchiveDebian(self): + test_resource_01 = resource_class( + name='foo.deb', + location='', + path='', + rid='', + pid='', + is_file='file', + mime_type='', + file_type='', + programming_language='' + ) + assert file_cat.ArchiveDebian.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == 'archive' -# test_data_dir = os.path.join(os.path.dirname(__file__), 'data') + test_resource_02 = resource_class( + name='foo.2', + location='', + path='', + rid='', + pid='', + is_file='file', + mime_type='', + file_type='', + programming_language='' + ) + assert not file_cat.ArchiveDebian.categorize(test_resource_02) -# def test_ArchiveAndroid(self): -# test_resource = Resource( -# path='', -# type='file', -# name='', -# extension='.apk', -# mime_type='', -# file_type='', -# programming_language='' -# ) -# assert file_cat_rules.ArchiveAndroid.categorize(test_resource) + def test_ArchiveGeneral(self): + test_resource_01 = resource_class( + name='foo.7zip', + location='', + path='', + rid='', + pid='', + is_file='file', + mime_type='', + file_type='', + programming_language='' + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == 'archive' From d5271c7dee174a377cdf45b01e1397ebdee0d038 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Tue, 17 May 2022 10:26:35 -0700 Subject: [PATCH 06/13] Update help test file to reflect file-cat plugin #2945 Signed-off-by: John M. Horan --- tests/scancode/data/help/help.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/scancode/data/help/help.txt b/tests/scancode/data/help/help.txt index 3076354074c..46b0b53683a 100644 --- a/tests/scancode/data/help/help.txt +++ b/tests/scancode/data/help/help.txt @@ -94,6 +94,7 @@ Options: post-scan: --classify Classify files with flags telling if the file is a legal, or readme or test file, etc. + --file-cat Categorize files. --consolidate Group resources by Packages or license and copyright holder and return those groupings as a list of consolidated packages and a list of consolidated From e762679cb8525c4ccab199621c39bedeb13b959b Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Wed, 18 May 2022 18:48:22 -0700 Subject: [PATCH 07/13] Add next group of file-cat rule tests #2945 Signed-off-by: John M. Horan --- tests/summarycode/test_file_cat.py | 2103 +++++++++++++++++++++++++++- 1 file changed, 2048 insertions(+), 55 deletions(-) diff --git a/tests/summarycode/test_file_cat.py b/tests/summarycode/test_file_cat.py index 2c601390eea..01fd8ce6b77 100644 --- a/tests/summarycode/test_file_cat.py +++ b/tests/summarycode/test_file_cat.py @@ -24,85 +24,2078 @@ resource_class = attr.make_class( - name='TestResource', - attrs=InfoScanner.resource_attributes, - slots=True, - bases=(Resource,) - ) + name="TestResource", + attrs=InfoScanner.resource_attributes, + slots=True, + bases=(Resource,), +) class TestFileCat(FileBasedTesting): - test_data_dir = os.path.join(os.path.dirname(__file__), 'data') + test_data_dir = os.path.join(os.path.dirname(__file__), "data") def test_ArchiveAndroid(self): test_resource_01 = resource_class( - name='foo.apk', - location='', - path='', - rid='', - pid='', - is_file='file', - mime_type='', - file_type='', - programming_language='' + name="foo.apk", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", ) assert file_cat.ArchiveAndroid.categorize(test_resource_01) - assert file_cat.categorize_resource(test_resource_01).file_category == 'archive' + assert file_cat.categorize_resource(test_resource_01).file_category == "archive" test_resource_02 = resource_class( - name='foo.aar', - location='', - path='', - rid='', - pid='', - is_file='file', - mime_type='', - file_type='', - programming_language='' + name="foo.aar", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", ) assert file_cat.ArchiveAndroid.categorize(test_resource_02) - assert file_cat.categorize_resource(test_resource_02).file_category == 'archive' + assert file_cat.categorize_resource(test_resource_02).file_category == "archive" def test_ArchiveDebian(self): test_resource_01 = resource_class( - name='foo.deb', - location='', - path='', - rid='', - pid='', - is_file='file', - mime_type='', - file_type='', - programming_language='' + name="foo.deb", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", ) assert file_cat.ArchiveDebian.categorize(test_resource_01) - assert file_cat.categorize_resource(test_resource_01).file_category == 'archive' + assert file_cat.categorize_resource(test_resource_01).file_category == "archive" test_resource_02 = resource_class( - name='foo.2', - location='', - path='', - rid='', - pid='', - is_file='file', - mime_type='', - file_type='', - programming_language='' + name="foo.2", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", ) assert not file_cat.ArchiveDebian.categorize(test_resource_02) def test_ArchiveGeneral(self): test_resource_01 = resource_class( - name='foo.7zip', - location='', - path='', - rid='', - pid='', - is_file='file', - mime_type='', - file_type='', - programming_language='' + name="foo.7zip", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", ) assert file_cat.ArchiveGeneral.categorize(test_resource_01) - assert file_cat.categorize_resource(test_resource_01).file_category == 'archive' + assert file_cat.categorize_resource(test_resource_01).file_category == "archive" + + test_resource_02 = resource_class( + name="foo.bz", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.bz2", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.bzip", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.gz", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_05) + + test_resource_06 = resource_class( + name="foo.gzi", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_06) + + test_resource_07 = resource_class( + name="foo.tar", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_07) + + test_resource_08 = resource_class( + name="foo.tgz", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_08) + + test_resource_09 = resource_class( + name="foo.xz", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_09) + + test_resource_10 = resource_class( + name="foo.zip", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveGeneral.categorize(test_resource_10) + + def test_ArchiveIos(self): + test_resource_01 = resource_class( + name="foo.ipa", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveIos.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "archive" + + def test_ArchiveRpm(self): + test_resource_01 = resource_class( + name="foo.rpm", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ArchiveRpm.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "archive" + + def test_BinaryAr(self): + test_resource_01 = resource_class( + name="", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="application/x-archive", + file_type="", + programming_language="", + ) + assert file_cat.BinaryAr.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "binary" + + def test_BinaryElfExec(self): + test_resource_01 = resource_class( + name="", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="application/x-executable", + file_type="", + programming_language="", + ) + assert file_cat.BinaryElfExec.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "binary" + + def test_BinaryElfKo(self): + test_resource_01 = resource_class( + name="foo.ko", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="application/x-object", + file_type="", + programming_language="", + ) + assert file_cat.BinaryElfKo.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "binary" + + def test_BinaryElfO(self): + test_resource_01 = resource_class( + name="", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="application/x-object", + file_type="", + programming_language="", + ) + assert file_cat.BinaryElfO.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "binary" + + def test_BinaryElfSo(self): + test_resource_01 = resource_class( + name="", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="application/x-sharedlib", + file_type="", + programming_language="", + ) + assert file_cat.BinaryElfSo.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "binary" + + def test_BinaryJava(self): + test_resource_01 = resource_class( + name="foo.class", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BinaryJava.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "binary" + + test_resource_02 = resource_class( + name="foo.jar", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BinaryJava.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.ear", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BinaryJava.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.sar", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BinaryJava.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.war", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BinaryJava.categorize(test_resource_05) + + def test_BinaryPython(self): + test_resource_01 = resource_class( + name="foo.pyc", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BinaryPython.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "binary" + + test_resource_02 = resource_class( + name="foo.pyo", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BinaryPython.categorize(test_resource_02) + + def test_BinaryWindows(self): + test_resource_01 = resource_class( + name="", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="application/x-dosexec", + file_type="", + programming_language="", + ) + assert file_cat.BinaryWindows.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "binary" + assert ( + file_cat.categorize_resource(test_resource_01).category_notes + == "For DLL and EXE binaries in Windows" + ) + + def test_BuildBazel(self): + test_resource_01 = resource_class( + name="foo.bzl", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildBazel.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "build" + + test_resource_02 = resource_class( + name="build.bazel", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildBazel.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.bazel", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert not file_cat.BuildBazel.categorize(test_resource_03) + + def test_BuildBuck(self): + test_resource_01 = resource_class( + name="buck", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildBuck.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "build" + + test_resource_02 = resource_class( + name="Buck", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildBuck.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="Buck.c", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert not file_cat.BuildBuck.categorize(test_resource_03) + + def test_BuildDocker(self): + test_resource_01 = resource_class( + name="dockerfile", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildDocker.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "build" + + test_resource_02 = resource_class( + name="dockerfile.c", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildDocker.categorize(test_resource_02) + + def test_BuildMake(self): + test_resource_01 = resource_class( + name="makefile", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildMake.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "build" + + test_resource_02 = resource_class( + name="makefile.c", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildMake.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.mk", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildMake.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.make", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildMake.categorize(test_resource_04) + + def test_BuildQt(self): + test_resource_01 = resource_class( + name="foo.pri", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildQt.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "build" + + test_resource_02 = resource_class( + name="foo.pro", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.BuildQt.categorize(test_resource_02) + + def test_Certificate(self): + test_resource_01 = resource_class( + name="foo.crt", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Certificate.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category + == "certificate" + ) + + test_resource_02 = resource_class( + name="foo.der", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Certificate.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.pem", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Certificate.categorize(test_resource_03) + + def test_ConfigGeneral(self): + test_resource_01 = resource_class( + name="foo.cfg", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigGeneral.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "config" + + test_resource_02 = resource_class( + name="foo.conf", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigGeneral.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.config", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigGeneral.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.jxs", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigGeneral.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.properties", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigGeneral.categorize(test_resource_05) + + test_resource_06 = resource_class( + name="foo.yaml", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigGeneral.categorize(test_resource_06) + + test_resource_07 = resource_class( + name="foo.yml", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigGeneral.categorize(test_resource_07) + + def test_ConfigInitialPeriod(self): + test_resource_01 = resource_class( + name=".c", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigInitialPeriod.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "config" + + def test_ConfigMacro(self): + test_resource_01 = resource_class( + name="foo.m4", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigMacro.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "config" + + def test_ConfigPython(self): + test_resource_01 = resource_class( + name="__init__.py", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigPython.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "config" + + def test_ConfigTemplate(self): + test_resource_01 = resource_class( + name="foo.tmpl", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigTemplate.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "config" + + def test_ConfigVisualCpp(self): + test_resource_01 = resource_class( + name="foo.vcxproj", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigVisualCpp.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "config" + + test_resource_02 = resource_class( + name="foo.vcproj", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigVisualCpp.categorize(test_resource_02) + + def test_ConfigXcode(self): + test_resource_01 = resource_class( + name="info.plist", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigXcode.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "config" + + def test_ConfigXml(self): + test_resource_01 = resource_class( + name="foo.dtd", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigXml.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "config" + + test_resource_02 = resource_class( + name="foo.xml", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigXml.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.xsd", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigXml.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.xsl", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigXml.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.xslt", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ConfigXml.categorize(test_resource_05) + + def test_DataJson(self): + test_resource_01 = resource_class( + name="foo.json", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DataJson.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "data" + + def test_DataProtoBuf(self): + test_resource_01 = resource_class( + name="foo.proto", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DataProtoBuf.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "data" + + def test_Directory(self): + test_resource_01 = resource_class( + name="foo", + location="", + path="", + rid="", + pid="", + is_file=False, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Directory.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "directory" + ) + assert file_cat.categorize_resource(test_resource_01).analysis_priority == "4" + + test_resource_02 = resource_class( + name=".foo", + location="", + path="", + rid="", + pid="", + is_file=False, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Directory.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert not file_cat.Directory.categorize(test_resource_03) + + def test_DocGeneral(self): + test_resource_01 = resource_class( + name="foo.csv", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "doc" + + test_resource_02 = resource_class( + name="foo.doc", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.docx", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.man", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.md", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_05) + + test_resource_06 = resource_class( + name="foo.odp", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_06) + + test_resource_07 = resource_class( + name="foo.ods", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_07) + + test_resource_08 = resource_class( + name="foo.odt", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_08) + + test_resource_09 = resource_class( + name="foo.pdf", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_09) + + test_resource_10 = resource_class( + name="foo.ppt", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_10) + + test_resource_11 = resource_class( + name="foo.pptx", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_11) + + test_resource_12 = resource_class( + name="foo.rtf", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_12) + + test_resource_13 = resource_class( + name="foo.tex", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_13) + + test_resource_14 = resource_class( + name="foo.txt", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_14) + + test_resource_15 = resource_class( + name="foo.xls", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_15) + + test_resource_16 = resource_class( + name="foo.xlsm", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_16) + + test_resource_17 = resource_class( + name="foo.xlsx", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_17) + + test_resource_18 = resource_class( + name="changelog", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_18) + + test_resource_19 = resource_class( + name="changes", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocGeneral.categorize(test_resource_19) + + def test_DocLicense(self): + test_resource_01 = resource_class( + name="copying", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocLicense.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "doc" + + test_resource_02 = resource_class( + name="copyright", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocLicense.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="license", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocLicense.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="notice", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocLicense.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="LICENSE.c", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocLicense.categorize(test_resource_05) + + def test_DocReadme(self): + test_resource_01 = resource_class( + name="READmeplease", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocReadme.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "doc" + + test_resource_02 = resource_class( + name="READme.foo", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.DocReadme.categorize(test_resource_02) + + def test_Font(self): + test_resource_01 = resource_class( + name="foo.fnt", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Font.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "font" + + test_resource_02 = resource_class( + name="foo.otf", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Font.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.ttf", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Font.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.woff", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Font.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.woff2", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Font.categorize(test_resource_05) + + test_resource_06 = resource_class( + name="foo.eot", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.Font.categorize(test_resource_06) + + def test_ManifestBower(self): + test_resource_01 = resource_class( + name="bower.json", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestBower.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + def test_ManifestCargo(self): + test_resource_01 = resource_class( + name="cargo.toml", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestCargo.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + test_resource_02 = resource_class( + name="cargo.lock", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestCargo.categorize(test_resource_02) + + def test_ManifestCocoaPod(self): + test_resource_01 = resource_class( + name="foo.podspec", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestCocoaPod.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + def test_ManifestComposer(self): + test_resource_01 = resource_class( + name="composer.json", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestComposer.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + test_resource_02 = resource_class( + name="composer.lock", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestComposer.categorize(test_resource_02) + + def test_ManifestGolang(self): + test_resource_01 = resource_class( + name="go.mod", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestGolang.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + test_resource_02 = resource_class( + name="go.sum", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestGolang.categorize(test_resource_02) + + def test_ManifestGradle(self): + test_resource_01 = resource_class( + name="build.gradle", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestGradle.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + def test_ManifestHaxe(self): + test_resource_01 = resource_class( + name="haxelib.json", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestHaxe.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + def test_ManifestIvy(self): + test_resource_01 = resource_class( + name="ivy.xml", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestIvy.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + def test_ManifestMaven(self): + test_resource_01 = resource_class( + name="pom.xml", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestMaven.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + def test_ManifestNpm(self): + test_resource_01 = resource_class( + name="package.json", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestNpm.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + test_resource_02 = resource_class( + name="package-lock.json", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestNpm.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="yarn.lock", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestNpm.categorize(test_resource_03) + + def test_ManifestNuGet(self): + test_resource_01 = resource_class( + name="foo.nuspec", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestNuGet.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + def test_ManifestPyPi(self): + test_resource_01 = resource_class( + name="requirements.txt", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestPyPi.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + def test_ManifestRubyGem(self): + test_resource_01 = resource_class( + name="gemfile", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestRubyGem.categorize(test_resource_01) + assert ( + file_cat.categorize_resource(test_resource_01).file_category == "manifest" + ) + + test_resource_02 = resource_class( + name="gemfile.lock", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ManifestRubyGem.categorize(test_resource_02) + + def test_MediaAudio(self): + test_resource_01 = resource_class( + name="foo.3pg", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "media" + + test_resource_02 = resource_class( + name="foo.aac", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.amr", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.awb", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.m4a", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_05) + + test_resource_06 = resource_class( + name="foo.mp3", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_06) + + test_resource_07 = resource_class( + name="foo.mpa", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_07) + + test_resource_08 = resource_class( + name="foo.ogg", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_08) + + test_resource_09 = resource_class( + name="foo.opus", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_09) + + test_resource_10 = resource_class( + name="foo.wav", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_10) + + test_resource_11 = resource_class( + name="foo.wma", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaAudio.categorize(test_resource_11) + + def test_MediaImage(self): + test_resource_01 = resource_class( + name="foo.bmp", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaImage.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "media" + + test_resource_02 = resource_class( + name="foo.gif", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaImage.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.ico", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaImage.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.jpg", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaImage.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.jpeg", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaImage.categorize(test_resource_05) + + test_resource_06 = resource_class( + name="foo.png", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaImage.categorize(test_resource_06) + + test_resource_07 = resource_class( + name="foo.svg", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaImage.categorize(test_resource_07) + + test_resource_08 = resource_class( + name="foo.webp", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaImage.categorize(test_resource_08) + + def test_MediaVideo(self): + test_resource_01 = resource_class( + name="foo.avi", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaVideo.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "media" + + test_resource_02 = resource_class( + name="foo.h264", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaVideo.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.mp4", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaVideo.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.mpg", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaVideo.categorize(test_resource_04) + + test_resource_05 = resource_class( + name="foo.mpeg", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaVideo.categorize(test_resource_05) + + test_resource_06 = resource_class( + name="foo.swf", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaVideo.categorize(test_resource_06) + + test_resource_07 = resource_class( + name="foo.wmv", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.MediaVideo.categorize(test_resource_07) + + # ================================== + + def test_WebRuby(self): + test_resource_01 = resource_class( + name="foo.erb", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.WebRuby.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "web" From 260707b01892b5c09c59e6ea8ba9a5b54554cb64 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Thu, 19 May 2022 10:43:35 -0700 Subject: [PATCH 08/13] Add next set of file-cat rule tests #2945 Signed-off-by: John M. Horan --- src/summarycode/file_cat.py | 1120 ++++++++++++---------------- tests/summarycode/test_file_cat.py | 252 +++++++ 2 files changed, 739 insertions(+), 633 deletions(-) diff --git a/src/summarycode/file_cat.py b/src/summarycode/file_cat.py index 26d792c4468..6339be51363 100644 --- a/src/summarycode/file_cat.py +++ b/src/summarycode/file_cat.py @@ -20,6 +20,7 @@ # Tracing flag TRACE = False + def logger_debug(*args): pass @@ -38,15 +39,13 @@ def emit(self, record): except Exception: self.handleError(record) - logger = logging.getLogger(__name__) logger.handlers = [ClickHandler()] logger.propagate = False logger.setLevel(logging.DEBUG) - def logger_debug(*args): - return logger.debug(' '.join(isinstance(a, str) and a or repr(a) for a in args)) + return logger.debug(" ".join(isinstance(a, str) and a or repr(a) for a in args)) @post_scan_impl @@ -54,23 +53,23 @@ class FileCategorizer(PostScanPlugin): """ Categorize a file. """ - resource_attributes = dict([ - ('analysis_priority', - String(help='Analysis priority.')), - ('file_category', - String(help='File category.')), - - ('file_subcategory', - String(help='File subcategory.')), - ]) + resource_attributes = dict( + [ + ("analysis_priority", String(help="Analysis priority.")), + ("file_category", String(help="File category.")), + ("file_subcategory", String(help="File subcategory.")), + ] + ) sort_order = 50 options = [ - PluggableCommandLineOption(('--file-cat',), - is_flag=True, default=False, - help='Categorize files.', + PluggableCommandLineOption( + ("--file-cat",), + is_flag=True, + default=False, + help="Categorize files.", help_group=POST_SCAN_GROUP, sort_order=50, ) @@ -113,1184 +112,1039 @@ def categorize(cls, resource): class ArchiveAndroid(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'archive' - file_subcategory = 'Android' - rule_applied = 'ArchiveAndroid' + analysis_priority = "1" + file_category = "archive" + file_subcategory = "Android" + rule_applied = "ArchiveAndroid" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.apk', '.aar']) - ) + return extension_in(resource.extension, [".apk", ".aar"]) class ArchiveDebian(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'archive' - file_subcategory = 'debian' - category_notes = 'special type of archive' - rule_applied = 'ArchiveDebian' + analysis_priority = "1" + file_category = "archive" + file_subcategory = "debian" + category_notes = "special type of archive" + rule_applied = "ArchiveDebian" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.deb']) - ) + return extension_in(resource.extension, [".deb"]) class ArchiveGeneral(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'archive' - file_subcategory = 'general' - rule_applied = 'ArchiveGeneral' + analysis_priority = "1" + file_category = "archive" + file_subcategory = "general" + rule_applied = "ArchiveGeneral" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, [ - '.7zip', '.bz', '.bz2', '.bzip', '.gz', '.gzi', '.tar', '.tgz', '.xz', '.zip' - ]) + return extension_in( + resource.extension, + [ + ".7zip", + ".bz", + ".bz2", + ".bzip", + ".gz", + ".gzi", + ".tar", + ".tgz", + ".xz", + ".zip", + ], ) class ArchiveIos(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'archive' - file_subcategory = 'iOS' - rule_applied = 'ArchiveIos' + analysis_priority = "1" + file_category = "archive" + file_subcategory = "iOS" + rule_applied = "ArchiveIos" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.ipa']) - ) + return extension_in(resource.extension, [".ipa"]) class ArchiveRpm(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'archive' - file_subcategory = 'rpm' - category_notes = 'special type of archive' - rule_applied = 'ArchiveRpm' + analysis_priority = "1" + file_category = "archive" + file_subcategory = "rpm" + category_notes = "special type of archive" + rule_applied = "ArchiveRpm" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.rpm']) - ) + return extension_in(resource.extension, [".rpm"]) class BinaryAr(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'binary' - file_subcategory = 'ar' - rule_applied = 'BinaryAr' + analysis_priority = "1" + file_category = "binary" + file_subcategory = "ar" + rule_applied = "BinaryAr" @classmethod def categorize(cls, resource): - return ( - resource.mime_type in ['application/x-archive'] - ) + return resource.mime_type in ["application/x-archive"] class BinaryElfExec(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'binary' - file_subcategory = 'elf-exec' - rule_applied = 'BinaryElfExec' + analysis_priority = "1" + file_category = "binary" + file_subcategory = "elf-exec" + rule_applied = "BinaryElfExec" @classmethod def categorize(cls, resource): - return ( - resource.mime_type in ['application/x-executable'] - ) + return resource.mime_type in ["application/x-executable"] class BinaryElfKo(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'binary' - file_subcategory = 'elf-ko' - rule_applied = 'BinaryElfKo' + analysis_priority = "1" + file_category = "binary" + file_subcategory = "elf-ko" + rule_applied = "BinaryElfKo" @classmethod def categorize(cls, resource): - return ( - resource.mime_type in ['application/x-object'] - and - extension_startswith(resource.extension, ('.ko')) + return resource.mime_type in ["application/x-object"] and extension_startswith( + resource.extension, (".ko") ) class BinaryElfO(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'binary' - file_subcategory = 'elf-o' - rule_applied = 'BinaryElfO' + analysis_priority = "1" + file_category = "binary" + file_subcategory = "elf-o" + rule_applied = "BinaryElfO" @classmethod def categorize(cls, resource): - return ( - resource.mime_type in ['application/x-object'] - and - not extension_startswith(resource.extension, ('.ko')) - ) + return resource.mime_type in [ + "application/x-object" + ] and not extension_startswith(resource.extension, (".ko")) class BinaryElfSo(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'binary' - file_subcategory = 'elf-so' - rule_applied = 'BinaryElfSo' + analysis_priority = "1" + file_category = "binary" + file_subcategory = "elf-so" + rule_applied = "BinaryElfSo" @classmethod def categorize(cls, resource): - return ( - resource.mime_type in ['application/x-sharedlib'] - ) + return resource.mime_type in ["application/x-sharedlib"] class BinaryJava(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'binary' - file_subcategory = 'java' - rule_applied = 'BinaryJava' + analysis_priority = "1" + file_category = "binary" + file_subcategory = "java" + rule_applied = "BinaryJava" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.class', '.jar', '.ear', '.sar', '.war']) + return extension_in( + resource.extension, [".class", ".jar", ".ear", ".sar", ".war"] ) class BinaryPython(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'binary' - file_subcategory = 'Python' - rule_applied = 'BinaryPython' + analysis_priority = "1" + file_category = "binary" + file_subcategory = "Python" + rule_applied = "BinaryPython" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.pyc', '.pyo']) - ) + return extension_in(resource.extension, [".pyc", ".pyo"]) class BinaryWindows(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'binary' - file_subcategory = 'windows' - category_notes = 'For DLL and EXE binaries in Windows' - rule_applied = 'BinaryWindows' + analysis_priority = "1" + file_category = "binary" + file_subcategory = "windows" + category_notes = "For DLL and EXE binaries in Windows" + rule_applied = "BinaryWindows" @classmethod def categorize(cls, resource): - return ( - resource.mime_type in ['application/x-dosexec'] - ) + return resource.mime_type in ["application/x-dosexec"] class BuildBazel(Categorizer): order = 0 - analysis_priority = '3' - file_category = 'build' - file_subcategory = 'Bazel' - rule_applied = 'BuildBazel' + analysis_priority = "3" + file_category = "build" + file_subcategory = "Bazel" + rule_applied = "BuildBazel" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.bzl']) - or - ( - name_in(resource.name, ['build.bazel']) - and - resource.type == 'file' - ) + return extension_in(resource.extension, [".bzl"]) or ( + name_in(resource.name, ["build.bazel"]) and resource.type == "file" ) class BuildBuck(Categorizer): order = 0 - analysis_priority = '3' - file_category = 'build' - file_subcategory = 'BUCK' - rule_applied = 'BuildBuck' + analysis_priority = "3" + file_category = "build" + file_subcategory = "BUCK" + rule_applied = "BuildBuck" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['buck']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["buck"]) and resource.type == "file" class BuildDocker(Categorizer): order = 0 - analysis_priority = '2' - file_category = 'build' - file_subcategory = 'Docker' + analysis_priority = "2" + file_category = "build" + file_subcategory = "Docker" category_notes = 'May have an "arbitrary" extension' - rule_applied = 'BuildDocker' + rule_applied = "BuildDocker" @classmethod def categorize(cls, resource): - return ( - name_substring(resource.name, ['dockerfile']) - and - resource.type == 'file' - ) + return name_substring(resource.name, ["dockerfile"]) and resource.type == "file" class BuildMake(Categorizer): order = 0 - analysis_priority = '3' - file_category = 'build' - file_subcategory = 'make' + analysis_priority = "3" + file_category = "build" + file_subcategory = "make" category_notes = '"Makefile" may have an "arbitrary" extension' - rule_applied = 'BuildMake' + rule_applied = "BuildMake" @classmethod def categorize(cls, resource): return ( - ( - name_substring(resource.name, ['makefile']) - and - resource.type == 'file' - ) - or - extension_in(resource.extension, ['.mk', '.make']) - ) + name_substring(resource.name, ["makefile"]) and resource.type == "file" + ) or extension_in(resource.extension, [".mk", ".make"]) class BuildQt(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'build' - file_subcategory = 'Qt' - rule_applied = 'BuildQt' + analysis_priority = "3" + file_category = "build" + file_subcategory = "Qt" + rule_applied = "BuildQt" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.pri', '.pro']) - ) + return extension_in(resource.extension, [".pri", ".pro"]) class Certificate(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'certificate' - file_subcategory = '' - rule_applied = 'Certificate' + analysis_priority = "3" + file_category = "certificate" + file_subcategory = "" + rule_applied = "Certificate" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.crt', '.der', '.pem']) - ) + return extension_in(resource.extension, [".crt", ".der", ".pem"]) class ConfigGeneral(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'config' - file_subcategory = 'general' - rule_applied = 'ConfigGeneral' + analysis_priority = "3" + file_category = "config" + file_subcategory = "general" + rule_applied = "ConfigGeneral" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, [ - '.cfg', '.conf', '.config', '.jxs', '.properties', '.yaml', '.yml' - ]) + return extension_in( + resource.extension, + [".cfg", ".conf", ".config", ".jxs", ".properties", ".yaml", ".yml"], ) class ConfigInitialPeriod(Categorizer): order = 0 - analysis_priority = '3' - file_category = 'config' - file_subcategory = 'initial_period' - rule_applied = 'ConfigInitialPeriod' + analysis_priority = "3" + file_category = "config" + file_subcategory = "initial_period" + rule_applied = "ConfigInitialPeriod" @classmethod def categorize(cls, resource): - return ( - name_startswith(resource.name, ('.')) - and - resource.type == 'file' - ) + return name_startswith(resource.name, (".")) and resource.type == "file" class ConfigMacro(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'config' - file_subcategory = 'macro' - rule_applied = 'ConfigMacro' + analysis_priority = "3" + file_category = "config" + file_subcategory = "macro" + rule_applied = "ConfigMacro" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.m4']) - ) + return extension_in(resource.extension, [".m4"]) class ConfigPython(Categorizer): order = 0 - analysis_priority = '3' - file_category = 'config' - file_subcategory = 'Python' - rule_applied = 'ConfigPython' + analysis_priority = "3" + file_category = "config" + file_subcategory = "Python" + rule_applied = "ConfigPython" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['__init__.py']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["__init__.py"]) and resource.type == "file" class ConfigTemplate(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'config' - file_subcategory = 'template' - rule_applied = 'ConfigTemplate' + analysis_priority = "3" + file_category = "config" + file_subcategory = "template" + rule_applied = "ConfigTemplate" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.tmpl']) - ) + return extension_in(resource.extension, [".tmpl"]) class ConfigVisualCpp(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'config' - file_subcategory = 'Visual-CPP' - category_notes = 'vcproj is older version' - rule_applied = 'ConfigVisualCpp' + analysis_priority = "3" + file_category = "config" + file_subcategory = "Visual-CPP" + category_notes = "vcproj is older version" + rule_applied = "ConfigVisualCpp" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.vcxproj', '.vcproj']) - ) + return extension_in(resource.extension, [".vcxproj", ".vcproj"]) class ConfigXcode(Categorizer): order = 0 - analysis_priority = '3' - file_category = 'config' - file_subcategory = 'xcode' - rule_applied = 'ConfigXcode' + analysis_priority = "3" + file_category = "config" + file_subcategory = "xcode" + rule_applied = "ConfigXcode" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['info.plist']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["info.plist"]) and resource.type == "file" class ConfigXml(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'config' - file_subcategory = 'xml' - rule_applied = 'ConfigXml' + analysis_priority = "3" + file_category = "config" + file_subcategory = "xml" + rule_applied = "ConfigXml" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.dtd', '.xml', '.xsd', '.xsl', '.xslt']) + return extension_in( + resource.extension, [".dtd", ".xml", ".xsd", ".xsl", ".xslt"] ) class DataJson(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'data' - file_subcategory = 'json' - rule_applied = 'DataJson' + analysis_priority = "3" + file_category = "data" + file_subcategory = "json" + rule_applied = "DataJson" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.json']) - ) + return extension_in(resource.extension, [".json"]) class DataProtoBuf(Categorizer): order = 10 - analysis_priority = '2' - file_category = 'data' - file_subcategory = 'ProtoBuf' - rule_applied = 'DataProtoBuf' + analysis_priority = "2" + file_category = "data" + file_subcategory = "ProtoBuf" + rule_applied = "DataProtoBuf" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.proto']) - ) + return extension_in(resource.extension, [".proto"]) class Directory(Categorizer): order = 10 - analysis_priority = '4' - file_category = 'directory' - file_subcategory = '' - rule_applied = 'Directory' + analysis_priority = "4" + file_category = "directory" + file_subcategory = "" + rule_applied = "Directory" @classmethod def categorize(cls, resource): - return ( - resource.type == 'directory' - ) + return resource.type == "directory" class DocGeneral(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'doc' - file_subcategory = 'general' - rule_applied = 'DocGeneral' - - @classmethod - def categorize(cls, resource): - return ( - extension_in(resource.extension, [ - '.csv', '.doc', '.docx', '.man', '.md', '.odp', '.ods', '.odt', '.pdf', '.ppt', '.pptx', - '.rtf', '.tex', '.txt', '.xls', '.xlsm', '.xlsx' - ]) - or - ( - name_in(resource.name, ['changelog', 'changes']) - and - resource.type == 'file' - ) + analysis_priority = "3" + file_category = "doc" + file_subcategory = "general" + rule_applied = "DocGeneral" + + @classmethod + def categorize(cls, resource): + return extension_in( + resource.extension, + [ + ".csv", + ".doc", + ".docx", + ".man", + ".md", + ".odp", + ".ods", + ".odt", + ".pdf", + ".ppt", + ".pptx", + ".rtf", + ".tex", + ".txt", + ".xls", + ".xlsm", + ".xlsx", + ], + ) or ( + name_in(resource.name, ["changelog", "changes"]) and resource.type == "file" ) class DocLicense(Categorizer): order = 0 - analysis_priority = '3' - file_category = 'doc' - file_subcategory = 'license' - rule_applied = 'DocLicense' + analysis_priority = "3" + file_category = "doc" + file_subcategory = "license" + rule_applied = "DocLicense" @classmethod def categorize(cls, resource): return ( - name_substring(resource.name, ['copying', 'copyright', 'license', 'notice']) - and - resource.type == 'file' + name_substring(resource.name, ["copying", "copyright", "license", "notice"]) + and resource.type == "file" ) class DocReadme(Categorizer): order = 0 - analysis_priority = '3' - file_category = 'doc' - file_subcategory = 'readme' - rule_applied = 'DocReadme' + analysis_priority = "3" + file_category = "doc" + file_subcategory = "readme" + rule_applied = "DocReadme" @classmethod def categorize(cls, resource): - return ( - name_substring(resource.name, ['readme']) - and - resource.type == 'file' - ) + return name_substring(resource.name, ["readme"]) and resource.type == "file" class Font(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'font' - rule_applied = 'Font' + analysis_priority = "1" + file_category = "font" + rule_applied = "Font" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.fnt', '.otf', '.ttf', '.woff', '.woff2', '.eot']) + return extension_in( + resource.extension, [".fnt", ".otf", ".ttf", ".woff", ".woff2", ".eot"] ) class ManifestBower(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'Bower' - rule_applied = 'ManifestBower' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "Bower" + rule_applied = "ManifestBower" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['bower.json']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["bower.json"]) and resource.type == "file" class ManifestCargo(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'Cargo' - category_notes = 'For Rust, Not sure about Cargo.toml ?' - rule_applied = 'ManifestCargo' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "Cargo" + category_notes = "For Rust, Not sure about Cargo.toml ?" + rule_applied = "ManifestCargo" @classmethod def categorize(cls, resource): return ( - name_in(resource.name, ['cargo.toml', 'cargo.lock']) - and - resource.type == 'file' + name_in(resource.name, ["cargo.toml", "cargo.lock"]) + and resource.type == "file" ) class ManifestCocoaPod(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'CocoaPod' - rule_applied = 'ManifestCocoaPod' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "CocoaPod" + rule_applied = "ManifestCocoaPod" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.podspec']) - ) + return extension_in(resource.extension, [".podspec"]) class ManifestComposer(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'Composer' - category_notes = 'For PHP' - rule_applied = 'ManifestComposer' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "Composer" + category_notes = "For PHP" + rule_applied = "ManifestComposer" @classmethod def categorize(cls, resource): return ( - name_in(resource.name, ['composer.json', 'composer.lock']) - and - resource.type == 'file' + name_in(resource.name, ["composer.json", "composer.lock"]) + and resource.type == "file" ) class ManifestGolang(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'Golang' - rule_applied = 'ManifestGolang' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "Golang" + rule_applied = "ManifestGolang" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['go.mod', 'go.sum']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["go.mod", "go.sum"]) and resource.type == "file" class ManifestGradle(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'Gradle' - rule_applied = 'ManifestGradle' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "Gradle" + rule_applied = "ManifestGradle" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['build.gradle']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["build.gradle"]) and resource.type == "file" class ManifestHaxe(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'Haxe' - rule_applied = 'ManifestHaxe' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "Haxe" + rule_applied = "ManifestHaxe" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['haxelib.json']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["haxelib.json"]) and resource.type == "file" class ManifestIvy(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'Ivy' - rule_applied = 'ManifestIvy' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "Ivy" + rule_applied = "ManifestIvy" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['ivy.xml']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["ivy.xml"]) and resource.type == "file" class ManifestMaven(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'maven' - rule_applied = 'ManifestMaven' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "maven" + rule_applied = "ManifestMaven" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['pom.xml']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["pom.xml"]) and resource.type == "file" class ManifestNpm(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'npm' - rule_applied = 'ManifestNpm' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "npm" + rule_applied = "ManifestNpm" @classmethod def categorize(cls, resource): return ( - name_in(resource.name, ['package.json', 'package-lock.json', 'yarn.lock']) - and - resource.type == 'file' + name_in(resource.name, ["package.json", "package-lock.json", "yarn.lock"]) + and resource.type == "file" ) class ManifestNuGet(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'NuGet' - rule_applied = 'ManifestNuGet' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "NuGet" + rule_applied = "ManifestNuGet" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.nuspec']) - ) + return extension_in(resource.extension, [".nuspec"]) class ManifestPyPi(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'PyPi' - rule_applied = 'ManifestPyPi' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "PyPi" + rule_applied = "ManifestPyPi" @classmethod def categorize(cls, resource): - return ( - name_in(resource.name, ['requirements.txt']) - and - resource.type == 'file' - ) + return name_in(resource.name, ["requirements.txt"]) and resource.type == "file" class ManifestRubyGem(Categorizer): order = 0 - analysis_priority = '1' - file_category = 'manifest' - file_subcategory = 'RubyGem' - rule_applied = 'ManifestRubyGem' + analysis_priority = "1" + file_category = "manifest" + file_subcategory = "RubyGem" + rule_applied = "ManifestRubyGem" @classmethod def categorize(cls, resource): return ( - name_in(resource.name, ['gemfile', 'gemfile.lock']) - and - resource.type == 'file' + name_in(resource.name, ["gemfile", "gemfile.lock"]) + and resource.type == "file" ) class MediaAudio(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'media' - file_subcategory = 'audio' - rule_applied = 'MediaAudio' + analysis_priority = "3" + file_category = "media" + file_subcategory = "audio" + rule_applied = "MediaAudio" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, [ - '.3pg', '.aac', '.amr', '.awb', '.m4a', '.mp3', - '.mpa', '.ogg', '.opus', '.wav', '.wma' - ]) + return extension_in( + resource.extension, + [ + ".3pg", + ".aac", + ".amr", + ".awb", + ".m4a", + ".mp3", + ".mpa", + ".ogg", + ".opus", + ".wav", + ".wma", + ], ) class MediaImage(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'media' - file_subcategory = 'image' - rule_applied = 'MediaImage' + analysis_priority = "3" + file_category = "media" + file_subcategory = "image" + rule_applied = "MediaImage" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, [ - '.bmp', '.gif', '.ico', '.jpg', '.jpeg', '.png', '.svg', '.webp' - ]) + return extension_in( + resource.extension, + [".bmp", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".svg", ".webp"], ) class MediaVideo(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'media' - file_subcategory = 'video' - rule_applied = 'MediaVideo' + analysis_priority = "3" + file_category = "media" + file_subcategory = "video" + rule_applied = "MediaVideo" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, [ - '.avi', '.h264', '.mp4', '.mpg', '.mpeg', '.swf', '.wmv' - ]) + return extension_in( + resource.extension, + [".avi", ".h264", ".mp4", ".mpg", ".mpeg", ".swf", ".wmv"], ) class ScriptBash(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'script' - file_subcategory = 'bash' - rule_applied = 'ScriptBash' + analysis_priority = "3" + file_category = "script" + file_subcategory = "bash" + rule_applied = "ScriptBash" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.bash']) - ) + return extension_in(resource.extension, [".bash"]) class ScriptBatSh(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'script' - file_subcategory = 'bat_sh' - rule_applied = 'ScriptBatSh' + analysis_priority = "3" + file_category = "script" + file_subcategory = "bat_sh" + rule_applied = "ScriptBatSh" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.bat', '.sh']) - ) + return extension_in(resource.extension, [".bat", ".sh"]) class ScriptBuild(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'script' - file_subcategory = 'build' - rule_applied = 'ScriptBuild' + analysis_priority = "3" + file_category = "script" + file_subcategory = "build" + rule_applied = "ScriptBuild" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.cmake', '.cmakelist']) - or - resource.mime_type in ['text/x-makefile'] - ) + return extension_in( + resource.extension, [".cmake", ".cmakelist"] + ) or resource.mime_type in ["text/x-makefile"] class ScriptData(Categorizer): order = 10 - analysis_priority = '3' - file_category = 'script' - file_subcategory = 'data' - rule_applied = 'ScriptData' + analysis_priority = "3" + file_category = "script" + file_subcategory = "data" + rule_applied = "ScriptData" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.sql', '.psql']) - ) + return extension_in(resource.extension, [".sql", ".psql"]) class SourceAssembler(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'assembler' - category_notes = 'upper case only' - rule_applied = 'SourceAssembler' + analysis_priority = "1" + file_category = "source" + file_subcategory = "assembler" + category_notes = "upper case only" + rule_applied = "SourceAssembler" @classmethod def categorize(cls, resource): - return ( - extension_uppercase_in(resource.extension, ['.S']) - ) + return extension_uppercase_in(resource.extension, [".S"]) class SourceC(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'c' - rule_applied = 'SourceC' + analysis_priority = "1" + file_category = "source" + file_subcategory = "c" + rule_applied = "SourceC" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.c']) - or - ( - extension_in(resource.extension, ['.h']) - and - resource.mime_type in ['text/x-c'] - ) + return extension_in(resource.extension, [".c"]) or ( + extension_in(resource.extension, [".h"]) + and resource.mime_type in ["text/x-c"] ) class SourceCoffeeScript(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'CoffeeScript' - category_notes = 'transcompiles to JS' - rule_applied = 'SourceCoffeeScript' + analysis_priority = "1" + file_category = "source" + file_subcategory = "CoffeeScript" + category_notes = "transcompiles to JS" + rule_applied = "SourceCoffeeScript" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.coffee']) - ) + return extension_in(resource.extension, [".coffee"]) class SourceCpp(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'c++' - rule_applied = 'SourceCpp' + analysis_priority = "1" + file_category = "source" + file_subcategory = "c++" + rule_applied = "SourceCpp" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.cpp', '.hpp', '.cc']) - or - ( - extension_in(resource.extension, ['.h']) - and - resource.mime_type in ['text/x-c++'] - ) + return extension_in(resource.extension, [".cpp", ".hpp", ".cc"]) or ( + extension_in(resource.extension, [".h"]) + and resource.mime_type in ["text/x-c++"] ) class SourceCsharp(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'c#' - rule_applied = 'SourceCsharp' + analysis_priority = "1" + file_category = "source" + file_subcategory = "c#" + rule_applied = "SourceCsharp" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.cs']) - ) + return extension_in(resource.extension, [".cs"]) class SourceGo(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'go' - rule_applied = 'SourceGo' + analysis_priority = "1" + file_category = "source" + file_subcategory = "go" + rule_applied = "SourceGo" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.go']) - ) + return extension_in(resource.extension, [".go"]) class SourceHaskell(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'haskell' - rule_applied = 'SourceHaskell' + analysis_priority = "1" + file_category = "source" + file_subcategory = "haskell" + rule_applied = "SourceHaskell" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.hs', '.lhs']) - ) + return extension_in(resource.extension, [".hs", ".lhs"]) class SourceJava(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'java' - rule_applied = 'SourceJava' + analysis_priority = "1" + file_category = "source" + file_subcategory = "java" + rule_applied = "SourceJava" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.java']) - ) + return extension_in(resource.extension, [".java"]) class SourceJavascript(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'javascript' - rule_applied = 'SourceJavascript' + analysis_priority = "1" + file_category = "source" + file_subcategory = "javascript" + rule_applied = "SourceJavascript" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.js']) - ) + return extension_in(resource.extension, [".js"]) class SourceJavaserverpage(Categorizer): order = 10 - analysis_priority = '2' - file_category = 'source' - file_subcategory = 'javaserverpage' - rule_applied = 'SourceJavaserverpage' + analysis_priority = "2" + file_category = "source" + file_subcategory = "javaserverpage" + rule_applied = "SourceJavaserverpage" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.jsp']) - ) + return extension_in(resource.extension, [".jsp"]) class SourceKotlin(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'kotlin' - rule_applied = 'SourceKotlin' + analysis_priority = "1" + file_category = "source" + file_subcategory = "kotlin" + rule_applied = "SourceKotlin" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.kt']) - ) + return extension_in(resource.extension, [".kt"]) class SourceObjectivec(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'objectivec' - rule_applied = 'SourceObjectivec' + analysis_priority = "1" + file_category = "source" + file_subcategory = "objectivec" + rule_applied = "SourceObjectivec" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.m', '.mm']) - or - ( - extension_in(resource.extension, ['.h']) - and - resource.mime_type in ['text/x-objective-c'] - ) + return extension_in(resource.extension, [".m", ".mm"]) or ( + extension_in(resource.extension, [".h"]) + and resource.mime_type in ["text/x-objective-c"] ) class SourcePerl(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'perl' - rule_applied = 'SourcePerl' + analysis_priority = "1" + file_category = "source" + file_subcategory = "perl" + rule_applied = "SourcePerl" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.pl', '.pm']) - ) + return extension_in(resource.extension, [".pl", ".pm"]) class SourcePhp(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'php' - rule_applied = 'SourcePhp' + analysis_priority = "1" + file_category = "source" + file_subcategory = "php" + rule_applied = "SourcePhp" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.php', '.php3', '.php4', '.php5']) - ) + return extension_in(resource.extension, [".php", ".php3", ".php4", ".php5"]) class SourcePython(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'python' - rule_applied = 'SourcePython' + analysis_priority = "1" + file_category = "source" + file_subcategory = "python" + rule_applied = "SourcePython" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.py']) - ) + return extension_in(resource.extension, [".py"]) class SourceRuby(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'ruby' - rule_applied = 'SourceRuby' + analysis_priority = "1" + file_category = "source" + file_subcategory = "ruby" + rule_applied = "SourceRuby" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.rb', '.rake']) - ) + return extension_in(resource.extension, [".rb", ".rake"]) class SourceRust(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'rust' - rule_applied = 'SourceRust' + analysis_priority = "1" + file_category = "source" + file_subcategory = "rust" + rule_applied = "SourceRust" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.rs']) - ) + return extension_in(resource.extension, [".rs"]) class SourceScala(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'scala' - rule_applied = 'SourceScala' + analysis_priority = "1" + file_category = "source" + file_subcategory = "scala" + rule_applied = "SourceScala" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.scala']) - ) + return extension_in(resource.extension, [".scala"]) class SourceSwift(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'swift' - rule_applied = 'SourceSwift' + analysis_priority = "1" + file_category = "source" + file_subcategory = "swift" + rule_applied = "SourceSwift" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.swift']) - ) + return extension_in(resource.extension, [".swift"]) class SourceTypescript(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'source' - file_subcategory = 'typescript' - category_notes = '.ts extension is not definitive' - rule_applied = 'SourceTypescript' + analysis_priority = "1" + file_category = "source" + file_subcategory = "typescript" + category_notes = ".ts extension is not definitive" + rule_applied = "SourceTypescript" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.ts']) - and - resource.programming_language in ['TypeScript'] - ) + return extension_in( + resource.extension, [".ts"] + ) and resource.programming_language in ["TypeScript"] class WebCss(Categorizer): order = 10 - analysis_priority = '1' - file_category = 'web' - file_subcategory = 'css' - rule_applied = 'WebCss' + analysis_priority = "1" + file_category = "web" + file_subcategory = "css" + rule_applied = "WebCss" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.css', '.less', '.scss']) - ) + return extension_in(resource.extension, [".css", ".less", ".scss"]) class WebHtml(Categorizer): order = 10 - analysis_priority = '2' - file_category = 'web' - file_subcategory = 'html' + analysis_priority = "2" + file_category = "web" + file_subcategory = "html" rule_applied = "WebHtml" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.htm', '.html']) - ) + return extension_in(resource.extension, [".htm", ".html"]) class WebRuby(Categorizer): order = 10 - analysis_priority = '2' - file_category = 'web' - file_subcategory = 'Ruby' + analysis_priority = "2" + file_category = "web" + file_subcategory = "Ruby" rule_applied = "WebRuby" @classmethod def categorize(cls, resource): - return ( - extension_in(resource.extension, ['.erb']) - ) + return extension_in(resource.extension, [".erb"]) categories = [ @@ -1370,7 +1224,7 @@ def categorize(cls, resource): SourceTypescript, WebCss, WebHtml, - WebRuby + WebRuby, ] diff --git a/tests/summarycode/test_file_cat.py b/tests/summarycode/test_file_cat.py index 01fd8ce6b77..6bdb0a02599 100644 --- a/tests/summarycode/test_file_cat.py +++ b/tests/summarycode/test_file_cat.py @@ -2083,6 +2083,258 @@ def test_MediaVideo(self): ) assert file_cat.MediaVideo.categorize(test_resource_07) + def test_ScriptBash(self): + test_resource_01 = resource_class( + name="foo.bash", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ScriptBash.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "script" + + def test_ScriptBatSh(self): + test_resource_01 = resource_class( + name="foo.bat", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ScriptBatSh.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "script" + + test_resource_02 = resource_class( + name="foo.sh", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ScriptBatSh.categorize(test_resource_02) + + def test_ScriptBuild(self): + test_resource_01 = resource_class( + name="foo.cmake", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ScriptBuild.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "script" + + test_resource_02 = resource_class( + name="foo.cmakelist", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ScriptBuild.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.bar", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="text/x-makefile", + file_type="", + programming_language="", + ) + assert file_cat.ScriptBuild.categorize(test_resource_03) + + def test_ScriptData(self): + test_resource_01 = resource_class( + name="foo.sql", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ScriptData.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "script" + + test_resource_02 = resource_class( + name="foo.psql", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.ScriptData.categorize(test_resource_02) + + def test_SourceAssembler(self): + test_resource_01 = resource_class( + name="foo.S", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceAssembler.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + test_resource_02 = resource_class( + name="foo.s", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert not file_cat.SourceAssembler.categorize(test_resource_02) + + def test_SourceC(self): + test_resource_01 = resource_class( + name="foo.c", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceC.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + test_resource_02 = resource_class( + name="foo.h", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="text/x-c", + file_type="", + programming_language="", + ) + assert file_cat.SourceC.categorize(test_resource_02) + + def test_SourceCoffeeScript(self): + test_resource_01 = resource_class( + name="foo.coffee", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceCoffeeScript.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceCpp(self): + test_resource_01 = resource_class( + name="foo.cpp", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceCpp.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + test_resource_02 = resource_class( + name="foo.hpp", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceCpp.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.cc", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceCpp.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.h", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="text/x-c++", + file_type="", + programming_language="", + ) + assert file_cat.SourceCpp.categorize(test_resource_04) + + def test_SourceCsharp(self): + test_resource_01 = resource_class( + name="foo.cs", + location="", + path="", + rid="", + pid="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceCsharp.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + # ================================== def test_WebRuby(self): From e9c7ef68344484e42d3bcff7fb097c7fdc354466 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Thu, 19 May 2022 17:59:31 -0700 Subject: [PATCH 09/13] Remove 'rid' and 'pid' attributes from failing tests Reference: https://github.com/nexB/scancode-toolkit/issues/2945 Signed-off-by: John M. Horan --- tests/summarycode/test_file_cat.py | 332 ----------------------------- 1 file changed, 332 deletions(-) diff --git a/tests/summarycode/test_file_cat.py b/tests/summarycode/test_file_cat.py index 6bdb0a02599..4b0484d49d4 100644 --- a/tests/summarycode/test_file_cat.py +++ b/tests/summarycode/test_file_cat.py @@ -40,8 +40,6 @@ def test_ArchiveAndroid(self): name="foo.apk", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -54,8 +52,6 @@ def test_ArchiveAndroid(self): name="foo.aar", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -69,8 +65,6 @@ def test_ArchiveDebian(self): name="foo.deb", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -83,8 +77,6 @@ def test_ArchiveDebian(self): name="foo.2", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -97,8 +89,6 @@ def test_ArchiveGeneral(self): name="foo.7zip", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -111,8 +101,6 @@ def test_ArchiveGeneral(self): name="foo.bz", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -124,8 +112,6 @@ def test_ArchiveGeneral(self): name="foo.bz2", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -137,8 +123,6 @@ def test_ArchiveGeneral(self): name="foo.bzip", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -150,8 +134,6 @@ def test_ArchiveGeneral(self): name="foo.gz", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -163,8 +145,6 @@ def test_ArchiveGeneral(self): name="foo.gzi", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -176,8 +156,6 @@ def test_ArchiveGeneral(self): name="foo.tar", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -189,8 +167,6 @@ def test_ArchiveGeneral(self): name="foo.tgz", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -202,8 +178,6 @@ def test_ArchiveGeneral(self): name="foo.xz", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -215,8 +189,6 @@ def test_ArchiveGeneral(self): name="foo.zip", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -229,8 +201,6 @@ def test_ArchiveIos(self): name="foo.ipa", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -244,8 +214,6 @@ def test_ArchiveRpm(self): name="foo.rpm", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -259,8 +227,6 @@ def test_BinaryAr(self): name="", location="", path="", - rid="", - pid="", is_file=True, mime_type="application/x-archive", file_type="", @@ -274,8 +240,6 @@ def test_BinaryElfExec(self): name="", location="", path="", - rid="", - pid="", is_file=True, mime_type="application/x-executable", file_type="", @@ -289,8 +253,6 @@ def test_BinaryElfKo(self): name="foo.ko", location="", path="", - rid="", - pid="", is_file=True, mime_type="application/x-object", file_type="", @@ -304,8 +266,6 @@ def test_BinaryElfO(self): name="", location="", path="", - rid="", - pid="", is_file=True, mime_type="application/x-object", file_type="", @@ -319,8 +279,6 @@ def test_BinaryElfSo(self): name="", location="", path="", - rid="", - pid="", is_file=True, mime_type="application/x-sharedlib", file_type="", @@ -334,8 +292,6 @@ def test_BinaryJava(self): name="foo.class", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -348,8 +304,6 @@ def test_BinaryJava(self): name="foo.jar", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -361,8 +315,6 @@ def test_BinaryJava(self): name="foo.ear", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -374,8 +326,6 @@ def test_BinaryJava(self): name="foo.sar", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -387,8 +337,6 @@ def test_BinaryJava(self): name="foo.war", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -401,8 +349,6 @@ def test_BinaryPython(self): name="foo.pyc", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -415,8 +361,6 @@ def test_BinaryPython(self): name="foo.pyo", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -429,8 +373,6 @@ def test_BinaryWindows(self): name="", location="", path="", - rid="", - pid="", is_file=True, mime_type="application/x-dosexec", file_type="", @@ -448,8 +390,6 @@ def test_BuildBazel(self): name="foo.bzl", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -462,8 +402,6 @@ def test_BuildBazel(self): name="build.bazel", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -475,8 +413,6 @@ def test_BuildBazel(self): name="foo.bazel", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -489,8 +425,6 @@ def test_BuildBuck(self): name="buck", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -503,8 +437,6 @@ def test_BuildBuck(self): name="Buck", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -516,8 +448,6 @@ def test_BuildBuck(self): name="Buck.c", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -530,8 +460,6 @@ def test_BuildDocker(self): name="dockerfile", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -544,8 +472,6 @@ def test_BuildDocker(self): name="dockerfile.c", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -558,8 +484,6 @@ def test_BuildMake(self): name="makefile", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -572,8 +496,6 @@ def test_BuildMake(self): name="makefile.c", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -585,8 +507,6 @@ def test_BuildMake(self): name="foo.mk", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -598,8 +518,6 @@ def test_BuildMake(self): name="foo.make", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -612,8 +530,6 @@ def test_BuildQt(self): name="foo.pri", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -626,8 +542,6 @@ def test_BuildQt(self): name="foo.pro", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -640,8 +554,6 @@ def test_Certificate(self): name="foo.crt", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -657,8 +569,6 @@ def test_Certificate(self): name="foo.der", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -670,8 +580,6 @@ def test_Certificate(self): name="foo.pem", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -684,8 +592,6 @@ def test_ConfigGeneral(self): name="foo.cfg", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -698,8 +604,6 @@ def test_ConfigGeneral(self): name="foo.conf", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -711,8 +615,6 @@ def test_ConfigGeneral(self): name="foo.config", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -724,8 +626,6 @@ def test_ConfigGeneral(self): name="foo.jxs", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -737,8 +637,6 @@ def test_ConfigGeneral(self): name="foo.properties", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -750,8 +648,6 @@ def test_ConfigGeneral(self): name="foo.yaml", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -763,8 +659,6 @@ def test_ConfigGeneral(self): name="foo.yml", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -777,8 +671,6 @@ def test_ConfigInitialPeriod(self): name=".c", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -792,8 +684,6 @@ def test_ConfigMacro(self): name="foo.m4", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -807,8 +697,6 @@ def test_ConfigPython(self): name="__init__.py", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -822,8 +710,6 @@ def test_ConfigTemplate(self): name="foo.tmpl", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -837,8 +723,6 @@ def test_ConfigVisualCpp(self): name="foo.vcxproj", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -851,8 +735,6 @@ def test_ConfigVisualCpp(self): name="foo.vcproj", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -865,8 +747,6 @@ def test_ConfigXcode(self): name="info.plist", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -880,8 +760,6 @@ def test_ConfigXml(self): name="foo.dtd", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -894,8 +772,6 @@ def test_ConfigXml(self): name="foo.xml", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -907,8 +783,6 @@ def test_ConfigXml(self): name="foo.xsd", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -920,8 +794,6 @@ def test_ConfigXml(self): name="foo.xsl", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -933,8 +805,6 @@ def test_ConfigXml(self): name="foo.xslt", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -947,8 +817,6 @@ def test_DataJson(self): name="foo.json", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -962,8 +830,6 @@ def test_DataProtoBuf(self): name="foo.proto", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -977,8 +843,6 @@ def test_Directory(self): name="foo", location="", path="", - rid="", - pid="", is_file=False, mime_type="", file_type="", @@ -994,8 +858,6 @@ def test_Directory(self): name=".foo", location="", path="", - rid="", - pid="", is_file=False, mime_type="", file_type="", @@ -1007,8 +869,6 @@ def test_Directory(self): name="foo", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1021,8 +881,6 @@ def test_DocGeneral(self): name="foo.csv", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1035,8 +893,6 @@ def test_DocGeneral(self): name="foo.doc", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1048,8 +904,6 @@ def test_DocGeneral(self): name="foo.docx", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1061,8 +915,6 @@ def test_DocGeneral(self): name="foo.man", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1074,8 +926,6 @@ def test_DocGeneral(self): name="foo.md", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1087,8 +937,6 @@ def test_DocGeneral(self): name="foo.odp", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1100,8 +948,6 @@ def test_DocGeneral(self): name="foo.ods", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1113,8 +959,6 @@ def test_DocGeneral(self): name="foo.odt", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1126,8 +970,6 @@ def test_DocGeneral(self): name="foo.pdf", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1139,8 +981,6 @@ def test_DocGeneral(self): name="foo.ppt", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1152,8 +992,6 @@ def test_DocGeneral(self): name="foo.pptx", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1165,8 +1003,6 @@ def test_DocGeneral(self): name="foo.rtf", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1178,8 +1014,6 @@ def test_DocGeneral(self): name="foo.tex", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1191,8 +1025,6 @@ def test_DocGeneral(self): name="foo.txt", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1204,8 +1036,6 @@ def test_DocGeneral(self): name="foo.xls", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1217,8 +1047,6 @@ def test_DocGeneral(self): name="foo.xlsm", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1230,8 +1058,6 @@ def test_DocGeneral(self): name="foo.xlsx", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1243,8 +1069,6 @@ def test_DocGeneral(self): name="changelog", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1256,8 +1080,6 @@ def test_DocGeneral(self): name="changes", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1270,8 +1092,6 @@ def test_DocLicense(self): name="copying", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1284,8 +1104,6 @@ def test_DocLicense(self): name="copyright", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1297,8 +1115,6 @@ def test_DocLicense(self): name="license", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1310,8 +1126,6 @@ def test_DocLicense(self): name="notice", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1323,8 +1137,6 @@ def test_DocLicense(self): name="LICENSE.c", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1337,8 +1149,6 @@ def test_DocReadme(self): name="READmeplease", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1351,8 +1161,6 @@ def test_DocReadme(self): name="READme.foo", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1365,8 +1173,6 @@ def test_Font(self): name="foo.fnt", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1379,8 +1185,6 @@ def test_Font(self): name="foo.otf", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1392,8 +1196,6 @@ def test_Font(self): name="foo.ttf", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1405,8 +1207,6 @@ def test_Font(self): name="foo.woff", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1418,8 +1218,6 @@ def test_Font(self): name="foo.woff2", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1431,8 +1229,6 @@ def test_Font(self): name="foo.eot", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1445,8 +1241,6 @@ def test_ManifestBower(self): name="bower.json", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1462,8 +1256,6 @@ def test_ManifestCargo(self): name="cargo.toml", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1478,8 +1270,6 @@ def test_ManifestCargo(self): name="cargo.lock", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1492,8 +1282,6 @@ def test_ManifestCocoaPod(self): name="foo.podspec", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1509,8 +1297,6 @@ def test_ManifestComposer(self): name="composer.json", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1525,8 +1311,6 @@ def test_ManifestComposer(self): name="composer.lock", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1539,8 +1323,6 @@ def test_ManifestGolang(self): name="go.mod", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1555,8 +1337,6 @@ def test_ManifestGolang(self): name="go.sum", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1569,8 +1349,6 @@ def test_ManifestGradle(self): name="build.gradle", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1586,8 +1364,6 @@ def test_ManifestHaxe(self): name="haxelib.json", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1603,8 +1379,6 @@ def test_ManifestIvy(self): name="ivy.xml", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1620,8 +1394,6 @@ def test_ManifestMaven(self): name="pom.xml", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1637,8 +1409,6 @@ def test_ManifestNpm(self): name="package.json", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1653,8 +1423,6 @@ def test_ManifestNpm(self): name="package-lock.json", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1666,8 +1434,6 @@ def test_ManifestNpm(self): name="yarn.lock", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1680,8 +1446,6 @@ def test_ManifestNuGet(self): name="foo.nuspec", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1697,8 +1461,6 @@ def test_ManifestPyPi(self): name="requirements.txt", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1714,8 +1476,6 @@ def test_ManifestRubyGem(self): name="gemfile", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1730,8 +1490,6 @@ def test_ManifestRubyGem(self): name="gemfile.lock", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1744,8 +1502,6 @@ def test_MediaAudio(self): name="foo.3pg", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1758,8 +1514,6 @@ def test_MediaAudio(self): name="foo.aac", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1771,8 +1525,6 @@ def test_MediaAudio(self): name="foo.amr", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1784,8 +1536,6 @@ def test_MediaAudio(self): name="foo.awb", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1797,8 +1547,6 @@ def test_MediaAudio(self): name="foo.m4a", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1810,8 +1558,6 @@ def test_MediaAudio(self): name="foo.mp3", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1823,8 +1569,6 @@ def test_MediaAudio(self): name="foo.mpa", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1836,8 +1580,6 @@ def test_MediaAudio(self): name="foo.ogg", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1849,8 +1591,6 @@ def test_MediaAudio(self): name="foo.opus", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1862,8 +1602,6 @@ def test_MediaAudio(self): name="foo.wav", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1875,8 +1613,6 @@ def test_MediaAudio(self): name="foo.wma", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1889,8 +1625,6 @@ def test_MediaImage(self): name="foo.bmp", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1903,8 +1637,6 @@ def test_MediaImage(self): name="foo.gif", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1916,8 +1648,6 @@ def test_MediaImage(self): name="foo.ico", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1929,8 +1659,6 @@ def test_MediaImage(self): name="foo.jpg", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1942,8 +1670,6 @@ def test_MediaImage(self): name="foo.jpeg", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1955,8 +1681,6 @@ def test_MediaImage(self): name="foo.png", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1968,8 +1692,6 @@ def test_MediaImage(self): name="foo.svg", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1981,8 +1703,6 @@ def test_MediaImage(self): name="foo.webp", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -1995,8 +1715,6 @@ def test_MediaVideo(self): name="foo.avi", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2009,8 +1727,6 @@ def test_MediaVideo(self): name="foo.h264", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2022,8 +1738,6 @@ def test_MediaVideo(self): name="foo.mp4", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2035,8 +1749,6 @@ def test_MediaVideo(self): name="foo.mpg", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2048,8 +1760,6 @@ def test_MediaVideo(self): name="foo.mpeg", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2061,8 +1771,6 @@ def test_MediaVideo(self): name="foo.swf", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2074,8 +1782,6 @@ def test_MediaVideo(self): name="foo.wmv", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2088,8 +1794,6 @@ def test_ScriptBash(self): name="foo.bash", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2103,8 +1807,6 @@ def test_ScriptBatSh(self): name="foo.bat", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2117,8 +1819,6 @@ def test_ScriptBatSh(self): name="foo.sh", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2131,8 +1831,6 @@ def test_ScriptBuild(self): name="foo.cmake", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2145,8 +1843,6 @@ def test_ScriptBuild(self): name="foo.cmakelist", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2158,8 +1854,6 @@ def test_ScriptBuild(self): name="foo.bar", location="", path="", - rid="", - pid="", is_file=True, mime_type="text/x-makefile", file_type="", @@ -2172,8 +1866,6 @@ def test_ScriptData(self): name="foo.sql", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2186,8 +1878,6 @@ def test_ScriptData(self): name="foo.psql", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2200,8 +1890,6 @@ def test_SourceAssembler(self): name="foo.S", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2214,8 +1902,6 @@ def test_SourceAssembler(self): name="foo.s", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2228,8 +1914,6 @@ def test_SourceC(self): name="foo.c", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2242,8 +1926,6 @@ def test_SourceC(self): name="foo.h", location="", path="", - rid="", - pid="", is_file=True, mime_type="text/x-c", file_type="", @@ -2256,8 +1938,6 @@ def test_SourceCoffeeScript(self): name="foo.coffee", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2271,8 +1951,6 @@ def test_SourceCpp(self): name="foo.cpp", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2285,8 +1963,6 @@ def test_SourceCpp(self): name="foo.hpp", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2298,8 +1974,6 @@ def test_SourceCpp(self): name="foo.cc", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2311,8 +1985,6 @@ def test_SourceCpp(self): name="foo.h", location="", path="", - rid="", - pid="", is_file=True, mime_type="text/x-c++", file_type="", @@ -2325,8 +1997,6 @@ def test_SourceCsharp(self): name="foo.cs", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", @@ -2342,8 +2012,6 @@ def test_WebRuby(self): name="foo.erb", location="", path="", - rid="", - pid="", is_file=True, mime_type="", file_type="", From f8fae3579f7961210545024f66c5169beb5ccbb3 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Fri, 20 May 2022 13:13:22 -0700 Subject: [PATCH 10/13] Add file-cat rule tests Reference: https://github.com/nexB/scancode-toolkit/issues/2945 Signed-off-by: John M. Horan --- tests/summarycode/test_file_cat.py | 342 ++++++++++++++++++++++++++++- 1 file changed, 341 insertions(+), 1 deletion(-) diff --git a/tests/summarycode/test_file_cat.py b/tests/summarycode/test_file_cat.py index 4b0484d49d4..3f165641e6d 100644 --- a/tests/summarycode/test_file_cat.py +++ b/tests/summarycode/test_file_cat.py @@ -2005,7 +2005,347 @@ def test_SourceCsharp(self): assert file_cat.SourceCsharp.categorize(test_resource_01) assert file_cat.categorize_resource(test_resource_01).file_category == "source" - # ================================== + def test_SourceGo(self): + test_resource_01 = resource_class( + name="foo.go", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceGo.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceHaskell(self): + test_resource_01 = resource_class( + name="foo.hs", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceHaskell.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + test_resource_02 = resource_class( + name="foo.lhs", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceHaskell.categorize(test_resource_02) + + def test_SourceJava(self): + test_resource_01 = resource_class( + name="foo.java", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceJava.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceJavascript(self): + test_resource_01 = resource_class( + name="foo.js", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceJavascript.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceJavaserverpage(self): + test_resource_01 = resource_class( + name="foo.jsp", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceJavaserverpage.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceKotlin(self): + test_resource_01 = resource_class( + name="foo.kt", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceKotlin.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceObjectivec(self): + test_resource_01 = resource_class( + name="foo.m", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceObjectivec.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + test_resource_02 = resource_class( + name="foo.mm", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceObjectivec.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.h", + location="", + path="", + is_file=True, + mime_type="text/x-objective-c", + file_type="", + programming_language="", + ) + assert file_cat.SourceObjectivec.categorize(test_resource_03) + + def test_SourcePerl(self): + test_resource_01 = resource_class( + name="foo.pl", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourcePerl.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + test_resource_02 = resource_class( + name="foo.pm", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourcePerl.categorize(test_resource_02) + + def test_SourcePhp(self): + test_resource_01 = resource_class( + name="foo.php", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourcePhp.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + test_resource_02 = resource_class( + name="foo.php3", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourcePhp.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.php4", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourcePhp.categorize(test_resource_03) + + test_resource_04 = resource_class( + name="foo.php5", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourcePhp.categorize(test_resource_04) + + def test_SourcePython(self): + test_resource_01 = resource_class( + name="foo.py", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourcePython.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceRuby(self): + test_resource_01 = resource_class( + name="foo.rb", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceRuby.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + test_resource_02 = resource_class( + name="foo.rake", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceRuby.categorize(test_resource_02) + + def test_SourceRust(self): + test_resource_01 = resource_class( + name="foo.rs", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceRust.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceScala(self): + test_resource_01 = resource_class( + name="foo.scala", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceScala.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceSwift(self): + test_resource_01 = resource_class( + name="foo.swift", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.SourceSwift.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_SourceTypescript(self): + test_resource_01 = resource_class( + name="foo.ts", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="TypeScript", + ) + assert file_cat.SourceTypescript.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "source" + + def test_WebCss(self): + test_resource_01 = resource_class( + name="foo.css", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.WebCss.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "web" + + test_resource_02 = resource_class( + name="foo.less", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.WebCss.categorize(test_resource_02) + + test_resource_03 = resource_class( + name="foo.scss", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.WebCss.categorize(test_resource_03) + + def test_WebHtml(self): + test_resource_01 = resource_class( + name="foo.htm", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.WebHtml.categorize(test_resource_01) + assert file_cat.categorize_resource(test_resource_01).file_category == "web" + + test_resource_02 = resource_class( + name="foo.html", + location="", + path="", + is_file=True, + mime_type="", + file_type="", + programming_language="", + ) + assert file_cat.WebHtml.categorize(test_resource_02) def test_WebRuby(self): test_resource_01 = resource_class( From a27d7b519eed3949e3732454c61c689f59f1f26d Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Mon, 23 May 2022 08:44:55 -0700 Subject: [PATCH 11/13] Add initial JSON-based test Reference: https://github.com/nexB/scancode-toolkit/issues/2945 Signed-off-by: John M. Horan --- .../file_cat/code/media01/jarDependImg.png | Bin 0 -> 105451 bytes .../scans/media01/media01-info-scan.json | 93 ++++++++++++++++++ tests/summarycode/test_file_cat.py | 12 +++ 3 files changed, 105 insertions(+) create mode 100644 tests/summarycode/data/file_cat/code/media01/jarDependImg.png create mode 100644 tests/summarycode/data/file_cat/scans/media01/media01-info-scan.json diff --git a/tests/summarycode/data/file_cat/code/media01/jarDependImg.png b/tests/summarycode/data/file_cat/code/media01/jarDependImg.png new file mode 100644 index 0000000000000000000000000000000000000000..6e27d703aad6523ba3760aa713b95ed9f56408a4 GIT binary patch literal 105451 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�KK=C5n?r{Vh4WK{ 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

$DiUV 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 Date: Mon, 23 May 2022 12:58:20 -0700 Subject: [PATCH 12/13] Include directories in codebase.walk, ignore file date in JSON tests Reference: https://github.com/nexB/scancode-toolkit/issues/2945 Signed-off-by: John M. Horan --- src/summarycode/file_cat.py | 15 ++- .../file_cat/archive/media01-info-scan.json | 93 +++++++++++++++++++ .../scans/media01/media01-info-scan.json | 12 +-- tests/summarycode/test_file_cat.py | 4 +- 4 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 tests/summarycode/data/file_cat/archive/media01-info-scan.json diff --git a/src/summarycode/file_cat.py b/src/summarycode/file_cat.py index 6339be51363..55c4964f59d 100644 --- a/src/summarycode/file_cat.py +++ b/src/summarycode/file_cat.py @@ -83,14 +83,13 @@ def process_codebase(self, codebase, file_cat, **kwargs): return for resource in codebase.walk(topdown=True): - if resource.is_file: - category = categorize_resource(resource) - if not category: - continue - resource.analysis_priority = category.analysis_priority - resource.file_category = category.file_category - resource.file_subcategory = category.file_subcategory - resource.save(codebase) + category = categorize_resource(resource) + if not category: + continue + resource.analysis_priority = category.analysis_priority + resource.file_category = category.file_category + resource.file_subcategory = category.file_subcategory + resource.save(codebase) class Categorizer: diff --git a/tests/summarycode/data/file_cat/archive/media01-info-scan.json b/tests/summarycode/data/file_cat/archive/media01-info-scan.json new file mode 100644 index 00000000000..673b9e77727 --- /dev/null +++ b/tests/summarycode/data/file_cat/archive/media01-info-scan.json @@ -0,0 +1,93 @@ +{ + "headers": [ + { + "tool_name": "scancode-toolkit", + "tool_version": "31.0.0b5", + "options": { + "input": [ + "tests/summarycode/data/file_cat/code/media01" + ], + "--file-cat": true, + "--info": true, + "--json-pp": "tests/summarycode/data/file_cat/scans/media01/media01-info-scan.json" + }, + "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", + "start_timestamp": "2022-05-21T010012.311818", + "end_timestamp": "2022-05-21T010012.367067", + "output_format_version": "2.0.0", + "duration": 0.0552670955657959, + "message": null, + "errors": [], + "warnings": [], + "extra_data": { + "system_environment": { + "operating_system": "linux", + "cpu_architecture": "64", + "platform": "Linux-5.10.102.1-microsoft-standard-WSL2-x86_64-with-glibc2.29", + "platform_version": "#1 SMP Wed Mar 2 00:30:59 UTC 2022", + "python_version": "3.8.10 (default, Mar 15 2022, 12:22:08) \n[GCC 9.4.0]" + }, + "spdx_license_list_version": "3.16", + "files_count": 1 + } + } + ], + "files": [ + { + "path": "media01", + "type": "directory", + "name": "media01", + "base_name": "media01", + "extension": "", + "size": 0, + "date": null, + "sha1": null, + "md5": null, + "sha256": null, + "mime_type": null, + "file_type": null, + "programming_language": null, + "is_binary": false, + "is_text": false, + "is_archive": false, + "is_media": false, + "is_source": false, + "is_script": false, + "analysis_priority": null, + "file_category": null, + "file_subcategory": null, + "files_count": 1, + "dirs_count": 0, + "size_count": 105451, + "scan_errors": [] + }, + { + "path": "media01/jarDependImg.png", + "type": "file", + "name": "jarDependImg.png", + "base_name": "jarDependImg", + "extension": ".png", + "size": 105451, + "date": "2008-01-06", + "sha1": "d8b1b95b564b776e24b8de15699471c8e871fd34", + "md5": "cbaa0aefb8e2505c2670db5832c2316f", + "sha256": "9bf513e9a63aad6d99c3a76759ba2b5f4bb795568a35ddd41d8d9460c8fb9673", + "mime_type": "image/png", + "file_type": "PNG image data, 600 x 400, 8-bit/color RGBA, non-interlaced", + "programming_language": null, + "is_binary": true, + "is_text": false, + "is_archive": false, + "is_media": true, + "is_source": false, + "is_script": false, + "analysis_priority": "3", + "file_category": "media", + "file_subcategory": "image", + "files_count": 0, + "dirs_count": 0, + "size_count": 0, + "scan_errors": [] + } + ] +} \ No newline at end of file diff --git a/tests/summarycode/data/file_cat/scans/media01/media01-info-scan.json b/tests/summarycode/data/file_cat/scans/media01/media01-info-scan.json index 673b9e77727..d2d2e01ec19 100644 --- a/tests/summarycode/data/file_cat/scans/media01/media01-info-scan.json +++ b/tests/summarycode/data/file_cat/scans/media01/media01-info-scan.json @@ -12,10 +12,10 @@ "--json-pp": "tests/summarycode/data/file_cat/scans/media01/media01-info-scan.json" }, "notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.", - "start_timestamp": "2022-05-21T010012.311818", - "end_timestamp": "2022-05-21T010012.367067", + "start_timestamp": "2022-05-23T194805.069626", + "end_timestamp": "2022-05-23T194805.131938", "output_format_version": "2.0.0", - "duration": 0.0552670955657959, + "duration": 0.06232571601867676, "message": null, "errors": [], "warnings": [], @@ -53,9 +53,9 @@ "is_media": false, "is_source": false, "is_script": false, - "analysis_priority": null, - "file_category": null, - "file_subcategory": null, + "analysis_priority": "4", + "file_category": "directory", + "file_subcategory": "", "files_count": 1, "dirs_count": 0, "size_count": 105451, diff --git a/tests/summarycode/test_file_cat.py b/tests/summarycode/test_file_cat.py index a99afa9beb2..f4e3e2ab74a 100644 --- a/tests/summarycode/test_file_cat.py +++ b/tests/summarycode/test_file_cat.py @@ -2370,4 +2370,6 @@ def test_media01_info(): result_file = test_env.get_temp_file("json") run_scan_click(["--info", "--file-cat", test_dir, "--json", result_file]) expected = test_env.get_test_loc("file_cat/scans/media01/media01-info-scan.json") - check_json_scan(expected, result_file, regen=REGEN_TEST_FIXTURES) + check_json_scan( + expected, result_file, remove_file_date=True, regen=REGEN_TEST_FIXTURES + ) From ecdaeefc02d5b48573d6ce570193227c74eafd09 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Mon, 12 Jun 2023 16:11:19 -0700 Subject: [PATCH 13/13] Corrected setup.cfg #2945 * Add in postscan plugin entry points Signed-off-by: John M. Horan --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c123b89c106..fb6ecd65358 100644 --- a/setup.cfg +++ b/setup.cfg @@ -195,7 +195,10 @@ scancode_post_scan = mark-source = scancode.plugin_mark_source:MarkSource filter-clues = cluecode.plugin_filter_clues:RedundantCluesFilter consolidate = summarycode.plugin_consolidate:Consolidator - + license-references = licensedcode.licenses_reference:LicenseReference + todo = summarycode.todo:AmbiguousDetectionsToDoPlugin + classify = summarycode.classify_plugin:FileClassifier + file-cat = summarycode.file_cat:FileCategorizer # scancode_output_filter is the entry point for filter plugins executed after