Source code for statick_tool.discovery_plugin
"""Discovery plugin."""
import logging
import os
import subprocess
import sys
from typing import Any, Optional, Union
from statick_tool.exceptions import Exceptions
from statick_tool.package import Package
from statick_tool.plugin_context import PluginContext
[docs]
class DiscoveryPlugin:
"""Default implementation of discovery plugin."""
plugin_context = None
[docs]
def get_name(self) -> Optional[str]:
"""Get name of plugin.
Returns:
Name of plugin.
"""
[docs]
@classmethod
def get_discovery_dependencies(cls) -> list[str]:
"""Get a list of discovery plugins that must run before this one.
Returns:
List of discovery plugin names.
"""
return []
[docs]
def gather_args(self, args: Any) -> None:
"""Gather arguments for plugin.
Args:
args: Flags for plugins will be added to existing arguments.
"""
[docs]
def scan(
self, package: Package, level: str, exceptions: Optional[Exceptions] = None
) -> None:
"""Scan package to discover files for analysis.
If exceptions is passed, then the plugin should (if practical) use it to filter
which files the plugin detects.
Args:
package: Package to scan.
level: Level at which to scan.
exceptions: Exceptions to apply to discovery.
"""
[docs]
def find_files(self, package: Package) -> None:
"""Walk the package path exactly once to discover files for analysis.
Args:
package: Package to scan.
"""
if package._walked: # pylint: disable=protected-access
return
for root, _, files in os.walk(package.path):
for fname in files:
full_path = os.path.join(root, fname)
abs_path = os.path.abspath(full_path)
file_output = self.get_file_cmd_output(full_path)
file_dict = {
"name": fname.lower(),
"path": abs_path,
"file_cmd_out": file_output,
}
package.files[abs_path] = file_dict
package._walked = True # pylint: disable=protected-access
[docs]
def get_file_cmd_output(self, full_path: str) -> str:
"""Run the file command (if it exists) on the supplied path.
The output from the file command is converted to lowercase.
There are two recommended ways to check it:
1. When searching for a single string just use the python "in" operator:
if "search string" in file_dict["file_cmd_out"]:
2. When searching for multiple different strings, use the `any()` function:
expected_output = ("output_1", "output_2")
if any(item in file_dict["file_cmd_out"] for item in expected_output):
Args:
full_path: Full path to file.
Returns:
Output of file command.
"""
if not self.file_command_exists():
return ""
try:
output: str = subprocess.check_output(
["file", full_path], universal_newlines=True
)
return output.lower()
except subprocess.CalledProcessError as ex:
logging.warning(
"Failed to run 'file' command. Returncode = %d", ex.returncode
)
logging.warning("Exception output: %s", ex.output)
return ""
except OSError:
logging.warning("OSError on file command for %s", full_path)
return ""
[docs]
def set_plugin_context(self, plugin_context: Union[None, PluginContext]) -> None:
"""Set the plugin context.
Args:
plugin_context: The plugin context.
"""
self.plugin_context = plugin_context
[docs]
@staticmethod
def file_command_exists() -> bool:
"""Return whether the 'file' command is available on $PATH.
Returns:
True if the 'file' command is available on $PATH, False otherwise.
"""
if sys.platform == "win32":
command_name = "file.exe"
else:
command_name = "file"
for path in os.environ["PATH"].split(os.pathsep):
exe_path = os.path.join(path, command_name)
if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
return True
return False