Class: Brut::TUI::Script

Inherits:
Object
  • Object
show all
Defined in:
lib/brut/tui/script.rb

Overview

A TUI script is a set of steps that are executed in order. Steps can be grouped int phases, and each step can either shell out to an external command or execute Ruby code.

You are intended to subclass this class and implement #execute, which can use the various methods described here to describe the script. In the context of a Brut CLI, your subclass would be used inside the CLI::Command#execute method, where it would call #run! (not #execute).

Inside #execute you should call #phase for each logical phase/grouping of steps. There must be at least one phase. Inside each phase you should call #step one or more times. When a phase is actually executed, it's contents are executed, so anything inside the block will be called. Intermediate variables to allow steps to communicate will work.

After all phases, call done with a success message.

Examples:

def execute
  phase "Set up test data" do
    step "Initializing database",
         exec: "bin/db rebuild -e test"
    step "Loading data" do
      MyData.each do |data|
        notify "Inserting #{data}"
        data.insert!
      end
    end
  end
  phase "Checking data integrity" do
    problems = []
    step "Analyzing data" do
      MyData.each do |data|
        if !data.analyze
          problems << data
        end
      end
    end
    step "Checking problems" do
      abort = false
      problems.each do |problem|
        if problem.warning?
          warning "#{problem} may be an issue, but we can proceed"
        else
          error "#{problem} will prevent our test from working"
          abort = true
        end
      end
      if abort
        raise "Problems prevented script from working"
      end
    end
  end

  done "All ready to go!"
end

Defined Under Namespace

Modules: Events Classes: BlockStep, ExecStep, LoggingSubscriber, PutsSubscriber, Step

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root_dir: nil, ansi: true) ⇒ Script

Create the script. By default, two subscribers are set up: LoggingSubscriber and PutsSubscriber. The logging subscriber will output a detailed log into logs/ using the file named by log_filename. The PutsSubscriber will output ANSI-Fancy messages as the script proceeds.

Parameters:

  • root_dir (String|Pathname) (defaults to: nil)

    path to the root of the Brut project.

  • ansi (true|false) (defaults to: true)

    if true, the PutsSubscriber will use ANSI escape codes to format the output. If false, it won't.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/brut/tui/script.rb', line 84

def initialize(root_dir: nil, ansi: true)
  @root_dir = root_dir ? Pathname(root_dir).expand_path : nil
  logs_dir = if @root_dir
              @root_dir / "logs" 
            else
              Pathame(".") / "logs"
            end
  FileUtils.mkdir_p(logs_dir)
  @event_loop = Brut::TUI::EventLoop.new

  logging_subscriber = LoggingSubscriber.new($0, logfile: logs_dir / "#{self.class.log_filename}.log")
  terminal = Brut::TUI::Terminal.new
  theme = if ansi
             Brut::TUI::TerminalTheme.based_on_background(terminal)
           else
             Brut::TUI::Themes::None.new(terminal)
           end
  puts_subscriber = Brut::TUI::Script::PutsSubscriber.new($0, terminal:, theme:)

  @event_loop.subscribe(Brut::TUI::Events::EventLoopStarted,self)
  @event_loop.subscribe_to_all(logging_subscriber)
  @event_loop.subscribe_to_all(puts_subscriber)

end

Class Method Details

.log_filenameObject

Return the basename of a log file unique to this script. This will use the subclass name to come up with a reasonable name, however your class can override this.



71
72
73
74
# File 'lib/brut/tui/script.rb', line 71

def self.log_filename
  name = self.name.gsub(/Script$/,"").gsub(/::$/,"").split("::").last
  name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
end

Instance Method Details

#done(message) ⇒ Object

Message to show if the script completes successful. Should only be called once and not inside a phase or step.

Parameters:

  • message (String)

    Message to show



190
191
192
# File 'lib/brut/tui/script.rb', line 190

def done(message)
  @done_message = message
end

#error(message) ⇒ Object

Let the user know there was an error. Note that this will not stop the script. Raise an exception to do that.

Parameters:

  • message (String)

    Message to show



198
199
200
# File 'lib/brut/tui/script.rb', line 198

def error(message)
  @event_loop << Events::Message.new(message:, type: :error)
end

#filename(path) ⇒ Object

Wrap a fully-qualified filename in code markup and trim the path to only show the path relative to the root dir. This is much friendlier than showing a long expanded path.

Parameters:

  • path (String|Pathname)

    a fully-qualified path to something inside your Brut app.



207
208
209
210
# File 'lib/brut/tui/script.rb', line 207

def filename(path)
  path = Pathname(path).expand_path
  "`" + (@root_dir ? path.relative_path_from(@root_dir) : path).to_s + "`"
end

#notify(message) ⇒ Object

Notify the user of an event

Parameters:

  • message (String)

    Message to show



168
169
170
# File 'lib/brut/tui/script.rb', line 168

def notify(message)
  @event_loop << Events::Message.new(message:, type: :notification)
end

#phase(name, &block) ⇒ Object

Create a new phase for the script. A phase is basically a named group of steps. The block is not executed immediately, so you may not pass data from one phase to another.

Parameters:

  • name (String)

    The name of the phase

  • block (Proc)

    The block to execute for the phase



145
146
147
# File 'lib/brut/tui/script.rb', line 145

def phase(name, &block)
  @phases << [ name, block ]
end

#run!Object

Entry point for a script. This method starts the event loop.



131
132
133
134
135
136
137
# File 'lib/brut/tui/script.rb', line 131

def run!
  if !self.methods.include?(:execute)
    raise "You must implement the execute method in your Brut::TUI::Script subclass"
  end
  @event_loop.run
  0
end

#step(description, exec: nil) { ... } ⇒ Object

A step to run inside a phase. Steps within phases are executed immediately once the phase has started, so they can pass data to one anther.

Parameters:

  • description (String)

    Message to show the user about this step.

  • exec (String|nil) (defaults to: nil)

    if non-nil, this step will execute this as a command. A block given is ignored. If nil, a block should be given that contains the step's code.

Yields:

  • if exec is nil, this block will be executed for the step



156
157
158
159
160
161
162
163
# File 'lib/brut/tui/script.rb', line 156

def step(description, exec: nil, &block)
  step = if exec.nil?
           BlockStep.new(@event_loop, description, &block)
         else
           ExecStep.new(@event_loop, description, command: exec)
         end
  step.run!
end

#success(message) ⇒ Object

Let the user know something succeeded

Parameters:

  • message (String)

    Message to show



182
183
184
# File 'lib/brut/tui/script.rb', line 182

def success(message)
  @event_loop << Events::Message.new(message:, type: :success)
end

#warning(message) ⇒ Object

Warn the user of an event

Parameters:

  • message (String)

    Message to show



175
176
177
# File 'lib/brut/tui/script.rb', line 175

def warning(message)
  @event_loop << Events::Message.new(message:, type: :warning)
end