| 21 | |
| 22 | |
| 23 | class CSVStack(CSVKitUtility): |
| 24 | description = 'Stack up the rows from multiple CSV files, optionally adding a grouping value.' |
| 25 | # Override 'f' because the utility accepts multiple files. |
| 26 | override_flags = ['f', 'L', 'I'] |
| 27 | |
| 28 | def add_arguments(self): |
| 29 | self.argparser.add_argument( |
| 30 | metavar='FILE', nargs='*', dest='input_paths', default=['-'], |
| 31 | help='The CSV file(s) to operate on. If omitted, will accept input as piped data via STDIN.') |
| 32 | self.argparser.add_argument( |
| 33 | '-g', '--groups', dest='groups', |
| 34 | help='A comma-separated list of values to add as "grouping factors", one per CSV being stacked. These are ' |
| 35 | 'added to the output as a new column. You may specify a name for the new column using the -n flag.') |
| 36 | self.argparser.add_argument( |
| 37 | '-n', '--group-name', dest='group_name', |
| 38 | help='A name for the grouping column, e.g. "year". Only used when also specifying -g.') |
| 39 | self.argparser.add_argument( |
| 40 | '--filenames', dest='group_by_filenames', action='store_true', |
| 41 | help='Use the filename of each input file as its grouping value. When specified, -g will be ignored.') |
| 42 | |
| 43 | def main(self): |
| 44 | if isatty(sys.stdin) and self.args.input_paths == ['-']: |
| 45 | sys.stderr.write('No input file or piped data provided. Waiting for standard input:\n') |
| 46 | |
| 47 | has_groups = self.args.groups is not None or self.args.group_by_filenames |
| 48 | |
| 49 | if self.args.groups is not None and not self.args.group_by_filenames: |
| 50 | groups = self.args.groups.split(',') |
| 51 | |
| 52 | if len(groups) != len(self.args.input_paths): |
| 53 | self.argparser.error( |
| 54 | 'The number of grouping values must be equal to the number of CSV files being stacked.') |
| 55 | else: |
| 56 | groups = None |
| 57 | |
| 58 | group_name = self.args.group_name if self.args.group_name else 'group' |
| 59 | use_fieldnames = not self.args.no_header_row |
| 60 | |
| 61 | if use_fieldnames: |
| 62 | Reader = agate.csv.DictReader |
| 63 | else: |
| 64 | Reader = agate.csv.reader |
| 65 | |
| 66 | headers = [] |
| 67 | stdin_fieldnames = [] |
| 68 | stdin_first_row = [] |
| 69 | |
| 70 | for path in self.args.input_paths: |
| 71 | f = self._open_input_file(path) |
| 72 | file_is_stdin = path == '-' |
| 73 | |
| 74 | _skip_lines(f, self.args) |
| 75 | rows = Reader(f, **self.reader_kwargs) |
| 76 | |
| 77 | if use_fieldnames: |
| 78 | if rows.fieldnames: |
| 79 | for field in rows.fieldnames: |
| 80 | if field not in headers: |
no outgoing calls
no test coverage detected