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: "brut 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



194
195
196
# File 'lib/brut/tui/script.rb', line 194

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



202
203
204
# File 'lib/brut/tui/script.rb', line 202

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.



211
212
213
214
# File 'lib/brut/tui/script.rb', line 211

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



172
173
174
# File 'lib/brut/tui/script.rb', line 172

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, stdout: false, stderr: false) { ... } ⇒ 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.

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

    if exec is set, and this is true, it means this command's stdout is relevant to the script and should be shown if possible.

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

    if exec is set, and this is true, it means this command's stderr is relevant to the script and should be shown if possible.

Yields:

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



160
161
162
163
164
165
166
167
# File 'lib/brut/tui/script.rb', line 160

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

#success(message) ⇒ Object

Let the user know something succeeded

Parameters:

  • message (String)

    Message to show



186
187
188
# File 'lib/brut/tui/script.rb', line 186

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



179
180
181
# File 'lib/brut/tui/script.rb', line 179

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