man_parser 0.1.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/README.markdown +42 -0
- data/Rakefile +21 -0
- data/VERSION +1 -0
- data/lib/man_parser.rb +95 -0
- data/man_parser.gemspec +45 -0
- data/spec/man_parser_spec.rb +105 -0
- data/spec/spec_helper.rb +4 -0
- metadata +62 -0
data/README.markdown
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
Parse man source
|
2
|
+
|
3
|
+
Install
|
4
|
+
=======
|
5
|
+
sudo gem install grosser-man_parser -s http://gems.github.com/
|
6
|
+
|
7
|
+
Usage
|
8
|
+
=====
|
9
|
+
ManParser.parse('ls')
|
10
|
+
# all the sections
|
11
|
+
:sections=>{
|
12
|
+
"NAME"=>"printf \\- format and print data",
|
13
|
+
"SYNOPSIS"=>".B printf\n\\f...",
|
14
|
+
"AUTHOR"=>"Written by David MacKenzie.",
|
15
|
+
...
|
16
|
+
},
|
17
|
+
|
18
|
+
# options parsed into :name, :alias, :argument, :description
|
19
|
+
:options=>[
|
20
|
+
{:name=>"help", :description=>"display this help and exit"},
|
21
|
+
{:name=>"version", :description=>"output version information and exit"},
|
22
|
+
{:alias=>"Z", :name => 'context', :description=>"print any SELinux security context of each file"}
|
23
|
+
],
|
24
|
+
|
25
|
+
# description without options
|
26
|
+
:description=>".PPPrint ARGUMENT(s) according to FORMAT\n bla bla...."}
|
27
|
+
|
28
|
+
### available_commands
|
29
|
+
ManParser.available_commands => array of commands that are available for parsing
|
30
|
+
|
31
|
+
### source
|
32
|
+
ManParser.source('ls') => uncleaned source of man file
|
33
|
+
|
34
|
+
TODO
|
35
|
+
====
|
36
|
+
- add to_html("\fBxx\fR") == "<b>xx</b>"
|
37
|
+
|
38
|
+
Author
|
39
|
+
======
|
40
|
+
[Michael Grosser](http://pragmatig.wordpress.com)
|
41
|
+
grosser.michael@gmail.com
|
42
|
+
Hereby placed under public domain, do what you want, just do not hold me accountable...
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
desc "Run all specs in spec directory"
|
2
|
+
task :default do
|
3
|
+
options = "--colour --format progress --loadby --reverse"
|
4
|
+
files = FileList['spec/**/*_spec.rb']
|
5
|
+
system("spec #{options} #{files}")
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'jeweler'
|
10
|
+
project_name = 'man_parser'
|
11
|
+
Jeweler::Tasks.new do |gem|
|
12
|
+
gem.name = project_name
|
13
|
+
gem.summary = "Parse unix man pages into ruby-readable format"
|
14
|
+
gem.email = "grosser.michael@gmail.com"
|
15
|
+
gem.homepage = "http://github.com/grosser/#{project_name}"
|
16
|
+
gem.authors = ["Michael Grosser"]
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
21
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/man_parser.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
class ManParser
|
2
|
+
def self.available_commands
|
3
|
+
`ls #{root}`.split("\n").map{|c| c.sub('.1.gz','')}
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.source(cmd)
|
7
|
+
`gzip -dc #{root}/#{cmd}.1.gz`
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse(cmd)
|
11
|
+
sections = sections(source(cmd))
|
12
|
+
description, options = parse_description(sections['DESCRIPTION'])
|
13
|
+
options = options.map{|option| parse_option(option*' ') }
|
14
|
+
{:description => description.map{|l|l.strip}.join(''), :options=>options, :sections=>sections}
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def self.root
|
20
|
+
'/usr/share/man/man1'
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.parse_option(option)
|
24
|
+
option = option.gsub(/\\f[IB](.*?)\\fR/,"\\1").gsub('\\','')
|
25
|
+
|
26
|
+
found = if option =~ /^-(\w+), --([-\w]+)(=(\w+))?(.*)/
|
27
|
+
{:alias=>$1, :name=>$2, :argument=>$4, :description=>$5}
|
28
|
+
elsif option =~ /^--([-\w]+)(=(\w+))?(.*)/
|
29
|
+
{:name=>$1, :argument=>$3, :description=>$4}
|
30
|
+
elsif option =~ /^-([-\w]+)(.*)/
|
31
|
+
{:alias=>$1, :description=>$2}
|
32
|
+
end
|
33
|
+
|
34
|
+
if not found
|
35
|
+
puts "#{option} <-> nil !"
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
found[:description] = found[:description].to_s.strip.sub(/\s*\.TP$/,'')
|
40
|
+
found.delete(:argument) unless found[:argument]
|
41
|
+
|
42
|
+
found
|
43
|
+
end
|
44
|
+
|
45
|
+
# description can be split like "description, options, descriptions"
|
46
|
+
# so we remove the options part, and combind the 2 descriptions parts
|
47
|
+
def self.parse_description(text)
|
48
|
+
in_option = false
|
49
|
+
already_switched = false
|
50
|
+
options = []
|
51
|
+
description = []
|
52
|
+
|
53
|
+
text.split("\n")[1..-1].each do |line|
|
54
|
+
|
55
|
+
if start_of_option?(line) and not already_switched
|
56
|
+
in_option = true
|
57
|
+
options << [] #new option
|
58
|
+
elsif line =~ /^\.PP/ and in_option
|
59
|
+
already_switched = true
|
60
|
+
in_option = false
|
61
|
+
end
|
62
|
+
|
63
|
+
next if line.strip.empty?
|
64
|
+
|
65
|
+
if in_option
|
66
|
+
options.last << line
|
67
|
+
else
|
68
|
+
description << line
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
[description * "\n", options]
|
73
|
+
end
|
74
|
+
|
75
|
+
# split into sections according to "SectionHead" aka .SH
|
76
|
+
def self.sections(text)
|
77
|
+
name = 'OUT_OF_SECTION'
|
78
|
+
sections = Hash.new([])
|
79
|
+
|
80
|
+
text.split("\n").each do |line|
|
81
|
+
if line =~ /^\.SH (.*)$/
|
82
|
+
name = $1
|
83
|
+
else
|
84
|
+
sections[name] += [line]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
sections.each{|k,v| sections[k] = v*"\n"}
|
89
|
+
sections
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.start_of_option?(line)
|
93
|
+
!!( line =~ /^\\fB\\-/)
|
94
|
+
end
|
95
|
+
end
|
data/man_parser.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{man_parser}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Grosser"]
|
12
|
+
s.date = %q{2009-10-03}
|
13
|
+
s.email = %q{grosser.michael@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"README.markdown"
|
16
|
+
]
|
17
|
+
s.files = [
|
18
|
+
"README.markdown",
|
19
|
+
"Rakefile",
|
20
|
+
"VERSION",
|
21
|
+
"lib/man_parser.rb",
|
22
|
+
"man_parser.gemspec",
|
23
|
+
"spec/man_parser_spec.rb",
|
24
|
+
"spec/spec_helper.rb"
|
25
|
+
]
|
26
|
+
s.homepage = %q{http://github.com/grosser/man_parser}
|
27
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
28
|
+
s.require_paths = ["lib"]
|
29
|
+
s.rubygems_version = %q{1.3.5}
|
30
|
+
s.summary = %q{Parse unix man pages into ruby-readable format}
|
31
|
+
s.test_files = [
|
32
|
+
"spec/man_parser_spec.rb",
|
33
|
+
"spec/spec_helper.rb"
|
34
|
+
]
|
35
|
+
|
36
|
+
if s.respond_to? :specification_version then
|
37
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
38
|
+
s.specification_version = 3
|
39
|
+
|
40
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
41
|
+
else
|
42
|
+
end
|
43
|
+
else
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe ManParser do
|
4
|
+
describe :parse do
|
5
|
+
it "finds the description" do
|
6
|
+
d = ManParser.parse('ls')[:description]
|
7
|
+
d.should =~ /^\.PPList information about the FILEs \(the current/
|
8
|
+
d.should =~ /problems, 2 if serious trouble\.$/
|
9
|
+
d.should_not include('\\-\\-all')
|
10
|
+
d.should_not include('do not ignore entries starting with')
|
11
|
+
d.should_not include(" ")
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'options' do
|
15
|
+
def options
|
16
|
+
ManParser.parse('ls')[:options]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "finds all options" do
|
20
|
+
options.size.should == 58
|
21
|
+
end
|
22
|
+
|
23
|
+
it "extracts the name" do
|
24
|
+
options.first[:name].should == 'all'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "extracts the alias" do
|
28
|
+
options.first[:alias].should == 'a'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "extracts the description" do
|
32
|
+
options.first[:description].should == 'do not ignore entries starting with .'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "understands format with only name (--author)" do
|
36
|
+
options[2].should == {:name=>'author', :description=>'with -l, print the author of each file'}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe :source do
|
42
|
+
it "reads the source" do
|
43
|
+
ManParser.source('printf').should =~ /^.\\\" DO NOT MODIFY THIS F(.*) access to the complete manual.\n$/m
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe :available_commands do
|
48
|
+
it "finds them" do
|
49
|
+
ManParser.available_commands.should include('printf')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe :parse_option do
|
54
|
+
def parse(x)
|
55
|
+
ManParser.send(:parse_option, x)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "parses single --" do
|
59
|
+
x = parse('\fB\-\-help\fR display this help and exit .TP')
|
60
|
+
x.should == {:name=>"help", :description=>"display this help and exit"}
|
61
|
+
end
|
62
|
+
|
63
|
+
it "parses single -- with =" do
|
64
|
+
x = parse('\fB\-\-block\-size\fR=\fISIZE\fR xyz')
|
65
|
+
x.should == {:name => 'block-size', :argument=>'SIZE', :description=>"xyz"}
|
66
|
+
end
|
67
|
+
|
68
|
+
it "parses single -" do
|
69
|
+
x = parse('\fB\-1\fR list one file per line .TP')
|
70
|
+
x.should == {:alias=>"1", :description=>"list one file per line"}
|
71
|
+
end
|
72
|
+
|
73
|
+
it "parses - and --" do
|
74
|
+
x = parse('\fB\-Z\fR, \fB\-\-context\fR print any SELinux security context of each file .TP')
|
75
|
+
x.should == {:alias=>"Z", :name => 'context', :description=>"print any SELinux security context of each file"}
|
76
|
+
end
|
77
|
+
|
78
|
+
it "parses - and -- with =" do
|
79
|
+
x = parse('\fB\-T\fR, \fB\-\-tabsize\fR=\fICOLS\fR assume tab stops at each COLS instead of 8 .TP')
|
80
|
+
x.should == {:alias=>"T", :name => 'tabsize', :argument=>'COLS', :description=>"assume tab stops at each COLS instead of 8"}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "does not parse random stuff" do
|
84
|
+
ManParser.stub!(:puts)
|
85
|
+
x = parse('as we say: \fB\-T\fR, \fB\-\-tabsize\fR=\fICOLS\fR assume tab stops at each COLS instead of 8')
|
86
|
+
x.should == nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe :start_of_option? do
|
91
|
+
{
|
92
|
+
'\fB\-\-version\fR'=>true,
|
93
|
+
'\fB\-1\fR'=>true,
|
94
|
+
'\fB\-\-color\fR=\fIauto\fR'=>true,
|
95
|
+
'\fB\-T\fR, \fB\-\-tabsize\fR=\fICOLS\fR'=>true,
|
96
|
+
'\fB\-U\fR'=>true,
|
97
|
+
'\-\-\-\-\-'=>false,
|
98
|
+
' asdadas'=>false
|
99
|
+
}.each do |line, success|
|
100
|
+
it "recognises #{line} -- #{success}" do
|
101
|
+
ManParser.send(:start_of_option?, line).should == success
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: man_parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Grosser
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-03 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: grosser.michael@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.markdown
|
24
|
+
files:
|
25
|
+
- README.markdown
|
26
|
+
- Rakefile
|
27
|
+
- VERSION
|
28
|
+
- lib/man_parser.rb
|
29
|
+
- man_parser.gemspec
|
30
|
+
- spec/man_parser_spec.rb
|
31
|
+
- spec/spec_helper.rb
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://github.com/grosser/man_parser
|
34
|
+
licenses: []
|
35
|
+
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options:
|
38
|
+
- --charset=UTF-8
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.3.5
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: Parse unix man pages into ruby-readable format
|
60
|
+
test_files:
|
61
|
+
- spec/man_parser_spec.rb
|
62
|
+
- spec/spec_helper.rb
|