2626from cyclonedx .model .bom import Bom , Tool
2727from cyclonedx .output import BaseOutput , get_instance , OutputFormat , SchemaVersion
2828from cyclonedx .parser import BaseParser
29+ from cyclonedx .parser .conda import CondaListExplicitParser , CondaListJsonParser
2930from cyclonedx .parser .environment import EnvironmentParser
30- from cyclonedx .parser .pipenv import PipEnvFileParser
31- from cyclonedx .parser .poetry import PoetryFileParser
32- from cyclonedx .parser .requirements import RequirementsFileParser
31+ from cyclonedx .parser .pipenv import PipEnvParser
32+ from cyclonedx .parser .poetry import PoetryParser
33+ from cyclonedx .parser .requirements import RequirementsParser
3334
3435
3536class CycloneDxCmd :
@@ -100,6 +101,16 @@ def get_arg_parser() -> argparse.ArgumentParser:
100101 arg_parser = argparse .ArgumentParser (description = 'CycloneDX SBOM Generator' )
101102
102103 input_group = arg_parser .add_mutually_exclusive_group (required = True )
104+ input_group .add_argument (
105+ '-c' , '--conda' , action = 'store_true' ,
106+ help = 'Build a SBOM based on the output from `conda list --explicit` or `conda list --explicit --md5`' ,
107+ dest = 'input_from_conda_explicit'
108+ )
109+ input_group .add_argument (
110+ '-cj' , '--conda-json' , action = 'store_true' ,
111+ help = 'Build a SBOM based on the output from `conda list --json`' ,
112+ dest = 'input_from_conda_json'
113+ )
103114 input_group .add_argument (
104115 '-e' , '--e' , '--environment' , action = 'store_true' ,
105116 help = 'Build a SBOM based on the packages installed in your current Python environment (default)' ,
@@ -124,34 +135,14 @@ def get_arg_parser() -> argparse.ArgumentParser:
124135 dest = 'input_from_requirements'
125136 )
126137
127- req_input_group = arg_parser .add_argument_group (
128- title = 'Poetry' ,
129- description = 'Additional optional arguments if you are setting the input type to `poetry`'
130- )
131- req_input_group .add_argument (
132- '-pf' , '--pf' , '--poetry-file' , action = 'store' , metavar = 'FILE_PATH' , default = 'poetry.lock' ,
133- help = 'Path to a the `poetry.lock` file you wish to parse' ,
134- dest = 'input_poetry_file' , required = False
135- )
136-
137- req_input_group = arg_parser .add_argument_group (
138- title = 'PipEnv' ,
139- description = 'Additional optional arguments if you are setting the input type to `pipenv`'
138+ input_method_group = arg_parser .add_argument_group (
139+ title = 'Input Method' ,
140+ description = 'Flags to determine how `cyclonedx-bom` obtains it\' s input'
140141 )
141- req_input_group .add_argument (
142- '--pip-file' , action = 'store' , metavar = 'FILE_PATH' , default = 'Pipfile.lock' ,
143- help = 'Path to a the `Pipfile.lock` file you wish to parse' ,
144- dest = 'input_pipenv_file' , required = False
145- )
146-
147- req_input_group = arg_parser .add_argument_group (
148- title = 'Requirements' ,
149- description = 'Additional optional arguments if you are setting the input type to `requirements`.'
150- )
151- req_input_group .add_argument (
152- '-rf' , '--rf' , '--requirements-file' , action = 'store' , metavar = 'FILE_PATH' , default = 'requirements.txt' ,
153- help = 'Path to a the `requirements.txt` file you wish to parse' ,
154- dest = 'input_requirements_file' , required = False
142+ input_method_group .add_argument (
143+ '-i' , '--in-file' , action = 'store' , metavar = 'FILE_PATH' ,
144+ type = argparse .FileType ('r' ), default = (None if sys .stdin .isatty () else sys .stdin ),
145+ help = 'File to read input from, or STDIN if not specified' , dest = 'input_source' , required = False
155146 )
156147
157148 output_group = arg_parser .add_argument_group (
@@ -193,38 +184,26 @@ def _error_and_exit(message: str, exit_code: int = 1):
193184 def _get_input_parser (self ) -> BaseParser :
194185 if self ._arguments .input_from_environment :
195186 return EnvironmentParser ()
187+
188+ # All other Parsers will require some input - grab it now!
189+ input_data_fh = self ._arguments .input_source
190+ with input_data_fh :
191+ input_data = input_data_fh .read ()
192+ input_data_fh .close ()
193+
194+ if self ._arguments .input_from_conda_explicit :
195+ return CondaListExplicitParser (conda_data = input_data )
196+ elif self ._arguments .input_from_conda_json :
197+ return CondaListJsonParser (conda_data = input_data )
196198 elif self ._arguments .input_from_pip :
197- pipfile_lock_file = os .path .realpath (self ._arguments .input_pipenv_file )
198- if CycloneDxCmd ._validate_file_exists (self ._arguments .input_pipenv_file ):
199- # A Pipfile.lock path was provided
200- return PipEnvFileParser (pipenv_lock_filename = pipfile_lock_file )
201- else :
202- self ._error_and_exit (f'The provided file \' { pipfile_lock_file } \' does not exist' )
199+ return PipEnvParser (pipenv_contents = input_data )
203200 elif self ._arguments .input_from_poetry :
204- poetry_lock_file = os .path .realpath (self ._arguments .input_poetry_file )
205- if CycloneDxCmd ._validate_file_exists (self ._arguments .input_poetry_file ):
206- # A poetry.lock path was provided
207- return PoetryFileParser (poetry_lock_filename = poetry_lock_file )
208- else :
209- self ._error_and_exit ('The provided file \' {}\' does not exist' .format (
210- poetry_lock_file
211- ))
201+ return PoetryParser (poetry_lock_contents = input_data )
212202 elif self ._arguments .input_from_requirements :
213- requirements_file = os .path .realpath (self ._arguments .input_requirements_file )
214- if CycloneDxCmd ._validate_file_exists (self ._arguments .input_requirements_file ):
215- # A requirements.txt path was provided
216- return RequirementsFileParser (requirements_file = requirements_file )
217- else :
218- self ._error_and_exit ('The provided file \' {}\' does not exist' .format (
219- requirements_file
220- ))
203+ return RequirementsParser (requirements_content = input_data )
221204 else :
222205 raise ValueError ('Parser type could not be determined.' )
223206
224- @staticmethod
225- def _validate_file_exists (file_path : str ) -> bool :
226- return os .path .exists (file_path )
227-
228207
229208def main ():
230209 parser = CycloneDxCmd .get_arg_parser ()
0 commit comments