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,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