diff options
-rw-r--r-- | .gitignore | 7 | ||||
-rw-r--r-- | Build.PL | 59 | ||||
-rw-r--r-- | MANIFEST | 15 | ||||
-rw-r--r-- | lib/App/DocKnot.pm | 391 | ||||
-rw-r--r-- | share/licenses.json | 5 | ||||
-rw-r--r-- | share/licenses/Expat | 17 | ||||
-rw-r--r-- | share/templates/readme.tmpl | 48 | ||||
-rwxr-xr-x | t/api/basic.t | 52 | ||||
-rw-r--r-- | t/data/lbcd/metadata/metadata.json | 28 | ||||
-rw-r--r-- | t/data/lbcd/metadata/sections/blurb | 6 | ||||
-rw-r--r-- | t/data/lbcd/metadata/sections/description | 24 | ||||
-rw-r--r-- | t/data/lbcd/metadata/sections/installation | 30 | ||||
-rw-r--r-- | t/data/lbcd/metadata/sections/requirements | 14 | ||||
-rw-r--r-- | t/data/lbcd/metadata/sections/support | 16 | ||||
-rw-r--r-- | t/data/lbcd/metadata/sections/testing | 13 | ||||
-rw-r--r-- | t/data/lbcd/output/readme | 183 |
16 files changed, 908 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44dad35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/Build +/MANIFEST.bak +/MYMETA.json +/MYMETA.yml +/_build/ +/blib/ +/cover_db diff --git a/Build.PL b/Build.PL new file mode 100644 index 0000000..aef481a --- /dev/null +++ b/Build.PL @@ -0,0 +1,59 @@ +#!/usr/bin/perl +# +# Build script for the docknot application. +# +# Written by Russ Allbery <rra@stanford.edu> +# Copyright 2013 +# The Board of Trustees of the Leland Stanford Junior University +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +use 5.018; +use autodie; +use warnings; + +use Module::Build; + +# Basic package configuration. +my $build = Module::Build->new( + dist_name => 'docknot', + dist_abstract => 'Package documentation generator', + dist_author => 'Russ Allbery <eagle@eyrie.org>', + dist_version => '1.00', + license => 'mit', + module_name => 'App::DocKnot', + recursive_test_files => 1, + + # Module static data files. + share_dir => { module => { 'App::DocKnot' => 'share' } }, + + # Other package relationships. + configure_requires => { 'Module::Build' => 0.36 }, + requires => { + 'File::BaseDir' => 0, + 'File::ShareDir' => 0, + 'JSON' => 0, + 'Perl6::Slurp' => 0, + 'Template' => 0, + perl => '5.018', + }, +); + +# Generate the build script. +$build->create_build_script; diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..c9c0472 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,15 @@ +Build.PL +lib/App/DocKnot.pm +MANIFEST This list of files +share/licenses.json +share/licenses/Expat +share/templates/readme.tmpl +t/api/basic.t +t/data/lbcd/metadata/metadata.json +t/data/lbcd/metadata/sections/blurb +t/data/lbcd/metadata/sections/description +t/data/lbcd/metadata/sections/installation +t/data/lbcd/metadata/sections/requirements +t/data/lbcd/metadata/sections/support +t/data/lbcd/metadata/sections/testing +t/data/lbcd/output/readme diff --git a/lib/App/DocKnot.pm b/lib/App/DocKnot.pm new file mode 100644 index 0000000..0d8d3f2 --- /dev/null +++ b/lib/App/DocKnot.pm @@ -0,0 +1,391 @@ +# Implementation of the DocKnot application. +# +# This is the primary class for the DocKnot application, which supports +# generation of various documentation files based on package metadata and +# general templates. +# +# Written by Russ Allbery <rra@stanford.edu> +# Copyright 2013 +# The Board of Trustees of the Leland Stanford Junior University +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +############################################################################## +# Modules and declarations +############################################################################## + +package App::DocKnot 1.00; + +use 5.018; +use autodie; +use warnings; + +use Carp qw(croak); +use File::BaseDir qw(config_files); +use File::ShareDir qw(module_file); +use File::Spec; +use JSON; +use Perl6::Slurp; +use Template; +use Text::Wrap qw(wrap); + +############################################################################## +# Generator functions +############################################################################## + +# The internal helper object methods in this section are generators. They +# return code references intended to be passed into Template Toolkit as code +# references so that they can be called inside templates, incorporating data +# from the App::DocKnot configuration or the package metadata. + +# Returns code to center a line in $self->{width} characters given the text of +# the line. The returned code will take a line of text and return that line +# with leading whitespace added as required. +# +# $self - The App::DocKnot object +# +# Returns: Code reference to a closure that uses $self->{width} for width +sub _code_for_center { + my ($self) = @_; + my $center = sub { + my ($text) = @_; + my $space = $self->{width} - length($text); + if ($space <= 0) { + return $text; + } else { + return q{ } x int($space / 2) . $text; + } + }; + return $center; +} + +# Returns code that formats the copyright notices for the package. The +# resulting code reference takes one parameter, the indentation level, and +# wraps the copyright notices accordingly. They will be wrapped with a +# four-space outdent and kept within $self->{width} columns. +# +# $self - The App::DocKnot object +# $copyrights_ref - A reference to a list of anonymous hashes, each with keys: +# holder - The copyright holder for that copyright +# years - The years of that copyright +# +# Returns: Code reference to a closure taking an indent level and returning +# the formatted copyright notice +sub _code_for_copyright { + my ($self, $copyrights_ref) = @_; + my $copyright = sub { + my ($indent) = @_; + my $prefix = q{ } x $indent; + my $notice; + for my $copyright (@{$copyrights_ref}) { + my $holder = $copyright->{holder}; + my $years = $copyright->{years}; + + # Build the initial notice with the word copyright and the years. + my $text = "Copyright " . $copyright->{years}; + local $Text::Wrap::columns = $self->{width} + 1; + local $Text::Wrap::unexpand = 0; + $text = wrap($prefix, $prefix . q{ } x 4, $text); + + # See if the holder fits on the last line. If so, add it there; + # otherwise, add another line. + my $last_length; + if (rindex($text, "\n") == -1) { + $last_length = length($text); + } else { + $last_length = length($text) - rindex($text, "\n"); + } + if ($last_length + length($holder) < $self->{width}) { + $text .= " $holder"; + } else { + $text .= "\n" . $prefix . q{ } x 4 . $holder; + } + $notice .= $text . "\n"; + } + chomp($notice); + return $notice; + }; + return $copyright; +} + +# Returns code to indent each line of a paragraph by a given number of spaces. +# This is constructed as a method returning a closure so that its behavior can +# be influenced by App::DocKnot configuration in the future, but it currently +# doesn't use any configuration. +# +# $self - The App::DocKnot object +# +# Returns: Code reference to a closure +sub _code_for_indent { + my ($self) = @_; + my $indent = sub { + my ($text, $space) = @_; + my @text = split(m{\n}xms, $text); + return join("\n", map { q{ } x $space . $_ } @text); + }; + return $indent; +} + +############################################################################## +# Helper methods +############################################################################## + +# Internal helper routine to return the path of a file from the application +# data. These data files are installed with App::DocKnot, but each file can +# be overridden by the user via files in $HOME/.config/docknot or +# /etc/xdg/docknot (or whatever $XDG_CONFIG_DIRS is set to). +# +# We therefore try File::BaseDir first (which handles the XDG paths) and fall +# back on using File::ShareDir to locate the data. +# +# $self - The App::DocKnot object +# @path - The relative path of the file as a list of components +# +# Returns: The absolute path to the application data +# Throws: Text exception on failure to locate the desired file +sub _appdata_path { + my ($self, @path) = @_; + + # Try XDG paths first. + my $path = config_files(@path); + + # If that doesn't work, use the data that came with the module. + if (!defined($path)) { + $path = module_file('App::DocKnot', File::Spec->catfile(@path)); + } + return $path; +} + +# Internal helper routine that locates an application data file, interprets it +# as JSON, and returns the resulting decoded contents. This uses the relaxed +# parsing mode, so comments and commas after data elements are supported. +# +# $self - The App::DocKnot object +# @path - The path of the file to load, as a list of components +# +# Returns: Anonymous hash or array resulting from decoding the JSON object +# Throws: slurp or JSON exception on failure to load or decode the object +sub _load_appdata_json { + my ($self, @path) = @_; + my $path = $self->_appdata_path(@path); + my $json = JSON->new; + $json->relaxed; + return $json->decode(scalar(slurp($path))); +} + +# Internal helper routine to return the path of a file or directory from the +# package metadata directory. The resulting file or directory path is not +# checked for existence. +# +# $self - The App::DocKnot object +# @path - The relative path of the file as a list of components +# +# Returns: The absolute path in the metadata directory +sub _metadata_path { + my ($self, @path) = @_; + return File::Spec->catdir($self->{metadata}, @path); +} + +# Internal helper routine to read a file from the package metadata directory +# and return the contents. The file is specified as a list of path +# components. +# +# $self - The App::DocKnot object +# @path - The path of the file to load, as a list of components +# +# Returns: The contents of the file as a string +# Throws: slurp exception on failure to read the file +sub _load_metadata { + my ($self, @path) = @_; + return slurp($self->_metadata_path(@path)); +} + +# Like _load_metadata, but interprets the contents of the metadata file as +# JSON and decodes it, returning the resulting object. This uses the relaxed +# parsing mode, so comments and commas after data elements are supported. +# +# $self - The App::DocKnot object +# @path - The path of the file to load, as a list of components +# +# Returns: Anonymous hash or array resulting from decoding the JSON object +# Throws: slurp or JSON exception on failure to load or decode the object +sub _load_metadata_json { + my ($self, @path) = @_; + my $data = $self->_load_metadata(@path); + my $json = JSON->new; + $json->relaxed; + return $json->decode($data); +} + +############################################################################## +# Public interface +############################################################################## + +# Create a new App::DocKnot object, which will be used for subsequent calls. +# +# $class - Class of object to create +# $args - Anonymous hash of arguments with the following keys: +# width - Line length at which to wrap output files +# metadata - Path to the directory containing package metadata +# +# Returns: Newly created object +# Throws: Text exceptions on invalid metadata directory path +sub new { + my ($class, $args_ref) = @_; + + # Ensure we were given a valid metadata argument. + my $metadata = $args_ref->{metadata}; + if (!defined($metadata)) { + croak('Missing metadata argument to new'); + } + if (!-d $metadata) { + croak("Metadata path $metadata does not exist or is not a directory"); + } + + # Create and return the object. + my $self = { + metadata => $metadata, + width => $args_ref->{width} // 74, + }; + bless($self, $class); + return $self; +} + +# Generate a documentation file from the package metadata. Takes the template +# to use and returns the generated documentation. +# +# $self - The App::DocKnot object +# $template - Name of the documentation template (using Template Toolkit) +# +# Returns: The generated documentation as a string +# Throws: autodie exception on failure to read metadata or write the output +# Text exception on Template Toolkit failures +# Text exception on inconsistencies in the package data +sub generate { + my ($self, $template) = @_; + + # Load the package metadata from JSON. + my $data_ref = $self->_load_metadata_json('metadata.json'); + + # Load documentation sections. readme.sections will contain a list of + # sections to add to the README file. + for my $section (@{ $data_ref->{readme}{sections} }) { + my $title = $section->{title}; + + # The file containing the section data will match the title, converted + # to lowercase and with spaces changed to dashes. + my $file = lc($title); + $file =~ s{ [ ] }{-}xms; + + # Load the section content. + $section->{body} = $self->_load_metadata('sections', $file); + } + + # Expand the package license into license text. + my $license = $data_ref->{license}; + my $licenses_ref = $self->_load_appdata_json('licenses.json'); + if (!exists($licenses_ref->{$license})) { + die "Unknown license $license"; + } + my $license_text = slurp($self->_appdata_path('licenses', $license)); + $data_ref->{license} = { + %{ $licenses_ref->{$license} }, + full => $license_text, + }; + + # Create the variable information for the template. Start with all + # metadata as loaded above. + my %vars = %{$data_ref}; + + # Add code references for our defined helper functions. + $vars{center} = $self->_code_for_center; + $vars{copyright} = $self->_code_for_copyright($data_ref->{copyrights}); + $vars{indent} = $self->_code_for_indent; + + # Find the path to the relevant template. + $template = $self->_appdata_path('templates', "${template}.tmpl"); + + # Run Template Toolkit processing. + my $tt = Template->new({ ABSOLUTE => 1 }) or die Template->error; + my $result; + $tt->process($template, \%vars, \$result) or die $tt->error; + + # Word-wrap the results to our width. This requires some annoying + # heuristics, but the alternative is to try to get the template to always + # produce correctly-wrapped results, which is far harder. + # + # First, break the resulting text up into paragraphs. (This will also + # turn more than two consecutive newlines into just two newlines.) + my @paragraphs = split(m{ \n(?:[ ]*\n)+ }xms, $result); + + # Add back the trailing newlines at the end of each paragraph. + @paragraphs = map { $_ . "\n" } @paragraphs; + + # For each paragraph that looks like regular text, which means indented by + # two or four spaces and consistently on each line, remove the indentation + # and then add it back in while wrapping the text. + for my $paragraph (@paragraphs) { + my ($indent) = ($paragraph =~ m{ \A ([ ]+) \S }xms); + + # If the paragraph is not indented, skip it. + next if !defined($indent); + + # If the indent is longer than four characters, leave it alone. + next if length($indent) > 4; + + # If this looks like a bullet list, leave it alone. + next if $paragraph =~ m{ \A \s+ [*] }xms; + + # If this paragraph is not consistently indented, leave it alone. + next if $paragraph !~ m{ \A (?: \Q$indent\E \S[^\n]+ \n )+ \z }xms; + + # Strip the indent from each line. + $paragraph =~ s{ (?: \A | (?<=\n) ) \Q$indent\E }{}xmsg; + + # Remove any existing newlines, preserving two spaces after periods. + $paragraph =~ s{ [.] \n (\S) }{. $1}xmsg; + $paragraph =~ s{ \n(\S) }{ $1}xmsg; + + # Force locally correct configuration of Text::Wrap. + local $Text::Wrap::break = qr{\s+}xms; + local $Text::Wrap::columns = $self->{width} + 1; + local $Text::Wrap::unexpand = 0; + + # Do the wrapping. This modifies @paragraphs in place. + $paragraph = wrap($indent, $indent, $paragraph); + + # Strip any trailing whitespace, since some gets left behind after + # periods by Text::Wrap. + $paragraph =~ s{ [ ]+ \n }{\n}xmsg; + } + + # Glue the paragraphs back together and return the result. Because the + # last newline won't get stripped by the split above, we have to strip an + # extra newline from the end of the file. + $result = join("\n", @paragraphs); + $result =~ s{ \n+ \z }{\n}xms; + return $result; +} + +############################################################################## +# Module return value and documentation +############################################################################## + +1; +__END__ diff --git a/share/licenses.json b/share/licenses.json new file mode 100644 index 0000000..23fd756 --- /dev/null +++ b/share/licenses.json @@ -0,0 +1,5 @@ +{ + "Expat": { + "summary": "BSD-style license", + }, +} diff --git a/share/licenses/Expat b/share/licenses/Expat new file mode 100644 index 0000000..1e4a5f8 --- /dev/null +++ b/share/licenses/Expat @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/share/templates/readme.tmpl b/share/templates/readme.tmpl new file mode 100644 index 0000000..d9f5ffa --- /dev/null +++ b/share/templates/readme.tmpl @@ -0,0 +1,48 @@ +[% center("$name version $version") %] +[% center("($synopsis)") %] +[% center("Maintained by $maintainer") %] + + [% FOREACH copr IN copyrights %]Copyright [% copr.years %] + [% copr.holder %]. [% END %]This software is distributed under a + [% license.summary %]. Please see the section LICENSE below for more + information. + +[% FOREACH section IN readme.sections %][% section.title %] + +[% indent(section.body, 2) %] + +[% END %]SOURCE REPOSITORY + + [% name %] is maintained using [% vcs.type %]. You can access the + current source by cloning the repository at: + + [% vcs.url %] + + or view the repository via the web at: + + [% vcs.browse %] +[% IF vcs.type == 'Git' %] + When contributing modifications, patches (possibly generated by + git-format-patch) are preferred to Git pull requests. +[% END %] +LICENSE + + The [% name %] package as a whole is covered by the following copyright + statement and license: + +[% copyright(4) %] + +[% indent(license.full, 4) %] + + All individual files without an explicit exception below are released + under this license. Some files may have additional copyright holders as + noted in those files. There is detailed information about the licensing + of each file in the LICENSE file in this distribution. + + Some files in this distribution are individually released under + different licenses, all of which are compatible with the above general + package license but which may require preservation of additional + notices. All required notices are preserved in the LICENSE file. + + For any copyright range specified by files in this package as YYYY-ZZZZ, + the range specifies every single year in that closed interval. diff --git a/t/api/basic.t b/t/api/basic.t new file mode 100755 index 0000000..7b93898 --- /dev/null +++ b/t/api/basic.t @@ -0,0 +1,52 @@ +#!/usr/bin/perl +# +# Tests for the App::DocKnot module API. +# +# Written by Russ Allbery <rra@stanford.edu> +# Copyright 2013 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +use 5.018; +use autodie; +use warnings; + +use File::Spec; +use IPC::System::Simple qw(capturex); +use Perl6::Slurp; +use Test::More tests => 3; + +# Load the module. +BEGIN { use_ok('App::DocKnot') } + +# We have a set of test cases in the data directory. Each of them contains +# metadata and output directories. +my $dataroot = File::Spec->catfile('t', 'data'); +opendir(my $tests, File::Spec->catfile('t', 'data')); +my @tests = File::Spec->no_upwards(readdir($tests)); +closedir($tests); +@tests = grep { -d File::Spec->catfile($dataroot, $_, 'metadata') } @tests; + +# For each of those cases, initialize an object from the metadata directory, +# generate a file from the readme template, and compare that with the readme +# in the output directory. +for my $test (@tests) { + my $path = File::Spec->catfile($dataroot, $test, 'metadata'); + my $docknot = App::DocKnot->new({ metadata => $path }); + isa_ok($docknot, 'App::DocKnot', "for $test"); + my $got = $docknot->generate('readme'); + $path = File::Spec->catfile($dataroot, $test, 'output', 'readme'); + my $expected = slurp($path); + if ($got eq $expected) { + is($got, $expected, "README for $test"); + } else { + open(my $tmp, q{>}, 'tmp'); + print {$tmp} $got or die "Cannot write to tmp: $!\n"; + close($tmp); + my $diff = '# ' . capturex([0..1], 'diff', '-u', $path, 'tmp'); + diag($diff); + unlink('tmp'); + ok(0, "README for $test"); + } +} diff --git a/t/data/lbcd/metadata/metadata.json b/t/data/lbcd/metadata/metadata.json new file mode 100644 index 0000000..4d872cc --- /dev/null +++ b/t/data/lbcd/metadata/metadata.json @@ -0,0 +1,28 @@ +{ + "name": "lbcd", + "version": "3.4.2", + "synopsis": "responder for load balancing", + "maintainer": "Russ Allbery <eagle@eyrie.org>", + "copyrights": [ + { + "holder": "The Board of Trustees of the Leland Stanford Junior University", + "years": "1993-1994, 1996-1998, 2000, 2003-2009, 2012-2013", + }, + ], + "license": "Expat", + "vcs": { + "type": "Git", + "url": "git://git.eyrie.org/system/lbcd.git", + "browse": "http://git.eyrie.org/?p=system/lbcd.git", + }, + "readme": { + "sections": [ + { "title": "BLURB" }, + { "title": "DESCRIPTION" }, + { "title": "REQUIREMENTS" }, + { "title": "INSTALLATION" }, + { "title": "TESTING" }, + { "title": "SUPPORT" }, + ], + }, +} diff --git a/t/data/lbcd/metadata/sections/blurb b/t/data/lbcd/metadata/sections/blurb new file mode 100644 index 0000000..3689eb9 --- /dev/null +++ b/t/data/lbcd/metadata/sections/blurb @@ -0,0 +1,6 @@ +lbcd is a daemon that runs on a UNIX system and answers UDP queries with +information about system load, number of logged-on users, uptime, and free +/tmp space. This information can be used to accumulate system status +across a cluster with light-weight queries or can be used as input to a +load-balancing system to choose the best system to which to direct new +incoming connections. diff --git a/t/data/lbcd/metadata/sections/description b/t/data/lbcd/metadata/sections/description new file mode 100644 index 0000000..f634859 --- /dev/null +++ b/t/data/lbcd/metadata/sections/description @@ -0,0 +1,24 @@ +The lbcd daemon runs on a UNIX system and answers UDP queries (by default +on port 4330) with information about system load, number of logged on +users, uptime, and free /tmp space. This information can be used to +accumulate system status across a cluster with light-weight queries or can +be used as input to a load-balancing system to choose the best system to +which to direct new incoming connections. It was designed for use with +the lbnamed DNS load balancer, available at: + + <http://www.stanford.edu/~riepel/lbnamed/> + +It was originally written by Roland Schemers, was rewritten by Larry +Schwimmer to add protocol version 3 with some additional features and +service probing, and is currently maintained by Russ Allbery. + +The information provided isn't particularly sophisticated, and a good +hardware load balancer will be able to consider such things as connection +latency and responsiveness to make better decisions, but lbcd with lbnamed +works quite well for smaller scale problems, scales well to multiple load +balance pools for different services, and is much simpler and cheaper to +understand and deploy. + +Included in this package is a small client program, lbcdclient, which can +query an lbcd server and display a formatted version of the returned +information. diff --git a/t/data/lbcd/metadata/sections/installation b/t/data/lbcd/metadata/sections/installation new file mode 100644 index 0000000..bf56543 --- /dev/null +++ b/t/data/lbcd/metadata/sections/installation @@ -0,0 +1,30 @@ +You can build and install remctl with the standard commands: + + ./configure + make + make install + +Pass --enable-silent-rules to configure for a quieter build (similar to +the Linux kernel). Use make warnings instead of make to build with full +GCC compiler warnings (requires a relatively current version of GCC). + +The last step will probably have to be done as root. By default, remctl +installs itself under /usr/local; you can change that path by passing the +--prefix=PATH argument to configure. + +lbcd looks for $sysconfdir/nolbcd and returns the maximum load if that +file is present, allowing one to effectively drop a system out of a +load-balanced pool by touching that file. By default, the path is +/usr/local/etc/nolbcd, but you may want to pass --sysconfdir=/etc to +configure to use /etc/nolbcd. + +lbcdclient is written in Perl, so you may have to edit the first line of +the script to point to the correct Perl location on your system. It does +not use any sophisticated Perl features or add-on modules. + +You will generally want to start lbcd at system boot. All that is needed +is a simple init script to start lbcd with the appropriate options or kill +it again. It writes its PID into /var/run/lbcd.pid by default (and this +can be changed with the -P option). On many systems, lbcd will need to +run as root or as a member of particular groups to obtain system load +average and uptime information. diff --git a/t/data/lbcd/metadata/sections/requirements b/t/data/lbcd/metadata/sections/requirements new file mode 100644 index 0000000..c949b73 --- /dev/null +++ b/t/data/lbcd/metadata/sections/requirements @@ -0,0 +1,14 @@ +lbcd is written in C, so you'll need a C compiler. It also uses kernel +calls to obtain load and uptime information, and at present has only been +ported to Linux, Solaris, AIX, various BSD systems, Mac OS X, HP-UX, IRIX, +and Tru64. It is currently primarily tested on Linux. Platforms not +listed may require some porting effort, as may old or unusual platforms +that aren't regularly tested. + +The lbcdclient program requires Perl (any version should do). + +To bootstrap from a Git checkout, or if you change the Automake files and +need to regenerate Makefile.in, you will need Automake 1.11 or later. For +bootstrap or if you change configure.ac or any of the m4 files it includes +and need to regenerate configure or config.h.in, you will need Autoconf +2.64 or later. diff --git a/t/data/lbcd/metadata/sections/support b/t/data/lbcd/metadata/sections/support new file mode 100644 index 0000000..3fc91f7 --- /dev/null +++ b/t/data/lbcd/metadata/sections/support @@ -0,0 +1,16 @@ +The lbcd web page at: + + http://www.eyrie.org/~eagle/software/lbcd/ + +will always have the current version of this package, the current +documentation, and pointers to any additional resources. + +New lbcd releases are announced on the low-volume lbnamed-users mailing +list. To subscribe or see the list archives, go to: + + https://mailman.stanford.edu/mailman/listinfo/lbnamed-users + +I welcome bug reports and patches for this package at eagle@eyrie.org. +However, please be aware that I tend to be extremely busy and work +projects often take priority. I'll save your mail and get to it as soon +as I can, but it may take me a couple of months. diff --git a/t/data/lbcd/metadata/sections/testing b/t/data/lbcd/metadata/sections/testing new file mode 100644 index 0000000..4b753d9 --- /dev/null +++ b/t/data/lbcd/metadata/sections/testing @@ -0,0 +1,13 @@ +lbcd comes with a rudimentary test suite, which you can run after building +lbcd with: + + make check + +If a test case fails, please run the that individual test case with +verbose output using: + + tests/runtests -o <name-of-test> + +and send me the output when reporting the problem. Currently, the test +suite only checks the portability and utility libraries; tests for lbcd +and lbcdclient are coming in later releases. diff --git a/t/data/lbcd/output/readme b/t/data/lbcd/output/readme new file mode 100644 index 0000000..3ff1c88 --- /dev/null +++ b/t/data/lbcd/output/readme @@ -0,0 +1,183 @@ + lbcd version 3.4.2 + (responder for load balancing) + Maintained by Russ Allbery <eagle@eyrie.org> + + Copyright 1993-1994, 1996-1998, 2000, 2003-2009, 2012-2013 The Board of + Trustees of the Leland Stanford Junior University. This software is + distributed under a BSD-style license. Please see the section LICENSE + below for more information. + +BLURB + + lbcd is a daemon that runs on a UNIX system and answers UDP queries with + information about system load, number of logged-on users, uptime, and + free /tmp space. This information can be used to accumulate system + status across a cluster with light-weight queries or can be used as + input to a load-balancing system to choose the best system to which to + direct new incoming connections. + +DESCRIPTION + + The lbcd daemon runs on a UNIX system and answers UDP queries (by + default on port 4330) with information about system load, number of + logged on users, uptime, and free /tmp space. This information can be + used to accumulate system status across a cluster with light-weight + queries or can be used as input to a load-balancing system to choose the + best system to which to direct new incoming connections. It was + designed for use with the lbnamed DNS load balancer, available at: + + <http://www.stanford.edu/~riepel/lbnamed/> + + It was originally written by Roland Schemers, was rewritten by Larry + Schwimmer to add protocol version 3 with some additional features and + service probing, and is currently maintained by Russ Allbery. + + The information provided isn't particularly sophisticated, and a good + hardware load balancer will be able to consider such things as + connection latency and responsiveness to make better decisions, but lbcd + with lbnamed works quite well for smaller scale problems, scales well to + multiple load balance pools for different services, and is much simpler + and cheaper to understand and deploy. + + Included in this package is a small client program, lbcdclient, which + can query an lbcd server and display a formatted version of the returned + information. + +REQUIREMENTS + + lbcd is written in C, so you'll need a C compiler. It also uses kernel + calls to obtain load and uptime information, and at present has only + been ported to Linux, Solaris, AIX, various BSD systems, Mac OS X, + HP-UX, IRIX, and Tru64. It is currently primarily tested on Linux. + Platforms not listed may require some porting effort, as may old or + unusual platforms that aren't regularly tested. + + The lbcdclient program requires Perl (any version should do). + + To bootstrap from a Git checkout, or if you change the Automake files + and need to regenerate Makefile.in, you will need Automake 1.11 or + later. For bootstrap or if you change configure.ac or any of the m4 + files it includes and need to regenerate configure or config.h.in, you + will need Autoconf 2.64 or later. + +INSTALLATION + + You can build and install remctl with the standard commands: + + ./configure + make + make install + + Pass --enable-silent-rules to configure for a quieter build (similar to + the Linux kernel). Use make warnings instead of make to build with full + GCC compiler warnings (requires a relatively current version of GCC). + + The last step will probably have to be done as root. By default, remctl + installs itself under /usr/local; you can change that path by passing + the --prefix=PATH argument to configure. + + lbcd looks for $sysconfdir/nolbcd and returns the maximum load if that + file is present, allowing one to effectively drop a system out of a + load-balanced pool by touching that file. By default, the path is + /usr/local/etc/nolbcd, but you may want to pass --sysconfdir=/etc to + configure to use /etc/nolbcd. + + lbcdclient is written in Perl, so you may have to edit the first line of + the script to point to the correct Perl location on your system. It + does not use any sophisticated Perl features or add-on modules. + + You will generally want to start lbcd at system boot. All that is + needed is a simple init script to start lbcd with the appropriate + options or kill it again. It writes its PID into /var/run/lbcd.pid by + default (and this can be changed with the -P option). On many systems, + lbcd will need to run as root or as a member of particular groups to + obtain system load average and uptime information. + +TESTING + + lbcd comes with a rudimentary test suite, which you can run after + building lbcd with: + + make check + + If a test case fails, please run the that individual test case with + verbose output using: + + tests/runtests -o <name-of-test> + + and send me the output when reporting the problem. Currently, the test + suite only checks the portability and utility libraries; tests for lbcd + and lbcdclient are coming in later releases. + +SUPPORT + + The lbcd web page at: + + http://www.eyrie.org/~eagle/software/lbcd/ + + will always have the current version of this package, the current + documentation, and pointers to any additional resources. + + New lbcd releases are announced on the low-volume lbnamed-users mailing + list. To subscribe or see the list archives, go to: + + https://mailman.stanford.edu/mailman/listinfo/lbnamed-users + + I welcome bug reports and patches for this package at eagle@eyrie.org. + However, please be aware that I tend to be extremely busy and work + projects often take priority. I'll save your mail and get to it as soon + as I can, but it may take me a couple of months. + +SOURCE REPOSITORY + + lbcd is maintained using Git. You can access the current source by + cloning the repository at: + + git://git.eyrie.org/system/lbcd.git + + or view the repository via the web at: + + http://git.eyrie.org/?p=system/lbcd.git + + When contributing modifications, patches (possibly generated by + git-format-patch) are preferred to Git pull requests. + +LICENSE + + The lbcd package as a whole is covered by the following copyright + statement and license: + + Copyright 1993-1994, 1996-1998, 2000, 2003-2009, 2012-2013 + The Board of Trustees of the Leland Stanford Junior University + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + All individual files without an explicit exception below are released + under this license. Some files may have additional copyright holders as + noted in those files. There is detailed information about the licensing + of each file in the LICENSE file in this distribution. + + Some files in this distribution are individually released under + different licenses, all of which are compatible with the above general + package license but which may require preservation of additional + notices. All required notices are preserved in the LICENSE file. + + For any copyright range specified by files in this package as YYYY-ZZZZ, + the range specifies every single year in that closed interval. |