1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
|
# frozen_string_literal: true
module Toys
##
# Subclass of `Toys::CLI` configured for the behavior of the standard Toys
# executable. Specifically, this subclass:
#
# * Configures the standard names of files and directories, such as the
# `.toys.rb` file for an "index" tool, and the `.data` and `.lib` directory
# names.
# * Configures default descriptions for the root tool.
# * Configures a default error handler and logger that provide ANSI-colored
# formatted output.
# * Configures a set of middleware that implement online help, verbosity
# flags, and other features.
# * Provides a set of standard templates for typical project build and
# maintenance scripts (suh as clean, test, and rubocop).
# * Finds tool definitions in the standard Toys search path.
#
class StandardCLI < CLI
##
# Standard toys configuration directory name.
# @return [String]
#
CONFIG_DIR_NAME = ".toys"
##
# Standard toys configuration file name.
# @return [String]
#
CONFIG_FILE_NAME = ".toys.rb"
##
# Standard index file name in a toys configuration.
# @return [String]
#
INDEX_FILE_NAME = ".toys.rb"
##
# Standard preload directory name in a toys configuration.
# @return [String]
#
PRELOAD_DIR_NAME = ".preload"
##
# Standard preload file name in a toys configuration.
# @return [String]
#
PRELOAD_FILE_NAME = ".preload.rb"
##
# Standard data directory name in a toys configuration.
# @return [String]
#
DATA_DIR_NAME = ".data"
##
# Standard lib directory name in a toys configuration.
# @return [String]
#
LIB_DIR_NAME = ".lib"
##
# Name of the standard toys executable.
# @return [String]
#
EXECUTABLE_NAME = "toys"
##
# Delimiter characters recognized.
# @return [String]
#
EXTRA_DELIMITERS = ":."
##
# Short description for the standard root tool.
# @return [String]
#
DEFAULT_ROOT_DESC = "Your personal command line tool"
##
# Help text for the standard root tool.
# @return [String]
#
DEFAULT_ROOT_LONG_DESC =
"Toys is your personal command line tool. You can write commands using a simple Ruby DSL," \
" and Toys will automatically organize them, parse arguments, and provide documentation." \
" Tools can be global or scoped to specific directories. You can also use Toys instead of" \
" Rake to provide build and maintenance scripts for your projects." \
" For detailed information, see https://dazuma.github.io/toys"
##
# Short description for the version flag.
# @return [String]
#
DEFAULT_VERSION_FLAG_DESC = "Show the version of Toys."
##
# Name of the toys path environment variable.
# @return [String]
#
TOYS_PATH_ENV = "TOYS_PATH"
##
# Create a standard CLI, configured with the appropriate paths and
# middleware.
#
# @param custom_paths [String,Array<String>] Custom paths to use. If set,
# the CLI uses only the given paths. If not, the CLI will search for
# paths from the current directory and global paths.
# @param include_builtins [boolean] Add the builtin tools. Default is true.
# @param cur_dir [String,nil] Starting search directory for configs.
# Defaults to the current working directory.
#
def initialize(custom_paths: nil,
include_builtins: true,
cur_dir: nil)
require "toys/utils/standard_ui"
ui = Toys::Utils::StandardUI.new
super(
executable_name: EXECUTABLE_NAME,
config_dir_name: CONFIG_DIR_NAME,
config_file_name: CONFIG_FILE_NAME,
index_file_name: INDEX_FILE_NAME,
preload_file_name: PRELOAD_FILE_NAME,
preload_dir_name: PRELOAD_DIR_NAME,
data_dir_name: DATA_DIR_NAME,
lib_dir_name: LIB_DIR_NAME,
extra_delimiters: EXTRA_DELIMITERS,
middleware_stack: default_middleware_stack,
template_lookup: default_template_lookup,
**ui.cli_args
)
if custom_paths
Array(custom_paths).each { |path| add_config_path(path) }
else
add_current_directory_paths(cur_dir)
end
add_builtins if include_builtins
end
private
##
# Add paths for builtin tools
#
def add_builtins
builtins_path = ::File.join(::File.dirname(::File.dirname(__dir__)), "builtins")
add_config_path(builtins_path, source_name: "(builtin tools)", context_directory: nil)
self
end
##
# Add paths for the given current directory and its ancestors, plus the
# global paths.
#
# @param cur_dir [String] The starting directory path, or nil to use the
# current directory
# @return [self]
#
def add_current_directory_paths(cur_dir)
cur_dir = skip_toys_dir(cur_dir || ::Dir.pwd, CONFIG_DIR_NAME)
global_dirs = default_global_dirs
add_search_path_hierarchy(start: cur_dir, terminate: global_dirs)
global_dirs.each { |path| add_search_path(path) }
self
end
##
# Step out of any toys dir.
#
# @param dir [String] The starting path
# @param toys_dir_name [String] The name of the toys directory to look for
# @return [String] The final directory path
#
def skip_toys_dir(dir, toys_dir_name)
cur_dir = dir
loop do
parent = ::File.dirname(dir)
return cur_dir if parent == dir
if ::File.basename(dir) == toys_dir_name
cur_dir = dir = parent
else
dir = parent
end
end
end
##
# Returns the default set of global config directories.
#
# @return [Array<String>]
#
def default_global_dirs
paths = ::ENV[TOYS_PATH_ENV].to_s.split(::File::PATH_SEPARATOR)
paths = [::Dir.home, "/etc"] if paths.empty?
paths
.compact
.uniq
.select { |path| ::File.directory?(path) && ::File.readable?(path) }
.map { |path| ::File.realpath(::File.expand_path(path)) }
end
##
# Returns the middleware for the standard Toys CLI.
#
# @return [Array]
#
def default_middleware_stack
[
Middleware.spec(:set_default_descriptions,
default_root_desc: DEFAULT_ROOT_DESC,
default_root_long_desc: DEFAULT_ROOT_LONG_DESC),
Middleware.spec(:show_help,
help_flags: true,
usage_flags: true,
list_flags: true,
recursive_flags: true,
search_flags: true,
show_all_subtools_flags: true,
default_recursive: true,
allow_root_args: true,
show_source_path: true,
separate_sources: true,
use_less: true,
fallback_execution: true),
Middleware.spec(:show_root_version,
version_string: ::Toys::VERSION,
version_flag_desc: DEFAULT_VERSION_FLAG_DESC),
Middleware.spec(:handle_usage_errors),
Middleware.spec(:add_verbosity_flags),
]
end
##
# Returns a ModuleLookup for the default templates.
#
# @return [Toys::ModuleLookup]
#
def default_template_lookup
ModuleLookup.new.add_path("toys/templates")
end
end
end
|