DevEiate

class
Superclass
Rake::TaskLib
Included Modules
Rake::TraceOutput

A task library for maintaining an open-source library.

Constants

CERTS_DIR
CHECKSUM_DIR
DATA_DIR
DEFAULT_DESCRIPTION

The description to use if none is set

DEFAULT_GEMSERVER

The server to release to by default

DEFAULT_HISTORY_FILE
DEFAULT_LICENSE

The default license for the project in SPDX form: spdx.org/licenses

DEFAULT_MANIFEST_FILE
DEFAULT_PROJECT_FILES
DEFAULT_README_FILE
DEFAULT_VERSION

The version to use if one cannot be read from the source

DEVEIATE_DATADIR

The path to the data directory for the Prestigio library.

DOCS_DIR
DOCUMENTATION_SUFFIXES

The file suffixes to include in documentation

ENV_SETTING_NAME

Environment variable overrides for settings

EXT_DIR
GEMDEPS_FILE

The file that contains the project's dependencies

INT_SPEC_DIR
LIB_DIR
PACKAGE_IGNORE_WORDS

Words in the package/gem name that should not be included in deriving paths, file names, etc.

PKG_DIR
PROJECT_DIR

Paths

SPEC_DIR
VERSION

The version of this library

VERSION_PATTERN

Pattern for extracting a version constant

Attributes

allowed_push_host[RW]

The gemserver to push gems to

authors[RW]

The gem's authors in the form of strings in the format: `Name <email>`

cert_files[RW]

The public cetificates that can be used to verify signed gems

dependencies[RW]

The Gem::RequestSet that describes the gem's dependencies

description[RW]

The descriotion of the gem

executables[R]

The Array of project files that are in the bin/ directory and are executable.

extensions[R]

A FileMap of the paths to this project's extension config scripts.

homepage[RW]

The URI of the project's homepage as a String

licenses[RW]

The licenses the project is distributed under; usual practice is to list the SPDX name: spdx.org/licenses

name[R]

The name of the gem the task will build

options[R]

The options Hash the task lib was created with

project_files[RW]

The files which should be distributed with the project as a Rake::FileList

publish_to[RW]

The rsync-compatible target to publish documentation to.

rdoc_files[RW]

The files which should be used to generate documentation as a Rake::FileList

readme[RW]

The README of the project as an RDoc::Markup::Document

required_ruby_version[RW]

The version of Ruby required by this gem, in Gem version format.

summary[RW]

The summary description of the gem.

title[RW]

The title of the library for things like docs, gemspec, etc.

version[R]

The Gem::Version of the current library, extracted from the top-level namespace.

Public Class Methods

anchor
already_setup?()

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
anchor
new( name, **options, &block )

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
anchor
setup( name, **options, &block )

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

Public Instance Methods

anchor
author_names()

Return just the name parts of the library's authors setting.

# File lib/rake/deveiate.rb, line 450
def author_names
        return self.authors.map do |author|
                author[ /^(.*?) </, 1 ]
        end
end
anchor
default_manifest()

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
anchor
define_debug_tasks()

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
anchor
define_default_tasks()

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
anchor
define_tasks()

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
anchor
extract_authors()

Extract authors in the form `Firstname Lastname <email@address>` from the README.

# File lib/rake/deveiate.rb, line 458
def extract_authors
        readme = self.readme or return []
        content = readme.parts.grep_v( RDoc::Markup::BlankLine )

        heading, list = content.each_cons( 2 ).find do |heading, list|
                heading.is_a?( RDoc::Markup::Heading ) && heading.text =~ /^authors?/i &&
                        list.is_a?( RDoc::Markup::List )
        end

        unless list
                self.trace "Couldn't find an Author(s) section of the readme."
                return []
        end

        return list.items.map do |item|
                # unparse the name + email
                raw = item.parts.first.text or next
                name, email = raw.split( ' mailto:', 2 )
                if email
                        "%s <%s>" % [ name, email ]
                else
                        name
                end
        end
end
anchor
extract_default_title()

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
anchor
extract_description()

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
anchor
extract_homepage()

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
anchor
extract_summary()

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
anchor
find_dependencies()

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
anchor
find_executables()

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
anchor
find_history_file()

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
anchor
find_readme()

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
anchor
find_version()

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
anchor
generate_dependencies_table()

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
anchor
generate_project_files_table()

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
anchor
has_manifest?()

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
anchor
header_char_for( filename )

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
anchor
history_file()

The file that provides high-level change history

# File lib/rake/deveiate.rb, line 236
attr_pathname :history_file
anchor
indent( text, spaces=4 )

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
anchor
install_dependencies()

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
anchor
load_and_render_template( template_path, target_filename )

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
anchor
load_task_libraries()

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
anchor
make_rdoc_filelist()

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
anchor
manifest_file()

The file to read the list of distribution files from

# File lib/rake/deveiate.rb, line 240
attr_pathname :manifest_file
anchor
output_dependency_debugging()

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
anchor
output_documentation_debugging()

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
anchor
output_project_files_debugging()

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
anchor
parse_readme()

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
anchor
pastel()

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
anchor
prompt()

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
anchor
rakefile()

The project's Rakefile

# File lib/rake/deveiate.rb, line 228
attr_pathname :rakefile
anchor
read_manifest()

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
anchor
read_template( name )

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
anchor
readme_file()

The file that will be the main page of documentation

# File lib/rake/deveiate.rb, line 232
attr_pathname :readme_file
anchor
setup( name, **options )

Post-loading callback.

# File lib/rake/deveiate.rb, line 320
def setup( name, **options )
        # No-op
end
anchor
trace( *args )

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
anchor
version_from()

The pathname of the file to read the version from

# File lib/rake/deveiate.rb, line 224
attr_pathname :version_from
anchor
write_replacement_file( filename, **opts ) { |fh| ... }

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