Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move cli to thor #90

Merged
merged 7 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/sublayer
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env ruby

require "sublayer/cli"
require_relative "../lib/sublayer/cli"

Sublayer::CLI.start(ARGV)
194 changes: 11 additions & 183 deletions lib/sublayer/cli.rb
Original file line number Diff line number Diff line change
@@ -1,191 +1,19 @@
require "tty-prompt"
require "tty-progressbar"
require "tty-command"
require "tty-file"
require "fileutils"
require "yaml"
require "thor"
require "sublayer/version"
require "yaml"
require "fileutils"
require "active_support/inflector"

module Sublayer
class CLI
PLACEHOLDERS = {
"PROJECT_NAME" => { gsub: true, camelcase: false },
"ProjectName" => { gsub: true, camelcase: true },
"project_name" => { gsub: true, camelcase: false, underscore: true }
}

def self.start(args)
new.run(args)
end

def run(args)
command = args.shift

case command
when "new"
create_new_project(args.first)
when "help", nil
display_help
else
puts "Unknown command: #{command}"
display_help
end
end

private

def create_new_project(project_name)
prompt = TTY::Prompt.new

project_name ||= prompt.ask("What is the name of your project?")

project_type = prompt.select("Select a project template: ", ["CLI", "Quick Script"])
ai_provider = prompt.select("Select an AI provider:", ["OpenAI", "Claude", "Gemini"])

ai_model = if ai_provider == "OpenAI"
prompt.select("Which OpenAI model would you like to use?", ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"])
elsif ai_provider == "Claude"
prompt.select("Which Anthropic model would you like to use?", ["claude-3-5-sonnet-20240620", "claude-3-opus-20240229", "claude-3-haiku-20240307"])
elsif ai_provider == "Gemini"
prompt.select("Which Gemini model would you like to use?", ["gemini-1.5-pro-latest", "gemini-1.5-flash-latest"])
end

create_project(project_name, project_type, ai_provider, ai_model)
end

def create_project(project_name, project_type, ai_provider, ai_model)
project_path = File.join(Dir.pwd, project_name)

progress_bar = TTY::ProgressBar.new("Creating project [:bar] :percent", total: 8)

progress_bar.advance(1, log: "Creating project directory")
FileUtils.mkdir_p(project_path)

progress_bar.advance(1, log: "Copying template files")
copy_template_files(project_path, project_type)

progress_bar.advance(1, log: "Replacing placeholders")
replace_placeholders(project_path, project_name)

progress_bar.advance(1, log: "Generating configuration")
generate_config_file(project_path, project_name, project_type, ai_provider, ai_model)

progress_bar.advance(1, log: "Finalizing project")
finalize_project(project_path, project_type)

puts "\nSublayer project '#{project_name}' created successfully!"
end

def copy_template_files(project_path, project_type)
template_dir = project_type == "CLI" ? "cli" : "quick_script"
source_path = File.join(File.dirname(__FILE__), "templates", template_dir)
FileUtils.cp_r("#{source_path}/.", project_path)

FileUtils.mkdir_p(File.join(project_path, "log")) if should_add_log_folder(project_type)
end

def should_add_log_folder(project_type)
project_type == "CLI"
end

def generate_config_file(project_path, project_name, project_type, ai_provider, ai_model)
config = {
project_name: project_name,
project_type: project_type,
ai_provider: ai_provider,
ai_model: ai_model
}

TTY::File.create_file(File.join(project_path, "lib", project_name, "config", "sublayer.yml"), YAML.dump(config)) if project_type == "CLI"

if project_type == "Quick Script"
config_lines = <<~CONFIG
Sublayer.configuration.ai_provider = Sublayer::Providers::#{config[:ai_provider]}
Sublayer.configuration.ai_model = "#{config[:ai_model]}"
CONFIG
project_file = File.join(project_path, "#{project_name}.rb")
File.write(project_file, File.read(project_file) + config_lines)
end
end

def project_type_instructions(project_type)
if project_type == "CLI"
"Run your CLI application:\n ```\n ruby bin/#{File.basename(project_path)}\n ```"
else
"Start your web server:\n ```\n ruby app.rb\n ```\n Then visit http://localhost:4567 in your browser."
end
end

def replace_placeholders(project_path, project_name)
# First rename the lib/PROJECT_NAME directory to lib/project_name
FileUtils.mv(File.join(project_path, "lib", "PROJECT_NAME"), File.join(project_path, "lib", project_name.gsub("-", "_").downcase)) if File.directory?(File.join(project_path, "lib", "PROJECT_NAME"))

# Then walk through each file in the project and replace the placeholder content and filenames
Dir.glob("#{project_path}/**/*", File::FNM_DOTMATCH).each do |file_path|
next if File.directory?(file_path)
next if file_path.include?(".git/")

content = File.read(file_path)
PLACEHOLDERS.each do |placeholder, options|
replacement = if options[:camelcase]
project_name.split(/[_-]/).map(&:capitalize).join
elsif options[:underscore]
project_name.gsub("-", "_").downcase
else
project_name
end
content.gsub!(placeholder, replacement) if options[:gsub]
end

File.write(file_path, content)


if file_path.include?('PROJECT_NAME')
new_path = file_path.gsub("PROJECT_NAME", project_name.gsub("-", "_").downcase)

FileUtils.mkdir_p(File.dirname(new_path))
FileUtils.mv(file_path, new_path)
end

if file_path.include?('project_name')
new_path = file_path.gsub("project_name", project_name.gsub("-", "_").downcase)

FileUtils.mkdir_p(File.dirname(new_path))
FileUtils.mv(file_path, new_path)
end
end

# replace the sublayer version in the gemspec file with Sublayer::VERSION
if File.exist?(File.join(project_path, "#{project_name}.gemspec"))
gemspec_path = File.join(project_path, "#{project_name}.gemspec")
gemspec_content = File.read(gemspec_path)
gemspec_content.gsub!("SUBLAYER_VERSION", Sublayer::VERSION)
File.write(gemspec_path, gemspec_content)
end
end

def finalize_project(project_path, project_type)
cmd = TTY::Command.new(printer: :null)

if TTY::Prompt.new.yes?("Initialize a git repository?")
cmd.run("git init", chdir: project_path)
end
require_relative "cli/commands/new_project"

if TTY::Prompt.new.yes?("Install dependencies now?")
cmd.run("bundle install", chdir: project_path)
end
module Sublayer
class CLI < Thor

puts "To get started, run:"
puts " cd #{File.basename(project_path)}"
puts " ./bin/#{File.basename(project_path)}" if project_type == "CLI"
puts " ruby #{File.basename(project_path)}.rb" if project_type == "Quick Script"
end
register(Sublayer::Commands::NewProject, "new", "new PROJECT_NAME", "Creates a new Sublayer project")

def display_help
puts "Usage: sublayer [command] [arguments]"
puts "Commands:"
puts " new PROJECT_NAME Create a new Sublayer project"
puts " help Display this help message"
desc "version", "Prints the Sublayer version"
def version
puts Sublayer::VERSION
end
end
end
116 changes: 116 additions & 0 deletions lib/sublayer/cli/commands/new_project.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
module Sublayer
module Commands
class NewProject < Thor::Group
include Thor::Actions

argument :project_name, type: :string, desc: "The name of your project"

class_option :template, type: :string, desc: "Type of project (CLI or QuickScript)", aliases: :t
class_option :provider, type: :string, desc: "AI provider (OpenAI, Claude, or Gemini)", aliases: :p
class_option :model, type: :string, desc: "AI model name to use (e.g. gpt-4o, claude-3-haiku-20240307, gemini-1.5-flash-latest)", aliases: :m

def sublayer_version
Sublayer::VERSION
end

def self.source_root
File.dirname(__FILE__)
end

def ask_for_project_details
puts options[:template]
@project_template = options[:template] || ask("Select a project template:", default: "CLI", limited_to: %w[CLI QuickScript])
@ai_provider = options[:provider] || ask("Select an AI provider:", default: "OpenAI", limited_to: %w[OpenAI Claude Gemini])
@ai_model = options[:model] || select_ai_model
end

def create_project_directory
say "Creating project directory", :green
empty_directory project_name
end

def copy_template_files
say "Copying template files", :green
template_dir = @project_template == "CLI" ? "cli" : "quick_script"
directory "../templates/#{template_dir}", project_name
empty_directory File.join(project_name, "log") if @project_template =="CLI"
end

def generate_config_file
say "Generating configuration", :green

config = {
project_name: project_name,
project_template: @project_template,
ai_provider: @ai_provider,
ai_model: @ai_model
}

if @project_template == "CLI"
create_file File.join(project_name, "lib", project_name, "config", "sublayer.yml"), YAML.dump(config)
else
append_to_file File.join(project_name, "#{project_name}.rb") do
<<~CONFIG
Sublayer.configuration.ai_provider = Sublayer::Providers::#{config[:ai_provider]}
Sublayer.configuration.ai_model = "#{config[:ai_model]}"
CONFIG
end
end
end

def finalize_project
say "Finalizing project", :green
inside(project_name) do
if @project_template == "CLI"
chmod("bin/#{project_name}", "+x")
run("bundle install") if yes?("Install gems?")
else
append_to_file "#{project_name}.rb" do
<<~INSTRUCTIONS
puts "Welcome to your quick Sublayer script!"
puts "To get started, create some generators, actions, or agents in their respective directories and call them here"
puts "For more information, visit https://docs.sublayer.com"
INSTRUCTIONS
end
end

run("git init") if yes?("Initialize a git repository?")
end
end

def print_next_steps
say "\nSublayer project '#{project_name}' created successfully!", :green
say "To get started, run:"
say " cd #{project_name}"
if @project_template == "CLI"
say " ./bin/#{project_name}"
else
say " ruby #{project_name}.rb"
end
end

private

def select_ai_model
case @ai_provider
when "OpenAI"
ask("Which OpenAI model would you like to use?", default: "gpt-4o", limited_to: %w[gpt-4o gpt-4o-mini gpt-4-turbo gpt-3.5-turbo])
when "Claude"
ask("Which Anthropic model would you like to use?", default: "claude-3-5-sonnet-20240620", limited_to: %w[claude-3-5-sonnet-20240620 claude-3-opus-20240620 claude-3-haiku-20240307])
when "Gemini"
ask("Which Google model would you like to use?", default: "gemini-1.5-flash-latest", limited_to: %w[gemini-1.5-flash-latest gemini-1.5-pro-latest])
end
end

def rename_project_name_directory
old_path = File.join(project_name, "lib", "PROJECT_NAME")
new_path = File.join(project_name, "lib", project_name.gsub("-", "_").downcase)

if File.directory?(old_path)
say "Renaming project directory", :green
FileUtils.mv(old_path, new_path)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
require_relative "lib/PROJECT_NAME/version"
require_relative "lib/<%= project_name.gsub("-", "_") %>/version"

Gem::Specification.new do |spec|
spec.name = "PROJECT_NAME"
spec.version = ProjectName::VERSION
spec.name = "<%= project_name %>"
spec.version = <%= project_name.camelize %>::VERSION
spec.authors = ["Your Name"]
spec.email = ["your.email@example.com"]

spec.summary = "Summary of your project"
spec.description = "Longer description of your project"
spec.homepage = "https://github.com/yourusername/PROJECT_NAME"
spec.homepage = "https://github.com/yourusername/<%= project_name %>"
spec.license = "MIT"
spec.required_ruby_version = ">= 2.6.0"

spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://github.com/yourusername/PROJECT_NAME"
spec.metadata["changelog_uri"] = "https://github.com/yourusername/PROJECT_NAME/blob/master/CHANGELOG.md"
spec.metadata["source_code_uri"] = "https://github.com/yourusername/<%= project_name %>"
spec.metadata["changelog_uri"] = "https://github.com/yourusername/<%= project_name %>/blob/master/CHANGELOG.md"

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
Expand All @@ -30,6 +30,6 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

# Add dependencies here
spec.add_dependency "sublayer", "~> SUBLAYER_VERSION"
spec.add_dependency "sublayer", "~> <%= sublayer_version %>"
spec.add_dependency "thor", "~> 1.2"
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ProjectName
# <%= project_name %>

Welcome to your new Sublayer CLI project!

Expand All @@ -13,7 +13,7 @@ Execute:
To run your CLI application:

```
$ bin/PROJECT_NAME
$ bin/<%= project_name %>
```

Available commands:
Expand Down
5 changes: 5 additions & 0 deletions lib/sublayer/cli/templates/cli/bin/%project_name%.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env ruby

require_relative "../lib/<%= project_name %>"

<%= project_name.camelize %>::CLI.start(ARGV)
Loading
Loading