patcmd 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: df47864b3b6c9a8606d01d8c3ea358f27af72fe692323922ea69d2f8ef4fd9b1
4
+ data.tar.gz: a3aaabeaf982f67f7ee4ec58627b74f79841c9667c4cd7797d12e2e8377b078f
5
+ SHA512:
6
+ metadata.gz: 49aaa3694b43c32251556f93b4a27c787809df5e580191c41ef51c9b0d2fe04c1921ac7290d847ed2096f0a64cc0a83079af804543d3fffe1fb3943b705f98e9
7
+ data.tar.gz: fca5cbe7172f95b3903f47796f8f36a535d463f7e0b04a28f30eb097744ebd4e3d4e0bb4288b1ef7320aef151bbe934ef1d0ec3370a3f1afd0c3cd01c8cb8690
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ inherit_gem:
2
+ rubocop-shopify: rubocop.yml
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.0
6
+ Exclude:
7
+ - '.git/**/*'
8
+
9
+ Style/StringLiterals:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Style/StringLiteralsInInterpolation:
13
+ EnforcedStyle: double_quotes
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.3.0
@@ -0,0 +1,28 @@
1
+ {
2
+ "[ruby]": {
3
+ "editor.defaultFormatter": "Shopify.ruby-lsp",
4
+ "editor.formatOnSave": true,
5
+ "editor.tabSize": 2,
6
+ "editor.insertSpaces": true,
7
+ "editor.semanticHighlighting.enabled": true,
8
+ "editor.formatOnType": true
9
+ },
10
+ "rubyLsp.enabledFeatures": {
11
+ "codeActions": true,
12
+ "diagnostics": true,
13
+ "documentHighlights": true,
14
+ "documentLink": true,
15
+ "documentSymbols": true,
16
+ "foldingRanges": true,
17
+ "formatting": true,
18
+ "hover": true,
19
+ "inlayHint": true,
20
+ "onTypeFormatting": true,
21
+ "selectionRanges": true,
22
+ "semanticHighlighting": true,
23
+ "completion": true,
24
+ "codeLens": true
25
+ },
26
+ "rubyLsp.formatter": "rubocop",
27
+ "ruby.rubocop.configFilePath": ".rubocop.yml"
28
+ }
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # PatCmd
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/patcmd.svg)](https://badge.fury.io/rb/patcmd)
4
+ [![Build Status](https://github.com/patrick204nqh/patcmd/actions/workflows/ruby.yml/badge.svg)](https://github.com/patrick204nqh/patcmd/actions)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ PatCmd is a Ruby-based command-line interface (CLI) tool built with Thor, designed to manage and execute tasks efficiently. Whether you're automating scripts, managing deployments, or handling routine operations, PatCmd provides a streamlined and customizable solution.
8
+
9
+ ## Features
10
+
11
+ - **Task Management:** Easily add, list, and execute tasks.
12
+ - **Configurable Environments:** Define environment variables per task.
13
+ - **Extensible:** Add new commands and functionalities as needed.
14
+ - **User-Friendly:** Intuitive CLI with clear help documentation.
15
+
16
+ ## Installation
17
+
18
+ Ensure you have [Ruby](https://www.ruby-lang.org/en/downloads/) (version 3.0 or higher) installed on your system.
19
+
20
+ ### Install via RubyGems
21
+
22
+ ```bash
23
+ gem install patcmd
24
+ ```
25
+
26
+ ### From Source
27
+
28
+ 1. Clone the Repository:
29
+
30
+ ```bash
31
+ git clone https://github.com/yourusername/patcmd.git
32
+ cd patcmd
33
+ ```
34
+
35
+ 2. Build the Gem:
36
+
37
+ ```bash
38
+ gem build patcmd.gemspec
39
+ ```
40
+
41
+ 3. Install the Gem Locally:
42
+
43
+ ```bash
44
+ gem install ./patcmd-0.1.0.gem
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ After installation, you can use `patcmd` directly from your terminal.
50
+
51
+ ### Initialize Configuration
52
+
53
+ Initialize the PatCmd configuration file. This creates a default configuration file where tasks are stored.
54
+
55
+ ```bash
56
+ patcmd init
57
+ ```
58
+
59
+ Example Output:
60
+
61
+ ```bash
62
+ Configuration initialized at /home/username/.patcmd/config.yml
63
+ ```
64
+
65
+ ### Add a New Task
66
+
67
+ ```bash
68
+ patcmd add \
69
+ --name "Backup" \
70
+ --description "Backup the database" \
71
+ --category "Utility" \
72
+ --path "/usr/local/bin" \
73
+ --action "execute" \
74
+ --command "backup_db" \
75
+ --args db1 db2 \
76
+ --environments NOdE
77
+ ```
78
+
79
+ ### List All Tasks
80
+
81
+ Display all configured tasks.
82
+
83
+ ```bash
84
+ patcmd list
85
+ ```
86
+
87
+ ### Execute a Task
88
+
89
+ ```bash
90
+ patcmd exec Utility Backup execute
91
+ ```
92
+
93
+ ### Help
94
+
95
+ Access help information for all commands or a specific command.
96
+
97
+ ```bash
98
+ patcmd help
99
+ patcmd help add
100
+ patcmd help list
101
+ ```
102
+
103
+ ### Configuration
104
+
105
+ PatCmd uses a YAML configuration file to store tasks and settings. The default configuration file is located at `~/.patcmd/config.yml`. You can specify a different configuration file using the --config option.
106
+
107
+ **Example:**
108
+
109
+ ```bash
110
+ patcmd list --config=/path/to/custom_config.yml
111
+ ```
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/patcmd ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+ require "patcmd"
6
+
7
+ Patcmd::CLI.start(ARGV)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "configuration_manager"
4
+ require_relative "task_executor"
5
+ require_relative "helpers/helpers"
6
+ require_relative "commands/commands"
7
+
8
+ module Patcmd
9
+ module CLI
10
+ end
11
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ class AddCommand < BaseCommand
6
+ default_task :add
7
+
8
+ desc "add", "Add a new task to the config"
9
+ method_option :name, aliases: "-n", required: true, desc: "Task name"
10
+ method_option :description, aliases: "-d", required: true, desc: "Task description"
11
+ method_option :category, aliases: "-c", required: true, desc: "Category name"
12
+ method_option :path, aliases: "-p", required: true, desc: "Execution path"
13
+ method_option :action, aliases: "-a", required: true, desc: "Action name"
14
+ method_option :command, aliases: "-m", required: true, desc: "Command to execute"
15
+ method_option :args, aliases: "-g", type: :array, default: [], desc: "Arguments for the command"
16
+ method_option :environments, aliases: "-e", type: :hash, default: {}, desc: "Environment variables"
17
+ def add
18
+ task = build_task_from_options
19
+ validate_task(task)
20
+ config_manager.add_task(task)
21
+ puts "Added task '#{task["name"]}' under category '#{task["category"]}' with action '#{task["action"]}'."
22
+ end
23
+
24
+ private
25
+
26
+ def build_task_from_options
27
+ {
28
+ "name" => options[:name],
29
+ "description" => options[:description],
30
+ "category" => options[:category],
31
+ "path" => options[:path],
32
+ "action" => options[:action],
33
+ "command" => options[:command],
34
+ "args" => options[:args],
35
+ "environments" => options[:environments],
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ class BaseCommand < Thor
6
+ include Helpers::TaskHelper
7
+ include Helpers::Logger
8
+
9
+ CONFIG_PATH = File.expand_path("~/.patcmd/config.yml")
10
+
11
+ # Define global options
12
+ class_option :config, type: :string, default: CONFIG_PATH, desc: "Path to configuration file"
13
+
14
+ def initialize(*args)
15
+ super
16
+ @config_manager = ConfigurationManager.new(options[:config])
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :config_manager
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ require_relative "core"
6
+
7
+ module Patcmd
8
+ module CLI
9
+ class << self
10
+ def start(argv)
11
+ Core.start(argv)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_command"
4
+ require_relative "init_command"
5
+ require_relative "add_command"
6
+ require_relative "list_command"
7
+ require_relative "exec_command"
8
+
9
+ module Patcmd
10
+ module CLI
11
+ class Core < Thor
12
+ class << self
13
+ # Ensure the CLI exits with a non-zero status code on failure
14
+ def exit_on_failure?
15
+ true
16
+ end
17
+ end
18
+
19
+ register InitCommand, "init", "init", "Initialize the PatCmd configuration"
20
+ register AddCommand, "add", "add", "Add a new task to the config"
21
+ register ListCommand, "list", "list", "List all configured tasks"
22
+ register ExecCommand, "exec", "exec CATEGORY NAME ACTION", "Execute a defined task"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ class ExecCommand < BaseCommand
6
+ default_task :exec
7
+
8
+ desc "exec CATEGORY NAME ACTION", "Execute a defined task"
9
+ def exec(category, name, action)
10
+ task = config_manager.find_task(category, name, action)
11
+ if task
12
+ executor = TaskExecutor.new(task, options)
13
+ executor.execute
14
+ else
15
+ puts "Task not found for category '#{category}', name '#{name}', and action '#{action}'."
16
+ exit(1)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ class InitCommand < BaseCommand
6
+ default_task :init
7
+
8
+ desc "init", "Initialize the PatCmd configuration"
9
+ def init
10
+ config_manager.init_config
11
+ puts "Configuration initialized at #{config_manager.config_path}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ class ListCommand < BaseCommand
6
+ default_task :list
7
+
8
+ desc "list", "List all configured tasks"
9
+ def list
10
+ tasks = config_manager.all_tasks
11
+ if tasks.empty?
12
+ puts "No tasks configured."
13
+ else
14
+ tasks.each { |task| display_task(task) }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ class ConfigurationManager
6
+ require "yaml"
7
+ require "fileutils"
8
+
9
+ attr_reader :config_path
10
+
11
+ def initialize(config_path)
12
+ @config_path = config_path
13
+ ensure_config_file
14
+ @config = load_config
15
+ end
16
+
17
+ def init_config
18
+ unless File.exist?(@config_path)
19
+ default_task = {
20
+ "category" => "Default",
21
+ "name" => "HelloWorld",
22
+ "description" => "A simple Hello World task",
23
+ "path" => "/usr/local/bin",
24
+ "action" => "execute",
25
+ "command" => "echo",
26
+ "args" => ["Hello, World!"],
27
+ "environments" => { "ENV" => "development" },
28
+ }
29
+
30
+ default_config = { "tasks" => [default_task] }
31
+ FileUtils.mkdir_p(File.dirname(@config_path))
32
+ File.write(@config_path, default_config.to_yaml)
33
+ end
34
+ end
35
+
36
+ def add_task(task)
37
+ @config["tasks"] << task
38
+ save_config
39
+ end
40
+
41
+ def find_task(category, name, action)
42
+ @config["tasks"].find do |task|
43
+ task["category"] == category && task["name"] == name && task["action"] == action
44
+ end
45
+ end
46
+
47
+ def all_tasks
48
+ @config["tasks"]
49
+ end
50
+
51
+ private
52
+
53
+ def ensure_config_file
54
+ unless File.exist?(@config_path)
55
+ init_config
56
+ end
57
+ end
58
+
59
+ def load_config
60
+ YAML.load_file(@config_path)
61
+ end
62
+
63
+ def save_config
64
+ File.write(@config_path, @config.to_yaml)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "task_helper"
4
+ require_relative "logger"
5
+
6
+ module Patcmd
7
+ module CLI
8
+ module Helpers
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ module Helpers
6
+ module Logger
7
+ # Logs an informational message with a timestamp.
8
+ #
9
+ # @param message [String] The message to log.
10
+ # @return [void]
11
+ def log_info(message)
12
+ puts "[INFO] #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{message}"
13
+ end
14
+
15
+ # Logs an error message with a timestamp.
16
+ #
17
+ # @param message [String] The message to log.
18
+ # @return [void]
19
+ def log_error(message)
20
+ puts "[ERROR] #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{message}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ module Helpers
6
+ module TaskHelper
7
+ class ValidationError < StandardError; end
8
+
9
+ def validate_task(task)
10
+ required_fields = ["name", "description", "category", "path", "action", "command"]
11
+ missing_fields = required_fields.select { |field| task[field].nil? || task[field].strip.empty? }
12
+
13
+ unless missing_fields.empty?
14
+ raise ValidationError, "Missing required fields: #{missing_fields.join(", ")}"
15
+ exit(1)
16
+ end
17
+
18
+ unless task["args"].is_a?(Array)
19
+ rails(ValidationError, "The 'args' field must be an array.")
20
+ exit(1)
21
+ end
22
+
23
+ unless task["environments"].is_a?(Hash)
24
+ rails(ValidationError, "The 'environments' field must be a hash.")
25
+ exit(1)
26
+ end
27
+ end
28
+
29
+ def display_task(task)
30
+ puts "Category: #{task["category"]}"
31
+ puts " Name: #{task["name"]}"
32
+ puts " Action: #{task["action"]}"
33
+ puts " Description: #{task["description"]}"
34
+ puts " Path: #{task["path"]}"
35
+ puts " Command: #{task["command"]}"
36
+ puts " Args: #{task["args"].join(" ")}" if task["args"].any?
37
+ if task["environments"].any?
38
+ envs = task["environments"].map { |k, v| "#{k}=#{v}" }.join(", ")
39
+ puts " Environments: #{envs}"
40
+ end
41
+ puts "-" * 40
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ module CLI
5
+ class TaskExecutor
6
+ require "shellwords"
7
+
8
+ def initialize(task, options)
9
+ @task = task
10
+ @options = options
11
+ end
12
+
13
+ def execute
14
+ command = prepare_command
15
+ env_vars = prepare_environment_variables
16
+ path = expand_path(@task["path"])
17
+
18
+ unless Dir.exist?(path)
19
+ puts "Path not found: #{path}"
20
+ exit(1)
21
+ end
22
+
23
+ puts "Executing '#{@task["description"]}' in #{path}"
24
+ puts "Command: #{command}" if @options[:verbose]
25
+ puts "Environment Variables: #{env_vars}" if @options[:verbose] && env_vars.any?
26
+
27
+ Dir.chdir(path) do
28
+ result = system(env_vars, command)
29
+ unless result
30
+ puts "Command execution failed."
31
+ exit(1)
32
+ end
33
+ end
34
+ rescue KeyError => e
35
+ puts "Missing option for command substitution: #{e.message}"
36
+ exit(1)
37
+ end
38
+
39
+ private
40
+
41
+ def prepare_command
42
+ cmd = @task["command"]
43
+ args = @task["args"] || []
44
+
45
+ # Handle command substitution
46
+ if @options[:options] && !@options[:options].empty?
47
+ substitution_vars = @options[:options].transform_keys(&:to_sym)
48
+ cmd %= substitution_vars
49
+ args = args.map { |arg| arg % substitution_vars }
50
+ end
51
+
52
+ ([cmd] + args).join(" ")
53
+ end
54
+
55
+ def prepare_environment_variables
56
+ env_vars = @task["environments"] || {}
57
+
58
+ if @options[:options] && !@options[:options].empty?
59
+ substitution_vars = @options[:options].transform_keys(&:to_sym)
60
+ env_vars = env_vars.transform_values { |v| v % substitution_vars }
61
+ end
62
+
63
+ env_vars
64
+ end
65
+
66
+ def expand_path(path)
67
+ File.expand_path(path.gsub("~", Dir.home))
68
+ end
69
+ end
70
+ end
71
+ end
data/lib/patcmd/cli.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cli/cli"
4
+ module Patcmd
5
+ module CLI
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Patcmd
4
+ VERSION = "0.1.0"
5
+ end
data/lib/patcmd.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "patcmd/version"
4
+ require_relative "patcmd/cli"
5
+
6
+ module Patcmd
7
+ end
data/sig/patcmd.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Patcmd
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: patcmd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Huy Nguyen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: PatCmd allows you to define and execute tasks using a simple command-line
28
+ interface.
29
+ email:
30
+ - patrick204nqh@gmail.com
31
+ executables:
32
+ - patcmd
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - ".tool-versions"
39
+ - ".vscode/settings.json"
40
+ - CODE_OF_CONDUCT.md
41
+ - LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - bin/patcmd
45
+ - lib/patcmd.rb
46
+ - lib/patcmd/cli.rb
47
+ - lib/patcmd/cli/cli.rb
48
+ - lib/patcmd/cli/commands/add_command.rb
49
+ - lib/patcmd/cli/commands/base_command.rb
50
+ - lib/patcmd/cli/commands/commands.rb
51
+ - lib/patcmd/cli/commands/core.rb
52
+ - lib/patcmd/cli/commands/exec_command.rb
53
+ - lib/patcmd/cli/commands/init_command.rb
54
+ - lib/patcmd/cli/commands/list_command.rb
55
+ - lib/patcmd/cli/configuration_manager.rb
56
+ - lib/patcmd/cli/helpers/helpers.rb
57
+ - lib/patcmd/cli/helpers/logger.rb
58
+ - lib/patcmd/cli/helpers/task_helper.rb
59
+ - lib/patcmd/cli/task_executor.rb
60
+ - lib/patcmd/version.rb
61
+ - sig/patcmd.rbs
62
+ homepage: https://github.com/patrick204nqh/patcmd
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.0.0
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.5.3
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: A CLI tool for running tasks quickly.
85
+ test_files: []