rtfm 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Michael Edgar
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,97 @@
1
+ # rtfm
2
+
3
+ Using RTFM, you can declaratively create nice, standard man pages for your
4
+ Ruby projects using a slick, maintainable DSL. It also includes rake tasks
5
+ to aid in debugging, generating, and installing your man files.
6
+
7
+ ## Normal manfiles are... interesting
8
+
9
+ You might say: "but wait, aren't man pages already written in a DSL?", and you'd
10
+ be right! However, here's an example of it:
11
+
12
+ .It Fl p
13
+ Acts mostly same as -n switch, but print the value of variable
14
+ .Li "$_"
15
+ at the each end of the loop. For example:
16
+ .Bd -literal -offset indent
17
+ % echo matz | ruby -p -e '$_.tr! "a-z", "A-Z"'
18
+ MATZ
19
+ .Ed
20
+ .Pp
21
+ .It Fl r Ar library
22
+ Causes Ruby to load the library using require. It is useful when using
23
+ .Fl n
24
+ or
25
+ .Fl p .
26
+
27
+ That's from the Ruby manfile. This is using the `groff` macro language,
28
+ which is awfully spiffy. However, it makes man files extremely tedious to
29
+ maintain. It even spits out errors if you put a blank line in your manfile!
30
+
31
+ ## A word of caution
32
+
33
+ Now, manfiles are long and complex, and contain a lot of information. Chances
34
+ are, your RTFM-based manfile isn't actually going to be too much shorter than
35
+ your normal groff-based entry. The difference is that by using the RTFM
36
+ DSL, it should be very, very simple to maintain. Which is a huge
37
+ win for your users.
38
+
39
+ ## Example Manual Page:
40
+
41
+ RTFM::ManPage.new("testing", 2) do |page|
42
+ page.summary = "testing man page"
43
+ page.option :verbose, "The verbose flag does a lot of stuff."
44
+ page.option :silliness, "Set how silly the application should be.", :argument => "n"
45
+ page.option :input, "The input flag takes a filename", :argument => "<input>"
46
+
47
+ page.description do |desc|
48
+ desc.body = "This is a small, temporary description of the testing " +
49
+ "man page."
50
+ end
51
+ page.see_also do |also|
52
+ also.reference "rails", 1
53
+ also.reference "ruby"
54
+ end
55
+ page.bugs = "There are a few bugs, but nothing too serious."
56
+ page.history = "This program has a storied history that I am too " +
57
+ "lazy to include here."
58
+ page.authors do |authors|
59
+ authors.add "Michael Edgar", "adgar@carboni.ca"
60
+ end
61
+ end
62
+
63
+ ## Other wins
64
+
65
+ The weird thing about making manfiles is that each section has its own semantics -
66
+ sometimes, macros even change their meanings slightly. There are idioms and "best practices"
67
+ for each. The cool part of RTFM is that we can try to match the idioms for you - you
68
+ just provide the information we need.
69
+
70
+ An example: in the "SEE ALSO" section, you can provide a list of other manual pages
71
+ that are related to yours. In the example above, you see a couple of simple references.
72
+ If you read the deep, dark documentation on the subject, you'll know that these references
73
+ should be sorted by manual section, and then alphabetically within sections. RTFM will do that
74
+ for you. It's the little things that count.
75
+
76
+ ## Upcoming
77
+
78
+ Hopefully we can get some of this integrated into RubyGems, though that's a long shot -
79
+ it's a long-standing, cross-platform project. But it's nice to dream.
80
+
81
+ I hope to add the ability to insert raw groff if you are an advanced user and want some
82
+ really spiffy formatting.
83
+
84
+ ## Note on Patches/Pull Requests
85
+
86
+ * Fork the project.
87
+ * Make your feature addition or bug fix.
88
+ * Add tests for it. This is important so I don't break it in a
89
+ future version unintentionally.
90
+ * Commit, do not mess with rakefile, version, or history.
91
+ (if you want to have your own version, that is fine but
92
+ bump version in a commit by itself I can ignore when I pull)
93
+ * Send me a pull request. Bonus points for topic branches.
94
+
95
+ ## Copyright
96
+
97
+ Copyright (c) 2010 Michael Edgar. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,89 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rtfm"
8
+ gem.summary = "Create makefiles declaratively. Comes with rake tasks."
9
+ gem.description = <<-EOF
10
+ Using RTFM, you can declaratively create nice, standard man pages for your
11
+ Ruby projects using a slick, maintainable DSL. It also includes rake tasks
12
+ to aid in debugging, generating, and installing your man files.
13
+ EOF
14
+ gem.email = "michael.j.edgar@dartmouth.edu"
15
+ gem.homepage = "http://github.com/michaeledgar/rtfm"
16
+ gem.authors = ["Michael Edgar"]
17
+ gem.add_development_dependency "bacon", ">= 0"
18
+ gem.add_development_dependency "yard", ">= 0"
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
24
+ end
25
+
26
+ require 'rake/testtask'
27
+ Rake::TestTask.new(:spec) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.verbose = true
31
+ end
32
+
33
+ begin
34
+ require 'rcov/rcovtask'
35
+ Rcov::RcovTask.new do |test|
36
+ test.libs << 'test'
37
+ test.pattern = 'test/**/test_*.rb'
38
+ test.verbose = true
39
+ end
40
+ rescue LoadError
41
+ task :rcov do
42
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
43
+ end
44
+ end
45
+
46
+ task :spec => :check_dependencies
47
+
48
+ task :default => :spec
49
+
50
+ begin
51
+ require 'yard'
52
+ YARD::Rake::YardocTask.new
53
+ rescue LoadError
54
+ task :yardoc do
55
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
56
+ end
57
+ end
58
+
59
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "lib"))
60
+ require 'rtfm'
61
+ require 'rtfm/tasks'
62
+
63
+ RTFM::ManPage.new("testing", 2) do |page|
64
+ page.summary = "testing man page"
65
+ page.option :r, "Some r flag"
66
+ page.option :j, "Some j flag"
67
+ page.option :k, "Some k flag"
68
+ page.option :"0", "Some zero flag"
69
+ page.option :A, "Some capitalized flag"
70
+ page.option :Z, "some big-z flag"
71
+ page.option :verbose, "The verbose flag does a lot of stuff."
72
+ page.option :silliness, "Set how silly the application should be.", :argument => "n"
73
+ page.option :input, "The input flag takes a filename", :argument => "<input>"
74
+
75
+ page.description do |desc|
76
+ desc.body = "This is a small, temporary description of the testing " +
77
+ "man page."
78
+ end
79
+ page.see_also do |also|
80
+ also.reference "rails", 1
81
+ also.reference "ruby"
82
+ end
83
+ page.bugs = "There are a few bugs, but nothing too serious."
84
+ page.history = "This program has a storied history that I am too " +
85
+ "lazy to include here."
86
+ page.authors do |section|
87
+ section.author "Michael Edgar", "adgar@carboni.ca"
88
+ end
89
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
data/lib/rtfm.rb ADDED
@@ -0,0 +1,20 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ if RUBY_VERSION < "1.9"
4
+ class String
5
+ unless method_defined?(:ord)
6
+ def ord
7
+ self[0]
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ require 'rtfm/groffstring'
14
+ require 'rtfm/option'
15
+
16
+ Dir[File.expand_path(File.join(File.dirname(__FILE__), "rtfm", "sections", "**"))].each do |f|
17
+ require f
18
+ end
19
+
20
+ require 'rtfm/manpage'
@@ -0,0 +1,45 @@
1
+ module RTFM
2
+ class GroffString
3
+
4
+ attr_reader :source
5
+
6
+ def self.groffify(str = "")
7
+ out = self.new(str)
8
+ yield out
9
+ out.to_s
10
+ end
11
+
12
+ def initialize(str = "")
13
+ @source = str.dup
14
+ end
15
+
16
+ def to_s
17
+ source
18
+ end
19
+
20
+ def rstrip
21
+ source.rstrip
22
+ end
23
+
24
+ def add_line(line)
25
+ source << line.rstrip << "\n"
26
+ end
27
+ alias_method :<<, :add_line
28
+
29
+ def section(section)
30
+ self.Sh section.upcase
31
+ end
32
+
33
+ def put_name
34
+ self.Nm
35
+ end
36
+
37
+ def reference(page, section = nil)
38
+ self.Xr page, (section || "")
39
+ end
40
+
41
+ def method_missing(meth, *args, &block)
42
+ add_line ".#{meth} #{args.join(" ")}"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,71 @@
1
+ module RTFM
2
+ class ManPage < Struct.new(:name, :section, :date, :summary)
3
+ class << self
4
+ def text_section(*args)
5
+ args.each do |sect|
6
+ class_eval %Q{
7
+ def #{sect}
8
+ @#{sect} ||= TextSection.new(:#{sect}, "")
9
+ end
10
+ def #{sect}=(str)
11
+ @#{sect} = TextSection.new(:#{sect}, str)
12
+ end
13
+ }
14
+ end
15
+ end
16
+ alias_method :text_sections, :text_section
17
+
18
+ def add_section(name, klass)
19
+ klass = klass.to_s.intern
20
+ class_eval %Q{
21
+ def #{name}
22
+ @#{name} ||= #{klass}.new
23
+ yield @#{name} if block_given?
24
+ @#{name}
25
+ end
26
+ }
27
+ end
28
+
29
+ def all_pages
30
+ @pages ||= []
31
+ end
32
+ end
33
+
34
+ text_section :bugs, :diagnostics, :compatibility, :standards, :history
35
+ add_section :see_also, SeeAlsoSection
36
+ add_section :description, DescriptionSection
37
+ add_section :authors, AuthorsSection
38
+ add_section :synopsis, SynopsisSection
39
+
40
+ def initialize(name, section=nil)
41
+ self.class.all_pages << self
42
+ self.name, self.section = name, section
43
+ self.date = DateTime.now
44
+ yield self
45
+ end
46
+
47
+ def add_option(name, desc, opts={})
48
+ opt = Option.new(name, desc, opts)
49
+ description.add_option opt
50
+ unless opts.has_key?(:synopsis) && !opts.delete(:synopsis)
51
+ synopsis.add_option(opt)
52
+ end
53
+ end
54
+ alias_method :option, :add_option
55
+
56
+ def to_groff
57
+ GroffString.groffify do |out|
58
+ out.Dd date.strftime("%B %d, %Y")
59
+ out.Os
60
+ out.Dt name, (section || "")
61
+ out.section "NAME"
62
+ out.Nm name
63
+ out.Nd summary
64
+ [@synopsis, @description, @see_also, @history, @authors, @bugs].each do |sect|
65
+ out << sect.to_groff if sect
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,37 @@
1
+ module RTFM
2
+ class Option < Struct.new(:title, :desc, :opts)
3
+ def title
4
+ super.to_s
5
+ end
6
+
7
+ def argument
8
+ opts && (opts[:argument] || opts[:arg])
9
+ end
10
+
11
+ def to_groff(how)
12
+ GroffString.groffify do |out|
13
+ args = [:Fl, self.title]
14
+
15
+ if self.argument
16
+ argument = self.argument.to_s
17
+ if argument[0,1] == "<" && argument[-1,1] == ">"
18
+ args << "Ao" << argument[1..-2] << "Ac"
19
+ elsif argument[0,1] == "[" && argument[-1,1] == "]"
20
+ args << "Oo" << argument[1..-2] << "Oc"
21
+ else
22
+ args << :Ar << argument
23
+ end
24
+ end
25
+
26
+ case how
27
+ when :option
28
+ out.Op *args
29
+ when :item
30
+ out.Pp
31
+ out.It *args
32
+ out << self.desc
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ module RTFM
2
+ class AuthorsSection
3
+ def initialize
4
+ @authors = []
5
+ yield self if block_given?
6
+ end
7
+ def add_author(name, email = nil)
8
+ @authors << {:name => name, :email => email}
9
+ end
10
+ alias_method :author, :add_author
11
+ alias_method :add, :add_author
12
+
13
+ def to_groff
14
+ GroffString.groffify do |out|
15
+ out.section "authors"
16
+ @authors.each do |author|
17
+ args = ["\"#{author[:name]}\""]
18
+ if author[:email] then args << "Aq" << author[:email] end
19
+ out.An *args
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,32 @@
1
+ module RTFM
2
+ class DescriptionSection < Struct.new(:body, :options)
3
+ def initialize(*args)
4
+ super
5
+ self.options ||= []
6
+ yield self if block_given?
7
+ end
8
+
9
+ def add_option(*args)
10
+ if args.size == 1 && args.first.is_a?(Option)
11
+ then self.options << args.first
12
+ else self.options << Option.new(*args)
13
+ end
14
+ end
15
+ alias_method :option, :add_option
16
+
17
+ def to_groff
18
+ GroffString.groffify do |out|
19
+ out.section "description"
20
+ out << self.body
21
+ if options.any?
22
+ out.Bl "-tag", "-width", "\"mmmmmmmmmm\"", "-compact"
23
+ options.each do |option|
24
+ out << option.to_groff(:item)
25
+ end
26
+ out.El
27
+ out.Pp
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module RTFM
2
+ class SeeAlsoSection
3
+ def initialize
4
+ @references = {}
5
+ yield self if block_given?
6
+ end
7
+ def reference(title, section = 0)
8
+ (@references[section] ||= []) << title
9
+ end
10
+ def to_groff
11
+ GroffString.groffify do |out|
12
+ out.section "SEE ALSO"
13
+ @references.keys.sort.each do |section|
14
+ @references[section].sort.each do |title|
15
+ if section == 0
16
+ then out.reference title
17
+ else out.reference title, section
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ module RTFM
2
+ class SynopsisSection
3
+ def initialize
4
+ @options = []
5
+ yield self if block_given?
6
+ end
7
+
8
+ def add_option(*args)
9
+ if args.size == 1 && args.first.is_a?(Option)
10
+ then @options << args.first
11
+ else @options << Option.new(*args)
12
+ end
13
+ end
14
+ alias_method :option, :add_option
15
+
16
+ def compare_flags(a, b)
17
+ a_ord, b_ord = a.ord, b.ord
18
+ if ('0'.ord .. '9'.ord).include?(a_ord)
19
+ a_ord += 'z'.ord
20
+ end
21
+ if ('0'.ord .. '9'.ord).include?(b_ord)
22
+ b_ord += 'z'.ord
23
+ end
24
+ a_ord <=> b_ord
25
+ end
26
+
27
+ def to_groff
28
+ flags = @options.select {|opt| opt.title.size == 1 && !opt.argument}
29
+ long_args = @options - flags
30
+
31
+ GroffString.groffify do |out|
32
+ out.section "synopsis"
33
+ out.put_name
34
+ out.Op "Fl", flags.map {|flag| flag.title}.sort {|a, b| compare_flags(a,b)}.join
35
+ long_args.each do |opt|
36
+ out << opt.to_groff(:option)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ module RTFM
2
+ class TextSection < Struct.new(:title, :body)
3
+ def to_groff
4
+ GroffString.groffify do |out|
5
+ out.section title.to_s.upcase
6
+ out << body.to_s
7
+ end
8
+ end
9
+ end
10
+ end
data/lib/rtfm/tasks.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'rake'
2
+ require 'rtfm'
3
+
4
+ namespace :man do
5
+ desc "Display your man files in man."
6
+ task :debug do
7
+ require 'tempfile'
8
+ require 'rtfm'
9
+ RTFM::ManPage.all_pages.each do |page|
10
+ Tempfile.open("#{page.name}.#{page.section}") do |f|
11
+ f << page.to_groff
12
+ f.flush
13
+ system("man #{f.path}")
14
+ end
15
+ end
16
+ end
17
+
18
+ desc "Generate your manual files in the man/ directory"
19
+ task :gen do
20
+ FileUtils.makedirs "man/"
21
+ RTFM::ManPage.all_pages.each do |page|
22
+ File.open("man/#{page.name}.#{page.section}", "w") do |f|
23
+ f << page.to_groff
24
+ end
25
+ end
26
+ end
27
+
28
+ desc "Install your manual files globally"
29
+ task :install => :gen do
30
+ if Rake.application.unix?
31
+ Dir["man/*"].each do |manfile|
32
+ section = manfile.split(".").last
33
+ begin
34
+ FileUtils.cp(manfile, "/usr/share/man/man#{section}/")
35
+ rescue Errno::EACCES
36
+ puts "I'm sorry, but you need root privileges to install man pages with this version"+
37
+ " of RTFM."
38
+ end
39
+ end
40
+ else
41
+ raise "Can't install man pages without a unix-based OS."
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,171 @@
1
+ require 'rubygems/command_manager'
2
+
3
+ require 'rubygems/command'
4
+ require 'rubygems_analyzer'
5
+
6
+ class Gem::Commands::ManCommand < Gem::Command
7
+
8
+ def initialize
9
+ super 'man', 'Manage man-files bundled with gems'
10
+ options[:action] = :install
11
+
12
+ add_option('-v', '--view', "Views the manual files included in",
13
+ "the gem.") do |value, options|
14
+ options[:action] = :view
15
+ end
16
+
17
+ add_option('-i', '--install', "Installs all the gem's manual files globally") do |value, options|
18
+ options[:action] = :install
19
+ end
20
+
21
+ add_option('-r', '--remove', "Removes the gem's manual files globally") do |value, options|
22
+ options[:action] = :remove
23
+ end
24
+ end
25
+
26
+ def execute
27
+ if Gem.win_platform? || !has_man?
28
+ alert_error "You must have the 'man' command to use this extension."
29
+ return
30
+ end
31
+
32
+ get_all_gem_names.each do |name|
33
+ path = get_path name, options[:version]
34
+ if path then
35
+ man_path = File.join path, 'man'
36
+ if File.exist?(man_path) && File.directory?(man_path) then
37
+ Dir[File.join man_path, "**"].each do |man_file|
38
+ dispatch(man_file)
39
+ end
40
+ else
41
+ alert_error "Gem '#{name}' does not appear to have packaged man files."
42
+ end
43
+ else
44
+ alert_error "Gem '#{name}' not installed."
45
+ end
46
+ end
47
+ end
48
+
49
+
50
+ def dispatch(file)
51
+ case options[:action]
52
+ when :install
53
+ install(file)
54
+ when :view
55
+ view(file)
56
+ when :remove
57
+ remove(file)
58
+ end
59
+ end
60
+
61
+
62
+ MAN_DIR = "/usr/share/man/"
63
+
64
+ ##
65
+ # Views the man file in the man program
66
+ def view(source)
67
+ system("man #{source}")
68
+ end
69
+
70
+ ##
71
+ # Installs the given file into the appropriate man directory.
72
+ def install(source)
73
+ full_name = File.split(source).last
74
+ section = full_name.split(".").last
75
+ destination = File.join MAN_DIR, "man#{section}", full_name
76
+ File.open(destination, "wb") do |out|
77
+ out << watermark_file(source)
78
+ end
79
+ end
80
+
81
+ ##
82
+ # Installs the given file into the appropriate man directory.
83
+ def remove(source)
84
+ full_name = File.split(source).last
85
+ section = full_name.split(".").last
86
+ destination = File.join MAN_DIR, "man#{section}", full_name
87
+ if is_watermarked_file?(destination)
88
+ FileUtils.unlink(destination)
89
+ else
90
+ alert_error "The man file at #{destination} was not installed by Rubygems. It has "+
91
+ "not been deleted."
92
+ end
93
+ end
94
+
95
+ def watermark
96
+ %Q{.\\" Installed by Rubygems' Man Extension\n}
97
+ end
98
+
99
+ ##
100
+ # Watermarks a man page. Assumes input file is not already compressed.
101
+ def watermark_file(file)
102
+ add_watermark(File.read(file))
103
+ end
104
+
105
+ ##
106
+ # Watermarks some groff text, so you can tell it's been rubygem'd
107
+ def add_watermark(text)
108
+ "#{watermark}#{text}"
109
+ end
110
+
111
+ ##
112
+ # Watermarks a man page. Assumes input file is not already compressed.
113
+ def is_watermarked_file?(file)
114
+ is_watermarked?(File.read(file))
115
+ end
116
+
117
+ ##
118
+ # Is the given text watermarked?
119
+ def is_watermarked?(text)
120
+ text[0..(watermark.size-1)] == watermark
121
+ end
122
+
123
+ # Return the full path to the cached gem file matching the given
124
+ # name and version requirement. Returns 'nil' if no match.
125
+ #
126
+ # Example:
127
+ #
128
+ # get_path('rake', '> 0.4') # -> '/usr/lib/ruby/gems/1.8/cache/rake-0.4.2.gem'
129
+ # get_path('rake', '< 0.1') # -> nil
130
+ # get_path('rak') # -> nil (exact name required)
131
+ #--
132
+ # TODO: This should be refactored so that it's a general service. I don't
133
+ # think any of our existing classes are the right place though. Just maybe
134
+ # 'Cache'?
135
+ #
136
+ # TODO: It just uses Gem.dir for now. What's an easy way to get the list of
137
+ # source directories?
138
+ def get_path(gemname, version_req)
139
+ return gemname if gemname =~ /\.gem$/i
140
+
141
+ specs = Gem::source_index.find_name gemname, version_req
142
+
143
+ selected = specs.sort_by { |s| s.version }.last
144
+
145
+ return nil if selected.nil?
146
+
147
+ # We expect to find (basename).gem in the 'cache' directory.
148
+ # Furthermore, the name match must be exact (ignoring case).
149
+ if gemname =~ /^#{selected.name}$/i
150
+ filename = selected.full_name
151
+ path = nil
152
+
153
+ Gem.path.find do |gem_dir|
154
+ path = File.join gem_dir, 'gems', filename
155
+ File.exist? path
156
+ end
157
+
158
+ path
159
+ else
160
+ nil
161
+ end
162
+ end
163
+
164
+ def has_man?
165
+ system("man 1>/dev/null 2>&1")
166
+ end
167
+
168
+ end
169
+
170
+ Gem::CommandManager.instance.register_command :man
171
+
data/man/testing.2 ADDED
@@ -0,0 +1,54 @@
1
+ .\" Hello it's a comment
2
+ .Dd January 09, 2010
3
+ .Os
4
+ .Dt testing 2
5
+ .Sh NAME
6
+ .Nm testing
7
+ .Nd testing man page
8
+ .Sh SYNOPSIS
9
+ .Nm
10
+ .Op Fl AZjkr0
11
+ .Op Fl verbose
12
+ .Op Fl silliness Ar n
13
+ .Op Fl input Ao input Ac
14
+ .Sh DESCRIPTION
15
+ This is a small, temporary description of the testing man page.
16
+ .Bl -tag -width "mmmmmmmmmm" -compact
17
+ .Pp
18
+ .It Fl r
19
+ Some r flag
20
+ .Pp
21
+ .It Fl j
22
+ Some j flag
23
+ .Pp
24
+ .It Fl k
25
+ Some k flag
26
+ .Pp
27
+ .It Fl 0
28
+ Some zero flag
29
+ .Pp
30
+ .It Fl A
31
+ Some capitalized flag
32
+ .Pp
33
+ .It Fl Z
34
+ some big-z flag
35
+ .Pp
36
+ .It Fl verbose
37
+ The verbose flag does a lot of stuff.
38
+ .Pp
39
+ .It Fl silliness Ar n
40
+ Set how silly the application should be.
41
+ .Pp
42
+ .It Fl input Ao input Ac
43
+ The input flag takes a filename
44
+ .El
45
+ .Pp
46
+ .Sh SEE ALSO
47
+ .Xr ruby
48
+ .Xr rails 1
49
+ .Sh HISTORY
50
+ This program has a storied history that I am too lazy to include here.
51
+ .Sh AUTHORS
52
+ .An "Michael Edgar" Aq adgar@carboni.ca
53
+ .Sh BUGS
54
+ There are a few bugs, but nothing too serious.
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe "authors section" do
4
+ before do
5
+ @authors = AuthorsSection.new do |sect|
6
+ sect.author "Michael Edgar"
7
+ sect.author "Ari Brown", "seydar@carboni.ca"
8
+ end
9
+ @groffed = @authors.to_groff
10
+ end
11
+
12
+ it "makes an authors section" do
13
+ @groffed.should.match(/^\.Sh AUTHORS$/)
14
+ end
15
+
16
+ it "adds an author without an email" do
17
+ @groffed.should.match(/^\.An "Michael Edgar"$/)
18
+ end
19
+
20
+ it "adds an author with an email" do
21
+ @groffed.should.match(/^\.An "Ari Brown" Aq seydar@carboni.ca/)
22
+ end
23
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe "description section" do
4
+
5
+ FULL_DESC = "This is the description for the description section. Typically, this would" +
6
+ " be much longer, but for now, it is short. Huzzah!"
7
+
8
+ before do
9
+ @desc = DescriptionSection.new do |desc|
10
+ desc.body = FULL_DESC
11
+ desc.option :verbose, "Makes output wordy and unnecessarily long"
12
+ desc.option :input, "Specifies an input file", :arg => "<file>"
13
+ desc.option :output, "Specifies an output file", :argument => "[output]"
14
+ desc.option :silly, "Has a silly argument", :arg => "silliness"
15
+ end
16
+ @groffed = @desc.to_groff
17
+ end
18
+
19
+ it "creates a description section" do
20
+ @groffed.should.match(/^\.Sh DESCRIPTION$/)
21
+ end
22
+
23
+ it "displays its body below the section header" do
24
+ @groffed.should.match(/^\.Sh DESCRIPTION\nThis is the/)
25
+ end
26
+
27
+ it "contains the full body text" do
28
+ @groffed.should.include(FULL_DESC)
29
+ end
30
+
31
+ it "creates a list for options" do
32
+ @groffed.should.match(/^\.Bl -tag/)
33
+ end
34
+
35
+ it "creates list items with option names" do
36
+ @groffed.should.match(/^\.It Fl verbose/)
37
+ @groffed.should.match(/^\.It Fl input/)
38
+ end
39
+
40
+ it "displays arguments for options" do
41
+ @groffed.should.match(/^\.It Fl silly Ar silliness/)
42
+ end
43
+
44
+ it "changes arguments with angle brackets to proper groff" do
45
+ @groffed.should.match(/Ao file Ac/)
46
+ end
47
+
48
+ it "changes arguments with square brackets to proper groff" do
49
+ @groffed.should.match(/Oo output Oc/)
50
+ end
51
+ it "puts option descriptions next to option names" do
52
+ @groffed.should.match(/^\.It Fl verbose\nMakes output wordy/)
53
+ end
54
+
55
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe "groff_string" do
4
+ before do
5
+ @groff = GroffString.new
6
+ @groff_with_line = GroffString.new("Some text")
7
+ end
8
+
9
+ it "uses its initial value" do
10
+ @groff.to_s.should.equal ""
11
+ @groff_with_line.to_s.should.equal("Some text")
12
+ end
13
+
14
+ it "converts to a string" do
15
+ @groff.should.respond_to(:to_s)
16
+ end
17
+
18
+ it "adds references using the .Xr macro" do
19
+ @groff.reference("rails", 1)
20
+ @groff.to_s.should.include(".Xr rails 1")
21
+ end
22
+
23
+ it "generates sections" do
24
+ @groff.section("NAME")
25
+ @groff.to_s.should.include(".Sh NAME")
26
+ end
27
+
28
+ it "generates arbitrary macros" do
29
+ @groff.Br("some", "arguments", :here)
30
+ @groff.to_s.should.include(".Br some arguments here")
31
+ end
32
+
33
+ it "adds lines using add_line" do
34
+ @groff.add_line("some arbitrary line goes here")
35
+ @groff.to_s.should.include("some arbitrary line goes here\n")
36
+ end
37
+
38
+ it "adds lines with <<" do
39
+ @groff << "werd some line"
40
+ @groff.to_s.should.include("werd some line\n")
41
+ end
42
+ end
data/spec/rtfm_spec.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe "rtfm" do
4
+ before do
5
+ @rtfm = ManPage.new("testing", 2) do |page|
6
+ page.date = Date.parse("1/2/2010")
7
+ page.summary = "testing man page"
8
+ page.see_also do |also|
9
+ also.reference "madeup", 4
10
+ also.reference "rails", 1
11
+ also.reference "ruby"
12
+ also.reference "perl", 1
13
+ end
14
+ page.bugs = "There are a few bugs, but nothing too serious."
15
+ page.history = "This program has a storied history that I am too lazy to include here."
16
+ end
17
+ @groff = @rtfm.to_groff
18
+ end
19
+
20
+ it "displays the correct date" do
21
+ @groff.should.match(/^\.Dd January 02, 2010$/)
22
+ end
23
+
24
+ it "has a NAME section" do
25
+ @groff.should.match(/^\.Sh NAME$/)
26
+ end
27
+
28
+ it "generates a name line" do
29
+ @groff.should.match(/^\.Nm testing/)
30
+ end
31
+
32
+ it "generates a summary line" do
33
+ @groff.should.match(/^\.Nd testing man page/)
34
+ end
35
+
36
+ describe "rtfm-sections" do
37
+ it "has a bugs section" do
38
+ @groff.should.match(/^\.Sh BUGS$/)
39
+ end
40
+
41
+ it "includes its bugs text" do
42
+ @groff.should.match(/^There are a few bugs/)
43
+ end
44
+
45
+ it "has a history section" do
46
+ @groff.should.match(/^\.Sh HISTORY$/)
47
+ end
48
+
49
+ it "includes its history text" do
50
+ @groff.should.match(/^This program has a storied history/)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe "see also section" do
4
+ before do
5
+ @see_also = SeeAlsoSection.new do |also|
6
+ also.reference "madeup", 4
7
+ also.reference "rails", 1
8
+ also.reference "ruby"
9
+ also.reference "perl", 1
10
+ end
11
+ end
12
+
13
+ it "has a see also section" do
14
+ @see_also.to_groff.should.match(/^\.Sh SEE ALSO/)
15
+ end
16
+
17
+ it "includes references in its see also section" do
18
+ groffed = @see_also.to_groff
19
+ groffed.should.match(/^\.Xr rails 1$/)
20
+ groffed.should.match(/^\.Xr ruby$/)
21
+ groffed.should.match(/^\.Xr perl 1$/)
22
+ groffed.should.match(/^\.Xr madeup 4$/)
23
+ end
24
+
25
+ it "sorts by section" do
26
+ groffed = @see_also.to_groff
27
+ groffed.index(/^\.Xr ruby$/).should.be < groffed.index(/^\.Xr rails 1$/)
28
+ groffed.index(/^\.Xr rails 1$/).should.be < groffed.index(/^\.Xr madeup 4$/)
29
+ end
30
+
31
+ it "sorts within sections" do
32
+ groffed = @see_also.to_groff
33
+ groffed.index(/^\.Xr perl 1$/).should.be < groffed.index(/^\.Xr rails 1$/)
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'bacon'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'rtfm'
7
+
8
+ include RTFM
9
+
10
+ Bacon.summary_on_exit
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe "synopsis section" do
4
+
5
+ before do
6
+ @synopsis = SynopsisSection.new do |syno|
7
+ syno.option :r, "Some r flag"
8
+ syno.option :j, "Some j flag"
9
+ syno.option :k, "Some k flag"
10
+ syno.option :"0", "Some zero flag"
11
+ syno.option :A, "Some capitalized flag"
12
+ syno.option :Z, "some big-z flag"
13
+ syno.option :verbose, "The verbose flag does a lot of stuff."
14
+ syno.option :silliness, "Set how silly the application should be.", :argument => "n"
15
+ syno.option :input, "The input flag takes a filename", :argument => "<input>"
16
+ end
17
+ @groffed = @synopsis.to_groff
18
+ end
19
+
20
+ it "contains the name of the man page" do
21
+ @groffed.should.match(/^\.Nm$/)
22
+ end
23
+
24
+ it "displays long-form flags" do
25
+ @groffed.should.match(/^\.Op Fl verbose$/)
26
+ end
27
+
28
+ it "combines short-form flags into one entry" do
29
+ @groffed.should.match(/^\.Op Fl ([rjk0AZ]{6})/)
30
+ end
31
+
32
+ it "sorts short-form flags within their entry" do
33
+ @groffed.should.match(/AZjkr0/)
34
+ end
35
+
36
+ it "encodes < and > in option arguments" do
37
+ @groffed.should.match(/Ao input Ac/)
38
+ end
39
+
40
+ it "underlines arguments" do
41
+ @groffed.should.match(/\.Op Fl silliness Ar n/)
42
+ end
43
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rtfm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Edgar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-10 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bacon
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: yard
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: |
36
+ Using RTFM, you can declaratively create nice, standard man pages for your
37
+ Ruby projects using a slick, maintainable DSL. It also includes rake tasks
38
+ to aid in debugging, generating, and installing your man files.
39
+
40
+ email: michael.j.edgar@dartmouth.edu
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ extra_rdoc_files:
46
+ - LICENSE
47
+ - README.markdown
48
+ files:
49
+ - .document
50
+ - .gitignore
51
+ - LICENSE
52
+ - README.markdown
53
+ - Rakefile
54
+ - VERSION
55
+ - lib/rtfm.rb
56
+ - lib/rtfm/groffstring.rb
57
+ - lib/rtfm/manpage.rb
58
+ - lib/rtfm/option.rb
59
+ - lib/rtfm/sections/authors.rb
60
+ - lib/rtfm/sections/description.rb
61
+ - lib/rtfm/sections/see_also.rb
62
+ - lib/rtfm/sections/synopsis.rb
63
+ - lib/rtfm/sections/text.rb
64
+ - lib/rtfm/tasks.rb
65
+ - lib/rubygems_plugin.rb
66
+ - man/testing.2
67
+ - spec/authors_spec.rb
68
+ - spec/description_spec.rb
69
+ - spec/groffstring_spec.rb
70
+ - spec/rtfm_spec.rb
71
+ - spec/see_also_spec.rb
72
+ - spec/spec_helper.rb
73
+ - spec/synopsis_spec.rb
74
+ has_rdoc: true
75
+ homepage: http://github.com/michaeledgar/rtfm
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options:
80
+ - --charset=UTF-8
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ requirements: []
96
+
97
+ rubyforge_project:
98
+ rubygems_version: 1.3.5
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Create makefiles declaratively. Comes with rake tasks.
102
+ test_files:
103
+ - spec/authors_spec.rb
104
+ - spec/description_spec.rb
105
+ - spec/groffstring_spec.rb
106
+ - spec/rtfm_spec.rb
107
+ - spec/see_also_spec.rb
108
+ - spec/spec_helper.rb
109
+ - spec/synopsis_spec.rb