A task library for maintaining an open-source library.
The description to use if none is set
The server to release to by default
The default license for the project in SPDX form: spdx.org/licenses
The version to use if one cannot be read from the source
The path to the data directory for the Prestigio library.
The file suffixes to include in documentation
Environment variable overrides for settings
The file that contains the project's dependencies
Words in the package/gem name that should not be included in deriving paths, file names, etc.
Paths
The version of this library
Pattern for extracting a version constant
The gemserver to push gems to
The gem's authors in the form of strings in the format: `Name <email>`
The public cetificates that can be used to verify signed gems
The Gem::RequestSet that describes the gem's dependencies
The descriotion of the gem
The Array of project files that are in the bin/ directory and are executable.
A FileMap of the paths to this project's extension config scripts.
The URI of the project's homepage as a String
The licenses the project is distributed under; usual practice is to list the SPDX name: spdx.org/licenses
The name of the gem the task will build
The options Hash the task lib was created with
The files which should be distributed with the project as a Rake::FileList
The rsync-compatible target to publish documentation to.
The files which should be used to generate documentation as a Rake::FileList
The README of the project as an RDoc::Markup::Document
The version of Ruby required by this gem, in Gem version format.
The summary description of the gem.
The title of the library for things like docs, gemspec, etc.
The Gem::Version of the current library, extracted from the top-level namespace.
Returns true
if Rake::DevEiate
has already been set up.
# File lib/rake/deveiate.rb, line 138
def self::already_setup?
Rake::Task.task_defined?( 'deveiate' )
end
Create the devEiate tasks for a gem with the given name
.
# File lib/rake/deveiate.rb, line 144
def initialize( name, **options, &block )
@name = validate_gemname( name )
@options = options
@rakefile = PROJECT_DIR + 'Rakefile'
@manifest_file = DEFAULT_MANIFEST_FILE.dup
@project_files = self.read_manifest
@executables = self.find_executables
@readme_file = self.find_readme
@history_file = self.find_history_file
@readme = self.parse_readme
@rdoc_files = self.make_rdoc_filelist
@cert_files = Rake::FileList[ CERTS_DIR + '*.pem' ]
@licenses = [ DEFAULT_LICENSE ]
@version_from = env( :version_from, as_pathname: true ) ||
LIB_DIR + "%s.rb" % [ version_file_from(name) ]
@docs_dir = DOCS_DIR.dup
@title = self.extract_default_title
@authors = self.extract_authors
@homepage = self.extract_homepage
@description = self.extract_description || DEFAULT_DESCRIPTION
@summary = nil
@dependencies = self.find_dependencies
@extensions = Rake::FileList.new
@version = nil
@publish_to = nil
@required_ruby_version = nil
super()
self.load_task_libraries
if block
if block.arity.nonzero?
block.call( self )
else
self.instance_exec( self, &block )
end
end
end
Set up common development tasks
# File lib/rake/deveiate.rb, line 130
def self::setup( name, **options, &block )
tasklib = self.new( name, **options, &block )
tasklib.define_tasks
return tasklib
end
Return the Rake::FileList that's used in lieu of the manifest file if it isn't present.
# File lib/rake/deveiate.rb, line 556
def default_manifest
return DEFAULT_PROJECT_FILES.dup
end
Set up tasks for debugging the task library.
# File lib/rake/deveiate.rb, line 378
def define_debug_tasks
task( :base_debug ) do
self.output_documentation_debugging
self.output_project_files_debugging
self.output_dependency_debugging
end
task :debug => :base_debug
end
Set up a simple default task
# File lib/rake/deveiate.rb, line 335
def define_default_tasks
# task used to indicate that rake-deveiate has already been setup once; for
# global rakefiles.
task :deveiate do
# no-op
end
desc "The task that runs by default"
task( :default => :spec )
desc "Check in the current changes"
task :checkin => [ :precheckin, :check, :test ]
task :commit => :checkin
task :ci => :checkin
task :precheckin
desc "Sanity-check the project"
task :check
desc "Update the history file"
task :update_history
desc "Package up and push a release"
task :release => [ :prerelease, :gem, :release_gem, :postrelease ]
task :prerelease
task :release_gem
task :postrelease
desc "Run all the project's tests"
task :test
task :spec
task :integration
desc "Set up the project for development"
task :setup do
self.install_dependencies
end
end
Task-definition hook.
# File lib/rake/deveiate.rb, line 326
def define_tasks
self.define_default_tasks
self.define_debug_tasks
super if defined?( super )
end
Extract the default title from the README if possible, or derive it from the gem name.
# File lib/rake/deveiate.rb, line 425
def extract_default_title
return self.name unless self.readme&.table_of_contents&.first
title = self.readme.table_of_contents.first.text
title ||= self.name
end
Extract a description from the README if possible. Returns nil
if not.
# File lib/rake/deveiate.rb, line 439
def extract_description
parts = self.readme&.parts or return nil
desc_para = parts.find {|part| part.is_a?(RDoc::Markup::Paragraph) }&.text or return nil
formatter = RDoc::Markup::ToHtml.new( RDoc::Options.new )
html = formatter.convert( desc_para )
return html.gsub( /<.*?>/, '' ).strip
end
Extract the URI of the homepage from the `home` item of the first NOTE-type list in the README. Returns nil
if no such URI could be found.
# File lib/rake/deveiate.rb, line 487
def extract_homepage
return fail_extraction( :homepage, "no README" ) unless self.readme
list = self.readme.parts.find {|part| RDoc::Markup::List === part && part.type == :NOTE } or
return fail_extraction(:homepage, "No NOTE list")
item = list.items.find {|item| item.label.include?('home') } or
return fail_extraction(:homepage, "No `home` item")
return item.parts.first.text
end
Extract a summary from the README if possible. Returns nil
if not.
# File lib/rake/deveiate.rb, line 433
def extract_summary
return self.description.split( /(?<=\.)\s+/ ).first.gsub( /\n/, ' ' )
end
Load the gemdeps file if it exists, and return a Gem::RequestSet with the regular dependencies contained in it.
# File lib/rake/deveiate.rb, line 652
def find_dependencies
unless GEMDEPS_FILE.readable?
self.prompt.warn "Deps file (%s) is missing or unreadable, assuming no dependencies." %
[ GEMDEPS_FILE ]
return []
end
finder = Rake::DevEiate::GemDepFinder.new( GEMDEPS_FILE )
finder.load
return finder.dependencies
end
Return an Array of the paths for the executables contained in the project files.
# File lib/rake/deveiate.rb, line 500
def find_executables
paths = self.project_files.find_all do |path|
path.start_with?( 'bin/' )
end
return paths.map {|path| path[%r{^bin/(.*)}, 1] }
end
Find the history file in the list of project files and return it as a Pathname.
# File lib/rake/deveiate.rb, line 589
def find_history_file
file = self.project_files.find {|file| file =~ /^History\.(md|rdoc)$/ }
if file
return Pathname( file )
else
self.prompt.warn "No History.{md,rdoc} found in the project files."
return DEFAULT_HISTORY_FILE
end
end
Find the README file in the list of project files and return it as a Pathname.
# File lib/rake/deveiate.rb, line 576
def find_readme
file = self.project_files.find {|file| file =~ /^README\.(md|rdoc)$/ }
if file
return Pathname( file )
else
self.prompt.warn "No README found in the project files."
return DEFAULT_README_FILE
end
end
Find the file that contains the VERSION
constant and return it as a Gem::Version.
# File lib/rake/deveiate.rb, line 517
def find_version
version_file = self.version_from
unless version_file.readable?
self.prompt.warn "Version could not be read from %s" % [ version_file ]
return nil
end
version_line = version_file.readlines.find {|l| l =~ VERSION_PATTERN } or
abort "Can't read the VERSION from #{version_file}!"
version = version_line[ VERSION_PATTERN, :version ] or
abort "Couldn't find a semantic version in %p" % [ version_line ]
return Gem::Version.new( version )
end
Generate
a TTY::Table from the current dependency list and return it.
# File lib/rake/deveiate.rb, line 624
def generate_dependencies_table
table = TTY::Table.new( header: ['Gem', 'Version', 'Type'] )
self.dependencies.each do |dep|
table << [ dep.name, dep.requirement.to_s, dep.type ]
end
return table
end
Generate
a TTY::Table from the current project files and return it.
# File lib/rake/deveiate.rb, line 601
def generate_project_files_table
columns = [
self.project_files.sort,
self.rdoc_files.sort
]
max_length = columns.map( &:length ).max
columns.each do |col|
self.trace "Filling out columns %d-%d" % [ col.length, max_length ]
next if col.length == max_length
col.fill( '', col.length .. max_length - 1 )
end
table = TTY::Table.new(
header: ['Project', 'Documentation'],
rows: columns.transpose,
)
return table
end
Returns true
if the manifest file exists and is readable.
# File lib/rake/deveiate.rb, line 535
def has_manifest?
return self.manifest_file.readable?
end
Return the character used to build headings give the filename of the file to be generated.
# File lib/rake/deveiate.rb, line 674
def header_char_for( filename )
case File.extname( filename )
when '.md' then return '#'
when '.rdoc' then return '='
when ''
if filename == 'Rakefile'
return '#'
end
end
raise "Don't know what header character is appropriate for %s" % [ filename ]
end
The file that provides high-level change history
# File lib/rake/deveiate.rb, line 236
attr_pathname :history_file
Return a copy of the given text prefixed by spaces
number of spaces.
# File lib/rake/deveiate.rb, line 782
def indent( text, spaces=4 )
prefix = ' ' * spaces
return text.gsub( /(?<=\A|\n)/m, prefix )
end
Install the gems listed in the gem dependencies file.
# File lib/rake/deveiate.rb, line 666
def install_dependencies
self.prompt.say "Installing dependencies"
ruby '-S', 'gem', 'i', '-Ng'
end
Load the template at the specified template_path
, and render it with suitable settings for the given target_filename
.
# File lib/rake/deveiate.rb, line 701
def load_and_render_template( template_path, target_filename )
template = self.read_template( template_path )
header_char = self.header_char_for( target_filename )
return template.result_with_hash(
header_char: header_char,
project: self
)
end
Load the deveiate task libraries.
# File lib/rake/deveiate.rb, line 297
def load_task_libraries
taskdir = Pathname( __FILE__.delete_suffix('.rb') )
tasklibs = Rake::FileList[ taskdir + '*.rb' ].pathmap( '%-2d/%n' )
self.trace( "Loading task libs: %p" % [ tasklibs ] )
tasklibs.each do |lib|
require( lib )
end
self.class.constants.
map {|c| self.class.const_get(c) }.
select {|c| c.respond_to?(:instance_methods) }.
select {|c| c.instance_methods(false).include?(:define_tasks) }.
each do |mod|
self.trace "Loading tasks from %p" % [ mod ]
extend( mod )
end
self.setup( self.name, **self.options )
end
Make a Rake::FileList of the files that should be used to generate documentation.
# File lib/rake/deveiate.rb, line 563
def make_rdoc_filelist
list = self.project_files.dup
list.exclude do |fn|
fn =~ %r:^(spec|data)/: || !fn.end_with?( *DOCUMENTATION_SUFFIXES )
end
return list
end
The file to read the list of distribution files from
# File lib/rake/deveiate.rb, line 240
attr_pathname :manifest_file
Output debugging about the project's dependencies.
# File lib/rake/deveiate.rb, line 769
def output_dependency_debugging
self.prompt.say( "Dependencies", color: :bright_green )
table = self.generate_dependencies_table
if table.empty?
self.prompt.warn( "None." )
else
self.prompt.say( table.render(:unicode, padding: [0,1]) )
end
self.prompt.say( "\n" )
end
Output debugging information about documentation.
# File lib/rake/deveiate.rb, line 733
def output_documentation_debugging
summary = self.extract_summary
description = self.extract_description
homepage = self.extract_homepage
self.prompt.say( "Documentation", color: :bright_green )
self.prompt.say( "Authors:" )
self.authors.each do |author|
self.prompt.say( " • " )
self.prompt.say( author, color: :bold )
end
self.prompt.say( "Summary: " )
self.prompt.say( summary, color: :bold )
self.prompt.say( "Description:" )
self.prompt.say( " " + description, color: :bold )
self.prompt.say( "Homepage:" )
self.prompt.say( " " + homepage, color: :bold )
self.prompt.say( "\n" )
end
Output debugging info related to the list of project files the build operates on.
# File lib/rake/deveiate.rb, line 756
def output_project_files_debugging
self.prompt.say( "Project files:", color: :bright_green )
table = self.generate_project_files_table
if table.empty?
self.prompt.warn( "None." )
else
self.prompt.say( table.render(:unicode, padding: [0,1]) )
end
self.prompt.say( "\n" )
end
Parse the README into an RDoc::Markup::Document and return it
# File lib/rake/deveiate.rb, line 636
def parse_readme
return nil unless self.readme_file.readable?
case self.readme_file.extname
when '.md'
return RDoc::Markdown.parse( self.readme_file.read )
when '.rdoc'
return RDoc::Markup.parse( self.readme_file.read )
else
raise "Can't parse %s: unhandled format %p" % [ self.readme_file, README_FILE.extname ]
end
end
Fetch the Pastel object, creating it if necessary.
# File lib/rake/deveiate.rb, line 400
def pastel
return @pastel ||= begin
pastel = Pastel.new( enabled: $stdout.tty? )
pastel.alias_color( :headline, :bold, :white, :on_black )
pastel.alias_color( :success, :bold, :green )
pastel.alias_color( :error, :bold, :red )
pastel.alias_color( :warning, :yellow )
pastel.alias_color( :added, :green )
pastel.alias_color( :removed, :red )
pastel.alias_color( :prompt, :cyan )
pastel.alias_color( :even_row, :bold )
pastel.alias_color( :odd_row, :reset )
pastel
end
end
Fetch the TTY-Prompt, creating it if necessary.
# File lib/rake/deveiate.rb, line 394
def prompt
return @prompt ||= TTY::Prompt.new( output: $stderr )
end
The project's Rakefile
# File lib/rake/deveiate.rb, line 228
attr_pathname :rakefile
Read the manifest file if there is one, falling back to a default list if there isn't a manifest.
# File lib/rake/deveiate.rb, line 542
def read_manifest
if self.has_manifest?
entries = self.manifest_file.readlines.map( &:chomp )
return Rake::FileList[ *entries ]
else
self.prompt.warn "No manifest (%s): falling back to a default list" %
[ self.manifest_file ]
return self.default_manifest
end
end
Read a template with the given name
from the data directory and return it as an ERB object.
# File lib/rake/deveiate.rb, line 690
def read_template( name )
name = "%s.erb" % [ name ] unless name.to_s.end_with?( '.erb' )
template_path = DEVEIATE_DATADIR + name
template_src = template_path.read( encoding: 'utf-8' )
return ERB.new( template_src, trim_mode: '-' )
end
The file that will be the main page of documentation
# File lib/rake/deveiate.rb, line 232
attr_pathname :readme_file
Post-loading callback.
# File lib/rake/deveiate.rb, line 320
def setup( name, **options )
# No-op
end
Output args
to $stderr if tracing is enabled.
# File lib/rake/deveiate.rb, line 418
def trace( *args )
Rake.application.trace( *args ) if Rake.application.options.trace
end
The pathname of the file to read the version from
# File lib/rake/deveiate.rb, line 224
attr_pathname :version_from
Yield an IO to the block open to a file that will replace filename
if the block returns successfully.
# File lib/rake/deveiate.rb, line 714
def write_replacement_file( filename, **opts )
path = Pathname( filename ).expand_path
mode = path.stat.mode
owner = path.stat.uid
group = path.stat.gid
tmpfile = Tempfile.create( path.basename.to_s, **opts ) do |fh|
yield( fh )
newfile = Pathname( fh.path ).expand_path
newfile.rename( path )
path.chown( owner, group )
path.chmod( mode )
end
end