man_parser 0.1.0 → 0.1.1

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 CHANGED
@@ -2,7 +2,7 @@ Parse man source
2
2
 
3
3
  Install
4
4
  =======
5
- sudo gem install grosser-man_parser -s http://gems.github.com/
5
+ sudo gem install man_parser -s http://gemcutter.org
6
6
 
7
7
  Usage
8
8
  =====
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
data/lib/man_parser.rb CHANGED
@@ -9,52 +9,67 @@ class ManParser
9
9
 
10
10
  def self.parse(cmd)
11
11
  sections = sections(source(cmd))
12
- description, options = parse_description(sections['DESCRIPTION'])
13
- options = options.map{|option| parse_option(option*' ') }
12
+ description, options = find_options(sections['OPTIONS']||sections['DESCRIPTION'])
13
+ description ||= sections['DESCRIPTION']
14
+ options = parse_options(options)
14
15
  {:description => description.map{|l|l.strip}.join(''), :options=>options, :sections=>sections}
15
16
  end
16
17
 
17
18
  private
18
19
 
20
+ # remove common prefix from options
21
+ def self.parse_options(options)
22
+ options = options.map{|option| parse_option(option*' ') }.reject{|o| o.empty?}
23
+ common = common_prefix(options.map{|o| o[:description]})
24
+ options.each{|o| o[:description] = o[:description].split(//)[common..-1].to_s}
25
+ options
26
+ end
27
+
19
28
  def self.root
20
29
  '/usr/share/man/man1'
21
30
  end
22
31
 
23
32
  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
-
33
+ option = without_markup(option)
34
+ found = option_parts(option)
34
35
  if not found
35
- puts "#{option} <-> nil !"
36
- return
36
+ puts "#{option} <-> nothing found !"
37
+ return {}
37
38
  end
38
39
 
39
- found[:description] = found[:description].to_s.strip.sub(/\s*\.TP$/,'')
40
+ #remove ending .TP and any unnecessary whitespace
41
+ found[:description] = found[:description].to_s.strip.sub(/\s*\.TP$/,'').gsub(/\s{2,}/,' ')
40
42
  found.delete(:argument) unless found[:argument]
41
43
 
42
44
  found
43
45
  end
44
46
 
47
+ def self.common_prefix(texts)
48
+ shortest = texts.map{|t| t.size}.min || 0
49
+ shortest.downto(1) do |i|
50
+ common = texts.map{|t| t[0...i]}.uniq
51
+ next if common.size != 1
52
+ next if common.first =~ /^\w+$/ # ['\d hello world','\d hell is hot'] -> '\d '
53
+ return i
54
+ end
55
+ return 0
56
+ end
57
+
45
58
  # 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)
59
+ # so we remove the options part, and combine the 2 descriptions parts
60
+ def self.find_options(text)
48
61
  in_option = false
49
62
  already_switched = false
50
63
  options = []
64
+ known_names = []
51
65
  description = []
52
66
 
53
- text.split("\n")[1..-1].each do |line|
67
+ text.split("\n").each do |line|
54
68
 
55
- if start_of_option?(line) and not already_switched
69
+ if is_unknown_option?(line, known_names) and not already_switched
56
70
  in_option = true
57
71
  options << [] #new option
72
+ known_names << parse_option(line)[:name]
58
73
  elsif line =~ /^\.PP/ and in_option
59
74
  already_switched = true
60
75
  in_option = false
@@ -74,22 +89,48 @@ class ManParser
74
89
 
75
90
  # split into sections according to "SectionHead" aka .SH
76
91
  def self.sections(text)
92
+ lines = text.split("\n")+[".SH END"]
77
93
  name = 'OUT_OF_SECTION'
78
- sections = Hash.new([])
94
+ sections = {}
95
+ temp = []
79
96
 
80
- text.split("\n").each do |line|
97
+ lines.each do |line|
81
98
  if line =~ /^\.SH (.*)$/
82
- name = $1
99
+ sections[name] = temp * "\n"
100
+ temp = []
101
+ name = $1.gsub('"','').strip
83
102
  else
84
- sections[name] += [line]
103
+ temp << line
85
104
  end
86
105
  end
87
106
 
88
- sections.each{|k,v| sections[k] = v*"\n"}
89
107
  sections
90
108
  end
91
109
 
92
- def self.start_of_option?(line)
93
- !!( line =~ /^\\fB\\-/)
110
+ def self.without_markup(text)
111
+ text = text.gsub(/\\f[IB](.*?)\\f[RP]/,"\\1").gsub('\\','') # remove styles
112
+ text = text.gsub(/-\^-(\w)/,"--\\1") # remove weird ^ in e.g. grep options
113
+ end
114
+
115
+ # duplicate prevention, e.g. many lines in grep start with --mmap
116
+ def self.is_unknown_option?(line, known_names)
117
+ return if not is_option?(line)
118
+ name = parse_option(line)[:name]
119
+ name == nil or not known_names.include?(name)
120
+ end
121
+
122
+ def self.is_option?(line)
123
+ !! option_parts(line)
124
+ end
125
+
126
+ def self.option_parts(text)
127
+ text = without_markup(text).sub(/\.[A-Z]+ (")?/,'') # remove any initial markup
128
+ if text =~ /^-(\w+)[,"| ]+--(\w[-\w]*)(=(\w+)| <(\w+)>)?(.*)/
129
+ {:alias=>$1, :name=>$2, :argument=>$4||$5, :description=>$6}
130
+ elsif text =~ /^--(\w[-\w]*)(=(\w+))?(.*)/
131
+ {:name=>$1, :argument=>$3, :description=>$4}
132
+ elsif text =~ /^-(\w[-\w]*)(.*)/
133
+ {:alias=>$1, :description=>$2}
134
+ end
94
135
  end
95
136
  end
data/man_parser.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{man_parser}
8
- s.version = "0.1.0"
8
+ s.version = "0.1.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Michael Grosser"]
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
  "VERSION",
21
21
  "lib/man_parser.rb",
22
22
  "man_parser.gemspec",
23
+ "spec/call",
23
24
  "spec/man_parser_spec.rb",
24
25
  "spec/spec_helper.rb"
25
26
  ]
data/spec/call ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << 'lib'
3
+ require 'man_parser'
4
+ print ManParser.send(ARGV[0], ARGV[1])
@@ -4,20 +4,20 @@ describe ManParser do
4
4
  describe :parse do
5
5
  it "finds the description" do
6
6
  d = ManParser.parse('ls')[:description]
7
- d.should =~ /^\.PPList information about the FILEs \(the current/
7
+ d.should =~ /^\.\\\" Add any additional description here\.PPList infor/
8
8
  d.should =~ /problems, 2 if serious trouble\.$/
9
9
  d.should_not include('\\-\\-all')
10
10
  d.should_not include('do not ignore entries starting with')
11
11
  d.should_not include(" ")
12
12
  end
13
13
 
14
- describe 'options' do
14
+ describe 'options in description' do
15
15
  def options
16
16
  ManParser.parse('ls')[:options]
17
17
  end
18
18
 
19
19
  it "finds all options" do
20
- options.size.should == 58
20
+ options.size.should == 57
21
21
  end
22
22
 
23
23
  it "extracts the name" do
@@ -36,6 +36,34 @@ describe ManParser do
36
36
  options[2].should == {:name=>'author', :description=>'with -l, print the author of each file'}
37
37
  end
38
38
  end
39
+
40
+ describe 'options in OPTIONS section' do
41
+ def options
42
+ ManParser.parse('acpi')[:options]
43
+ end
44
+
45
+ it "finds all options x" do
46
+ options.size.should == 18
47
+ end
48
+
49
+ it "extracts the name" do
50
+ options.first[:name].should == 'battery'
51
+ end
52
+
53
+ it "extracts the alias" do
54
+ options.first[:alias].should == 'b'
55
+ end
56
+
57
+ it "extracts the description" do
58
+ options.first[:description].should == 'show battery information'
59
+ end
60
+ end
61
+
62
+ describe 'for grep' do
63
+ it "finds some options" do
64
+ ManParser.parse('grep')[:options].size.should == 70
65
+ end
66
+ end
39
67
  end
40
68
 
41
69
  describe :source do
@@ -80,15 +108,22 @@ describe ManParser do
80
108
  x.should == {:alias=>"T", :name => 'tabsize', :argument=>'COLS', :description=>"assume tab stops at each COLS instead of 8"}
81
109
  end
82
110
 
111
+ it "parses - and -- with <dir>" do
112
+ x = parse('.IP "\fB-d | --directory <dir>\fP " 10 bla bla')
113
+ x.should == {:alias=>'d', :name=>'directory', :argument=>'dir', :description=>'" 10 bla bla'}
114
+ end
115
+
83
116
  it "does not parse random stuff" do
84
117
  ManParser.stub!(:puts)
85
118
  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
119
+ x.should == {}
87
120
  end
88
121
  end
89
122
 
90
- describe :start_of_option? do
123
+ describe :is_option? do
91
124
  {
125
+ '.BR \-P ", " \-\^\-perl\-regexp' => true,
126
+ '.IP "\fB-c | --cooling\fP " 10' => true,
92
127
  '\fB\-\-version\fR'=>true,
93
128
  '\fB\-1\fR'=>true,
94
129
  '\fB\-\-color\fR=\fIauto\fR'=>true,
@@ -98,7 +133,7 @@ describe ManParser do
98
133
  ' asdadas'=>false
99
134
  }.each do |line, success|
100
135
  it "recognises #{line} -- #{success}" do
101
- ManParser.send(:start_of_option?, line).should == success
136
+ ManParser.send(:is_option?, line).should == success
102
137
  end
103
138
  end
104
139
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: man_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
@@ -27,6 +27,7 @@ files:
27
27
  - VERSION
28
28
  - lib/man_parser.rb
29
29
  - man_parser.gemspec
30
+ - spec/call
30
31
  - spec/man_parser_spec.rb
31
32
  - spec/spec_helper.rb
32
33
  has_rdoc: true