lib/falkorlib/bootstrap/base.rb
# -*- encoding: utf-8 -*-
################################################################################
# Time-stamp: <Mon 2023-12-04 16:10 svarrette>
################################################################################
# Interface for the main Bootstrapping operations
#
require "falkorlib"
require "falkorlib/common"
require "falkorlib/bootstrap"
require 'erb' # required for module generation
require 'artii'
require 'facter'
include FalkorLib::Common
module FalkorLib #:nodoc:
module Config
# Default configuration for Bootstrapping processes
module Bootstrap
DEFAULTS =
{
:gitcrypt => {
:owner => `git config user.signingKey`.chomp,
:subkeys => [],
:hooksdir => 'config/hooks',
:hook => 'pre-commit.git-crypt.sh',
:ulhpc => [
#'0x5D08BCDD4F156AD7', # S. Varrette
],
# :hooks => {
# :precommit => 'https://gist.github.com/848c82daa63710b6c132bb42029b30ef.git',
# },
},
:motd => {
:file => 'motd',
:hostname => `hostname -f`.chomp,
:title => "Title",
:desc => "Brief server description",
:support => `git config user.email`.chomp,
:width => 80
},
:latex => {
:name => '',
:author => `git config user.name`.chomp,
:mail => `git config user.email`.chomp,
:title => 'Title',
:subtitle => 'Overview and Open Challenges',
:image => 'images/logo_ANSSI.png',
:logo => 'images/logo_RF.png',
:url => 'http://cyber.gouv.fr'
},
:letter => {
:author_title => 'PhD',
:institute => 'French National Cybersecurity Agency (ANSSI)',
:department => 'Laboratoire Architectures Matérielles et logicielles',
:department_acro => 'LAM',
:address => '51, boulevard de La Tour-Maubourg',
:zipcode => '75007',
:location => 'Paris, France',
:phone => '(+33) n/a',
:twitter => 'svarrette',
:linkedin => 'svarrette',
:skype => 'sebastien.varrette',
:scholar => '6PTStIcAAAAJ'
},
:metadata => {
:name => '',
:type => [],
:by => (ENV['USER']).to_s,
:author => `git config user.name`.chomp,
:mail => `git config user.email`.chomp,
:summary => "rtfm",
:description => '',
:forge => '',
:source => '',
:project_page => '',
:origin => '',
:license => '',
:issues_url => '',
:tags => []
},
:trashdir => '.Trash',
:types => [ :none, :latex, :gem, :vagrant, :puppet_module, :rvm, :pyenv ],
:licenses => {
"none" => {},
"Apache-2.0" => {
:url => "http://www.apache.org/licenses/LICENSE-2.0",
:logo => "https://www.apache.org/images/feather-small.gif"
},
"BSD" => {
:url => "http://www.linfo.org/bsdlicense.html",
:logo => "http://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/License_icon-bsd.svg/200px-License_icon-bsd.svg.png"
},
"CC-by-nc-sa" => {
:name => "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International",
:url => "http://creativecommons.org/licenses/by-nc-sa/4.0",
:logo => "https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png"
},
"GPL-2.0" => {
:url => "http://www.gnu.org/licenses/gpl-2.0.html",
:logo => "https://licensebuttons.net/l/GPL/2.0/88x62.png"
},
"GPL-3.0" => {
:url => "http://www.gnu.org/licenses/gpl-3.0.html",
:logo => "https://www.gnu.org/graphics/gplv3-88x31.png"
},
"LGPL-2.1" => {
:url => "https://www.gnu.org/licenses/lgpl-2.1.html",
:logo => "https://licensebuttons.net/l/LGPL/2.1/88x62.png"
},
"LGPL-3.0" => {
:url => "https://www.gnu.org/licenses/lgpl.html",
:logo => "https://www.gnu.org/graphics/lgplv3-88x31.png"
},
"MIT" => {
:url => "http://opensource.org/licenses/MIT",
:logo => "http://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/License_icon-mit-2.svg/200px-License_icon-mit-2.svg.png"
}
},
:puppet => {},
:forge => {
:none => { :url => '', :name => "None" },
:github => { :url => 'github.com', :name => 'Github', :login => (`whoami`.chomp.capitalize).to_s },
:gitlab => { :url => 'gitlab.com', :name => 'Gitlab', :login => (`whoami`.chomp.capitalize).to_s }
},
:vagrant => {
:os => :debian12,
:ram => 1024,
:vcpus => 2,
:domain => 'vagrant.dev',
:range => '192.168.56.0/21',
:provider => 'virtualbox',
:boxes => {
:debian12 => 'generic/debian12',
:debian12_uefi => 'ncrmro/debian-bookworm64-uefi',
:almalinux9 => 'almalinux/9',
:almalinux8 => 'almalinux/8',
:almalinux8_uefi => 'almalinux/8.uefi',
:rockylinux8 => 'rockylinux/8',
:centosstream8 => 'centos/stream8',
:ubuntu22 => 'ubuntu/jammy64',
:ubuntu20 => 'ubuntu/focal64',
:fedora38 => 'generic/fedora38',
:archlinux => 'archlinux/archlinux'
},
}
}
end
end
end
module FalkorLib
module Bootstrap #:nodoc:
module_function
###### makefile ######
# Supported options:
# * :master [string] git flow master/production branch
# * :develop [string] git flow development branch
# * :force [boolean] for overwritting
#......................................
def makefile(dir = Dir.pwd, options = {})
path = normalized_path(dir)
path = FalkorLib::Git.rootdir(path) if FalkorLib::Git.init?(path)
info "=> Setup a root repository makefile in '#{dir}'"
# Preparing submodule
submodules = {}
submodules['Makefiles'] = {
:url => 'https://github.com/Falkor/Makefiles.git',
:branch => 'devel'
}
FalkorLib::Git.submodule_init(path, submodules)
makefile = File.join(path, "Makefile")
if File.exist?( makefile )
puts " ... not overwriting the root Makefile which already exists"
else
src_makefile = File.join(path, FalkorLib.config.git[:submodulesdir],
'Makefiles', 'repo', 'Makefile')
FileUtils.cp src_makefile, makefile
gitflow_branches = FalkorLib::Config::GitFlow::DEFAULTS[:branches]
if FalkorLib::GitFlow.init?(path)
[ :master, :develop ].each do |b|
gitflow_branches[b.to_sym] = FalkorLib::GitFlow.branches(b.to_sym)
end
end
unless options.nil?
[ :master, :develop ].each do |b|
gitflow_branches[b.to_sym] = options[b.to_sym] if options[b.to_sym]
end
end
info "adapting Makefile to the gitflow branches"
Dir.chdir( path ) do
run %(
sed -i '' \
-e \"s/^GITFLOW_BR_MASTER=production/GITFLOW_BR_MASTER=#{gitflow_branches[:master]}/\" \
-e \"s/^GITFLOW_BR_DEVELOP=devel/GITFLOW_BR_DEVELOP=#{gitflow_branches[:develop]}/\" \
Makefile
)
end
FalkorLib::Git.add(makefile, 'Initialize root Makefile for the repo')
end
end # makefile
###
# Initialize a trash directory in path
##
def trash(path = Dir.pwd, dirname = FalkorLib.config[:templates][:trashdir], _options = {})
#args = method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge
#ap args
exit_status = 0
trashdir = File.join(File.realpath(path), dirname)
if Dir.exist?(trashdir)
warning "The trash directory '#{dirname}' already exists"
return 1
end
Dir.chdir(path) do
info "creating the trash directory '#{dirname}'"
exit_status = run %(
mkdir -p #{dirname}
echo '*' > #{dirname}/.gitignore
)
if FalkorLib::Git.init?(path)
exit_status = FalkorLib::Git.add(File.join(trashdir.to_s, '.gitignore' ),
'Add Trash directory',
:force => true )
end
end
exit_status.to_i
end # trash
###### versionfile ######
# Bootstrap a VERSION file at the root of a project
# Supported options:
# * :file [string] filename
# * :version [string] version to mention in the file
##
def versionfile(dir = Dir.pwd, options = {})
file = (options[:file]) ? options[:file] : 'VERSION'
version = (options[:version]) ? options[:version] : '0.0.0'
info " ==> bootstrapping a VERSION file"
path = normalized_path(dir)
path = FalkorLib::Git.rootdir(path) if FalkorLib::Git.init?(path)
unless Dir.exist?( path )
warning "The directory #{path} does not exists and will be created"
really_continue?
FileUtils.mkdir_p path
end
versionfile = File.join(path, file)
if File.exist?( versionfile )
puts " ... not overwriting the #{file} file which already exists"
else
FalkorLib::Versioning.set_version(version, path, :type => 'file',
:source => { :filename => file })
Dir.chdir( path ) do
run %( git tag #{options[:tag]} ) if options[:tag]
end
end
# unless File.exists?( versionfile )
# run %{ echo "#{version}" > #{versionfile} }
# if FalkorLib::Git.init?(path)
# FalkorLib::Git.add(versionfile, "Initialize #{file} file")
# Dir.chdir( path ) do
# run %{ git tag #{options[:tag]} } if options[:tag]
# end
# end
# else
# puts " ... not overwriting the #{file} file which already exists"
# end
end # versionfile
###### motd ######
# Generate a new motd (Message of the Day) file
# Supported options:
# * :force [boolean] force action
# * :title [string] title of the motd (in figlet)
# * :support [string] email address to use for getting support
# * :hostname [string] hostname of the server to mention in the motd
# * :width [number] width of the line used
##
def motd(dir = Dir.pwd, options = {})
config = FalkorLib::Config::Bootstrap::DEFAULTS[:motd].merge!(::ActiveSupport::HashWithIndifferentAccess.new(options).symbolize_keys)
path = normalized_path(dir)
erbfile = File.join( FalkorLib.templates, 'motd', 'motd.erb')
outfile = (config[:file] =~ /^\//) ? config[:file] : File.join(path, config[:file])
info "Generate a motd (Message of the Day) file '#{outfile}'"
FalkorLib::Config::Bootstrap::DEFAULTS[:motd].keys.each do |k|
next if [:file, :width].include?(k)
config[k.to_sym] = ask( "\t" + format("Message of the Day (MotD) %-10s", k.to_s), config[k.to_sym]) unless options[:no_interaction]
end
config[:os] = Facter.value(:lsbdistdescription) if Facter.value(:lsbdistdescription)
config[:os] = "Mac " + Facter.value(:sp_os_version) if Facter.value(:sp_os_version)
unless options[:nodemodel]
config[:nodemodel] = Facter.value(:sp_machine_name) if Facter.value(:sp_machine_name)
config[:nodemodel] += " (#{Facter.value(:sp_cpu_type)}" if Facter.value(:sp_cpu_type)
config[:nodemodel] += " " + Facter.value(:sp_current_processor_speed) if Facter.value(:sp_current_processor_speed)
config[:nodemodel] += " #{Facter.value(:sp_number_processors)} cores )" if Facter.value(:sp_number_processors)
end
config[:nodemodel] = Facter.value(:sp_machine_name) unless options[:nodemodel]
write_from_erb_template(erbfile, outfile, config, options)
end # motd
###### readme ######
# Bootstrap a README file for various context
# Supported options:
# * :no_interaction [boolean]: do not interact
# * :force [boolean] force overwritting
# * :gem [boolean] Ruby Gem
# * :latex [boolean] describe a LaTeX project
# * :license [string] License to use
# * :licensefile [string] License filename (default: LICENSE)
# * :octopress [boolean] octopress site
# * :pyenv [boolean] Python virtualenv/pyenv/direnv
# * :rvm [boolean] Ruby RVM
##
def readme(dir = Dir.pwd, options = {})
info "Bootstrap a README file for this project"
path = normalized_path(dir)
# get the local configuration
local_config = FalkorLib::Config.get(dir)
config = FalkorLib::Config::Bootstrap::DEFAULTS[:metadata].clone
name = get_project_name(dir)
if local_config[:project]
config.deep_merge!( local_config[:project])
else
config[:name] = ask("\tProject name: ", name) unless options[:name]
end
if options[:rake]
options[:make] = false
options[:rvm] = true
end
config[:license] = options[:license] if options[:license]
config[:type] << :rvm if options[:rake]
# Type of project
config[:type] << :latex if options[:latex]
if config[:type].empty?
t = select_from( FalkorLib::Config::Bootstrap::DEFAULTS[:types],
'Select the type of project to describe:', 1)
config[:type] << t
config[:type] << [ :ruby, :rvm ] if [ :gem, :rvm, :octopress, :puppet_module ].include?( t )
config[:type] << :python if t == :pyenv
end
config[:type].uniq!
#ap config
config[:type] = config[:type].uniq.flatten
# Apply options (if provided)
[ :name, :forge ].each do |k|
config[k.to_sym] = options[k.to_sym] if options[k.to_sym]
end
path = normalized_path(dir)
config[:filename] = (options[:filename]) ? options[:filename] : File.join(path, 'README.md')
if ( FalkorLib::Git.init?(dir) && FalkorLib::Git.remotes(dir).include?( 'origin' ))
config[:origin] = FalkorLib::Git.config('remote.origin.url')
if config[:origin] =~ /((gforge|gitlab|github)[\.\w_-]+)[:\d\/]+(\w*)/
config[:forge] = Regexp.last_match(2).to_sym
config[:by] = Regexp.last_match(3)
end
elsif config[:forge].empty?
config[:forge] = select_forge(config[:forge]).to_sym
end
forges = FalkorLib::Config::Bootstrap::DEFAULTS[:forge][ config[:forge].to_sym ]
#ap config
default_source = case config[:forge]
when :gforge
'https://' + forges[:url] + "/projects/" + name.downcase
when :github, :gitlab
'https://' + forges[:url] + "/" + config[:by] + "/" + name.downcase
else
""
end
FalkorLib::Config::Bootstrap::DEFAULTS[:metadata].each do |k, v|
next if v.is_a?(Array) || [ :license, :forge ].include?( k )
next if (k == :name) && !config[:name].empty?
next if (k == :issues_url) && ![ :github, :gitlab ].include?( config[:forge] )
#next unless [ :name, :summary, :description ].include?(k.to_sym)
default_answer = case k
when :author
(config[:by] == 'ULHPC') ? 'UL HPC Team' : config[:author]
when :mail
(config[:by] == 'ULHPC') ? 'hpc-team@uni.lu' : config[:mail]
when :description
(config[:description].empty?) ? (config[:summary]).to_s : (config[:description]).to_s
when :source
(config[:source].empty?) ? default_source : (config[:source]).to_s
when :project_page
(config[:source].empty?) ? v : config[:source]
when :issues_url
(config[:project_page].empty?) ? v : "#{config[:project_page]}/issues"
else
(config[k.to_sym].empty?) ? v : config[k.to_sym]
end
config[k.to_sym] = ask( "\t" + Kernel.format("Project %-20s", k.to_s), default_answer)
end
tags = ask("\tKeywords (comma-separated list of tags)", config[:tags].join(','))
config[:tags] = tags.split(',')
config[:license] = select_licence if config[:license].empty?
config[:rootdir] = path
# stack the ERB files required to generate the README
templatedir = File.join( FalkorLib.templates, 'README')
erbfiles = [ 'header_readme.erb' ]
[ :latex ].each do |type|
erbfiles << "readme_#{type}.erb" if options[type.to_sym] && File.exist?( File.join(templatedir, "readme_#{type}.erb"))
end
erbfiles << "readme_issues.erb"
erbfiles << "readme_git.erb" if FalkorLib::Git.init?(dir)
erbfiles << "readme_gitflow.erb" if FalkorLib::GitFlow.init?(dir)
erbfiles << "readme_rvm.erb" if config[:type].include?(:rvm)
erbfiles << "readme_mkdocs.erb" if options[:mkdocs]
erbfiles << "readme_pyenv.erb" if config[:type].include?(:pyenv)
erbfiles << "footer_readme.erb"
content = ""
ap options
ap config
erbfiles.each do |f|
erbfile = File.join(templatedir, f)
content += ERB.new(File.read(erbfile.to_s), nil, '<>').result(binding)
end
show_diff_and_write(content, config[:filename], options)
# Force save/upgrade local config
info "=> saving customization of the FalkorLib configuration in #{FalkorLib.config[:config_files][:local]}"
# really_continue?
FalkorLib::Config::Bootstrap::DEFAULTS[:metadata].keys.each do |k|
local_config[:project] = {} unless local_config[:project]
local_config[:project][k.to_sym] = config[k.to_sym]
end
if FalkorLib::GitFlow.init?(dir)
local_config[:gitflow] = {} unless local_config[:gitflow]
local_config[:gitflow][:branches] = FalkorLib.config[:gitflow][:branches].clone unless local_config[:gitflow][:branches]
[ :master, :develop ].each do |b|
local_config[:gitflow][:branches][b.to_sym] = FalkorLib::GitFlow.branches(b.to_sym)
end
end
FalkorLib::Config.save(dir, local_config, :local)
#
end # readme
###
# Select the forge (gforge, github, etc.) hosting the project sources
##
def select_forge(default = :gforge, _options = {})
forge = FalkorLib::Config::Bootstrap::DEFAULTS[:forge]
#ap forge
default_idx = forge.keys.index(default)
default_idx = 0 if default_idx.nil?
v = select_from(forge.map { |_k, u| u[:name] },
"Select the Forge hosting the project sources",
default_idx + 1,
forge.keys)
v
end # select_forge
###### select_licence ######
# Select a given licence for the project
##
def select_licence(default_licence = FalkorLib::Config::Bootstrap::DEFAULTS[:metadata][:license],
_options = {})
list_license = FalkorLib::Config::Bootstrap::DEFAULTS[:licenses].keys
idx = list_license.index(default_licence) unless default_licence.nil?
select_from(list_license,
'Select the license index for this project:',
(idx.nil?) ? 1 : idx + 1)
#licence
end # select_licence
###### license ######
# Generate the licence file
#
# Supported options:
# * :force [boolean] force action
# * :filename [string] License file name
# * :organization [string] Organization
##
def license(dir = Dir.pwd,
license = FalkorLib::Config::Bootstrap::DEFAULTS[:metadata][:license],
authors = '',
options = {
:filename => 'LICENSE'
})
return if ((license.empty?) or (license == 'none') or (license =~ /^CC/))
return unless FalkorLib::Config::Bootstrap::DEFAULTS[:licenses].keys.include?( license )
info "Generate the #{license} licence file"
path = normalized_path(dir)
use_git = FalkorLib::Git.init?(path)
rootdir = (use_git) ? FalkorLib::Git.rootdir(path) : path
Dir.chdir( rootdir ) do
run %( licgen #{license.downcase} #{authors} )
run %( mv LICENSE #{options[:filename]} ) if( options[:filename] and options[:filename] != 'LICENSE')
end
end # license
###### guess_project_config ######
# Guess the project configuration
##
def guess_project_config(dir = Dir.pwd, options = {})
path = normalized_path(dir)
use_git = FalkorLib::Git.init?(path)
rootdir = (use_git) ? FalkorLib::Git.rootdir(path) : path
local_config = FalkorLib::Config.get(rootdir, :local)
if FalkorLib::GitFlow.init?(rootdir)
local_config[:project][:gitflow] = FalkorLib::GitFlow.guess_gitflow_config(rootdir) if local_config[:project]
end
return local_config[:project] if local_config[:project]
# Otherwise, guess the rest of the configuration
config = FalkorLib::Config::Bootstrap::DEFAULTS[:metadata].clone
# Apply options (if provided)
[ :name, :forge ].each do |k|
config[k.to_sym] = options[k.to_sym] if options[k.to_sym]
end
config[:name] = ask("\tProject name: ", get_project_name(dir)) if config[:name].empty?
if (use_git)
config[:origin] = FalkorLib::Git.config('remote.origin.url')
if config[:origin] =~ /((gforge|gitlab|github)[\.\w_-]+)[:\d\/]+(\w*)/
config[:forge] = Regexp.last_match(2).to_sym
config[:by] = Regexp.last_match(3)
elsif config[:forge].empty?
config[:forge] = select_forge(config[:forge]).to_sym
end
end
forges = FalkorLib::Config::Bootstrap::DEFAULTS[:forge][ config[:forge].to_sym ]
default_source = case config[:forge]
when :gforge
'https://' + forges[:url] + "/projects/" + config[:name].downcase
when :github, :gitlab
'https://' + forges[:url] + "/" + config[:by] + "/" + config[:name].downcase
else
""
end
config[:source] = config[:project_page] = default_source
config[:issues_url] = "#{config[:project_page]}/issues"
config[:license] = select_licence if config[:license].empty?
[ :summary ].each do |k|
config[k.to_sym] = ask( "\t" + Kernel.format("Project %-20s", k.to_s))
end
config[:description] = config[:summary]
if FalkorLib::GitFlow.init?(rootdir)
config[:gitflow] = FalkorLib::GitFlow.guess_gitflow_config(rootdir)
end
config
end # guess_project_config
###### get_badge ######
# Return a Markdown-formatted string for a badge to display, typically in a README.
# Based on http://shields.io/
# Supported options:
# * :style [string] style of the badge, Elligible: ['plastic', 'flat', 'flat-square']
##
def get_badge(subject, status, color = 'blue', options = {})
st = status.gsub(/-/, '--').gsub(/_/, '__')
res = "https://img.shields.io/badge/#{subject}-#{st}-#{color}.svg"
res += "?style=#{options[:style]}" if options[:style]
res
end # get_licence_badge
###### get_project_name ######
# Return a "reasonable" project name from a given [sub] directory i.e. its basename
##
def get_project_name(dir = Dir.pwd, _options = {})
path = normalized_path(dir)
path = FalkorLib::Git.rootdir(path) if FalkorLib::Git.init?(path)
File.basename(path)
end # get_project_name
end # module Bootstrap
end # module FalkorLib