#!/usr/bin/env python3 """ Careful refactoring of download_data.py to class-based pattern. Handles nested functions and inner definitions correctly. """ import re from pathlib import Path def refactor_download_data(): backup_file = Path('cmdlets/download_data_backup.py') output_file = Path('cmdlets/download_data.py') print(f"Reading: {backup_file}") content = backup_file.read_text(encoding='utf-8') lines = content.split('\n') output = [] i = 0 in_cmdlet_def = False skip_old_run_wrapper = False class_added = False while i < len(lines): line = lines[i] # Skip old _run wrapper function if line.strip().startswith('def _run(result: Any'): while i < len(lines): i += 1 if lines[i] and not lines[i][0].isspace(): break continue # Skip old CMDLET definition if line.strip().startswith('CMDLET = Cmdlet('): while i < len(lines): i += 1 if lines[i].strip() == ')': i += 1 break output.append('') output.append('# Create and register the cmdlet') output.append('CMDLET = Download_Data()') output.append('') continue # Insert class definition before first top-level helper if not class_added and line.strip().startswith('def _download_torrent_worker('): # Add class header with __init__ and run() output.extend([ '', '', 'class Download_Data(Cmdlet):', ' """Class-based download-data cmdlet with self-registration."""', '', ' def __init__(self) -> None:', ' """Initialize download-data cmdlet."""', ' super().__init__(', ' name="download-data",', ' summary="Download data from url with playlist/clip support using yt-dlp",', ' usage="download-data [options] or search-file | download-data [options]",', ' alias=["download", "dl"],', ' arg=[', ' CmdletArg(name="url", type="string", required=False, description="URL to download (HTTP/HTTPS or file with URL list)", variadic=True),', ' CmdletArg(name="-url", type="string", description="URL to download (alias for positional argument)", variadic=True),', ' CmdletArg(name="list-formats", type="flag", description="List available formats without downloading"),', ' CmdletArg(name="audio", type="flag", alias="a", description="Download audio only (extract from video)"),', ' CmdletArg(name="video", type="flag", alias="v", description="Download video (default if not specified)"),', ' CmdletArg(name="format", type="string", alias="fmt", description="Explicit yt-dlp format selector (e.g., bestvideo+bestaudio)"),', ' CmdletArg(name="clip", type="string", description="Extract time range: MM:SS-MM:SS (e.g., 34:03-35:08) or seconds"),', ' CmdletArg(name="section", type="string", description="Download sections (yt-dlp only): TIME_RANGE[,TIME_RANGE...] (e.g., 1:30-1:35,0:05-0:15)"),', ' CmdletArg(name="cookies", type="string", description="Path to cookies.txt file for authentication"),', ' CmdletArg(name="torrent", type="flag", description="Download torrent/magnet via AllDebrid (requires API key in config)"),', ' CmdletArg(name="wait", type="float", description="Wait time (seconds) for magnet processing timeout"),', ' CmdletArg(name="background", type="flag", alias="bg", description="Start download in background and return to prompt immediately"),', ' CmdletArg(name="item", type="string", alias="items", description="Item selection for playlists/formats: use -item N to select format N, or -item to show table for @N selection in next command"),', ' SharedArgs.STORAGE,', ' ],', ' detail=["Download media from url with advanced features.", "", "See help for full usage examples."],', ' exec=self.run,', ' )', ' self.register()', '', ' def run(self, result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:', ' """Main execution method."""', ' stage_ctx = pipeline_context.get_stage_context()', ' in_pipeline = stage_ctx is not None and getattr(stage_ctx, "total_stages", 1) > 1', ' if in_pipeline and isinstance(config, dict):', ' config["_quiet_background_output"] = True', ' return self._run_impl(result, args, config, emit_results=True)', '', ' # ' + '='*70, ' # HELPER METHODS', ' # ' + '='*70, '', ]) class_added = True # Convert top-level helper functions to static methods if class_added and line and not line[0].isspace() and line.strip().startswith('def _'): output.append(' @staticmethod') output.append(f' {line}') i += 1 # Copy function body with indentation while i < len(lines): next_line = lines[i] # Stop at next top-level definition if next_line and not next_line[0].isspace() and (next_line.strip().startswith(('def ', 'class ', 'CMDLET'))): break # Add indentation if next_line.strip(): output.append(f' {next_line}') else: output.append(next_line) i += 1 continue output.append(line) i += 1 result_text = '\n'.join(output) # NOW: Update function calls carefully # Only update calls in _run_impl, not in nested function definitions # Pattern: match _func( but NOT when it's after "def " on the same line helper_funcs = [ '_download_torrent_worker', '_guess_libgen_title', '_is_libgen_entry', '_download_libgen_entry', '_libgen_background_worker', '_start_libgen_background_worker', '_run_pipeline_tail', '_download_http_background_worker', '_start_http_background_download', '_parse_torrent_file', '_download_torrent_file', '_is_torrent_file_or_url', '_process_torrent_input', '_show_playlist_table', '_parse_time_range', '_parse_section_ranges', '_parse_playlist_selection_indices', '_select_playlist_entries', '_sanitize_title_for_filename', '_find_playlist_files_from_entries', '_snapshot_playlist_paths', '_is_openlibrary_downloadable', '_as_dict', '_is_youtube_url', ] # Split into lines for careful replacement result_lines = result_text.split('\n') for idx, line in enumerate(result_lines): # Skip lines that are function definitions if 'def ' in line: continue # Replace helper function calls with self. for func in helper_funcs: # Pattern: _func( with word boundary before pattern = rf'\b({re.escape(func)})\(' if re.search(pattern, line): result_lines[idx] = re.sub(pattern, r'self.\1(', line) result_text = '\n'.join(result_lines) output_file.write_text(result_text, encoding='utf-8') print(f"✓ Written: {output_file}") print(f"✓ Class-based refactor complete") if __name__ == '__main__': refactor_download_data()