kmc 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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