kopipasta 0.14.0__tar.gz → 0.16.0__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kopipasta
3
- Version: 0.14.0
3
+ Version: 0.16.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
@@ -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, Set, 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':
@@ -519,9 +519,9 @@ def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
519
519
  else:
520
520
  print("Invalid choice. Please try again.")
521
521
 
522
- def process_directory(directory, ignore_patterns, current_char_count=0):
523
- files_to_include = []
524
- processed_dirs = set()
522
+ def process_directory(directory: str, ignore_patterns: List[str], current_char_count: int = 0) -> Tuple[List[FileTuple], Set[str], int]:
523
+ files_to_include: List[FileTuple] = []
524
+ processed_dirs: Set[str] = set()
525
525
 
526
526
  for root, dirs, files in os.walk(directory):
527
527
  dirs[:] = [d for d in dirs if not is_ignored(os.path.join(root, d), ignore_patterns)]
@@ -535,12 +535,8 @@ def process_directory(directory, ignore_patterns, current_char_count=0):
535
535
  if choice == 'y':
536
536
  selected_files, current_char_count = select_files_in_directory(root, ignore_patterns, current_char_count)
537
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))
538
+ full_path = os.path.join(root, file_tuple[0])
539
+ files_to_include.append((full_path, file_tuple[1], file_tuple[2], file_tuple[3]))
544
540
  processed_dirs.add(root)
545
541
  elif choice == 'n':
546
542
  dirs[:] = [] # Skip all subdirectories
@@ -553,20 +549,25 @@ def process_directory(directory, ignore_patterns, current_char_count=0):
553
549
 
554
550
  return files_to_include, processed_dirs, current_char_count
555
551
 
556
- def fetch_web_content(url):
552
+ def fetch_web_content(url: str) -> Tuple[Optional[FileTuple], Optional[str], Optional[str]]:
557
553
  try:
558
554
  response = requests.get(url)
559
555
  response.raise_for_status()
560
556
  content_type = response.headers.get('content-type', '').lower()
557
+ full_content = response.text
558
+ snippet = full_content[:10000] + "..." if len(full_content) > 10000 else full_content
559
+
561
560
  if 'json' in content_type:
562
- return response.json(), 'json'
561
+ content_type = 'json'
563
562
  elif 'csv' in content_type:
564
- return response.text, 'csv'
563
+ content_type = 'csv'
565
564
  else:
566
- return response.text, 'text'
565
+ content_type = 'text'
566
+
567
+ return (url, False, None, content_type), full_content, snippet
567
568
  except requests.RequestException as e:
568
569
  print(f"Error fetching content from {url}: {e}")
569
- return None, None
570
+ return None, None, None
570
571
 
571
572
  def read_file_content(file_path):
572
573
  _, ext = os.path.splitext(file_path)
@@ -670,34 +671,34 @@ def handle_env_variables(content, env_vars):
670
671
 
671
672
  return content
672
673
 
673
- def generate_prompt(files_to_include, ignore_patterns, web_contents, env_vars):
674
+ def generate_prompt(files_to_include: List[FileTuple], ignore_patterns: List[str], web_contents: Dict[str, Tuple[FileTuple, str]], env_vars: Dict[str, str]) -> str:
674
675
  prompt = "# Project Overview\n\n"
675
676
  prompt += "## Project Structure\n\n"
676
677
  prompt += "```\n"
677
678
  prompt += get_project_structure(ignore_patterns)
678
679
  prompt += "\n```\n\n"
679
680
  prompt += "## File Contents\n\n"
680
- for file_tuple in files_to_include:
681
- if len(file_tuple) == 4:
682
- file, content, is_snippet, content_type = file_tuple
683
- chunks = None
684
- else:
685
- file, content, is_snippet, content_type, chunks = file_tuple
681
+ for file, use_snippet, chunks, content_type in files_to_include:
686
682
  relative_path = get_relative_path(file)
687
- language = get_language_for_file(file) if content_type == 'text' else content_type
683
+ language = content_type if content_type else get_language_for_file(file)
688
684
 
689
685
  if chunks is not None:
690
686
  prompt += f"### {relative_path} (selected patches)\n\n```{language}\n"
691
687
  for chunk in chunks:
692
688
  prompt += f"{chunk}\n"
693
689
  prompt += "```\n\n"
690
+ elif use_snippet:
691
+ file_content = get_file_snippet(file)
692
+ prompt += f"### {relative_path} (snippet)\n\n```{language}\n{file_content}\n```\n\n"
694
693
  else:
695
- content = handle_env_variables(content, env_vars)
696
- prompt += f"### {relative_path}{' (snippet)' if is_snippet else ''}\n\n```{language}\n{content}\n```\n\n"
694
+ file_content = read_file_contents(file)
695
+ file_content = handle_env_variables(file_content, env_vars)
696
+ prompt += f"### {relative_path}\n\n```{language}\n{file_content}\n```\n\n"
697
697
 
698
698
  if web_contents:
699
699
  prompt += "## Web Content\n\n"
700
- for url, (content, is_snippet, content_type) in web_contents.items():
700
+ for url, (file_tuple, content) in web_contents.items():
701
+ _, is_snippet, _, content_type = file_tuple
701
702
  content = handle_env_variables(content, env_vars)
702
703
  language = content_type if content_type in ['json', 'csv'] else ''
703
704
  prompt += f"### {url}{' (snippet)' if is_snippet else ''}\n\n```{language}\n{content}\n```\n\n"
@@ -722,25 +723,52 @@ def main():
722
723
  ignore_patterns = read_gitignore()
723
724
  env_vars = read_env_file()
724
725
 
725
- files_to_include = []
726
+ files_to_include: List[FileTuple] = []
726
727
  processed_dirs = set()
727
- web_contents = {}
728
+ web_contents: Dict[str, Tuple[FileTuple, str]] = {}
728
729
  current_char_count = 0
729
730
 
730
731
  for input_path in args.inputs:
731
732
  if input_path.startswith(('http://', 'https://')):
732
- full_content, snippet = fetch_web_content(input_path)
733
- if full_content:
734
- web_contents[input_path] = (full_content, snippet)
735
- current_char_count += len(snippet if len(full_content) > 10000 else full_content)
736
- print(f"Added web content from: {input_path}")
733
+ result = fetch_web_content(input_path)
734
+ if result:
735
+ file_tuple, full_content, snippet = result
736
+ is_large = len(full_content) > 10000
737
+ if is_large:
738
+ print(f"\nContent from {input_path} is large. Here's a snippet:\n")
739
+ print(snippet)
740
+ print("\n" + "-"*40 + "\n")
741
+
742
+ while True:
743
+ choice = input("Use (f)ull content or (s)nippet? ").lower()
744
+ if choice in ['f', 's']:
745
+ break
746
+ print("Invalid choice. Please enter 'f' or 's'.")
747
+
748
+ if choice == 'f':
749
+ content = full_content
750
+ is_snippet = False
751
+ print("Using full content.")
752
+ else:
753
+ content = snippet
754
+ is_snippet = True
755
+ print("Using snippet.")
756
+ else:
757
+ content = full_content
758
+ is_snippet = False
759
+ print(f"Content from {input_path} is not large. Using full content.")
760
+
761
+ file_tuple = (file_tuple[0], is_snippet, file_tuple[2], file_tuple[3])
762
+ web_contents[input_path] = (file_tuple, content)
763
+ current_char_count += len(content)
764
+ print(f"Added {'snippet of ' if is_snippet else ''}web content from: {input_path}")
737
765
  elif os.path.isfile(input_path):
738
766
  if not is_ignored(input_path, ignore_patterns) and not is_binary(input_path):
739
767
  while True:
740
768
  file_choice = input(f"{input_path} (y)es include / (n)o skip / (p)atches / (q)uit? ").lower()
741
769
  if file_choice == 'y':
742
770
  use_snippet = is_large_file(input_path)
743
- files_to_include.append((input_path, use_snippet))
771
+ files_to_include.append((input_path, use_snippet, None, get_language_for_file(input_path)))
744
772
  if use_snippet:
745
773
  current_char_count += len(get_file_snippet(input_path))
746
774
  else:
@@ -752,7 +780,7 @@ def main():
752
780
  elif file_choice == 'p':
753
781
  chunks, char_count = select_file_patches(input_path)
754
782
  if chunks:
755
- files_to_include.append((input_path, False, chunks))
783
+ files_to_include.append((input_path, False, chunks, get_language_for_file(input_path)))
756
784
  current_char_count += char_count
757
785
  break
758
786
  elif file_choice == 'q':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kopipasta
3
- Version: 0.14.0
3
+ Version: 0.16.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
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="kopipasta",
8
- version="0.14.0",
8
+ version="0.16.0",
9
9
  author="Mikko Korpela",
10
10
  author_email="mikko.korpela@gmail.com",
11
11
  description="A CLI tool to generate prompts with project structure and file contents",
File without changes
File without changes
File without changes
File without changes
File without changes