ian 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fcf55fe8183aa26141bafc1011ff313f325531ff
4
- data.tar.gz: 43770450bb488b08e7b546274ecefc0646b133df
3
+ metadata.gz: 275263c7fe3189b9bcf5abcde074c7e6e1ea259e
4
+ data.tar.gz: c9874e5c5b93b50ee4712dd1cd20b50cd44d9823
5
5
  SHA512:
6
- metadata.gz: c705fadaad72699deedc70fd6e1a34c9bd8f2dcb44563dc0336b5aac206a0e80e203259d0c06dbc39b9824946fdb896a2e2a52a3cdae8556c802d9ae75510a22
7
- data.tar.gz: 0b0fe9a490eb2c685ad2f831c0995691b20ab44211f9cc82b9eef41260e65dd9d96657c28ff5688507462e9be2c11cba8048f7630e0eafc8deeacc1bfa7f82d3
6
+ metadata.gz: 08d1503a29ac3a3ded30c8ebe8d9f37e76e3f4b821e0005a5079e0576c202ee5be82ac87de7a39c1bb6717eb6e755cbbef2acc71b7b9630098b8f51cf4d99ffd
7
+ data.tar.gz: af8ab880603887338802c7d3e36fedfd60a554f410ae4b8013148bf4dd1b9a8e286cb8fc5cc34caa644adeabe8708892542c07a47f056309d681c4ad28705064
data/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  A Debian CLI package hacking tool named in memory of the late Ian Murdock.
4
4
 
5
- This tool will help you to create and maintain the source repos for Debain
6
- packages and tries to mimic the CLI of other popular tools such as git and
5
+ This tool will help you to create and maintain git repositories that contain simple
6
+ Debian packages and tries to mimic the CLI of other popular tools such as git and
7
7
  bundler.
8
8
 
9
9
  It is intended to be helpful when integrating other build tools/systems and
@@ -27,35 +27,101 @@ Or install it yourself as:
27
27
 
28
28
  ## Usage
29
29
 
30
- Create a new package (like `bundle new gemname`):
30
+ This tool is used for working with what Debian called "Binary packages" - that is
31
+ ones that have the `DEBIAN` folder in capitals to slap Debian packages together
32
+ quickly. It uses `dpkg-deb -b` in the background which most Debian package
33
+ maintainers frown at but it is suitable enough for rolling your own packages
34
+ quickly, and it scratches an itch.
35
+
36
+ Help is available using `-h` or `--help` with all commands.
37
+
38
+ **Create a new package** (like `bundle new gemname`):
31
39
 
32
40
  $ ian new mycoolpackage
33
41
 
34
- Init an existing directory (like `git init`):
42
+ This will create a folder called `mycoolpackage` as well as the `DEBIAN` folder
43
+ inside it, a `control` file with defaults and a `postinst` file. It will also
44
+ create an .ianignore file for stuff you don't want in the package.
45
+
46
+ **Init an existing directory** (like `git init`):
35
47
 
36
48
  $ ian init
37
49
 
38
- Show the info for the package (basically `cat DEBAIN/control`):
50
+ This does the same as the `new` command but assumes you are already in the folder
51
+ called `mycoolpackage`.
52
+
53
+ **Show info for the package** (basically `cat DEBAIN/control`):
39
54
 
40
55
  $ ian info
41
56
 
42
- Set info in the control file:
57
+ This will print the control file as parsed by ian. It will also warn you if you
58
+ are missing a mandatory field.
59
+
60
+ **Dependencies**:
61
+
62
+ This prints the dependencies from the control file each on a separate line:
63
+
64
+ $ ian deps
65
+
66
+ **Set info in the control file**:
43
67
 
44
68
  $ ian set -v 2.3.1-beta
45
69
  $ ian set -a amd64
46
70
 
47
- Build a package:
71
+ Using this you can programatically set the version or architecture in the control
72
+ file.
73
+
74
+ **Build a package**:
48
75
 
49
76
  $ ian pkg
50
77
 
51
- Before building a package ian will determine the installed size, leave out any
52
- cruft (such as the `.git` directory) and move READMEs and the like to `/usr/share/doc`.
78
+ Before building a package ian will determine the installed size and save it to
79
+ the control file. Then the contents are rsynced to a temp dir, excluding the `.git`
80
+ folder, the `.gitignore` and `.ianignore` files, as well as anything specified in
81
+ the `.ianignore` file.
82
+
83
+ Any files left in the root of the package will be moved to `/usr/share/doc` under
84
+ a folder of the same name as the generated package.
85
+
86
+ Finally the package is built into a `pkg` folder in the format `name_version_arch`.
87
+
88
+ **Releasing a package**:
89
+
90
+ Tagging commits with the package version can be done using this command:
91
+
92
+ $ ian release
93
+
94
+ This takes the version from the control file, tags the current commit with that
95
+ version (prepending it with a `v`) and then builds the package as above.
96
+
97
+ $ ian release 4.2.8-beta
98
+
99
+ This does the same as before, however first it will take the version number in
100
+ the argument, write it to the control file and commit the control file.
101
+
102
+ **Show versions**:
103
+
104
+ Show the versions by using this command:
105
+
106
+ $ ian versions
107
+
108
+ ## Help
109
+
110
+ Need help with the control files? Try these links:
111
+
112
+ * https://www.debian.org/doc/debian-policy/ch-controlfields.html
113
+ * https://www.debian.org/doc/debian-policy/ch-relationships.html
53
114
 
54
115
  ## TODO
55
116
 
56
117
  - [ ] MD5sums generation
57
118
  - [x] finish package generation code
58
- - [ ] ADD SPECS!!!!
119
+ - [x] ADD SPECS!!!!
120
+ - [ ] ADD MORE SPECS!!!!
121
+ - [ ] Nicer output
122
+ - [ ] add commands to help with releasing new versions/tagging
123
+ - [ ] allow packaging past versions from git tags
124
+ - [ ] add option for semver enforcement
59
125
 
60
126
  ## Development
61
127
 
data/bin/ian CHANGED
@@ -11,12 +11,17 @@ def initialized?
11
11
  File.exist?("#{IAN_DIR}/DEBIAN/control")
12
12
  end
13
13
 
14
+ def check_initialized!
15
+ abort "ERROR: Not initialized" unless initialized?
16
+ end
17
+
14
18
  log = Logger.new(STDOUT)
15
19
 
16
20
  Slop.parse help: true do
17
21
 
18
22
  on :v, "Print the version" do
19
- puts Ian::VERSION
23
+ puts "Version %s" % [Ian::VERSION]
24
+ puts "In memory of Ian Ashley Murdock (1973 - 2015)"
20
25
  exit
21
26
  end
22
27
 
@@ -34,7 +39,7 @@ Slop.parse help: true do
34
39
  abort "Directory '#{name}' exists"
35
40
  end
36
41
 
37
- Ian.create(name)
42
+ Ian.create(name, log)
38
43
  end
39
44
  end
40
45
 
@@ -52,6 +57,8 @@ Slop.parse help: true do
52
57
  description "Build a Debian package"
53
58
 
54
59
  run do |opts, args|
60
+ check_initialized!
61
+
55
62
  pkg = Ian.build_package(IAN_DIR, log)
56
63
  log.info "Package built to #{pkg}"
57
64
  end
@@ -64,6 +71,8 @@ Slop.parse help: true do
64
71
  on :a, :arch=, "Change the architecture"
65
72
 
66
73
  run do |opts, args|
74
+ check_initialized!
75
+
67
76
  c = Ian.control(IAN_DIR)
68
77
  c.update(opts.to_hash)
69
78
  c.save
@@ -75,14 +84,24 @@ Slop.parse help: true do
75
84
  description "Print information for this package"
76
85
 
77
86
  run do |opts, args|
78
- puts Ian.control(IAN_DIR)
87
+ check_initialized!
88
+
89
+ c = Ian.control(IAN_DIR)
90
+ puts c
91
+
92
+ unless c.valid?
93
+ puts "\n"
94
+ log.warn "Control file is not valid, missing mandatory fields: %s" % c.missing_mandatory_fields
95
+ end
79
96
  end
80
97
  end
81
98
 
82
99
  command :deps do
83
100
  description "Print dependencies for this package"
84
101
 
85
- run do |opts, args|
102
+ run do |opts, args|
103
+ check_initialized!
104
+
86
105
  ctrl = Ian.control(IAN_DIR)
87
106
  ctrl[:depends].each do |dep|
88
107
  puts dep
@@ -90,4 +109,48 @@ Slop.parse help: true do
90
109
  end
91
110
  end
92
111
 
112
+ command :versions do
113
+ description "Show all the known versions"
114
+
115
+ run do |opts, args|
116
+ check_initialized!
117
+
118
+ %x[git tag].scan(/^v.*$/).each do |v|
119
+ puts v
120
+ end
121
+ end
122
+ end
123
+
124
+ command :release do
125
+ description "Release the current or new version"
126
+ banner "Usage: ian release [version]\n\nBy omitting the version, the one in the control file will be used\n"
127
+
128
+ run do |opts, args|
129
+ check_initialized!
130
+
131
+ ctrl = Ian.control(IAN_DIR)
132
+ version = ctrl[:version]
133
+
134
+ if args.any?
135
+ version = args.first
136
+ version.gsub!(/^v/, '')
137
+ log.info "Releasing v#{version}"
138
+ log.info "Updating control file"
139
+ ctrl[:version] = version
140
+ ctrl.save
141
+
142
+ log.info "Committing control file"
143
+ system "git add #{ctrl.path}"
144
+ system "git commit -m 'releasing v#{version}'"
145
+
146
+ end
147
+
148
+ log.info "Tagging v#{version}"
149
+ system "git tag v#{version}"
150
+
151
+ pkg = Ian.build_package(IAN_DIR, log)
152
+ log.info "Package built to #{pkg}"
153
+ end
154
+ end
155
+
93
156
  end
data/lib/ian.rb CHANGED
@@ -18,25 +18,34 @@ module Ian
18
18
  "#{debpath(path)}/control"
19
19
  end
20
20
 
21
- def create(name)
22
- FileUtils.mkdir(name)
21
+ # create a new DEBIAN package
22
+ def create(name, log)
23
+ path = File.expand_path(name)
24
+ FileUtils.mkdir(path)
23
25
 
24
- init(name)
26
+ init(path, log)
25
27
  end
26
28
 
29
+ # initialize a new DEBIAN package
27
30
  def init(path, log)
28
- FileUtils.mkdir(debpath(path))
31
+ dpath = debpath(path)
32
+ cpath = ctrlpath(path)
33
+
34
+ FileUtils.mkdir_p(dpath)
35
+ log.info "Created DEBIAN folder"
29
36
 
30
- control(path).save
31
- log.info "Generated #{path}"
37
+ c = Control.default
38
+ c[:package] = File.basename(path)
39
+ Control.save(c, cpath)
40
+ log.info "Generated #{cpath}"
32
41
 
33
- pi = "#{debpath(path)}/postinst"
42
+ pi = "#{dpath}/postinst"
34
43
 
35
44
  File.write(pi, "#!/bin/bash\n\n\nexit 0;")
36
45
  log.info "Generated #{pi}"
37
46
 
38
- FileUtils.chmod(0755, Dir["#{debpath(path)}/*"])
39
- FileUtils.chmod(0755, debpath(path))
47
+ FileUtils.chmod(0755, Dir["#{dpath}/*"])
48
+ FileUtils.chmod(0755, dpath)
40
49
 
41
50
  #cfg = {}
42
51
 
@@ -47,8 +56,13 @@ module Ian
47
56
  log.info "Generated .ianignore"
48
57
  end
49
58
 
50
- def control(path)
51
- Ian::Control.new(ctrlpath(path))
59
+ def control(path=nil)
60
+ if path.nil?
61
+ return Control.default
62
+ else
63
+ path = ctrlpath(path) if File.basename(path) != "DEBIAN"
64
+ return Control.load_file(path)
65
+ end
52
66
  end
53
67
 
54
68
  def build_package(path, log)
@@ -60,7 +74,6 @@ module Ian
60
74
  pkgr.run
61
75
  end
62
76
 
63
-
64
77
  end
65
78
 
66
79
  end
@@ -1,20 +1,16 @@
1
- module Ian
2
- class Control
3
- attr_reader :path
1
+ require 'ian/utils'
4
2
 
5
- def initialize(path)
6
- @path = path
3
+ module Ian
4
+ class ValidationError < StandardError; end
7
5
 
8
- if File.exist?(@path)
9
- parse
10
- else
11
- @fields = defaults
12
- end
6
+ class Control
7
+ def initialize(**fields)
8
+ @fields = fields
13
9
  end
14
10
 
15
11
  # allow setting fields directly
16
12
  def []=(field, value)
17
- raise ArgumentError, "Invalid field: #{field}" unless defaults.keys.include?(field)
13
+ valid_field!(field)
18
14
  @fields[field] = value
19
15
  end
20
16
 
@@ -23,30 +19,24 @@ module Ian
23
19
  @fields[field]
24
20
  end
25
21
 
26
- # parse this control file into the fields hash
27
- def parse
28
- text = File.read(@path)
29
-
30
- @fields = {}
31
-
32
- fields.each do |f, name|
33
- m = text.match(/^#{name}: (.*)$/)
34
- next unless m
35
- @fields[f] = m[1]
36
- end
37
-
38
- if @fields[:depends]
39
- @fields[:depends] = @fields[:depends].split(",").map! {|d| d.strip }
40
- end
41
-
42
- @fields[:long_desc] = text.scan(/^ (.*)$/).flatten
22
+ # deletes a field from the hash
23
+ def delete(field)
24
+ field.to_sym if field.is_a? String
25
+ valid_field!(field)
26
+ raise ArgumentError, "Cannot remove mandatory field #{field.to_s}" if mandatory_fields.include?(field)
27
+ @fields.delete(field)
43
28
  end
44
29
 
45
- # update a bunch of fields
30
+ # update a bunch of fields at a time
46
31
  def update(hash)
47
- hash.each do |k, v|
48
- if fields.keys.include?(k) and v
49
- @fields[k] = v
32
+ hash.each do |key, value|
33
+ valid_field!(key)
34
+ raise ArgumentError, "Value for #{key} was empty" if value.nil? or value == ""
35
+
36
+ if self.class.relationship_fields.include?(key)
37
+ @fields[key] = value.split(",").map {|d| d.strip }
38
+ else
39
+ @fields[key] = value
50
40
  end
51
41
  end
52
42
  end
@@ -55,16 +45,20 @@ module Ian
55
45
  def to_s
56
46
  lines = []
57
47
 
48
+ # build the standard fields
58
49
  [:package, :version, :section, :priority, :arch, :essential, :size, :maintainer, :homepage].each do |key|
59
50
  lines << "#{fields[key]}: #{@fields[key]}"
60
51
  end
61
52
 
62
- if @fields[:depends] and @fields[:depends].any?
63
- lines << "Depends: #{@fields[:depends].join(", ")}"
53
+ # build the relationship fields that have been exploded into an array
54
+ self.class.relationship_fields.each do |key|
55
+ next unless @fields[key] and @fields[key].any?
56
+ lines << "%s: %s" % [ self.class.fields[key], @fields[key].join(", ") ]
64
57
  end
65
58
 
66
59
  lines << "Description: #{@fields[:desc]}"
67
60
 
61
+ # build the long description with the double space at the start for each line
68
62
  lines += @fields[:long_desc].map do |ld|
69
63
  " #{ld}"
70
64
  end
@@ -74,32 +68,31 @@ module Ian
74
68
  end
75
69
 
76
70
  # save the control file to disk
77
- def save
78
- File.write(@path, to_s)
79
- end
80
-
81
- # TODO: move this out of here
82
- def guess_maintainer
83
- text = File.read("#{ENV['HOME']}/.gitconfig")
84
- name = text.match(/name = (.*)$/)[1]
85
- email = text.match(/email = (.*)$/)[1]
86
-
87
- "#{name} <#{email}>"
88
- rescue
89
- return ""
71
+ # raises Ian::ValidationError
72
+ def self.save(ctrl, path)
73
+ c.valid!
74
+ File.write(path, c.to_s)
90
75
  end
91
76
 
92
- def defaults
77
+ # default values for a new control file
78
+ def self.defaults
93
79
  {
94
80
  package: "name",
95
81
  priority: "optional",
96
82
  section: "misc",
97
83
  essential: "no",
98
84
  size: 0,
99
- maintainer: guess_maintainer,
85
+ maintainer: Utils.guess_maintainer, # TODO: pass the maintainer in somehow
100
86
  homepage: "http://example.com",
101
87
  arch: "all",
102
88
  version: "0.0.1",
89
+ replaces: [],
90
+ conflicts: [],
91
+ breaks: [],
92
+ recommends: [],
93
+ suggests: [],
94
+ enhances: [],
95
+ predepends: [],
103
96
  depends: [],
104
97
  desc: "This is a description",
105
98
  long_desc: [
@@ -109,10 +102,18 @@ module Ian
109
102
  }
110
103
  end
111
104
 
112
- def fields
105
+ # a map of field symbols to field names
106
+ def self.fields
113
107
  {
114
108
  package: "Package",
115
109
  depends: "Depends",
110
+ replaces: "Replaces",
111
+ breaks: "Breaks",
112
+ conflicts: "Conflicts",
113
+ recommends: "Recommends",
114
+ suggests: "Suggests",
115
+ enhances: "Enhances",
116
+ predepends: "Pre-Depends",
116
117
  version: "Version",
117
118
  priority: "Priority",
118
119
  section: "Section",
@@ -121,9 +122,72 @@ module Ian
121
122
  maintainer: "Maintainer",
122
123
  homepage: "Homepage",
123
124
  arch: "Architecture",
124
- desc: "Description"
125
+ desc: "Description",
126
+ long_desc: " "
125
127
  }
126
128
  end
127
129
 
130
+ def self.relationship_fields
131
+ [:replaces, :conflicts, :recommends, :suggests, :enhances, :predepends, :depends, :breaks]
132
+ end
133
+
134
+ # return the mandatory fields that are missing from the control file
135
+ def missing_mandatory_fields
136
+ mandatory_fields.map do |f|
137
+ return f unless @fields.keys.include?(f)
138
+ end.reject {|f| f.nil? }
139
+ end
140
+
141
+ # an array of mandatory fields for a control file
142
+ def mandatory_fields
143
+ [:package, :version, :arch, :maintainer, :desc, :long_desc]
144
+ end
145
+
146
+ # checks if the control file is valid
147
+ def valid?
148
+ missing_mandatory_fields.empty?
149
+ end
150
+
151
+ def valid!
152
+ raise ValidationError, "Missing mandatory control fields: #{missing_mandatory_fields.join(",")}" unless valid?
153
+ end
154
+
155
+ def valid_field?(key)
156
+ self.class.fields.keys.include? key
157
+ end
158
+
159
+ def valid_field!(key)
160
+ raise ArgumentError, "Invalid field: #{key}" unless valid_field?(key)
161
+ end
162
+
163
+ def self.load_file(path)
164
+ self.new(self.parse(File.read(path)))
165
+ end
166
+
167
+ # parse this control file into the fields hash
168
+ def self.parse(text)
169
+ fields = {}
170
+
171
+ self.fields.each do |f, name|
172
+ m = text.match(/^#{name}: (.*)$/)
173
+ next unless m
174
+ fields[f] = m[1]
175
+ end
176
+
177
+ # for the relations fields, split the string out into an array
178
+ self.relationship_fields.each do |key|
179
+ next unless fields[key]
180
+ fields[key] = fields[key].split(",").map! {|d| d.strip }
181
+ end
182
+
183
+ fields[:long_desc] = text.scan(/^ (.*)$/).flatten
184
+
185
+ return fields
186
+ end
187
+
188
+ def self.default
189
+ self.new(self.defaults)
190
+ end
191
+
128
192
  end
129
193
  end
@@ -61,7 +61,7 @@ module Ian
61
61
  def build
62
62
  pkgdir = File.join(@path, "pkg")
63
63
  FileUtils.mkdir_p pkgdir
64
-
64
+
65
65
  FileUtils.chmod(0755, Dir["#{Ian.debpath(@dir)}/*"])
66
66
  FileUtils.chmod(0755, Ian.debpath(@dir))
67
67
 
@@ -97,14 +97,15 @@ module Ian
97
97
 
98
98
  def excludes
99
99
  files = %w[.git .gitignore .ianignore]
100
-
100
+
101
+ return files unless File.exist?(".ianignore")
101
102
  File.read(File.join(@path, ".ianignore")).lines.each do |ign|
102
103
  next if ign.start_with? "#"
103
-
104
+
104
105
  ign.chomp!
105
106
  igns = Dir["#{@path}/#{ign}"]
106
107
  next if igns.empty?
107
-
108
+
108
109
  files+= igns
109
110
  end
110
111
  end
@@ -6,5 +6,16 @@ module Ian
6
6
  %x[du #{path} -ks --exclude=".git"].split.first
7
7
  end
8
8
 
9
+ # try to guess the maintainer by reading the git config file
10
+ def guess_maintainer
11
+ text = File.read(File.join(ENV['HOME'], ".gitconfig"))
12
+ name = text.match(/name = (.*)$/)[1]
13
+ email = text.match(/email = (.*)$/)[1]
14
+
15
+ "#{name} <#{email}>"
16
+ rescue Errno::ENOENT
17
+ return ""
18
+ end
19
+
9
20
  end
10
21
  end
@@ -1,3 +1,3 @@
1
1
  module Ian
2
- VERSION = "0.4.3"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ian
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert McLeod
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-19 00:00:00.000000000 Z
11
+ date: 2016-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: slop