Compare commits

..

3 commits

Author SHA1 Message Date
e418f58f44 feat: start implementing pack and init commands
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-04 11:53:50 +01:00
ca4485b1ca chore: enable LFS 2024-01-04 11:53:26 +01:00
d873083ad8 docs: add example prompt 2024-01-04 11:52:59 +01:00
4 changed files with 253 additions and 77 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
*.svg filter=lfs diff=lfs merge=lfs -text

View file

@ -0,0 +1,15 @@
@@REQUEST
I would like to change CLI parameters structure add add two subcommands:
* init : which will create an example .code_preloader.yml file (with comments)
* pack : which will create the packed version of the current directory for LLM prompting
Most of current options (except --version and --help) must become options of the pack subcommand.
I already started some changes to achieve this goal, but it is not finished, and I need your help and expert advises.
Can you please tell me :
* where the changes should occur (which File? which Class? and which method?)
* what kind of changes must be made there?
Please do not write code yet, simply explain.

View file

@ -18,20 +18,52 @@ module CodePreloader
def initialize(args) def initialize(args)
@output_file_path = "" @output_file_path = ""
@config = Config.new() @config = Config.new()
@config.detect_config()
@config.parse_arguments(args) @config.parse_arguments(args)
end end
# Executes the main functionality of the CLI application. # Executes the main functionality of the CLI application.
def exec def exec
# get local values for typing case @config.subcommand
output_file_path = @output_file_path when Config::Subcommand::Init then exec_init(@config.init_options)
repository_path_list = @config.repository_path_list when Config::Subcommand::Pack then exec_pack(@config.pack_options)
header_prompt_file_path = @config.header_prompt_file_path when Config::Subcommand::Version then exec_version
footer_prompt_file_path = @config.footer_prompt_file_path when Config::Subcommand::Help then exec_help
when Config::Subcommand::None then exec_none
else
abort("Unknown subcommand #{@config.subcommand}!")
end
end
def exec_init(init_options)
abort("Unexpected nil value for init_options!") if init_options.nil?
abort("FIXME: Not implemented!")
end
def exec_version
abort("FIXME: Not implemented!")
end
def exec_none
abort("No command specified!")
end
def exec_help
abort("FIXME: Not implemented!")
end
def exec_pack(pack_options)
abort("Unexpected nil value for pack_options!") if pack_options.nil?
output_file_path = pack_options.output_file_path
repository_path_list = pack_options.repository_path_list
header_prompt_file_path = pack_options.header_prompt_file_path
footer_prompt_file_path = pack_options.footer_prompt_file_path
regular_output_file = false
filelist = FileList.new() filelist = FileList.new()
filelist.add(repository_path_list) filelist.add(repository_path_list)
@config.ignore_list.each do |ignore_pattern| pack_options.ignore_list.each do |ignore_pattern|
filelist.reject { |path| !!(path =~ Regex.new(ignore_pattern)) } filelist.reject { |path| !!(path =~ Regex.new(ignore_pattern)) }
end end
@ -45,27 +77,28 @@ module CodePreloader
footer_prompt = File.read(footer_prompt_file_path) footer_prompt = File.read(footer_prompt_file_path)
end end
unless output_file_path.nil? || output_file_path.try(&.empty?) || (output_file_path != "-") output_file_path.try do |path|
output_file = File.open(output_file_path, "w") break if path.empty?
invalid_output_file = false break if path == "-"
regular_output_file = true
output_file = File.open(path, "w")
end end
invalid_output_file = true
output_file = STDOUT output_file = STDOUT
header_prompt = "" header_prompt = ""
footer_prompt = "" footer_prompt = ""
output_file.puts header_prompt if @config.header_prompt_file_path output_file.puts header_prompt if header_prompt_file_path
STDERR.puts "Processing repository: #{@config.repository_path_list}" STDERR.puts "Processing repository: #{repository_path_list}"
filelist.each do |file_path| filelist.each do |file_path|
process_file(file_path, output_file) process_file(file_path, output_file)
end end
output_file.puts footer_prompt if @config.footer_prompt_file_path output_file.puts footer_prompt if footer_prompt_file_path
output_file.close if !invalid_output_file output_file.close if regular_output_file
STDERR.puts "Processing completed. Output written to: #{invalid_output_file ? "stdout" : output_file_path}" STDERR.puts "Processing completed. Output written to: #{regular_output_file ? output_file_path : "stdout" }"
rescue e : Exception rescue e : Exception
STDERR.puts "An error occurred during execution: #{e.message}" STDERR.puts "An error occurred during execution: #{e.message}"

View file

@ -6,63 +6,151 @@ require "./version"
module CodePreloader module CodePreloader
class Config class Config
property repository_path_list : Array(String) = [] of String
property ignore_list : Array(String) = [] of String enum Subcommand
property output_file_path : String? None
property header_prompt_file_path : String? Init
property footer_prompt_file_path : String? Pack
Help
Version
end
class InitOptions
property config_file_path : String? = nil
end
class PackOptions
property config_file_path : String? = nil
property repository_path_list : Array(String) = [] of String
property ignore_list : Array(String) = [] of String
property output_file_path : String?
property header_prompt_file_path : String?
property footer_prompt_file_path : String?
end
getter parser : OptionParser?
property subcommand : Subcommand = Subcommand::None
property pack_options : PackOptions?
property init_options : InitOptions?
def initialize() def initialize()
end end
def parse_init_options(parser)
@init_options = InitOptions.new
parser.banner = [
"#{PROGRAM_NAME} v#{VERSION}",
"Usage: code-preloader init [options]\n",
"Global options:"
].join("\n")
parser.separator "\nInit options:"
parser.unknown_args do |remaining_args, _|
# FIXME: detect and make error if there are more or less than one
remaining_args.each do |arg|
@init_options.try &.config_file_path = arg
end
end
parser.on(
"-c FILE",
"--config=FILE",
"Load parameters from FILE"
) do |config_file|
@init_options.try { |opt| opt.config_file_path = config_file }
end
parser.separator ""
parser.missing_option do |opt|
puts parser
abort("ERROR: Missing parameter for option #{opt}!")
end
parser.invalid_option do |opt|
puts parser
abort("ERROR: Invalid option #{opt}!")
end
end
def parse_pack_options(parser)
@pack_options = PackOptions.new
parser.banner = [
"#{PROGRAM_NAME} v#{VERSION}",
"Usage: code-preloader pack [options] DIR ...\n",
"Global options:"
].join("\n")
parser.separator "\nPack options:"
parser.on(
"-i REGEXP",
"--ignore=REGEXP",
"Ignore file or directory"
) do |ignore_file|
@pack_options.try { |opt| opt.ignore_list << ignore_file }
end
parser.on(
"-o FILE",
"--output=FILE",
"Write output to FILE"
) do |output_file|
@pack_options.try { |opt| opt.output_file_path = output_file }
end
parser.on(
"-H FILE",
"--header-prompt=FILE",
"Load header prompt from FILE"
) do |header_prompt_file|
@pack_options.try { |opt| opt.header_prompt_file_path = header_prompt_file }
end
parser.on(
"-F FILE",
"--footer-prompt=FILE",
"Load footer prompt from FILE"
) do |footer_prompt_file|
@pack_options.try { |opt| opt.footer_prompt_file_path = footer_prompt_file }
end
parser.on(
"-c FILE",
"--config=FILE",
"Load parameters from FILE"
) do |config_file|
@pack_options.try { |opt| load_pack_config(config_file) }
end
parser.separator ""
parser.unknown_args do |remaining_args, _|
remaining_args.each do |arg|
@pack_options.try { |opt| opt.repository_path_list << arg }
end
end
parser.missing_option do |opt|
puts parser
abort("ERROR: Missing parameter for option #{opt}!")
end
parser.invalid_option do |ex|
puts parser
abort("ERROR: Invalid option #{ex}")
end
end
def parse_arguments(args : Array(String)) def parse_arguments(args : Array(String))
OptionParser.parse(args) do |parser| @parser = OptionParser.new do |parser|
parser.banner = [ parser.banner = [
"#{PROGRAM_NAME} v#{VERSION}", "#{PROGRAM_NAME} v#{VERSION}",
"Usage: code-preloader [options] DIR ...\n", "Usage: code-preloader <subcommand> [options] [DIR] [...]\n",
"Options:" "Global options:"
].join("\n") ].join("\n")
parser.on(
"-c FILE",
"--config=FILE",
"Load parameters from FILE"
) do |config_file|
load_config(config_file)
end
parser.on(
"-i REGEXP",
"--ignore=REGEXP",
"Ignore file or directory"
) do |ignore_file|
@ignore_list << ignore_file
end
parser.on(
"-o FILE",
"--output=FILE",
"Write output to FILE"
) do |output_file|
@output_file_path = output_file
end
parser.on(
"-H FILE",
"--header-prompt=FILE",
"Load header prompt from FILE"
) do |header_prompt_file|
@header_prompt_file_path = header_prompt_file
end
parser.on(
"-F FILE",
"--footer-prompt=FILE",
"Load footer prompt from FILE"
) do |footer_prompt_file|
@footer_prompt_file_path = footer_prompt_file
end
parser.on("--version", "Show version") do parser.on("--version", "Show version") do
STDOUT.puts "#{PROGRAM_NAME} #{VERSION}" STDOUT.puts "#{PROGRAM_NAME} #{VERSION}"
exit(0) exit(0)
@ -73,20 +161,56 @@ module CodePreloader
exit exit
end end
parser.unknown_args do |remaining_args, _| parser.separator "\nSubcommands:"
remaining_args.each do |arg|
@repository_path_list << arg parser.on("init", "Create an example .code_preloader.yml file") do
end @subcommand = Subcommand::Init
parse_init_options(parser)
end
parser.on("pack", "Create the packed version of a directory for LLM prompting") do
@subcommand = Subcommand::Pack
parse_pack_options(parser)
end
parser.separator ""
parser.invalid_option do |ex|
puts parser
abort("ERROR: Invalid option #{ex}")
end end
end end
@parser.try &.parse(args)
validate validate
end end
private def validate def detect_config
abort("Missing repository path.") if @repository_path_list.empty? # FIXME: detect config name, if any
end
STDERR.puts("Output file path not specified (using STDOUT)") if @output_file_path.nil? || @output_file_path.try(&.empty?) private def validate
case @subcommand
when Subcommand::Init then validate_init
when Subcommand::Pack then validate_pack
else
abort("Unknown subcommand #{@subcommand}")
end
end
private def validate_init
abort("No init options defined!") if @init_options.nil?
end
private def validate_pack
abort("No pack options defined!") if @pack_options.nil?
@pack_options.try do |opts|
abort("Missing repository path.") if opts.repository_path_list.empty?
if opts.output_file_path.nil? || opts.output_file_path.try(&.empty?)
STDERR.puts("Output file path not specified (using STDOUT)")
end
end
end end
# Reads and returns a list of paths to ignore from the given file. # Reads and returns a list of paths to ignore from the given file.
@ -97,16 +221,19 @@ module CodePreloader
exit(1) exit(1)
end end
private def load_config(config_file_path : String) private def load_pack_config(config_file_path : String)
config_str = File.read(config_file_path) config_str = File.read(config_file_path)
root = Models::RootConfig.from_yaml(config_str) root = Models::RootConfig.from_yaml(config_str)
@repository_path = root.repository_path_list || @repository_path_list @pack_options.try do |opts|
@ignore_list = root.ignore_list || @ignore_list opts.config_file_path = config_file_path
@output_file_path = root.output_file_path || @output_file_path opts.repository_path_list = root.repository_path_list || opts.repository_path_list
@header_prompt_file_path = root.header_prompt_file_path || @header_prompt_file_path opts.ignore_list = root.ignore_list || opts.ignore_list
@footer_prompt_file_path = root.footer_prompt_file_path || @footer_prompt_file_path opts.output_file_path = root.output_file_path || opts.output_file_path
opts.header_prompt_file_path = root.header_prompt_file_path || opts.header_prompt_file_path
opts.footer_prompt_file_path = root.footer_prompt_file_path || opts.footer_prompt_file_path
end
rescue ex : Exception rescue ex : Exception
STDERR.puts "Failed to load config file: #{ex.message}" STDERR.puts "Failed to load config file: #{ex.message}"