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,71 @@
1
+ module Kmc
2
+ module GitAdapter
3
+ class << self
4
+ def init_repo(path)
5
+ Dir.chdir(path) do
6
+ `git init`
7
+ `git config user.name KMC`
8
+ `git config user.email kmc@kmc.kmc`
9
+ `git config core.autocrlf false`
10
+
11
+ File.open('.gitignore', 'w') do |file|
12
+ file.write "!*\n"
13
+ end
14
+ end
15
+ end
16
+
17
+ def commit_everything(repo_path, commit_message)
18
+ Dir.chdir(repo_path) do
19
+ `git add -A -f`
20
+ `git commit --allow-empty -m "#{commit_message}"`
21
+ end
22
+ end
23
+
24
+ def revert_commit(repo_path, commit)
25
+ Dir.chdir(repo_path) do
26
+ # Favor "ours" (which is always HEAD for our purposes) when git-revert
27
+ # can handle that on its own.
28
+ `git revert --no-commit --strategy=merge --strategy-option=ours #{commit.sha}`
29
+
30
+ # When files are being created or deleted, git will not do anything.
31
+ # In this case, keep all files alive; better to accidentally pollute
32
+ # than accidentally delete something important.
33
+ `git add *`
34
+ end
35
+ end
36
+
37
+ def list_commits(repo_path)
38
+ Dir.chdir(repo_path) do
39
+ `git log --oneline`.lines.map do |line|
40
+ sha, message = line.split(' ', 2)
41
+ Commit.new(message, sha)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ class Commit < Struct.new(:message, :sha)
48
+ def pre?
49
+ type == :pre
50
+ end
51
+
52
+ def post?
53
+ type == :post
54
+ end
55
+
56
+ def uninstall?
57
+ type == :uninstall
58
+ end
59
+
60
+ def type
61
+ # "POST: Example" --> :post
62
+ message.split(':').first.downcase.to_sym
63
+ end
64
+
65
+ def subject
66
+ # "POST: Example\n" --> "Example"
67
+ message.split(' ', 2).last.strip
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,142 @@
1
+ require 'pathname'
2
+ require 'httparty'
3
+ require 'zip'
4
+ require 'tmpdir'
5
+ require 'damerau-levenshtein'
6
+
7
+ require_relative 'package_utils'
8
+
9
+ module Kmc
10
+ class Package
11
+ include PackageDsl
12
+
13
+ class << self
14
+ include PackageAttrs, PackageUtils
15
+
16
+ attr_reader :ksp_path, :download_dir, :do_not_unzip
17
+
18
+ # Installs a list of packages, and outputs caveats when it's done.
19
+ def install_packages!(ksp_path, packages)
20
+ caveats = {}
21
+ packages.each { |package| package.install!(ksp_path, caveats) }
22
+ log_caveats(caveats)
23
+ end
24
+
25
+ # Internal version of the `install` method. Handles:
26
+ # * Pre- and post-requisites
27
+ # * Version control
28
+ # * Post-processors
29
+ # * Calling a package's #install method
30
+ # * Building up caveats
31
+ #
32
+ # The caveats argument is expected to be a hash going from Packages to
33
+ # caveat messages and will be modified in-place.
34
+ def install!(ksp_path, caveats)
35
+ return if Versioner.already_installed?(ksp_path, self)
36
+
37
+ Util.log "Installing package #{self.title}"
38
+ prepare_for_install(ksp_path, caveats)
39
+
40
+ Util.log "Saving your work before installing ..."
41
+ Versioner.mark_preinstall(ksp_path, self)
42
+
43
+ Util.log "Installing #{title} ..."
44
+ self.new.install
45
+
46
+ Util.log "Cleaning up ..."
47
+ Util.run_post_processors!(ksp_path)
48
+
49
+ Versioner.mark_postinstall(ksp_path, self)
50
+
51
+ Util.log "Done!"
52
+
53
+ install_postrequisites!(caveats)
54
+ end
55
+
56
+ def download_and_unzip!
57
+ PackageDownloads.download_and_unzip_package(self)
58
+ end
59
+
60
+ def download!
61
+ PackageDownloads.download_package(self)
62
+ end
63
+
64
+ def do_not_unzip!
65
+ @do_not_unzip = true
66
+ end
67
+
68
+ # a callback for when a subclass of this class is created
69
+ def inherited(package)
70
+ (@@packages ||= []) << package
71
+ end
72
+
73
+ def packages
74
+ @@packages
75
+ end
76
+
77
+ def load_packages!
78
+ Dir[File.join(Configuration.packages_path, '*.rb')].each do |file|
79
+ require file
80
+ end
81
+ end
82
+
83
+
84
+ private
85
+
86
+ # Run steps that take place before installation.
87
+ def prepare_for_install(ksp_path, caveats)
88
+ add_caveat_message!(caveats)
89
+ @ksp_path = ksp_path
90
+ install_prerequisites!(caveats)
91
+ @download_dir = download_and_unzip!
92
+ end
93
+
94
+ def add_caveat_message!(caveats)
95
+ if method_defined?(:caveats)
96
+ caveats.merge!(self => self.new.caveats)
97
+ end
98
+ end
99
+
100
+ def install_prerequisites!(caveats)
101
+ resolve_prerequisites.each do |package|
102
+ unless Versioner.already_installed?(ksp_path, package)
103
+ Util.log "#{title} has prerequisite #{package.title}."
104
+ package.install!(ksp_path, caveats)
105
+ end
106
+ end
107
+ end
108
+
109
+ def install_postrequisites!(caveats)
110
+ resolve_postrequisites.each do |package|
111
+ unless Versioner.already_installed?(ksp_path, package)
112
+ Util.log "#{title} has postrequisite #{package.title}."
113
+ package.install!(ksp_path, caveats)
114
+ end
115
+ end
116
+ end
117
+
118
+ def log_caveats(caveats)
119
+ if caveats.any?
120
+ Util.log "===> Caveats"
121
+
122
+ caveats.each do |package, message|
123
+ Util.log <<-EOS.undent
124
+ Caveat from #{package.title}:
125
+ #{message}
126
+ EOS
127
+ end
128
+ end
129
+ end
130
+
131
+ def load_post_processors!
132
+ processors_path = File.join(File.dirname(__FILE__), 'post_processors')
133
+ Dir["#{processors_path}/*.rb"].each do |file|
134
+ require file
135
+ end
136
+ end
137
+ end
138
+
139
+ load_packages!
140
+ load_post_processors!
141
+ end
142
+ end
@@ -0,0 +1,69 @@
1
+ module Kmc
2
+ module PackageAttrs
3
+ def title(title = nil)
4
+ if title
5
+ @title = title
6
+ else
7
+ @title
8
+ end
9
+ end
10
+
11
+ def url(url = nil)
12
+ if url
13
+ @url = url
14
+ else
15
+ @url
16
+ end
17
+ end
18
+
19
+ def aliases(*aliases)
20
+ @aliases ||= []
21
+
22
+ if aliases.any?
23
+ @aliases = aliases
24
+ else
25
+ @aliases
26
+ end
27
+ end
28
+
29
+ def names
30
+ [title, aliases].flatten
31
+ end
32
+
33
+ def prerequisites(*prerequisites)
34
+ @prerequisites ||= []
35
+
36
+ if prerequisites.any?
37
+ @prerequisites = prerequisites
38
+ else
39
+ @prerequisites
40
+ end
41
+ end
42
+
43
+ alias_method :prerequisite, :prerequisites
44
+ alias_method :pre_requisite, :prerequisites
45
+ alias_method :pre_requisites, :prerequisites
46
+
47
+ def postrequisites(*postrequisites)
48
+ @postrequisites ||= []
49
+
50
+ if postrequisites.any?
51
+ @postrequisites = postrequisites
52
+ else
53
+ @postrequisites
54
+ end
55
+ end
56
+
57
+ alias_method :postrequisite, :postrequisites
58
+ alias_method :post_requisite, :postrequisites
59
+ alias_method :post_requisites, :postrequisites
60
+
61
+ def resolve_prerequisites
62
+ prerequisites.map { |package_name| find(package_name) }
63
+ end
64
+
65
+ def resolve_postrequisites
66
+ postrequisites.map { |package_name| find(package_name) }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,122 @@
1
+ module Kmc
2
+ module PackageDownloads
3
+ class << self
4
+ # Downloads and unzips a package. This will call #download! on its own, and
5
+ # will return the location where the package was downloaded to as a
6
+ # Pathname.
7
+ def download_and_unzip_package(package, opts = {})
8
+ download_file = download_package(package, opts)
9
+ output_path = Pathname.new(download_file.path).parent.to_s
10
+
11
+ return output_path if package.do_not_unzip
12
+
13
+ Util.log "Unzipping ..."
14
+ unzip_file(download_file.path, output_path)
15
+
16
+ File.delete(File.absolute_path(download_file))
17
+
18
+ output_path
19
+ end
20
+
21
+ # Downloads the zipfile for a package using its URL, unless a cached version
22
+ # is found first. Uses DownloadUrl to intelligently resolve download URLs.
23
+ #
24
+ # Returns the file downloaded, which is created in a temp directory.
25
+ def download_package(package, opts = {})
26
+ download_uri, downloaded_file = fetch_package_file(package)
27
+
28
+ save_to_cache(package, downloaded_file) if opts[:cache_after_download]
29
+
30
+ download_to_tempdir(download_file_name(download_uri), downloaded_file)
31
+ end
32
+
33
+ def unzip_file(zipfile_path, output_path)
34
+ Zip::File.open(zipfile_path) do |zip_file|
35
+ zip_file.each do |entry|
36
+ destination = File.join(output_path, entry.name)
37
+ parent_dir = File.expand_path('..', destination)
38
+
39
+ FileUtils.mkdir_p(parent_dir) unless File.exists?(parent_dir)
40
+
41
+ entry.extract(destination)
42
+ end
43
+ end
44
+ end
45
+
46
+ # Places `file_contents` into a file called `file_name` in a temporary
47
+ # directory.
48
+ def download_to_tempdir(file_name, file_contents)
49
+ tmpdir = Dir.mktmpdir
50
+
51
+ file = File.new(File.join(tmpdir, file_name), 'wb+')
52
+ file.write(file_contents)
53
+ file.close
54
+
55
+ file
56
+ end
57
+
58
+ private
59
+
60
+ # Given a package, returns a two-entry array.
61
+ #
62
+ # The first entry is a String uri pointing to where the file was fetched
63
+ # from (either the filesystem or a website), and the second entry is the
64
+ # contents of the download.
65
+ def fetch_package_file(package)
66
+ cache = cache_file(package)
67
+ if cache
68
+ Util.log "Using a cached version of #{package.title} ..."
69
+
70
+ [cache, File.read(cache)]
71
+ else
72
+ Util.log "The package is found at #{package.url}."
73
+ Util.log "Finding the download URL ..."
74
+
75
+ download_uri = resolve_download_url(package)
76
+
77
+ Util.log "Found it. Downloading from #{download_uri} ..."
78
+ [download_uri, HTTParty.get(download_uri)]
79
+ end
80
+ end
81
+
82
+ # Returns the location of the cached version of a package, or some falsy
83
+ # value if no such file exists.
84
+ def cache_file(package)
85
+ if Kmc::Configuration.cache_dir
86
+ cache_file = File.join(Kmc::Configuration.cache_dir,
87
+ "#{package.title}.zip")
88
+
89
+ File.file?(cache_file) && cache_file
90
+ end
91
+ end
92
+
93
+ def resolve_download_url(package)
94
+ DownloadUrl.new(package.url).resolve_download_url
95
+ end
96
+
97
+ # Writes a downloaded package to the cache.
98
+ #
99
+ # This method assumes `Kmc.cache_dir` already exists.
100
+ def save_to_cache(package, downloaded_file)
101
+ Util.log "Saving #{package.title} to cache ..."
102
+
103
+ cache_location = File.join(Kmc.cache_dir, "#{package.title}.zip")
104
+ File.open(cache_location, 'wb+') do |file|
105
+ file.write(downloaded_file)
106
+ end
107
+ end
108
+
109
+ VALID_CHARS = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
110
+ "0123456789-._~:/?#[]@!$&'()*+,;=").chars
111
+
112
+ # Converts a string URI into the file name a downloaded file should be
113
+ # placed into.
114
+ def download_file_name(uri)
115
+ # Remove invalid characters to ensure a valid URI.
116
+ clean_uri = uri.chars.select { |char| VALID_CHARS.include?(char) }.join
117
+
118
+ File.basename(URI(clean_uri).path)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,13 @@
1
+ module Kmc
2
+ module PackageDsl
3
+ def merge_directory(from, opts = {})
4
+ destination = opts[:into] || '.'
5
+
6
+ FileUtils.cp_r(File.join(self.class.download_dir, from),
7
+ File.join(self.class.ksp_path, destination))
8
+ end
9
+ def remove_filepath(filepath)
10
+ FileUtils.rm_r(File.join(self.class.ksp_path, filepath))
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module Kmc
2
+ module PackageUtils
3
+ # Lowercases and hyphenates a package name; this is the format packages
4
+ # are expected to be supplied as when passed from the user.
5
+ def normalize_for_find(name)
6
+ name.downcase.gsub(/[ \-]+/, "-")
7
+ end
8
+
9
+ def normalized_title
10
+ normalize_for_find(title)
11
+ end
12
+
13
+ def find(name)
14
+ packages.find do |package|
15
+ package.names.any? do |candidate_name|
16
+ normalize_for_find(candidate_name) == normalize_for_find(name)
17
+ end
18
+ end
19
+ end
20
+
21
+ def search(name)
22
+ packages.min_by do |package|
23
+ package.names.map do |candidate_name|
24
+ DamerauLevenshtein.distance(name, candidate_name)
25
+ end.min
26
+ end
27
+ end
28
+ end
29
+ end