semaph 0.1.0 → 0.6.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 +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +18 -0
- data/.semaphore/semaphore.yml +23 -0
- data/.tool-versions +1 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +55 -0
- data/README.md +85 -11
- data/Rakefile +4 -1
- data/exe/semaph +5 -0
- data/lib/semaph.rb +17 -1
- data/lib/semaph/api.rb +94 -0
- data/lib/semaph/commands.rb +63 -0
- data/lib/semaph/commands/reload_command.rb +17 -0
- data/lib/semaph/commands/rerun_workflow_command.rb +16 -0
- data/lib/semaph/commands/stop_workflow_command.rb +16 -0
- data/lib/semaph/commands/visit_url_command.rb +16 -0
- data/lib/semaph/formatting.rb +9 -0
- data/lib/semaph/model/job.rb +101 -0
- data/lib/semaph/model/job_collection.rb +47 -0
- data/lib/semaph/model/pipeline.rb +48 -0
- data/lib/semaph/model/pipeline_collection.rb +20 -0
- data/lib/semaph/model/project.rb +34 -0
- data/lib/semaph/model/project_collection.rb +19 -0
- data/lib/semaph/model/workflow.rb +48 -0
- data/lib/semaph/model/workflow_collection.rb +19 -0
- data/lib/semaph/shells/organisation/organisation_shell.rb +33 -0
- data/lib/semaph/shells/organisation/projects_list_command.rb +21 -0
- data/lib/semaph/shells/organisation/projects_select_command.rb +26 -0
- data/lib/semaph/shells/organisations/organisations_list_command.rb +20 -0
- data/lib/semaph/shells/organisations/organisations_select_command.rb +25 -0
- data/lib/semaph/shells/organisations/organisations_shell.rb +24 -0
- data/lib/semaph/shells/pipeline/job_log_command.rb +44 -0
- data/lib/semaph/shells/pipeline/job_log_grep_command.rb +28 -0
- data/lib/semaph/shells/pipeline/jobs_list_command.rb +21 -0
- data/lib/semaph/shells/pipeline/jobs_poll_command.rb +62 -0
- data/lib/semaph/shells/pipeline/pipeline_shell.rb +45 -0
- data/lib/semaph/shells/project/project_shell.rb +61 -0
- data/lib/semaph/shells/project/workflows_list_command.rb +24 -0
- data/lib/semaph/shells/project/workflows_select_command.rb +31 -0
- data/lib/semaph/shells/workflow/pipelines_list_command.rb +21 -0
- data/lib/semaph/shells/workflow/pipelines_select_command.rb +30 -0
- data/lib/semaph/shells/workflow/workflow_shell.rb +35 -0
- data/lib/semaph/version.rb +1 -1
- data/semaph.gemspec +12 -7
- metadata +115 -7
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            module Semaph
         | 
| 2 | 
            +
              module Commands
         | 
| 3 | 
            +
                class RerunWorkflowCommand
         | 
| 4 | 
            +
                  attr_reader :help
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize(workflow)
         | 
| 7 | 
            +
                    @workflow = workflow
         | 
| 8 | 
            +
                    @help = "rerun workflow"
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def execute(_whatever)
         | 
| 12 | 
            +
                    ::Semaph::Shells::Workflow::WorkflowShell.new(@workflow.rerun).push
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| @@ -0,0 +1,101 @@ | |
| 1 | 
            +
            require "fileutils"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Semaph
         | 
| 4 | 
            +
              module Model
         | 
| 5 | 
            +
                class Job
         | 
| 6 | 
            +
                  attr_reader(
         | 
| 7 | 
            +
                    :pipeline,
         | 
| 8 | 
            +
                    :id,
         | 
| 9 | 
            +
                    :name,
         | 
| 10 | 
            +
                    :status,
         | 
| 11 | 
            +
                    :result,
         | 
| 12 | 
            +
                    :block_name,
         | 
| 13 | 
            +
                    :block_state,
         | 
| 14 | 
            +
                    :block_result,
         | 
| 15 | 
            +
                  )
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def initialize(pipeline, raw_block, raw_job)
         | 
| 18 | 
            +
                    @pipeline = pipeline
         | 
| 19 | 
            +
                    @raw_block = raw_block
         | 
| 20 | 
            +
                    @raw_job = raw_job
         | 
| 21 | 
            +
                    assign_from_job(raw_job)
         | 
| 22 | 
            +
                    assign_from_block(raw_block)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def write_log(base)
         | 
| 26 | 
            +
                    FileUtils.mkdir_p(base)
         | 
| 27 | 
            +
                    filename = "#{base}/#{id}.log"
         | 
| 28 | 
            +
                    return filename if File.exist?(filename)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    puts "retrieving log for job #{id}"
         | 
| 31 | 
            +
                    File.open(filename, "w") do |file|
         | 
| 32 | 
            +
                      file.puts pipeline.workflow.project.client.job_log(id)
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    filename
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def description
         | 
| 39 | 
            +
                    [
         | 
| 40 | 
            +
                      block_icon,
         | 
| 41 | 
            +
                      @block_name,
         | 
| 42 | 
            +
                      job_icon,
         | 
| 43 | 
            +
                      @name,
         | 
| 44 | 
            +
                    ].compact.join(" ")
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def finished?
         | 
| 48 | 
            +
                    @status == "FINISHED"
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def failed?
         | 
| 52 | 
            +
                    @result == "FAILED"
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  # block_state can be waiting/running/done
         | 
| 56 | 
            +
                  # block_result can be passed/failed/canceled/stopped
         | 
| 57 | 
            +
                  def block_icon
         | 
| 58 | 
            +
                    return "🟠" if @block_state == "waiting"
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    return "🔵" if @block_state == "running"
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    return "⚪" if @block_result == "canceled"
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    return "⛔" if @block_result == "stopped"
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    return "🟢" if @block_result == "passed"
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    "🔴"
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  # status can be FINISHED/RUNNING
         | 
| 72 | 
            +
                  # result can be PASSED/FAILED/STOPPED
         | 
| 73 | 
            +
                  def job_icon
         | 
| 74 | 
            +
                    return nil unless @status
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    return "🔵" unless @status == "FINISHED"
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                    return "⛔" if @result == "STOPPED"
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    return "🟢" if @result == "PASSED"
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    "🔴"
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  private
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  def assign_from_job(raw)
         | 
| 88 | 
            +
                    @id = raw["job_id"]
         | 
| 89 | 
            +
                    @status = raw["status"]
         | 
| 90 | 
            +
                    @name = raw["name"]
         | 
| 91 | 
            +
                    @result = raw["result"]
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def assign_from_block(raw)
         | 
| 95 | 
            +
                    @block_name = raw["name"]
         | 
| 96 | 
            +
                    @block_state = raw["state"]
         | 
| 97 | 
            +
                    @block_result = raw["result"]
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
            end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require "semaph/model/job"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Semaph
         | 
| 4 | 
            +
              module Model
         | 
| 5 | 
            +
                class JobCollection
         | 
| 6 | 
            +
                  attr_reader :all, :pipeline
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(pipeline)
         | 
| 9 | 
            +
                    @pipeline = pipeline
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def reload
         | 
| 13 | 
            +
                    workflow = @pipeline.workflow
         | 
| 14 | 
            +
                    project = workflow.project
         | 
| 15 | 
            +
                    @all = build_jobs(project.client.pipeline(@pipeline.id))
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def incomplete
         | 
| 19 | 
            +
                    @all.reject(&:finished?)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def failed
         | 
| 23 | 
            +
                    @all.select(&:failed?)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  private
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def build_jobs(content)
         | 
| 29 | 
            +
                    result = []
         | 
| 30 | 
            +
                    blocks = content.delete("blocks")
         | 
| 31 | 
            +
                    blocks.each do |block|
         | 
| 32 | 
            +
                      jobs = block.delete("jobs").sort_by { |job| job["index"] }
         | 
| 33 | 
            +
                      append_jobs(result, block, jobs)
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                    result
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def append_jobs(result, block, jobs)
         | 
| 39 | 
            +
                    if jobs.count.positive?
         | 
| 40 | 
            +
                      jobs.each { |job| result << Job.new(@pipeline, block, job) }
         | 
| 41 | 
            +
                    else
         | 
| 42 | 
            +
                      result << Job.new(@pipeline, block, {})
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            require "semaph/model/job_collection"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Semaph
         | 
| 4 | 
            +
              module Model
         | 
| 5 | 
            +
                class Pipeline
         | 
| 6 | 
            +
                  attr_reader :workflow, :raw, :id, :yaml, :state, :result
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(workflow, raw)
         | 
| 9 | 
            +
                    @workflow = workflow
         | 
| 10 | 
            +
                    @raw = raw
         | 
| 11 | 
            +
                    @id = raw["ppl_id"]
         | 
| 12 | 
            +
                    @yaml = raw["yaml_file_name"]
         | 
| 13 | 
            +
                    @state = raw["state"]
         | 
| 14 | 
            +
                    @result = raw["result"]
         | 
| 15 | 
            +
                    %w[created done pending queuing running stopping].each do |name|
         | 
| 16 | 
            +
                      extract_time(name)
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def job_collection
         | 
| 21 | 
            +
                    @job_collection ||= JobCollection.new(self)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def description
         | 
| 25 | 
            +
                    "#{icon} #{yaml}"
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def icon
         | 
| 29 | 
            +
                    return "🔵" unless @state == "DONE"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    return "⛔" if @result == "STOPPED"
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    return "🟢" if @result == "PASSED"
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    "🔴"
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  private
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def extract_time(name)
         | 
| 41 | 
            +
                    key = "#{name}_at"
         | 
| 42 | 
            +
                    return if raw[key]["seconds"].zero?
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    instance_variable_set("@#{key}", Time.at(raw[key]["seconds"]))
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require "semaph/model/pipeline"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Semaph
         | 
| 4 | 
            +
              module Model
         | 
| 5 | 
            +
                class PipelineCollection
         | 
| 6 | 
            +
                  attr_reader :all
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(workflow)
         | 
| 9 | 
            +
                    @workflow = workflow
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def reload
         | 
| 13 | 
            +
                    project = @workflow.project
         | 
| 14 | 
            +
                    @all = project.client.pipelines({ wf_id: @workflow.id }).map do |content|
         | 
| 15 | 
            +
                      Pipeline.new @workflow, content
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            require "semaph/model/workflow_collection"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Semaph
         | 
| 4 | 
            +
              module Model
         | 
| 5 | 
            +
                class Project
         | 
| 6 | 
            +
                  GITHUB_REGGEXP = %r{git@github.com:(.*)/(.*).git}.freeze
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  attr_reader :client, :raw, :id, :name
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(client, raw)
         | 
| 11 | 
            +
                    @client = client
         | 
| 12 | 
            +
                    @raw = raw
         | 
| 13 | 
            +
                    @id = raw["metadata"]["id"]
         | 
| 14 | 
            +
                    @name = raw["metadata"]["name"]
         | 
| 15 | 
            +
                    repo = raw["spec"]["repository"]["url"]
         | 
| 16 | 
            +
                    match = GITHUB_REGGEXP.match(repo)
         | 
| 17 | 
            +
                    return unless match
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    @repo_owner = match[1]
         | 
| 20 | 
            +
                    @repo_name = match[2]
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def github_url
         | 
| 24 | 
            +
                    return nil unless @repo_owner && @repo_name
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    "https://github.com/#{@repo_owner}/#{@repo_name}"
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def workflow_collection
         | 
| 30 | 
            +
                    @workflow_collection ||= WorkflowCollection.new(self)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            require "semaph/model/project"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Semaph
         | 
| 4 | 
            +
              module Model
         | 
| 5 | 
            +
                class ProjectCollection
         | 
| 6 | 
            +
                  attr_reader :all
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(client)
         | 
| 9 | 
            +
                    @client = client
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def reload
         | 
| 13 | 
            +
                    @all = @client.projects.map do |content|
         | 
| 14 | 
            +
                      Project.new @client, content
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            require "semaph/formatting"
         | 
| 2 | 
            +
            require "semaph/model/pipeline_collection"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Semaph
         | 
| 5 | 
            +
              module Model
         | 
| 6 | 
            +
                class Workflow
         | 
| 7 | 
            +
                  attr_reader :project, :raw, :id, :sha, :commit, :branch, :branch_id, :created_at
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(project, raw)
         | 
| 10 | 
            +
                    @project = project
         | 
| 11 | 
            +
                    @raw = raw
         | 
| 12 | 
            +
                    @id = raw["wf_id"]
         | 
| 13 | 
            +
                    @created_at = Time.at(raw["created_at"]["seconds"].to_i)
         | 
| 14 | 
            +
                    @branch = raw["branch_name"]
         | 
| 15 | 
            +
                    @branch_id = raw["branch_id"]
         | 
| 16 | 
            +
                    extract_git_details
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def extract_git_details
         | 
| 20 | 
            +
                    @sha = raw["commit_sha"]
         | 
| 21 | 
            +
                    @commit = @sha.slice(0..10)
         | 
| 22 | 
            +
                    @commit = `git log -n 1 --format="%h %an %s" #{sha}`.chomp if `git cat-file -t #{sha} 2>&1`.chomp == "commit"
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def pipeline_collection
         | 
| 26 | 
            +
                    @pipeline_collection ||= PipelineCollection.new(self)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def rerun
         | 
| 30 | 
            +
                    rerun_response = project.client.rerun_workflow(@id)
         | 
| 31 | 
            +
                    workflow_response = project.client.workflow(rerun_response["wf_id"])
         | 
| 32 | 
            +
                    Workflow.new(project, workflow_response["workflow"])
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def description
         | 
| 36 | 
            +
                    [
         | 
| 37 | 
            +
                      Semaph::Formatting.time(created_at),
         | 
| 38 | 
            +
                      branch,
         | 
| 39 | 
            +
                      commit,
         | 
| 40 | 
            +
                    ].join(" ")
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def stop
         | 
| 44 | 
            +
                    project.client.stop_workflow(@id)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            require "semaph/model/workflow"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Semaph
         | 
| 4 | 
            +
              module Model
         | 
| 5 | 
            +
                class WorkflowCollection
         | 
| 6 | 
            +
                  attr_reader :all
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(project)
         | 
| 9 | 
            +
                    @project = project
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def reload
         | 
| 13 | 
            +
                    @all = @project.client.workflows(@project.id).map do |content|
         | 
| 14 | 
            +
                      Workflow.new @project, content
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            require "semaph/api"
         | 
| 2 | 
            +
            require "semaph/commands/reload_command"
         | 
| 3 | 
            +
            require "semaph/model/project_collection"
         | 
| 4 | 
            +
            require "semaph/shells/organisation/projects_list_command"
         | 
| 5 | 
            +
            require "semaph/shells/organisation/projects_select_command"
         | 
| 6 | 
            +
            require "shell_shock/context"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Semaph
         | 
| 9 | 
            +
              module Shells
         | 
| 10 | 
            +
                module Organisation
         | 
| 11 | 
            +
                  class OrganisationShell
         | 
| 12 | 
            +
                    include ShellShock::Context
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    def initialize(organisation)
         | 
| 15 | 
            +
                      @client = ::Semaph::Api.new(organisation["auth"]["token"], organisation["host"])
         | 
| 16 | 
            +
                      @prompt = "🏗  #{@client.name} > "
         | 
| 17 | 
            +
                      add_commands
         | 
| 18 | 
            +
                      @project_list_command.execute("")
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    private
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def add_commands
         | 
| 24 | 
            +
                      project_collection = ::Semaph::Model::ProjectCollection.new(@client)
         | 
| 25 | 
            +
                      @project_list_command = ProjectsListCommand.new(project_collection)
         | 
| 26 | 
            +
                      add_command @project_list_command, "list-projects", "ls"
         | 
| 27 | 
            +
                      add_command ProjectsSelectCommand.new(project_collection), "select-project", "cd"
         | 
| 28 | 
            +
                      add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         |