spqr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +202 -0
- data/README.rdoc +28 -0
- data/Rakefile +105 -0
- data/TODO +33 -0
- data/VERSION +1 -0
- data/bin/spqr-gen.rb +60 -0
- data/examples/codegen-schema.xml +7 -0
- data/examples/codegen/EchoAgent.rb +33 -0
- data/examples/hello.rb +44 -0
- data/examples/logservice.rb +90 -0
- data/lib/rhubarb/rhubarb.rb +504 -0
- data/lib/spqr/app.rb +299 -0
- data/lib/spqr/codegen.rb +434 -0
- data/lib/spqr/constants.rb +64 -0
- data/lib/spqr/manageable.rb +222 -0
- data/lib/spqr/spqr.rb +16 -0
- data/lib/spqr/utils.rb +88 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/spqr_spec.rb +5 -0
- data/spqr.spec.in +95 -0
- data/test/helper.rb +11 -0
- data/test/test_rhubarb.rb +608 -0
- data/test/test_spqr.rb +7 -0
- metadata +97 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Constants for SQPR
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Red Hat, Inc.
|
6
|
+
#
|
7
|
+
# Author: William Benton (willb@redhat.com)
|
8
|
+
#
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
10
|
+
# you may not use this file except in compliance with the License.
|
11
|
+
# You may obtain a copy of the License at
|
12
|
+
#
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
14
|
+
|
15
|
+
module SPQR
|
16
|
+
|
17
|
+
module XmlConstants
|
18
|
+
Type = {
|
19
|
+
'absTime' => 'Qmf::TYPE_ABSTIME',
|
20
|
+
'array' => 'Qmf::TYPE_ARRAY', # XXX: is this right?
|
21
|
+
'bool' => 'Qmf::TYPE_BOOL',
|
22
|
+
'deltaTime' => 'Qmf::TYPE_DELTATIME',
|
23
|
+
'double' => 'Qmf::TYPE_DOUBLE',
|
24
|
+
'float' => 'Qmf::TYPE_FLOAT',
|
25
|
+
'int16' => 'Qmf::TYPE_INT16',
|
26
|
+
'int32' => 'Qmf::TYPE_INT32',
|
27
|
+
'int64' => 'Qmf::TYPE_INT64',
|
28
|
+
'int' => 'Qmf::TYPE_INT64',
|
29
|
+
'int8' => 'Qmf::TYPE_INT8',
|
30
|
+
'list' => 'Qmf::TYPE_LIST', # XXX: is this right?
|
31
|
+
'lstr' => 'Qmf::TYPE_LSTR',
|
32
|
+
'string' => 'Qmf::TYPE_LSTR',
|
33
|
+
'map' => 'Qmf::TYPE_MAP',
|
34
|
+
'objId' => 'Qmf::TYPE_REF',
|
35
|
+
'sstr' => 'Qmf::TYPE_SSTR',
|
36
|
+
'uint16' => 'Qmf::TYPE_UINT16',
|
37
|
+
'uint32' => 'Qmf::TYPE_UINT32',
|
38
|
+
'uint64' => 'Qmf::TYPE_UINT64',
|
39
|
+
'uint' => 'Qmf::TYPE_UINT64',
|
40
|
+
'uint8' => 'Qmf::TYPE_UINT8',
|
41
|
+
'uuid' => 'Qmf::TYPE_UUID'
|
42
|
+
}
|
43
|
+
|
44
|
+
Access = {
|
45
|
+
"RC" => 'Qmf::ACCESS_READ_CREATE',
|
46
|
+
"RW" => 'Qmf::ACCESS_READ_WRITE',
|
47
|
+
"RO" => 'Qmf::ACCESS_READ_ONLY',
|
48
|
+
"R" => 'Qmf::ACCESS_READ_ONLY'
|
49
|
+
}
|
50
|
+
|
51
|
+
Direction = {
|
52
|
+
"I" => 'Qmf::DIR_IN',
|
53
|
+
"O" => 'Qmf::DIR_OUT',
|
54
|
+
"IO" => 'Qmf::DIR_IN_OUT',
|
55
|
+
"i" => 'Qmf::DIR_IN',
|
56
|
+
"o" => 'Qmf::DIR_OUT',
|
57
|
+
"io" => 'Qmf::DIR_IN_OUT',
|
58
|
+
"in" => 'Qmf::DIR_IN',
|
59
|
+
"out" => 'Qmf::DIR_OUT',
|
60
|
+
"inout" => 'Qmf::DIR_IN_OUT'
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# SPQR: Schema Processor for QMF/Ruby agents
|
2
|
+
#
|
3
|
+
# Manageable object mixin and support classes.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Red Hat, Inc.
|
6
|
+
#
|
7
|
+
# Author: William Benton (willb@redhat.com)
|
8
|
+
#
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
10
|
+
# you may not use this file except in compliance with the License.
|
11
|
+
# You may obtain a copy of the License at
|
12
|
+
#
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
14
|
+
|
15
|
+
module SPQR
|
16
|
+
class ManageableMeta < Struct.new(:classname, :package, :description, :mmethods, :options, :statistics, :properties)
|
17
|
+
def initialize(*a)
|
18
|
+
super *a
|
19
|
+
self.options = (({} unless self.options) or self.options.dup)
|
20
|
+
self.statistics = [] unless self.statistics
|
21
|
+
self.properties = [] unless self.properties
|
22
|
+
self.mmethods ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
def declare_method(name, desc, options, blk=nil)
|
26
|
+
result = MethodMeta.new name, desc, options
|
27
|
+
blk.call(result.args) if blk
|
28
|
+
self.mmethods << result
|
29
|
+
self.mmethods[-1]
|
30
|
+
end
|
31
|
+
|
32
|
+
def declare_statistic(name, kind, options)
|
33
|
+
declare_basic(:statistic, name, kind, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def declare_property(name, kind, options)
|
37
|
+
declare_basic(:property, name, kind, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def declare_basic(what, name, kind, options)
|
42
|
+
what_plural = "#{what.to_s.gsub(/y$/, 'ie')}s"
|
43
|
+
w_get = what_plural.to_sym
|
44
|
+
w_set = "#{what_plural}=".to_sym
|
45
|
+
|
46
|
+
self.send(w_set, (self.send(w_get) or []))
|
47
|
+
|
48
|
+
w_class = "#{what.to_s.capitalize}Meta"
|
49
|
+
self.send(w_get) << SPQR.const_get(w_class).new(name, kind, options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class MethodMeta < Struct.new(:name, :description, :args, :options)
|
54
|
+
def initialize(*a)
|
55
|
+
super *a
|
56
|
+
self.options = (({} unless self.options) or self.options.dup)
|
57
|
+
self.args = gen_args
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def gen_args
|
62
|
+
result = []
|
63
|
+
|
64
|
+
def result.declare(name, kind, direction, description=nil, options=nil)
|
65
|
+
options ||= {}
|
66
|
+
arg = ::SPQR::ArgMeta.new name, kind, direction, description, options.dup
|
67
|
+
self << arg
|
68
|
+
end
|
69
|
+
|
70
|
+
result
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class ArgMeta < Struct.new(:name, :kind, :direction, :description, :options)
|
75
|
+
def initialize(*a)
|
76
|
+
super *a
|
77
|
+
self.options = (({} unless self.options) or self.options.dup)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class PropertyMeta < Struct.new(:name, :kind, :options)
|
82
|
+
def initialize(*a)
|
83
|
+
super *a
|
84
|
+
self.options = (({} unless self.options) or self.options.dup)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class StatisticMeta < Struct.new(:name, :kind, :options)
|
89
|
+
def initialize(*a)
|
90
|
+
super *a
|
91
|
+
self.options = (({} unless self.options) or self.options.dup)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module ManageableClassMixins
|
96
|
+
def spqr_meta
|
97
|
+
@spqr_meta ||= ::SPQR::ManageableMeta.new
|
98
|
+
end
|
99
|
+
|
100
|
+
def spqr_logger=(logger)
|
101
|
+
@spqr_log = logger
|
102
|
+
end
|
103
|
+
|
104
|
+
def spqr_logger
|
105
|
+
@spqr_log || ::SPQR::Sink.new
|
106
|
+
end
|
107
|
+
|
108
|
+
# Exposes a method to QMF
|
109
|
+
def spqr_expose(name, description=nil, options=nil, &blk)
|
110
|
+
spqr_meta.declare_method(name, description, options, blk)
|
111
|
+
end
|
112
|
+
|
113
|
+
def spqr_package(nm)
|
114
|
+
spqr_meta.package = nm
|
115
|
+
end
|
116
|
+
|
117
|
+
def spqr_class(nm)
|
118
|
+
spqr_meta.classname = nm
|
119
|
+
end
|
120
|
+
|
121
|
+
def spqr_description(d)
|
122
|
+
spqr_meta.description = d
|
123
|
+
end
|
124
|
+
|
125
|
+
def spqr_options(opts)
|
126
|
+
spqr_meta.options = opts.dup
|
127
|
+
end
|
128
|
+
|
129
|
+
def spqr_statistic(name, kind, options=nil)
|
130
|
+
spqr_meta.declare_statistic(name, kind, options)
|
131
|
+
|
132
|
+
self.class_eval do
|
133
|
+
# XXX: are we only interested in declaring a reader for
|
134
|
+
# statistics? Doesn't it really makes more sense for the managed
|
135
|
+
# class to declare a method with the same name as the
|
136
|
+
# statistic so we aren't declaring anything at all here?
|
137
|
+
|
138
|
+
# XXX: should cons up a "safe_attr_reader" method that works
|
139
|
+
# like this:
|
140
|
+
attr_reader name.to_sym unless instance_methods.include? "#{name}"
|
141
|
+
attr_writer name.to_sym unless instance_methods.include? "#{name}="
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def spqr_property(name, kind, options=nil)
|
146
|
+
spqr_meta.declare_property(name, kind, options)
|
147
|
+
|
148
|
+
# add a property accessor to instances of other
|
149
|
+
self.class_eval do
|
150
|
+
# XXX: should cons up a "safe_attr_accessor" method that works like this:
|
151
|
+
attr_reader name.to_sym unless instance_methods.include? "#{name}"
|
152
|
+
attr_writer name.to_sym unless instance_methods.include? "#{name}="
|
153
|
+
end
|
154
|
+
|
155
|
+
if options and options[:index]
|
156
|
+
# if this is an index property, add a find-by method if one
|
157
|
+
# does not already exist
|
158
|
+
spqr_define_index_find(name)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def spqr_define_index_find(name)
|
164
|
+
find_by_prop = "find_by_#{name}".to_sym
|
165
|
+
|
166
|
+
return if self.respond_to? find_by_prop
|
167
|
+
|
168
|
+
define_method find_by_prop do |arg|
|
169
|
+
raise "#{self} must define find_by_#{name}(arg)"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
module Manageable
|
175
|
+
def qmf_oid
|
176
|
+
result = 0
|
177
|
+
if self.respond_to? :spqr_object_id
|
178
|
+
result = spqr_object_id
|
179
|
+
else
|
180
|
+
result = object_id
|
181
|
+
end
|
182
|
+
|
183
|
+
result & 0x7fffffff
|
184
|
+
end
|
185
|
+
|
186
|
+
def qmf_id
|
187
|
+
[qmf_oid, self.class.class_id]
|
188
|
+
end
|
189
|
+
|
190
|
+
def log
|
191
|
+
self.class.spqr_logger
|
192
|
+
end
|
193
|
+
|
194
|
+
def self.included(other)
|
195
|
+
class << other
|
196
|
+
include ManageableClassMixins
|
197
|
+
end
|
198
|
+
|
199
|
+
unless other.respond_to? :find_by_id
|
200
|
+
def other.find_by_id(id)
|
201
|
+
raise "#{self} must define find_by_id(id)"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
unless other.respond_to? :find_all
|
206
|
+
def other.find_all
|
207
|
+
raise "#{self} must define find_all"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
unless other.respond_to? :class_id
|
212
|
+
def other.class_id
|
213
|
+
package_list = spqr_meta.package.to_s.split(".")
|
214
|
+
cls = spqr_meta.classname.to_s or self.name.to_s
|
215
|
+
((package_list.map {|pkg| pkg.capitalize} << cls).join("::")).hash & 0x7fffffff
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
other.spqr_class other.name.to_sym
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
data/lib/spqr/spqr.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# SPQR: Schema Processor for QMF/Ruby agents
|
2
|
+
#
|
3
|
+
# Copyright (c) 2009 Red Hat, Inc.
|
4
|
+
#
|
5
|
+
# Author: William Benton (willb@redhat.com)
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
|
13
|
+
require 'spqr/utils'
|
14
|
+
require 'spqr/constants'
|
15
|
+
require 'spqr/codegen'
|
16
|
+
require 'spqr/manageable'
|
data/lib/spqr/utils.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Utility functions and modules for SPQR
|
2
|
+
#
|
3
|
+
# Copyright (c) 2009 Red Hat, Inc.
|
4
|
+
#
|
5
|
+
# Author: William Benton (willb@redhat.com)
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
|
13
|
+
module SPQR
|
14
|
+
class Sink
|
15
|
+
def method_missing(*args)
|
16
|
+
yield if block_given?
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module PrettyPrinter
|
22
|
+
def writemode
|
23
|
+
$PP_WRITEMODE ||= File::WRONLY|File::CREAT|File::TRUNC
|
24
|
+
end
|
25
|
+
|
26
|
+
def stack
|
27
|
+
@fstack ||= [STDOUT]
|
28
|
+
end
|
29
|
+
|
30
|
+
def inc_indent
|
31
|
+
@indent = indent + 2
|
32
|
+
end
|
33
|
+
|
34
|
+
def dec_indent
|
35
|
+
@indent = indent - 2
|
36
|
+
end
|
37
|
+
|
38
|
+
def indent
|
39
|
+
@indent ||= 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def outfile
|
43
|
+
@fstack[-1] or STDOUT
|
44
|
+
end
|
45
|
+
|
46
|
+
def pp(s)
|
47
|
+
outfile.puts "#{' ' * indent}#{s}\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
def pp_decl(kind, name, etc=nil)
|
51
|
+
pp "#{kind} #{name}#{etc}"
|
52
|
+
inc_indent
|
53
|
+
yield if block_given?
|
54
|
+
dec_indent
|
55
|
+
pp "end"
|
56
|
+
end
|
57
|
+
|
58
|
+
def pp_call(callable, args)
|
59
|
+
arg_repr = args.map {|arg| (arg.inspect if arg.kind_of? Hash) or arg}.join(', ')
|
60
|
+
pp "#{callable}(#{arg_repr})"
|
61
|
+
end
|
62
|
+
|
63
|
+
def pp_invoke(receiver, method, args)
|
64
|
+
pp_call "#{receiver}.#{method}", args
|
65
|
+
end
|
66
|
+
|
67
|
+
def with_output_to(filename, &action)
|
68
|
+
File::open(filename, writemode) do |of|
|
69
|
+
stack << of
|
70
|
+
action.call
|
71
|
+
stack.pop
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module MiscUtil
|
77
|
+
def symbolize_dict(k, kz=nil)
|
78
|
+
k2 = {}
|
79
|
+
kz ||= k.keys
|
80
|
+
|
81
|
+
k.keys.each do |key|
|
82
|
+
k2[key.to_sym] = k[key] if (kz.include?(key) or kz.include?(key.to_sym))
|
83
|
+
end
|
84
|
+
|
85
|
+
k2
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
data/spec/spqr_spec.rb
ADDED
data/spqr.spec.in
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Generated from <%= File::basename(format.gem_path) %> by gem2rpm -*- rpm-spec -*-
|
2
|
+
%define ruby_sitelib %(ruby -rrbconfig -e "puts Config::CONFIG['sitelibdir']")
|
3
|
+
%define gemdir %(ruby -rubygems -e 'puts Gem::dir' 2>/dev/null)
|
4
|
+
%define gemname <%= spec.name %>
|
5
|
+
%define geminstdir %{gemdir}/gems/%{gemname}-%{version}
|
6
|
+
|
7
|
+
Summary: <%= spec.summary.gsub(/\.$/, "") %>
|
8
|
+
Name: rubygem-%{gemname}
|
9
|
+
Version: <%= spec.version %>
|
10
|
+
Release: 1%{?dist}
|
11
|
+
Group: Development/Languages
|
12
|
+
License: ASL 2.0
|
13
|
+
URL: <%= spec.homepage %>
|
14
|
+
Source0: <%= download_path %>%{gemname}-%{version}.gem
|
15
|
+
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
|
16
|
+
Requires: rubygems
|
17
|
+
Requires: ruby(abi) = 1.8
|
18
|
+
Requires: ruby-qmf
|
19
|
+
<% for d in spec.dependencies %>
|
20
|
+
<% for req in d.version_requirements.to_rpm %>
|
21
|
+
Requires: rubygem(<%= d.name %>) <%= req %>
|
22
|
+
<% end %>
|
23
|
+
<% end %>
|
24
|
+
BuildRequires: ruby > 1.8
|
25
|
+
BuildRequires: rubygems
|
26
|
+
<% if spec.extensions.empty? %>
|
27
|
+
BuildArch: noarch
|
28
|
+
<% end %>
|
29
|
+
Provides: rubygem(%{gemname}) = %{version}
|
30
|
+
|
31
|
+
%description
|
32
|
+
<%= spec.description.to_s.chomp.word_wrap(78) + "\n" %>
|
33
|
+
|
34
|
+
<% if nongem %>
|
35
|
+
%package -n ruby-%{gemname}
|
36
|
+
Summary: <%= spec.summary.gsub(/\.$/, "") %>
|
37
|
+
Group: Development/Languages
|
38
|
+
Requires: rubygem(%{gemname}) = %{version}
|
39
|
+
<% spec.files.select{ |f| spec.require_paths.include?(File::dirname(f)) }.reject { |f| f =~ /\.rb$/ }.collect { |f| File::basename(f) }.each do |p| %>
|
40
|
+
Provides: ruby(<%= p %>) = %{version}
|
41
|
+
<% end %>
|
42
|
+
%description -n ruby-%{gemname}
|
43
|
+
<%= spec.description.to_s.chomp.word_wrap(78) + "\n" %>
|
44
|
+
<% end # if nongem %>
|
45
|
+
|
46
|
+
%prep
|
47
|
+
|
48
|
+
%build
|
49
|
+
|
50
|
+
%install
|
51
|
+
rm -rf %{buildroot}
|
52
|
+
mkdir -p %{buildroot}%{gemdir}
|
53
|
+
<% rdoc_opt = spec.has_rdoc ? "--rdoc " : "" %>
|
54
|
+
gem install --local --install-dir %{buildroot}%{gemdir} \
|
55
|
+
--force <%= rdoc_opt %>%{SOURCE0}
|
56
|
+
<% unless spec.executables.empty? %>
|
57
|
+
mkdir -p %{buildroot}/%{_bindir}
|
58
|
+
mv %{buildroot}%{gemdir}/bin/* %{buildroot}/%{_bindir}
|
59
|
+
rmdir %{buildroot}%{gemdir}/bin
|
60
|
+
find %{buildroot}%{geminstdir}/bin -type f | xargs chmod a+x
|
61
|
+
<% end %>
|
62
|
+
<% if nongem %>
|
63
|
+
mkdir -p %{buildroot}%{ruby_sitelib}
|
64
|
+
<% spec.files.select{ |f| spec.require_paths.include?(File::dirname(f)) }.each do |p| %>
|
65
|
+
ln -s %{gemdir}/gems/%{gemname}-%{version}/<%= p %> %{buildroot}%{ruby_sitelib}
|
66
|
+
<% end %>
|
67
|
+
<% end # if nongem %>
|
68
|
+
|
69
|
+
%clean
|
70
|
+
rm -rf %{buildroot}
|
71
|
+
|
72
|
+
%files
|
73
|
+
%defattr(-, root, root, -)
|
74
|
+
<% for f in spec.executables %>
|
75
|
+
%{_bindir}/<%= f %>
|
76
|
+
<% end %>
|
77
|
+
%{gemdir}/gems/%{gemname}-%{version}/
|
78
|
+
<% if spec.has_rdoc %>
|
79
|
+
%doc %{gemdir}/doc/%{gemname}-%{version}
|
80
|
+
<% end %>
|
81
|
+
<% for f in spec.extra_rdoc_files %>
|
82
|
+
%doc %{geminstdir}/<%= f %>
|
83
|
+
<% end %>
|
84
|
+
%{gemdir}/cache/%{gemname}-%{version}.gem
|
85
|
+
%{gemdir}/specifications/%{gemname}-%{version}.gemspec
|
86
|
+
|
87
|
+
<% if nongem %>
|
88
|
+
%files -n ruby-%{gemname}
|
89
|
+
%defattr(-, root, root, -)
|
90
|
+
%{ruby_sitelib}/*
|
91
|
+
<% end # if nongem %>
|
92
|
+
|
93
|
+
%changelog
|
94
|
+
* <%= Time.now.strftime("%a %b %d %Y") %> <%= packager %> - <%= spec.version %>-1
|
95
|
+
- Initial package
|