kmc 0.0.6

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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +18 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +141 -0
  6. data/Rakefile +2 -0
  7. data/bin/kmc +20 -0
  8. data/kmc.gemspec +26 -0
  9. data/lib/kmc.rb +27 -0
  10. data/lib/kmc/configuration.rb +67 -0
  11. data/lib/kmc/download_url.rb +80 -0
  12. data/lib/kmc/git_adapter.rb +71 -0
  13. data/lib/kmc/package.rb +142 -0
  14. data/lib/kmc/package_attrs.rb +69 -0
  15. data/lib/kmc/package_downloads.rb +122 -0
  16. data/lib/kmc/package_dsl.rb +13 -0
  17. data/lib/kmc/package_utils.rb +29 -0
  18. data/lib/kmc/packages/lack_luster_labs.rb +22 -0
  19. data/lib/kmc/page_fetcher.js +47 -0
  20. data/lib/kmc/post_processors/module_manager_resolver.rb +32 -0
  21. data/lib/kmc/refresher.rb +36 -0
  22. data/lib/kmc/user_interface.rb +195 -0
  23. data/lib/kmc/util.rb +11 -0
  24. data/lib/kmc/version.rb +3 -0
  25. data/lib/kmc/versioner.rb +70 -0
  26. data/lib/kmc/web/app.rb +42 -0
  27. data/lib/kmc/web/public/css/main.css +69 -0
  28. data/lib/kmc/web/public/css/normalize.css +527 -0
  29. data/lib/kmc/web/public/index.html +107 -0
  30. data/lib/kmc/web/public/js/main.js +125 -0
  31. data/lib/kmc/web/public/js/plugins.js +24 -0
  32. data/lib/kmc/web/public/js/vendor/jquery-1.10.2.min.js +6 -0
  33. data/lib/kmc/web/public/js/vendor/modernizr-2.6.2.min.js +4 -0
  34. data/lib/kmc/web/public/js/vendor/underscore.js +5 -0
  35. data/package_generator.rb +197 -0
  36. data/spec/download_url_spec.rb +69 -0
  37. data/spec/fixtures/example_box.html +165 -0
  38. data/spec/fixtures/example_dropbox.com.html +114 -0
  39. data/spec/fixtures/example_mediafire.html +101 -0
  40. data/spec/package_spec.rb +113 -0
  41. data/spec/spec_helper.rb +28 -0
  42. data/spec/versioner_spec.rb +69 -0
  43. metadata +177 -0
@@ -0,0 +1,22 @@
1
+ class LackLusterLabs < Kosmos::Package
2
+ title 'Lack Luster Labs'
3
+ aliases 'LLL' 'Lack Luster Labs Pack'
4
+
5
+ url 'https://www.dropbox.com/s/0h82wo8rdxu4jya/LLL-12-2.zip'
6
+
7
+ def install
8
+ merge_directory 'GameData/LLL', into: 'GameData'
9
+ end
10
+ end
11
+
12
+ class StockExtension < Kosmos::Package
13
+ title 'Stock eXTension'
14
+ aliases 'SXT' 'LLL Stock eXTension'
15
+
16
+ url 'https://dl.dropboxusercontent.com/u/39086055/SXT-16.5.zip'
17
+
18
+ def install
19
+ merge_directory 'GameData/SXT', into: 'GameData'
20
+ end
21
+ end
22
+
@@ -0,0 +1,47 @@
1
+ /**
2
+ * A PhantomJS script to help find where to download a package's zipfile from.
3
+ * Execute it is:
4
+ *
5
+ * phantomjs page_fetcher.js <url>
6
+ *
7
+ * This script will "return" a value by sending it to STDOUT.
8
+ *
9
+ * There are two possible outputted values for this script.
10
+ *
11
+ * 1. If an external resource is received, and this external resource is a zip
12
+ * file (as determined from the HTTP contentType header), then the URL to
13
+ * that resource is returned.
14
+ * 2. If no external resource is found, then the HTML of the url provided,
15
+ * after all Javascript has been loaded, is outputted and another script
16
+ * must take care of determining where the URL of interest is found.
17
+ */
18
+
19
+ var system = require('system');
20
+ var page = require('webpage').create();
21
+
22
+ var target = system.args[1];
23
+
24
+ var externalResourceProvidedUrl = false;
25
+
26
+ page.onResourceReceived = function(resource) {
27
+ if (resource.contentType === 'application/x-zip-compressed' &&
28
+ !externalResourceProvidedUrl) {
29
+ externalResourceProvidedUrl = true;
30
+
31
+ console.log(resource.url);
32
+ }
33
+ }
34
+
35
+ page.open(target, function() {
36
+ // If this is set to true, then an external resource already provided the
37
+ // desired URL, and nothing more needs to be done -- the URL has already been
38
+ // sent to STDOUT.
39
+ //
40
+ // If done is still false, then the page itself contains the desired URL, so
41
+ // we output the HTML and let Ruby-side KMC determine the target URL.
42
+ if (!externalResourceProvidedUrl) {
43
+ console.log(page.content);
44
+ }
45
+
46
+ phantom.exit();
47
+ });
@@ -0,0 +1,32 @@
1
+ module Kmc
2
+ module PostProcessors
3
+ class ModuleManagerResolver
4
+ def self.post_process(ksp_path)
5
+ module_managers = Dir.chdir(ksp_path) do
6
+ Dir["GameData/*"].select do |file|
7
+ File.basename(file).start_with?('ModuleManager')
8
+ end
9
+ end
10
+
11
+ most_recent_manager = module_managers.max_by do |file|
12
+ # Converts a string like this:
13
+ #
14
+ # ModuleManager.5.2.3
15
+ #
16
+ # Into this:
17
+ #
18
+ # [5, 2, 3]
19
+ File.basename(file).scan(/\d+/).map(&:to_i)
20
+ end
21
+
22
+ (module_managers - [most_recent_manager]).each do |file|
23
+ file_name = File.basename(file)
24
+ Util.log "Detected and deleting outdated version of ModuleManager: " +
25
+ "#{file_name}"
26
+
27
+ File.delete(File.join(ksp_path, file))
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ module Kmc
2
+ module Refresher
3
+ class << self
4
+ # Fetch packages from online and delete the old packages, if any.
5
+ def update_packages!
6
+ new_packages_dir = File.join(fetch_packages,
7
+ 'packages-master', 'packages')
8
+
9
+ kmc_packages_path = Kmc::Configuration.packages_path
10
+ output_path = File.join(kmc_packages_path, '..')
11
+
12
+ remove_old_packages(kmc_packages_path)
13
+ FileUtils.cp_r(new_packages_dir, output_path)
14
+ end
15
+
16
+ private
17
+
18
+ def fetch_packages
19
+ zipped_packages = HTTParty.get(Kmc.config.packages_url)
20
+ tmp_zip = PackageDownloads.download_to_tempdir(
21
+ 'downloaded_packages.zip', zipped_packages)
22
+ output_path = Pathname.new(tmp_zip.path).parent.to_s
23
+
24
+ PackageDownloads.unzip_file(tmp_zip.path, output_path)
25
+
26
+ output_path
27
+ end
28
+
29
+ def remove_old_packages(kmc_packages_path)
30
+ Dir["#{kmc_packages_path}/*.rb"].each do |file|
31
+ File.delete(file)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,195 @@
1
+ class String
2
+ def undent
3
+ gsub(/^.{#{slice(/^ +/).length}}/, '')
4
+ end
5
+ end
6
+
7
+ module Kmc
8
+ module UserInterface
9
+ class << self
10
+ def init(args)
11
+ ksp_path = extract_ksp_path_from_args(args)
12
+
13
+ unless ksp_path
14
+ Util.log <<-EOS.undent
15
+ Error: You did not specify what folder you keep KSP in.
16
+
17
+ Before doing anything else, please execute the command:
18
+
19
+ kmc init ksp-folder
20
+
21
+ Where "ksp-folder" is the name of the folder where you keep KSP.
22
+ EOS
23
+
24
+ return
25
+ end
26
+
27
+ Util.log "Initializing KMC into #{ksp_path} (This will take a sec) ..."
28
+
29
+ Kmc::Versioner.init_repo(ksp_path)
30
+ Kmc::Configuration.save_ksp_path(ksp_path)
31
+
32
+ Util.log <<-EOS.undent
33
+ Done! You're ready to begin installing mods.
34
+
35
+ Install your first mod by running the command:
36
+
37
+ kmc install [name-of-the-mod]
38
+ EOS
39
+ end
40
+
41
+ def install(args)
42
+ return unless check_initialized!
43
+
44
+ ksp_path = Kmc::Configuration.load_ksp_path
45
+
46
+ packages = load_packages(args)
47
+ return unless packages
48
+
49
+ return unless check_installed_packages(ksp_path, packages)
50
+
51
+ Util.log "KMC is about to install #{packages.count} package(s):"
52
+ pretty_print_list(packages.map(&:title))
53
+
54
+ Package.install_packages!(ksp_path, packages)
55
+ end
56
+
57
+ def uninstall(args)
58
+ return unless check_initialized!
59
+
60
+ ksp_path = Kmc::Configuration.load_ksp_path
61
+
62
+ package_name = args.shift
63
+ unless package_name
64
+ Util.log <<-EOS.undent
65
+ Error: You need to specify what package to uninstall. Example:
66
+ kmc uninstall name-of-the-mod"
67
+ EOS
68
+
69
+ return
70
+ end
71
+
72
+ package = Kmc::Package.find(package_name)
73
+ installed_packages = Kmc::Versioner.installed_packages(ksp_path)
74
+
75
+ if !package
76
+ Util.log "Error: KMC couldn't find any packages with the name #{package_name.inspect}."
77
+ elsif !installed_packages.include?(package.title)
78
+ Util.log <<-EOS.undent
79
+ Error: #{package.title} is not currently installed.
80
+
81
+ There are three reasons you may get this error:
82
+
83
+ 1. You have already uninstalled #{package.title}.
84
+ 2. You have never previously installed #{package.title}.
85
+ 3. You did not use KMC to install #{package.title}.
86
+ EOS
87
+ else
88
+ Util.log "Preparing to uninstall #{package.title} ..."
89
+ Kmc::Versioner.uninstall_package(ksp_path, package)
90
+ Util.log "Done! Just uninstalled: #{package.title}."
91
+ end
92
+ end
93
+
94
+ def list(args)
95
+ return unless check_initialized!
96
+
97
+ ksp_path = Kmc::Configuration.load_ksp_path
98
+
99
+ packages = Kmc::Versioner.installed_packages(ksp_path)
100
+ Util.log "You have installed #{packages.length} mod(s) using KMC:"
101
+ pretty_print_list(packages.map(&:title))
102
+ end
103
+
104
+ def refresh(args)
105
+ Util.log "Getting the most up-to-date packages for KMC ..."
106
+ Kmc::Refresher.update_packages!
107
+ Util.log "Done. The KMC packages you have are all up-to-date."
108
+ end
109
+
110
+ def server(args)
111
+ Web::App.start!
112
+ end
113
+
114
+ private
115
+
116
+ # Extracts the path to init to from the user-supplied arguments. If the
117
+ # user passed arguments via the commandline, then spaces in paths are
118
+ # auto-handled beacuse they come from ARGV.
119
+ #
120
+ # If, however, the user passed arguments from the server, then these
121
+ # arguments will still be separated by spaces. This method will then re-
122
+ # join them.
123
+ def extract_ksp_path_from_args(args)
124
+ if args
125
+ path = args.join(' ')
126
+
127
+ # Trim leading quotation mark if any
128
+ path = path[1..-1] if path.start_with?('"')
129
+ path = path[0..-2] if path.end_with?('"')
130
+
131
+ if path.empty?
132
+ nil
133
+ else
134
+ path
135
+ end
136
+ end
137
+ end
138
+
139
+ def check_initialized!
140
+ if Kmc::Configuration.load_ksp_path
141
+ true
142
+ else
143
+ Util.log <<-EOS.undent
144
+ Error: You have not yet initialized KMC.
145
+
146
+ Before doing anything else, please execute the command:
147
+
148
+ kmc init ksp-folder
149
+
150
+ Where "ksp-folder" is the name of the folder where you keep KSP.
151
+ EOS
152
+ end
153
+ end
154
+
155
+ def pretty_print_list(list)
156
+ list.each { |value| Util.log " * #{value}" }
157
+ end
158
+
159
+ def load_packages(package_names)
160
+ packages = Hash[package_names.map do |name|
161
+ [name, Kmc::Package.find(name)]
162
+ end]
163
+
164
+ unknown_packages = packages.select { |_, package| package.nil? }
165
+ package_suggestions = unknown_packages.map do |name, _|
166
+ best_guess = Kmc::Package.search(name).normalized_title
167
+ "#{name} (Maybe you meant: #{best_guess.inspect}?)"
168
+ end
169
+
170
+ if unknown_packages.any?
171
+ Util.log "Error: KMC couldn't find any packages with the following names:"
172
+ pretty_print_list(package_suggestions)
173
+
174
+ return false
175
+ end
176
+
177
+ packages.values
178
+ end
179
+
180
+ def check_installed_packages(ksp_path, new_packages)
181
+ installed_packages = Kmc::Versioner.installed_packages(ksp_path)
182
+ already_installed = new_packages & installed_packages
183
+
184
+ if already_installed.any?
185
+ Util.log "Error: You have already installed the following packages using KMC:"
186
+ pretty_print_list(already_installed.map(&:title))
187
+
188
+ false
189
+ else
190
+ true
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,11 @@
1
+ module Kmc
2
+ module Util
3
+ def self.log(msg)
4
+ Kmc.config.output_method.call(msg) if Kmc.config.verbose
5
+ end
6
+
7
+ def self.run_post_processors!(ksp_path)
8
+ Kmc.config.post_processors.each { |p| p.post_process(ksp_path) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Kmc
2
+ VERSION = "0.0.6"
3
+ end
@@ -0,0 +1,70 @@
1
+ module Kmc
2
+ class Versioner
3
+ class << self
4
+ INIT_COMMIT_MESSAGE = "INIT: Initialize KMC"
5
+
6
+ def init_repo(path)
7
+ GitAdapter.init_repo(path)
8
+ GitAdapter.commit_everything(path, INIT_COMMIT_MESSAGE)
9
+ end
10
+
11
+ def mark_preinstall(path, package)
12
+ GitAdapter.commit_everything(path, pre_install_message(package))
13
+ end
14
+
15
+ def mark_postinstall(path, package)
16
+ GitAdapter.commit_everything(path, post_install_message(package))
17
+ end
18
+
19
+ def installed_packages(path)
20
+ commits = GitAdapter.list_commits(path)
21
+
22
+ postinstalls = commits.select(&:post?).map(&:subject)
23
+ uninstalls = commits.select(&:uninstall?).map(&:subject)
24
+
25
+ # The packages that have actually been installed are those that have a
26
+ # 'post-install' without any corresponding 'uninstall'. We can find
27
+ # these packages by getting post-installs, and removing the first
28
+ # instance of each package also found in uninstall.
29
+ #
30
+ # Note that we can't use Array#delete because that method will delete
31
+ # *all* instances of a package, which won't work because it's possible
32
+ # for a user to install, uninstall, then re-install a package.
33
+ uninstalls.each do |package|
34
+ postinstalls.delete_at(postinstalls.index(package))
35
+ end
36
+
37
+ postinstalls.map { |package_title| Package.find(package_title) }
38
+ end
39
+
40
+ # Has this package already been installed?
41
+ def already_installed?(path, package)
42
+ installed_packages(path).include?(package)
43
+ end
44
+
45
+ def uninstall_package(path, package)
46
+ to_revert = GitAdapter.list_commits(path).find do |commit|
47
+ commit.post? && commit.subject == package.title
48
+ end
49
+
50
+ GitAdapter.revert_commit(path, to_revert)
51
+ Util.run_post_processors!(path)
52
+ GitAdapter.commit_everything(path, uninstall_message(package))
53
+ end
54
+
55
+ private
56
+
57
+ def pre_install_message(package)
58
+ "PRE: #{package.title}"
59
+ end
60
+
61
+ def post_install_message(package)
62
+ "POST: #{package.title}"
63
+ end
64
+
65
+ def uninstall_message(package)
66
+ "UNINSTALL: #{package.title}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,42 @@
1
+ require 'sinatra'
2
+
3
+ module Kmc
4
+ module Web
5
+ class App < Sinatra::Application
6
+ set server: 'thin', connection: nil
7
+
8
+ get '/' do
9
+ send_file(File.join(settings.public_folder, 'index.html'))
10
+ end
11
+
12
+ get '/stream', provides: 'text/event-stream' do
13
+ stream :keep_open do |out|
14
+ settings.connection = out
15
+ end
16
+ end
17
+
18
+ post '/' do
19
+ Kmc.configure do |config|
20
+ config.output_method = Proc.new do |str|
21
+ # Send to STDOUT
22
+ puts str
23
+
24
+ # And to the in-browser UI
25
+ str.split("\n").each do |line|
26
+ settings.connection << "data: #{line}\n\n"
27
+ end
28
+ end
29
+ end
30
+
31
+ kmc_params = params[:params].split(' ')
32
+ kmc_command = %w(init install uninstall list).find do |command|
33
+ command == params[:command]
34
+ end
35
+
36
+ UserInterface.send(kmc_command, kmc_params)
37
+
38
+ 204
39
+ end
40
+ end
41
+ end
42
+ end