lib/glimmer/launcher.rb
# Copyright (c) 2007-2023 Andy Maleh
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
if ARGV.include?('--bundler') && File.exist?(File.expand_path('./Gemfile'))
require 'bundler'
Bundler.setup(:default)
end
require 'fileutils'
require 'os'
module Glimmer
# Launcher of glimmer applications and main entry point for the `glimmer` command.
class Launcher
# TODO update the verbiage below for Glimmer DSL for LibUI
TEXT_USAGE = <<~MULTI_LINE_STRING
Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library) - Ruby Gem: glimmer-dsl-libui v#{File.read(File.expand_path('../../../VERSION', __FILE__))}
Usage: glimmer [--bundler] [--pd] [--quiet] [--debug] [--log-level=VALUE] [[ENV_VAR=VALUE]...] [[-ruby-option]...] (application.rb or task[task_args])
Runs Glimmer applications and tasks.
When applications are specified, they are run using Ruby,
automatically preloading the glimmer-dsl-libui Ruby gem.
Optionally, extra Glimmer options, Ruby options, and/or environment variables may be passed in.
Glimmer options:
- "--bundler=GROUP" : Activates gems in Bundler default group in Gemfile
- "--pd=BOOLEAN" : Requires puts_debuggerer to enable pd method
- "--quiet=BOOLEAN" : Does not announce file path of Glimmer application being launched
- "--debug" : Displays extra debugging information and enables debug logging
- "--log-level=VALUE" : Sets Glimmer's Ruby logger level ("ERROR" / "WARN" / "INFO" / "DEBUG"; default is none)
Tasks are run via rake. Some tasks take arguments in square brackets (surround with double-quotes if using Zsh).
Available tasks are below (if you do not see any, please add `require 'glimmer/rake_task'` to Rakefile and rerun or run rake -T):
MULTI_LINE_STRING
GLIMMER_LIB_GEM = 'glimmer-dsl-libui'
GLIMMER_LIB_LOCAL = File.expand_path(File.join('lib', "#{GLIMMER_LIB_GEM}.rb"))
GLIMMER_OPTIONS = %w[--log-level --quiet --bundler --pd]
GLIMMER_OPTION_ENV_VAR_MAPPING = {
'--log-level' => 'GLIMMER_LOGGER_LEVEL' ,
'--bundler' => 'GLIMMER_BUNDLER_SETUP' ,
'--pd' => 'PD' ,
}
REGEX_RAKE_TASK_WITH_ARGS = /^([^\[]+)\[?([^\]]*)\]?$/
class << self
def is_arm64?
host_cpu = OS.host_cpu.downcase
host_cpu.include?('aarch64') || host_cpu.include?('arm')
end
def glimmer_lib
unless @glimmer_lib
@glimmer_lib = GLIMMER_LIB_GEM
if File.exist?(GLIMMER_LIB_LOCAL)
@glimmer_lib = GLIMMER_LIB_LOCAL
puts "[DEVELOPMENT MODE] (detected #{@glimmer_lib})"
end
end
@glimmer_lib
end
def dev_mode?
glimmer_lib == GLIMMER_LIB_LOCAL
end
def glimmer_option_env_vars(glimmer_options)
GLIMMER_OPTION_ENV_VAR_MAPPING.reduce({}) do |hash, pair|
glimmer_options[pair.first] ? hash.merge(GLIMMER_OPTION_ENV_VAR_MAPPING[pair.first] => glimmer_options[pair.first]) : hash
end
end
def load_env_vars(env_vars)
env_vars.each do |key, value|
ENV[key] = value
end
end
def launch(application, ruby_options: [], env_vars: {}, glimmer_options: {})
ruby_options_string = ruby_options.join(' ') + ' ' if ruby_options.any?
env_vars = env_vars.merge(glimmer_option_env_vars(glimmer_options))
load_env_vars(env_vars)
the_glimmer_lib = glimmer_lib
require 'puts_debuggerer' if (ENV['PD'] || ENV['pd']).to_s.downcase == 'true' || the_glimmer_lib == GLIMMER_LIB_LOCAL
is_rake_task = !application.end_with?('.rb')
rake_tasks = []
if is_rake_task
load File.expand_path('./Rakefile') if File.exist?(File.expand_path('./Rakefile')) && caller.join("\n").include?('/bin/glimmer:')
require_relative 'rake_task'
rake_tasks = Rake.application.tasks.map(&:to_s).map {|t| t.sub('glimmer:', '')}
potential_rake_task_parts = application.match(REGEX_RAKE_TASK_WITH_ARGS)
application = potential_rake_task_parts[1]
rake_task_args = potential_rake_task_parts[2].split(',')
end
if rake_tasks.include?(application)
rake_task = "glimmer:#{application}"
puts "Running Glimmer rake task: #{rake_task}" if ruby_options_string.to_s.include?('--debug')
Rake::Task[rake_task].invoke(*rake_task_args)
else
puts "Launching Glimmer Application: #{application}" if ruby_options_string.to_s.include?('--debug') || glimmer_options['--quiet'].to_s.downcase != 'true'
require the_glimmer_lib
load File.expand_path(application)
end
end
end
attr_reader :application_paths
attr_reader :env_vars
attr_reader :glimmer_options
attr_reader :ruby_options
def initialize(raw_options)
raw_options << '--quiet' if !caller.join("\n").include?('/bin/glimmer:') && !raw_options.join.include?('--quiet=')
raw_options << '--log-level=DEBUG' if raw_options.join.include?('--debug') && !raw_options.join.include?('--log-level=')
@application_path = extract_application_path(raw_options)
@env_vars = extract_env_vars(raw_options)
@glimmer_options = extract_glimmer_options(raw_options)
@ruby_options = raw_options
end
def launch
if @application_path.nil?
display_usage
else
launch_application
end
end
private
def launch_application
self.class.launch(
@application_path,
ruby_options: @ruby_options,
env_vars: @env_vars,
glimmer_options: @glimmer_options
)
end
def display_usage
puts TEXT_USAGE
display_tasks
end
def display_tasks
if OS.windows? || Launcher.is_arm64?
require 'rake'
Rake::TaskManager.record_task_metadata = true
require_relative 'rake_task'
tasks = Rake.application.tasks
task_lines = tasks.reject do |task|
task.comment.nil?
end.map do |task|
max_task_size = tasks.map(&:name_with_args).map(&:size).max + 1
task_name = task.name_with_args.sub('glimmer:', '')
line = "glimmer #{task_name.ljust(max_task_size)} # #{task.comment}"
end
puts task_lines.to_a
else
require 'rake-tui'
require 'tty-screen'
require_relative 'rake_task'
Rake::TUI.run(branding_header: nil, prompt_question: 'Select a Glimmer task to run:') do |task, tasks|
max_task_size = tasks.map(&:name_with_args).map(&:size).max + 1
task_name = task.name_with_args.sub('glimmer:', '')
line = "glimmer #{task_name.ljust(max_task_size)} # #{task.comment}"
bound = TTY::Screen.width - 6
line.size <= bound ? line : "#{line[0..(bound - 3)]}..."
end
end
end
# Extract application path (which can also be a rake task, basically a non-arg)
def extract_application_path(options)
application_path = options.detect do |option|
!option.start_with?('-') && !option.include?('=')
end.tap do
options.delete(application_path)
end
end
def extract_env_vars(options)
options.select do |option|
!option.start_with?('-') && option.include?('=')
end.each do |env_var|
options.delete(env_var)
end.reduce({}) do |hash, env_var_string|
match = env_var_string.match(/^([^=]+)=(.+)$/)
hash.merge(match[1] => match[2])
end
end
def extract_glimmer_options(options)
options.select do |option|
GLIMMER_OPTIONS.reduce(false) do |result, glimmer_option|
result || option.include?(glimmer_option)
end
end.each do |glimmer_option|
options.delete(glimmer_option)
end.reduce({}) do |hash, glimmer_option_string|
match = glimmer_option_string.match(/^([^=]+)=?(.+)?$/)
hash.merge(match[1] => (match[2] || 'true'))
end
end
end
end