Skip to content

Instantly share code, notes, and snippets.

@mokolabs
Created May 5, 2026 18:54
Show Gist options
  • Select an option

  • Save mokolabs/b0baf9aa4d908b0100baa778337c3027 to your computer and use it in GitHub Desktop.

Select an option

Save mokolabs/b0baf9aa4d908b0100baa778337c3027 to your computer and use it in GitHub Desktop.
Reporting tool for SERVICES.yml
require "io/console"
require "set"
require "terminal-table"
require "yaml"
# SERVICES REPORTER
# This script generates a report about all third-party services used by Ruby
# apps in the ~/Sites directory. The report includes the app name and all
# third-party services used by the app.
# Apps must have a SERVICES.yml file to be included.
# Install the `terminal-table` gem if you don't have it:
# gem install terminal-table
# Usage:
# ruby services.rb # Generate report on all services used by all apps
# ruby services.rb app # Generate report on all services used by single app
# ruby services.rb all # Generate report on all unique service providers
# ruby services.rb help # Show this help message
# METHODS
# Set terminal width
$terminal_width = IO.console ? IO.console.winsize[1] : 103
# Format report header
def header(text)
puts "\n..#{text}".ljust($terminal_width, ".")
end
# Output text and replace any `+` with newlines
# (but ignore terminal tables)
def output(text)
# Replace `+` at the start or end of the string, ignoring `+` near `-`
formatted_text = text.gsub(/\A\++(?!-)|(?<!-)\++\z/) { |match| "\n" * match.length }
puts formatted_text
end
# Load services from all apps
def load_services
data = []
Dir.glob("#{File.expand_path("~/Sites")}/*").select do |app|
File.directory?(app) && File.exist?(File.join(app, "SERVICES.yml"))
end.each do |app|
name = File.basename(app)
services = YAML.load_file(File.join(app, "SERVICES.yml"))
services.each do |category, value|
if value.is_a?(Hash)
value.each do |subcategory, provider|
Array(provider).each { |p| data << [name, "#{category} (#{subcategory})", p] }
end
else
Array(value).each { |v| data << [name, category, v] }
end
end
end
data
end
# Generate report on single app
if app_name = ARGV[0] and !app_name.match?(/help|all|-h|--help/)
app_path = File.expand_path("~/Sites/#{app_name}")
services_file = File.join(app_path, "SERVICES.yml")
if File.directory?(app_path) && File.exist?(services_file)
header "#{app_name.upcase} SERVICES"
services = YAML.load_file(services_file)
output "+#{YAML.dump(services).gsub("---\n", "")}+"
else
output "There was a problem! No services found in ~/Sites/#{app_name}"
end
exit
end
# Generate report on all apps
if ARGV[0].nil?
rows = []
categories = Set.new
data = load_services
grouped = data.group_by { |_, category, _| category }
grouped.sort.each do |category, entries|
entries.group_by { |_, _, provider| provider }.each do |provider, group|
group.each_with_index do |(app, _, _), index|
# Show category and provider only once
rows << [
categories.include?(category) ? "" : category,
index.zero? ? provider : "",
app
]
categories.add(category)
end
end
rows << :separator
end
rows.pop if rows.last == :separator
report = Terminal::Table.new do |table|
table.headings = %w[Category Provider App]
rows.each { |row| row == :separator ? table.add_separator : table.add_row(row) }
end
header "SERVICES"
output "+#{report}++"
exit
end
# Generate report on all providers (with counts)
if ARGV[0] == "all"
services = load_services
providers = services.each_with_object(Hash.new(0)) do |(_, _, provider), counts|
counts[provider] += 1
end
header "SERVICES"
output "+#{providers.sort.map { |provider, count| "#{provider} (#{count})" }.join("\n")}++"
exit
end
# Show help message
if ARGV[0] == "help" || ARGV[0] == "--help" || ARGV[0] == "-h"
puts "Usage: services [options]"
puts
puts "Description:"
puts " Generates reports about third-party services used by local Ruby apps"
puts
puts "Options:"
puts " (none) Generate report on all services used by all apps"
puts " app Generate report on all services used by single app"
puts " all Generate report on all unique service providers"
puts " ..."
puts " help Show this help message"
exit
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment