kopipasta 0.13.0__py3-none-any.whl → 0.15.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kopipasta might be problematic. Click here for more details.

kopipasta/main.py CHANGED
@@ -4,14 +4,15 @@ import io
4
4
  import json
5
5
  import os
6
6
  import argparse
7
- import ast
8
7
  import re
9
- from textwrap import dedent
8
+ from typing import Dict, List, Optional, Tuple
10
9
  import pyperclip
11
10
  import fnmatch
12
11
 
13
12
  import requests
14
13
 
14
+ FileTuple = Tuple[str, bool, Optional[List[str]], str]
15
+
15
16
  def read_gitignore():
16
17
  default_ignore_patterns = [
17
18
  '.git', 'node_modules', 'venv', '.venv', 'dist', '.idea', '__pycache__',
@@ -425,7 +426,7 @@ def print_char_count(count):
425
426
  token_estimate = count // 4
426
427
  print(f"\rCurrent prompt size: {count} characters (~ {token_estimate} tokens)", flush=True)
427
428
 
428
- def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
429
+ def select_files_in_directory(directory: str, ignore_patterns: List[str], current_char_count: int = 0) -> Tuple[List[FileTuple], int]:
429
430
  files = [f for f in os.listdir(directory)
430
431
  if os.path.isfile(os.path.join(directory, f)) and not is_ignored(os.path.join(directory, f), ignore_patterns) and not is_binary(os.path.join(directory, f))]
431
432
 
@@ -445,8 +446,8 @@ def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
445
446
  while True:
446
447
  print_char_count(current_char_count)
447
448
  choice = input("(y)es add all / (n)o ignore all / (s)elect individually / (q)uit? ").lower()
449
+ selected_files: List[FileTuple] = []
448
450
  if choice == 'y':
449
- selected_files = []
450
451
  for file in files:
451
452
  file_path = os.path.join(directory, file)
452
453
  if is_large_file(file_path):
@@ -456,13 +457,13 @@ def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
456
457
  break
457
458
  print("Invalid choice. Please enter 'f' or 's'.")
458
459
  if snippet_choice == 's':
459
- selected_files.append((file, True))
460
+ selected_files.append((file, True, None, get_language_for_file(file)))
460
461
  current_char_count += len(get_file_snippet(file_path))
461
462
  else:
462
- selected_files.append((file, False))
463
+ selected_files.append((file, False, None, get_language_for_file(file)))
463
464
  current_char_count += os.path.getsize(file_path)
464
465
  else:
465
- selected_files.append((file, False))
466
+ selected_files.append((file, False, None, get_language_for_file(file)))
466
467
  current_char_count += os.path.getsize(file_path)
467
468
  print(f"Added all files from {directory}")
468
469
  return selected_files, current_char_count
@@ -470,7 +471,6 @@ def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
470
471
  print(f"Ignored all files from {directory}")
471
472
  return [], current_char_count
472
473
  elif choice == 's':
473
- selected_files = []
474
474
  for file in files:
475
475
  file_path = os.path.join(directory, file)
476
476
  file_size = os.path.getsize(file_path)
@@ -489,13 +489,13 @@ def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
489
489
  break
490
490
  print("Invalid choice. Please enter 'f' or 's'.")
491
491
  if snippet_choice == 's':
492
- selected_files.append((file, True))
492
+ selected_files.append((file, True, None, get_language_for_file(file_path)))
493
493
  current_char_count += len(get_file_snippet(file_path))
494
494
  else:
495
- selected_files.append((file, False))
495
+ selected_files.append((file, False, None, get_language_for_file(file_path)))
496
496
  current_char_count += file_char_estimate
497
497
  else:
498
- selected_files.append((file, False))
498
+ selected_files.append((file, False, None, get_language_for_file(file_path)))
499
499
  current_char_count += file_char_estimate
500
500
  break
501
501
  elif file_choice == 'n':
@@ -503,7 +503,7 @@ def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
503
503
  elif file_choice == 'p':
504
504
  chunks, char_count = select_file_patches(file_path)
505
505
  if chunks:
506
- selected_files.append((file_path, False, chunks))
506
+ selected_files.append((file_path, False, chunks, get_language_for_file(file_path)))
507
507
  current_char_count += char_count
508
508
  break
509
509
  elif file_choice == 'q':
@@ -520,7 +520,7 @@ def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
520
520
  print("Invalid choice. Please try again.")
521
521
 
522
522
  def process_directory(directory, ignore_patterns, current_char_count=0):
523
- files_to_include = []
523
+ files_to_include: List[FileTuple] = []
524
524
  processed_dirs = set()
525
525
 
526
526
  for root, dirs, files in os.walk(directory):
@@ -530,15 +530,26 @@ def process_directory(directory, ignore_patterns, current_char_count=0):
530
530
  if root in processed_dirs:
531
531
  continue
532
532
 
533
- selected_files, current_char_count = select_files_in_directory(root, ignore_patterns, current_char_count)
534
- for file_tuple in selected_files:
535
- if len(file_tuple) == 3:
536
- f, use_snippet, chunks = file_tuple
537
- files_to_include.append((os.path.join(root, f), use_snippet, chunks))
538
- else:
539
- f, use_snippet = file_tuple
540
- files_to_include.append((os.path.join(root, f), use_snippet))
541
- processed_dirs.add(root)
533
+ print(f"\nExploring directory: {root}")
534
+ choice = input("(y)es explore / (n)o skip / (q)uit? ").lower()
535
+ if choice == 'y':
536
+ selected_files, current_char_count = select_files_in_directory(root, ignore_patterns, current_char_count)
537
+ for file_tuple in selected_files:
538
+ if len(file_tuple) == 3:
539
+ f, use_snippet, chunks = file_tuple
540
+ files_to_include.append((os.path.join(root, f), use_snippet, chunks))
541
+ else:
542
+ f, use_snippet = file_tuple
543
+ files_to_include.append((os.path.join(root, f), use_snippet))
544
+ processed_dirs.add(root)
545
+ elif choice == 'n':
546
+ dirs[:] = [] # Skip all subdirectories
547
+ continue
548
+ elif choice == 'q':
549
+ break
550
+ else:
551
+ print("Invalid choice. Skipping this directory.")
552
+ continue
542
553
 
543
554
  return files_to_include, processed_dirs, current_char_count
544
555
 
@@ -659,30 +670,29 @@ def handle_env_variables(content, env_vars):
659
670
 
660
671
  return content
661
672
 
662
- def generate_prompt(files_to_include, ignore_patterns, web_contents, env_vars):
673
+ def generate_prompt(files_to_include: List[FileTuple], ignore_patterns: List[str], web_contents: Dict[str, Tuple[str, str]], env_vars: Dict[str, str]) -> str:
663
674
  prompt = "# Project Overview\n\n"
664
675
  prompt += "## Project Structure\n\n"
665
676
  prompt += "```\n"
666
677
  prompt += get_project_structure(ignore_patterns)
667
678
  prompt += "\n```\n\n"
668
679
  prompt += "## File Contents\n\n"
669
- for file_tuple in files_to_include:
670
- if len(file_tuple) == 4:
671
- file, content, is_snippet, content_type = file_tuple
672
- chunks = None
673
- else:
674
- file, content, is_snippet, content_type, chunks = file_tuple
680
+ for file, use_snippet, chunks, content_type in files_to_include:
675
681
  relative_path = get_relative_path(file)
676
- language = get_language_for_file(file) if content_type == 'text' else content_type
682
+ language = content_type if content_type else get_language_for_file(file)
677
683
 
678
684
  if chunks is not None:
679
685
  prompt += f"### {relative_path} (selected patches)\n\n```{language}\n"
680
686
  for chunk in chunks:
681
687
  prompt += f"{chunk}\n"
682
688
  prompt += "```\n\n"
689
+ elif use_snippet:
690
+ file_content = get_file_snippet(file)
691
+ prompt += f"### {relative_path} (snippet)\n\n```{language}\n{file_content}\n```\n\n"
683
692
  else:
684
- content = handle_env_variables(content, env_vars)
685
- prompt += f"### {relative_path}{' (snippet)' if is_snippet else ''}\n\n```{language}\n{content}\n```\n\n"
693
+ file_content = read_file_contents(file)
694
+ file_content = handle_env_variables(file_content, env_vars)
695
+ prompt += f"### {relative_path}\n\n```{language}\n{file_content}\n```\n\n"
686
696
 
687
697
  if web_contents:
688
698
  prompt += "## Web Content\n\n"
@@ -711,79 +721,38 @@ def main():
711
721
  ignore_patterns = read_gitignore()
712
722
  env_vars = read_env_file()
713
723
 
714
- files_to_include = []
724
+ files_to_include:List[FileTuple] = []
725
+ processed_dirs = set()
715
726
  web_contents = {}
716
-
717
- def process_directory(directory):
718
- files = [f for f in os.listdir(directory)
719
- if os.path.isfile(os.path.join(directory, f)) and not is_ignored(os.path.join(directory, f), ignore_patterns) and not is_binary(os.path.join(directory, f))]
720
-
721
- if not files:
722
- return []
723
-
724
- print(f"\nDirectory: {directory}")
725
- print("Files:")
726
- for file in files:
727
- file_path = os.path.join(directory, file)
728
- file_size = os.path.getsize(file_path)
729
- file_size_readable = get_human_readable_size(file_size)
730
- print(f"- {file} ({file_size_readable})")
731
-
732
- while True:
733
- choice = input("(y)es add all / (n)o ignore all / (s)elect individually / (q)uit? ").lower()
734
- if choice == 'y':
735
- return [(os.path.join(directory, f), False) for f in files]
736
- elif choice == 'n':
737
- return []
738
- elif choice == 's':
739
- selected_files = []
740
- for file in files:
741
- file_path = os.path.join(directory, file)
742
- while True:
743
- file_choice = input(f"{file} (y/n/p/q)? ").lower()
744
- if file_choice == 'y':
745
- selected_files.append((file_path, False))
746
- break
747
- elif file_choice == 'n':
748
- break
749
- elif file_choice == 'p':
750
- chunks, _ = select_file_patches(file_path)
751
- if chunks:
752
- selected_files.append((file_path, True, chunks))
753
- break
754
- elif file_choice == 'q':
755
- return selected_files
756
- else:
757
- print("Invalid choice. Please enter 'y', 'n', 'p', or 'q'.")
758
- return selected_files
759
- elif choice == 'q':
760
- return []
761
- else:
762
- print("Invalid choice. Please try again.")
727
+ current_char_count = 0
763
728
 
764
729
  for input_path in args.inputs:
765
730
  if input_path.startswith(('http://', 'https://')):
766
- content, content_type = fetch_web_content(input_path)
767
- if content:
768
- content, is_snippet = handle_content(content, content_type, input_path)
769
- web_contents[input_path] = (content, is_snippet, content_type)
731
+ full_content, snippet = fetch_web_content(input_path)
732
+ if full_content:
733
+ web_contents[input_path] = (full_content, snippet)
734
+ current_char_count += len(snippet if len(full_content) > 10000 else full_content)
770
735
  print(f"Added web content from: {input_path}")
771
736
  elif os.path.isfile(input_path):
772
737
  if not is_ignored(input_path, ignore_patterns) and not is_binary(input_path):
773
738
  while True:
774
739
  file_choice = input(f"{input_path} (y)es include / (n)o skip / (p)atches / (q)uit? ").lower()
775
740
  if file_choice == 'y':
776
- content, content_type = read_file_content(input_path)
777
- content, is_snippet = handle_content(content, content_type, input_path)
778
- files_to_include.append((input_path, content, is_snippet, content_type))
779
- print(f"Added file: {input_path}{' (snippet)' if is_snippet else ''}")
741
+ use_snippet = is_large_file(input_path)
742
+ files_to_include.append((input_path, use_snippet, None, get_language_for_file(input_path)))
743
+ if use_snippet:
744
+ current_char_count += len(get_file_snippet(input_path))
745
+ else:
746
+ current_char_count += os.path.getsize(input_path)
747
+ print(f"Added file: {input_path}{' (snippet)' if use_snippet else ''}")
780
748
  break
781
749
  elif file_choice == 'n':
782
750
  break
783
751
  elif file_choice == 'p':
784
- chunks, _ = select_file_patches(input_path)
752
+ chunks, char_count = select_file_patches(input_path)
785
753
  if chunks:
786
- files_to_include.append((input_path, None, False, 'text', chunks))
754
+ files_to_include.append((input_path, False, chunks, get_language_for_file(input_path)))
755
+ current_char_count += char_count
787
756
  break
788
757
  elif file_choice == 'q':
789
758
  print("Quitting.")
@@ -793,16 +762,9 @@ def main():
793
762
  else:
794
763
  print(f"Ignored file: {input_path}")
795
764
  elif os.path.isdir(input_path):
796
- selected_files = process_directory(input_path)
797
- for file_info in selected_files:
798
- if len(file_info) == 2:
799
- file_path, use_snippet = file_info
800
- content, content_type = read_file_content(file_path)
801
- content, is_snippet = handle_content(content, content_type, file_path)
802
- files_to_include.append((file_path, content, is_snippet, content_type))
803
- else:
804
- file_path, _, chunks = file_info
805
- files_to_include.append((file_path, None, False, 'text', chunks))
765
+ dir_files, dir_processed, current_char_count = process_directory(input_path, ignore_patterns, current_char_count)
766
+ files_to_include.extend(dir_files)
767
+ processed_dirs.update(dir_processed)
806
768
  else:
807
769
  print(f"Warning: {input_path} is not a valid file, directory, or URL. Skipping.")
808
770
 
@@ -811,7 +773,8 @@ def main():
811
773
  return
812
774
 
813
775
  print("\nFile and web content selection complete.")
814
- print(f"Summary: Added {len(files_to_include)} files and {len(web_contents)} web sources.")
776
+ print_char_count(current_char_count)
777
+ print(f"Summary: Added {len(files_to_include)} files from {len(processed_dirs)} directories and {len(web_contents)} web sources.")
815
778
 
816
779
  prompt = generate_prompt(files_to_include, ignore_patterns, web_contents, env_vars)
817
780
  print("\n\nGenerated prompt:")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kopipasta
3
- Version: 0.13.0
3
+ Version: 0.15.0
4
4
  Summary: A CLI tool to generate prompts with project structure and file contents
5
5
  Home-page: https://github.com/mkorpela/kopipasta
6
6
  Author: Mikko Korpela
@@ -0,0 +1,8 @@
1
+ kopipasta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ kopipasta/main.py,sha256=51k6jZXChwZY_CYVnqCqeY_hoHCAj3flN8qnr0vEVQI,31652
3
+ kopipasta-0.15.0.dist-info/LICENSE,sha256=xw4C9TAU7LFu4r_MwSbky90uzkzNtRwAo3c51IWR8lk,1091
4
+ kopipasta-0.15.0.dist-info/METADATA,sha256=ehTVHvX8IWh7hISa0O3bxJqj4a7gBqX9H0JtU1xU2ys,5646
5
+ kopipasta-0.15.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
6
+ kopipasta-0.15.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
7
+ kopipasta-0.15.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
8
+ kopipasta-0.15.0.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- kopipasta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- kopipasta/main.py,sha256=Z5_iI0dDp8WeRLp_FMDBPzNWrUiHNIteOcPl_fh7e4k,32860
3
- kopipasta-0.13.0.dist-info/LICENSE,sha256=xw4C9TAU7LFu4r_MwSbky90uzkzNtRwAo3c51IWR8lk,1091
4
- kopipasta-0.13.0.dist-info/METADATA,sha256=80uGAXCQckifJnEkXCsgu5uNkFRFO7CWuwSi9wAVbDE,5646
5
- kopipasta-0.13.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
6
- kopipasta-0.13.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
7
- kopipasta-0.13.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
8
- kopipasta-0.13.0.dist-info/RECORD,,