Merge pull request 'refactor: store processed content for crinja (later)' (#15) from feature/issue/9-add-support-for-jinja-template into develop
Some checks failed
continuous-integration/drone/push Build is failing

Reviewed-on: #15
This commit is contained in:
Glenn Y. Rolland 2024-01-04 21:51:46 +00:00
commit 7ff161f4ee
11 changed files with 218 additions and 124 deletions

View file

@ -12,10 +12,10 @@ ignore_list:
- ^Makefile - ^Makefile
- .*\.svg$ - .*\.svg$
output_file_path: null output_path: null
header_prompt_file_path: null
footer_prompt_file_path: null
prompt:
header_path: null
footer_path: null
template_path: misc/templates/default.j2
# #

View file

@ -10,10 +10,10 @@ ignore_list:
- prompts - prompts
- Makefile - Makefile
output_file_path: null output_path: null
header_prompt_file_path: null prompt:
header_path: null
footer_prompt_file_path: prompts/footer.txt footer_path: prompts/footer.txt
# #

16
misc/templates/default.j2 Normal file
View file

@ -0,0 +1,16 @@
{%- if prompt_header -%}
@@ CONTEXT
{{ prompt_header }}
{%- endif -%}
{%- for file in prompt_files -%}
@@ FILE "{{ file.path }}" WITH MIME-TYPE "{{ file.mime_type }}"
{{- file.content -}}
{%- endfor -%}
{%- if prompt_footer -%}
@@ REQUEST
{{ prompt_footer }}
{%- endif -%}

View file

@ -1,8 +1,8 @@
version: 2.0 version: 2.0
shards: shards:
completion: crinja:
git: https://github.com/f/completion.git git: https://github.com/straight-shoota/crinja.git
version: 0.1.0+git.commit.d8799381b2de14430496199260eca64eb329625f version: 0.8.1
magic: magic:
git: https://github.com/dscottboggs/magic.cr.git git: https://github.com/dscottboggs/magic.cr.git

View file

@ -10,6 +10,8 @@ authors:
- Glenn Y. Rolland <glenux@glenux.net> - Glenn Y. Rolland <glenux@glenux.net>
dependencies: dependencies:
crinja:
github: straight-shoota/crinja
magic: magic:
github: dscottboggs/magic.cr github: dscottboggs/magic.cr
walk: walk:

View file

@ -1,10 +1,8 @@
# vim: set ts=2 sw=2 et ft=crystal:
require "colorize" require "colorize"
require "file" require "file"
require "option_parser" require "option_parser"
require "magic" require "magic"
require "crinja"
require "./config" require "./config"
require "./filelist" require "./filelist"
@ -12,14 +10,16 @@ require "./filelist"
# The CodePreloader module organizes classes and methods related to preloading code files. # The CodePreloader module organizes classes and methods related to preloading code files.
module CodePreloader module CodePreloader
# The Cli class handles command-line interface operations for the CodePreloader. # The Cli class handles command-line interface operations for the CodePreloader.
class Cli class Cli
alias ProcessedFile = NamedTuple(path: String, content: String, mime_type: String)
@config : Config @config : Config
# Initializes the Cli class with default values. # Initializes the Cli class with default values.
def initialize(args) def initialize(args)
@output_file_path = "" @output_path = ""
@config = Config.new() @config = Config.new()
@config.detect_config()
@config.parse_arguments(args) @config.parse_arguments(args)
end end
@ -43,7 +43,7 @@ module CodePreloader
default_config_path = "example.code_preloader.yml" default_config_path = "example.code_preloader.yml"
# Use the specified path if provided, otherwise use the default # Use the specified path if provided, otherwise use the default
config_file_path = init_options.config_file_path || default_config_path config_path = init_options.config_path || default_config_path
# Content of the .code_preloader.yml file # Content of the .code_preloader.yml file
config_content = [ config_content = [
@ -51,7 +51,7 @@ module CodePreloader
"# Example configuration for Code-Preloader", "# Example configuration for Code-Preloader",
"", "",
"# List of repository paths to preload", "# List of repository paths to preload",
"# repository_path_list:", "# source_list:",
"# - \"path/to/repo1\"", "# - \"path/to/repo1\"",
"# - \"path/to/repo2\"", "# - \"path/to/repo2\"",
"", "",
@ -60,19 +60,19 @@ module CodePreloader
" - ^\\.git/.*", " - ^\\.git/.*",
"", "",
"# Path to the output file (if null, output to STDOUT)", "# Path to the output file (if null, output to STDOUT)",
"output_file_path: null", "output_path: null",
"", "",
"# Optional: Path to a file containing the header prompt", "# Optional: Path to a file containing the header prompt",
"header_prompt_file_path: null", "header_path: null",
"", "",
"# Optional: Path to a file containing the footer prompt", "# Optional: Path to a file containing the footer prompt",
"footer_prompt_file_path: null", "footer_path: null",
"" ""
].join("\n") ].join("\n")
# Writing the configuration content to the file # Writing the configuration content to the file
File.write(config_file_path, config_content) File.write(config_path, config_content)
puts "Configuration file created at: #{config_file_path}" puts "Configuration file created at: #{config_path}"
rescue e : Exception rescue e : Exception
abort("ERROR: Unable to create the configuration file: #{e.message}") abort("ERROR: Unable to create the configuration file: #{e.message}")
end end
@ -88,62 +88,83 @@ module CodePreloader
end end
def exec_help def exec_help
puts @config.parser @config.help_options.try do |opts|
puts opts.parser_snapshot
end
exit(0) exit(0)
end end
def exec_pack(pack_options) def exec_pack(pack_options)
abort("Unexpected nil value for pack_options!") if pack_options.nil? abort("Unexpected nil value for pack_options!") if pack_options.nil?
output_file_path = pack_options.output_file_path preloaded_content = {} of String => NamedTuple(mime: String, content: String)
repository_path_list = pack_options.repository_path_list config_path = pack_options.config_path
header_prompt_file_path = pack_options.header_prompt_file_path output_path = pack_options.output_path
footer_prompt_file_path = pack_options.footer_prompt_file_path source_list = pack_options.source_list
prompt_header_path = pack_options.prompt_header_path
prompt_footer_path = pack_options.prompt_footer_path
prompt_template_path = pack_options.prompt_template_path
regular_output_file = false regular_output_file = false
header_prompt = "" prompt_header_content = nil
footer_prompt = "" prompt_footer_content = nil
prompt_template_content = ""
STDERR.puts "Loading config file from: #{config_path}".colorize(:yellow)
filelist = FileList.new() filelist = FileList.new()
filelist.add(repository_path_list) filelist.add(source_list)
pack_options.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
if !header_prompt_file_path.nil? abort("No prompt file defined!") if prompt_template_path.nil?
STDERR.puts "Loading header prompt from: #{header_prompt_file_path}".colorize(:yellow) prompt_template_content = File.read(prompt_template_path)
header_prompt = File.read(header_prompt_file_path)
if !prompt_header_path.nil?
STDERR.puts "Loading header prompt from: #{prompt_header_path}".colorize(:yellow)
prompt_header_content = File.read(prompt_header_path)
end end
if !footer_prompt_file_path.nil? if !prompt_footer_path.nil?
STDERR.puts "Loading footer prompt from: #{footer_prompt_file_path}".colorize(:yellow) STDERR.puts "Loading footer prompt from: #{prompt_footer_path}".colorize(:yellow)
footer_prompt = File.read(footer_prompt_file_path) prompt_footer_content = File.read(prompt_footer_path)
end end
output_file = STDOUT output_file = STDOUT
output_file_path.try do |path| output_path.try do |path|
break if path.empty? break if path.empty?
break if path == "-" break if path == "-"
regular_output_file = true regular_output_file = true
output_file = File.open(path, "w") output_file = File.open(path, "w")
end end
STDERR.puts "Writing output to: #{regular_output_file ? output_file_path : "stdout" }".colorize(:yellow) STDERR.puts "Writing output to: #{regular_output_file ? output_path : "stdout" }".colorize(:yellow)
# FIXME: prompt_header_path.try { output_file.puts prompt_header_content }
header_prompt_file_path.try { output_file.puts header_prompt } STDERR.puts "Processing source directories: #{source_list}".colorize(:yellow)
processed_files = [] of ProcessedFile
STDERR.puts "Processing repository: #{repository_path_list}".colorize(:yellow)
filelist.each do |file_path| filelist.each do |file_path|
STDERR.puts "Processing file: #{file_path}".colorize(:yellow) STDERR.puts "Processing file: #{file_path}".colorize(:yellow)
process_file(file_path, output_file) file_result = process_file(file_path, output_file)
processed_files << file_result
end end
footer_prompt_file_path.try { output_file.puts footer_prompt } # FIXME: prompt_footer_path.try { output_file.puts prompt_footer_content }
output_file.puts Crinja.render(
prompt_template_content,
{
"prompt_header": prompt_header_content,
"prompt_files": processed_files,
"prompt_footer": prompt_footer_content
}
)
output_file.close if regular_output_file output_file.close if regular_output_file
STDERR.puts "Processing completed.".colorize(:yellow) STDERR.puts "Processing completed.".colorize(:yellow)
rescue e : Exception rescue e : Exception
STDERR.puts "An error occurred during execution: #{e.message}" STDERR.puts "ERROR: #{e.message}"
exit(1) exit(1)
end end
@ -159,12 +180,11 @@ module CodePreloader
) )
end end
output_file.puts "@@ File \"#{file_path}\" (Mime-Type: #{mime.inspect})" return {
output_file.puts "" path: file_path,
if clean_content !~ /^\s*$/ content: clean_content,
output_file.puts(clean_content) mime_type: mime
output_file.puts "" }
end
end end
end end
end end

View file

@ -16,23 +16,30 @@ module CodePreloader
Version Version
end end
class HelpOptions
property parser_snapshot : OptionParser? = nil
end
class InitOptions class InitOptions
property config_file_path : String? = nil property config_path : String? = nil
end end
class PackOptions class PackOptions
property config_file_path : String? = nil property config_path : String? = nil
property repository_path_list : Array(String) = [] of String property source_list : Array(String) = [] of String
property ignore_list : Array(String) = [] of String property ignore_list : Array(String) = [] of String
property output_file_path : String? property output_path : String?
property header_prompt_file_path : String? property prompt_template_path : String?
property footer_prompt_file_path : String? property prompt_header_path : String?
property prompt_footer_path : String?
end end
getter verbose : Bool = false
getter parser : OptionParser? getter parser : OptionParser?
property subcommand : Subcommand = Subcommand::None getter subcommand : Subcommand = Subcommand::None
property pack_options : PackOptions? getter pack_options : PackOptions?
property init_options : InitOptions? getter init_options : InitOptions?
getter help_options : HelpOptions?
def initialize() def initialize()
end end
@ -49,7 +56,7 @@ module CodePreloader
parser.unknown_args do |remaining_args, _| parser.unknown_args do |remaining_args, _|
# FIXME: detect and make error if there are more or less than one # FIXME: detect and make error if there are more or less than one
remaining_args.each do |arg| remaining_args.each do |arg|
@init_options.try &.config_file_path = arg @init_options.try &.config_path = arg
end end
end end
@ -58,7 +65,7 @@ module CodePreloader
"--config=FILE", "--config=FILE",
"Load parameters from FILE" "Load parameters from FILE"
) do |config_file| ) do |config_file|
@init_options.try { |opt| opt.config_file_path = config_file } @init_options.try { |opt| opt.config_path = config_file }
end end
parser.separator "" parser.separator ""
@ -79,16 +86,44 @@ module CodePreloader
def parse_pack_options(parser) def parse_pack_options(parser)
@pack_options = PackOptions.new @pack_options = PackOptions.new
config_file = detect_config_file
config_file.try { |path| load_pack_config(path) }
parser.banner = [ parser.banner = [
"Usage: code-preloader pack [options] DIR ...\n", "Usage: code-preloader pack [options] DIR ...\n",
"Global options:" "Global options:"
].join("\n") ].join("\n")
parser.separator "\nPack options:" parser.separator "\nPack options:"
parser.on(
"-c FILE",
"--config=FILE",
"Load parameters from FILE\n(default: \".code_preload.yml\", if present)"
) do |config_file|
@pack_options.try { |opt| load_pack_config(config_file) }
end
parser.on(
"-F FILE",
"--prompt-footer=FILE",
"Load prompt footer from FILE (default: none)"
) do |prompt_footer_path|
@pack_options.try { |opt| opt.prompt_footer_path = prompt_footer_path }
end
parser.on(
"-H FILE",
"--prompt-header=FILE",
"Load prompt header from FILE (default: none)"
) do |prompt_header_path|
@pack_options.try { |opt| opt.prompt_header_path = prompt_header_path }
end
parser.on( parser.on(
"-i REGEXP", "-i REGEXP",
"--ignore=REGEXP", "--ignore=REGEXP",
"Ignore file or directory" "Ignore file or directory. Can be used\nmultiple times (default: none)"
) do |ignore_file| ) do |ignore_file|
@pack_options.try { |opt| opt.ignore_list << ignore_file } @pack_options.try { |opt| opt.ignore_list << ignore_file }
end end
@ -96,40 +131,24 @@ module CodePreloader
parser.on( parser.on(
"-o FILE", "-o FILE",
"--output=FILE", "--output=FILE",
"Write output to FILE" "Write output to FILE (default: \"-\", STDOUT)"
) do |output_file| ) do |output_file|
@pack_options.try { |opt| opt.output_file_path = output_file } @pack_options.try { |opt| opt.output_path = output_file }
end end
parser.on( parser.on(
"-H FILE", "-t FILE",
"--header-prompt=FILE", "--template=FILE",
"Load header prompt from FILE" "Load template from FILE (default: internal)"
) do |header_prompt_file| ) do |prompt_template_path|
@pack_options.try { |opt| opt.header_prompt_file_path = header_prompt_file } @pack_options.try { |opt| opt.prompt_template_path = prompt_template_path }
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 end
parser.separator "" parser.separator ""
parser.unknown_args do |remaining_args, _| parser.unknown_args do |remaining_args, _|
remaining_args.each do |arg| remaining_args.each do |arg|
@pack_options.try { |opt| opt.repository_path_list << arg } @pack_options.try { |opt| opt.source_list << arg }
end end
end end
@ -153,13 +172,22 @@ module CodePreloader
"Global options:" "Global options:"
].join("\n") ].join("\n")
parser.on("-h", "--help", "Show this help") do
@subcommand = Subcommand::Help
@help_options = HelpOptions.new
@help_options.try do |opts|
opts.parser_snapshot = parser.dup
end
end
parser.on("-v", "--verbose", "Enable verbose mode") do
@verbose = true
end
parser.on("--version", "Show version") do parser.on("--version", "Show version") do
@subcommand = Subcommand::Version @subcommand = Subcommand::Version
end end
parser.on("-h", "--help", "Show this help") do
@subcommand = Subcommand::Help
end
parser.separator "\nSubcommands:" parser.separator "\nSubcommands:"
@ -187,8 +215,24 @@ module CodePreloader
validate validate
end end
def detect_config def detect_config_file() : String?
# FIXME: detect config name, if any home_dir = ENV["HOME"]
possible_files = [
File.join(".code_preloader.yaml"),
File.join(".code_preloader.yml"),
File.join(home_dir, ".config", "code_preloader", "config.yaml"),
File.join(home_dir, ".config", "code_preloader", "config.yml"),
File.join(home_dir, ".config", "code_preloader.yaml"),
File.join(home_dir, ".config", "code_preloader.yml"),
File.join("/etc", "code_preloader", "config.yaml"),
File.join("/etc", "code_preloader", "config.yml"),
]
possible_files.each do |file_path|
return file_path if File.exists?(file_path)
end
return nil
end end
private def validate private def validate
@ -209,39 +253,42 @@ module CodePreloader
private def validate_pack private def validate_pack
opts = @pack_options opts = @pack_options
abort("No pack options defined!") if opts.nil? abort("No pack options defined!") if opts.nil?
abort("Missing repository path.") if opts.repository_path_list.empty? abort("Missing repository path.") if opts.source_list.empty?
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.
def self.get_ignore_list(ignore_file_path : String) : Array(String) def self.get_ignore_list(ignore_path : String) : Array(String)
File.exists?(ignore_file_path) ? File.read_lines(ignore_file_path).map(&.strip) : [] of String File.exists?(ignore_path) ? File.read_lines(ignore_path).map(&.strip) : [] of String
rescue e : IO::Error rescue e : IO::Error
STDERR.puts "Error reading ignore file: #{e.message}" STDERR.puts "Error reading ignore file: #{e.message}"
exit(1) exit(1)
end end
private def load_pack_config(config_file_path : String) private def load_pack_config(config_path : String)
opts = @pack_options opts = @pack_options
abort("FIXME") if opts.nil? abort("No pack options defined!") if opts.nil?
config_str = File.read(config_file_path) config_str = File.read(config_path)
root = Models::RootConfig.from_yaml(config_str) root = Models::RootConfig.from_yaml(config_str)
opts.config_file_path = config_file_path opts.config_path = config_path
if opts.repository_path_list.nil? || opts.repository_path_list.try &.empty? if opts.source_list.nil? || opts.source_list.try &.empty?
root.repository_path_list.try { |value| opts.repository_path_list = value } root.source_list.try { |value| opts.source_list = value }
end end
if opts.ignore_list.nil? || opts.ignore_list.try &.empty? if opts.ignore_list.nil? || opts.ignore_list.try &.empty?
root.ignore_list.try { |value| opts.ignore_list = value } root.ignore_list.try { |value| opts.ignore_list = value }
end end
if opts.output_file_path.nil? if opts.output_path.nil?
opts.output_file_path = root.output_file_path opts.output_path = root.output_path
end end
if opts.header_prompt_file_path.nil? if opts.prompt_header_path.nil?
root.header_prompt_file_path.try { |value| opts.header_prompt_file_path = value } root.prompt.try &.header_path.try { |value| opts.prompt_header_path = value }
end end
if opts.footer_prompt_file_path.nil? if opts.prompt_footer_path.nil?
root.footer_prompt_file_path.try { |value| opts.footer_prompt_file_path = value } root.prompt.try &.footer_path.try { |value| opts.prompt_footer_path = value }
end
if opts.prompt_template_path.nil?
root.prompt.try &.template_path.try { |value| opts.prompt_template_path = value }
end end
rescue ex : Exception rescue ex : Exception

View file

@ -2,10 +2,8 @@
require "walk" require "walk"
module CodePreloader module CodePreloader
# Manage a list of files # Manage a list of files
class FileList class FileList
alias Filter = String -> Bool alias Filter = String -> Bool
class NotADirectory < Exception class NotADirectory < Exception

View file

@ -1,10 +1,5 @@
# vim: set ts=2 sw=2 et ft=crystal:
require "./cli" require "./cli"
# Now that we have checked for nil, it's safe to use not_nil!
app = CodePreloader::Cli.new(ARGV) app = CodePreloader::Cli.new(ARGV)
app.exec() app.exec()

View file

@ -0,0 +1,18 @@
require "yaml"
module CodePreloader::Models
class PromptConfig
include YAML::Serializable
include YAML::Serializable::Strict
@[YAML::Field(key: "header_path")]
getter header_path : String?
@[YAML::Field(key: "footer_path")]
getter footer_path : String?
@[YAML::Field(key: "template_path")]
getter template_path : String?
end
end

View file

@ -1,22 +1,20 @@
require "yaml" require "yaml"
require "./prompt_config"
module CodePreloader::Models module CodePreloader::Models
class RootConfig class RootConfig
include YAML::Serializable include YAML::Serializable
include YAML::Serializable::Strict include YAML::Serializable::Strict
@[YAML::Field(key: "repository_path_list")] @[YAML::Field(key: "source_list")]
getter repository_path_list : Array(String)? getter source_list : Array(String)?
@[YAML::Field(key: "output_file_path")] @[YAML::Field(key: "output_path")]
getter output_file_path : String? getter output_path : String?
@[YAML::Field(key: "header_prompt_file_path")] @[YAML::Field(key: "prompt")]
getter header_prompt_file_path : String? getter prompt : PromptConfig?
@[YAML::Field(key: "footer_prompt_file_path")]
getter footer_prompt_file_path : String?
@[YAML::Field(key: "ignore_list")] @[YAML::Field(key: "ignore_list")]
getter ignore_list : Array(String)? getter ignore_list : Array(String)?