1#!/usr/local/bin/ruby
2# pkg-plist generator by Brian Fundakowski Feldman <green@FreeBSD.org>
3# (public domain)
4
5class Plist
6	def initialize(no_manpages = true, mtree = [])
7		@no_manpages = no_manpages
8		@mtree = mtree
9		self
10	end
11	def make(dir)
12		@root = dir.to_s + '/'
13		imake('', 0, '')
14	end
15	private
16	def imake(dir, level, prevwd)
17		thiswd = prevwd + dir				# always ends in '/'
18		rootedwd = @root + thiswd
19		subs = []
20		Dir.foreach(rootedwd) {|dirent|
21			next if dirent =~ /^\.\.?$/
22			if test(?d, rootedwd + dirent)
23				subs.concat(imake(dirent + '/', level + 1, thiswd))
24			else
25				if thiswd !~ /^man\// || !@no_manpages
26						subs.push(thiswd + dirent)
27				end
28			end
29		}
30		thiswd.chop!
31		# Strip mtree-created directories
32		if level > 0 && !@mtree.find {|x| x == thiswd}
33			subs.push('@dir ' + thiswd)
34		end
35		return subs
36	end
37end
38
39class Mtree
40	def initialize(strip = 1)
41		@paths = []
42		@curlevel = []
43		@strip = strip.to_i
44		@curline = ''
45		@global_settings = {}
46		self
47	end
48	def parse_line(line)
49		line.chomp!
50		if line.empty? || line[0, 1] == '#'
51			return
52		end
53		if line[-1, 1] == "\\"
54			@curline.concat(line[0..-2])
55			next
56		end
57		line = @curline + line
58		@curline = ''
59		case line[/\S.+/]
60		when /^\/(\S+)/
61			case $1
62			when 'set'
63				$'.split.each {|setting|
64					key, value, = setting.split(/=/)
65					@global_settings[key] = value
66				}
67			when 'unset'
68				$'.split.each {|setting| @global_settings.delete(setting)}
69			else
70				raise "invalid command \"#{$1}\""
71			end
72		when '..'
73			if @curlevel.pop.nil?
74				raise '".." with no previous directory'
75			end
76		else
77			spline = line.split()
78			path = spline[0]
79			settings = @global_settings.dup
80			if spline.size > 1
81				spline[1..-1].each {|setting|
82					key, value, = setting.split(/=/)
83					settings[key] = value
84				}
85			end
86			@paths.push(@curlevel + [path])
87			if settings['type'] == nil || settings['type'] == 'dir'
88				@curlevel.push(path)
89			end
90		end
91		self
92	end
93	def Mtree.read(filename)
94		m = Mtree.new
95		open(filename, 'r') {|file|
96			file.each_line {|line| m.parse_line(line)}
97		}
98		m
99	end
100	def paths(strip = @strip)
101		@paths.collect {|path| path[strip..-1].join('/')}
102	end
103end
104
105if __FILE__ == $0
106	require 'getopts'
107	if !getopts('Md', 'm:') || ARGV.size != 1
108		$stderr.print <<-USAGE_EOF
109usage: #{$0} [-Md] [-m mtree] somepath
110       Generate a pkg-plist to stdout given a previously empty somepath which
111       a port has been installed into (PREFIX=somepath).  The mtree file is
112       consulted to prevent base directories from being added to the plist.
113       The -M argument allows manpages to be added to the plist.
114       The -d argument puts all @dir commands at the end of the plist.
115USAGE_EOF
116		exit 1
117	end
118	mtree = $OPT_m || '/etc/mtree/BSD.local.dist'
119	pl = Plist.new(!$OPT_M, Mtree.read(mtree).paths).make(ARGV[0])
120	if $OPT_d
121		plnotdirrm = []
122		pldirrm = []
123		pl.each {|ent|
124			if ent =~ /^@dir /
125				pldirrm.push(ent)
126			else
127				plnotdirrm.push(ent)
128			end
129			plnotdirrm.sort!
130			pldirrm.sort!
131			pl = plnotdirrm + pldirrm.reverse
132		}
133	else
134	  	pl.sort!
135	end
136	puts(pl.join("\n"))
137end
138