ian 0.4.3 → 0.5.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.
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