jax 1.0.1 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. data/.gitmodules +3 -0
  2. data/.travis.yml +32 -0
  3. data/CHANGELOG +126 -0
  4. data/README.md +1 -1
  5. data/Rakefile +125 -19
  6. data/bin/jax +1 -1
  7. data/builtin/{shaders → app/shaders}/basic/common.ejs +0 -0
  8. data/builtin/{shaders → app/shaders}/basic/fragment.ejs +0 -0
  9. data/builtin/{shaders → app/shaders}/basic/vertex.ejs +0 -0
  10. data/builtin/{shaders → app/shaders}/depthmap/common.ejs +0 -0
  11. data/builtin/{shaders → app/shaders}/depthmap/fragment.ejs +0 -0
  12. data/builtin/{shaders → app/shaders}/depthmap/material.js +0 -0
  13. data/builtin/{shaders → app/shaders}/depthmap/vertex.ejs +0 -0
  14. data/builtin/{shaders → app/shaders}/fog/common.ejs +0 -0
  15. data/builtin/{shaders → app/shaders}/fog/fragment.ejs +0 -0
  16. data/builtin/{shaders → app/shaders}/fog/manifest.yml +0 -0
  17. data/builtin/{shaders → app/shaders}/fog/material.js +0 -0
  18. data/builtin/{shaders → app/shaders}/fog/vertex.ejs +0 -0
  19. data/builtin/{shaders → app/shaders}/functions/depth_map.ejs +0 -0
  20. data/builtin/{shaders → app/shaders}/functions/lights.ejs +0 -0
  21. data/builtin/{shaders → app/shaders}/functions/noise.ejs +0 -0
  22. data/builtin/{shaders → app/shaders}/lighting/common.ejs +0 -0
  23. data/builtin/{shaders → app/shaders}/lighting/fragment.ejs +0 -0
  24. data/builtin/{shaders → app/shaders}/lighting/manifest.yml +0 -0
  25. data/builtin/{shaders → app/shaders}/lighting/material.js +0 -0
  26. data/builtin/{shaders → app/shaders}/lighting/vertex.ejs +0 -0
  27. data/builtin/{shaders → app/shaders}/normal_map/common.ejs +0 -0
  28. data/builtin/{shaders → app/shaders}/normal_map/fragment.ejs +0 -0
  29. data/builtin/{shaders → app/shaders}/normal_map/manifest.yml +0 -0
  30. data/builtin/{shaders → app/shaders}/normal_map/material.js +0 -0
  31. data/builtin/{shaders → app/shaders}/normal_map/vertex.ejs +0 -0
  32. data/builtin/{shaders → app/shaders}/paraboloid/common.ejs +0 -0
  33. data/builtin/{shaders → app/shaders}/paraboloid/fragment.ejs +0 -0
  34. data/builtin/{shaders → app/shaders}/paraboloid/manifest.yml +0 -0
  35. data/builtin/{shaders → app/shaders}/paraboloid/material.js +0 -0
  36. data/builtin/{shaders → app/shaders}/paraboloid/vertex.ejs +0 -0
  37. data/builtin/{shaders → app/shaders}/picking/common.ejs +0 -0
  38. data/builtin/{shaders → app/shaders}/picking/fragment.ejs +0 -0
  39. data/builtin/{shaders → app/shaders}/picking/material.js +0 -0
  40. data/builtin/{shaders → app/shaders}/picking/vertex.ejs +0 -0
  41. data/builtin/{shaders → app/shaders}/shadow_map/common.ejs +0 -0
  42. data/builtin/{shaders → app/shaders}/shadow_map/fragment.ejs +0 -0
  43. data/builtin/{shaders → app/shaders}/shadow_map/manifest.yml +0 -0
  44. data/builtin/{shaders → app/shaders}/shadow_map/material.js +0 -0
  45. data/builtin/{shaders → app/shaders}/shadow_map/vertex.ejs +0 -0
  46. data/builtin/{shaders → app/shaders}/texture/common.ejs +0 -0
  47. data/builtin/{shaders → app/shaders}/texture/fragment.ejs +0 -0
  48. data/builtin/{shaders → app/shaders}/texture/manifest.yml +0 -0
  49. data/builtin/{shaders → app/shaders}/texture/material.js +0 -0
  50. data/builtin/{shaders → app/shaders}/texture/vertex.ejs +0 -0
  51. data/jax.gems +1 -1
  52. data/jax.gemspec +9 -7
  53. data/lib/jax.rb +23 -9
  54. data/lib/jax/application.rb +118 -60
  55. data/lib/jax/application/builtin.rb +12 -0
  56. data/lib/jax/application/builtin/configurable.rb +5 -0
  57. data/lib/jax/application/builtin/configuration.rb +5 -0
  58. data/lib/jax/application/configurable.rb +19 -0
  59. data/lib/jax/application/configuration.rb +46 -13
  60. data/lib/jax/application/railties.rb +26 -0
  61. data/lib/jax/core_ext/kernel.rb +7 -0
  62. data/lib/jax/engine.rb +64 -0
  63. data/lib/jax/engine/configurable.rb +19 -0
  64. data/lib/jax/engine/configuration.rb +49 -0
  65. data/lib/jax/generators/app.rb +3 -2
  66. data/lib/jax/generators/app/app_generator.rb +9 -2
  67. data/lib/jax/generators/app/templates/config/environment.rb.tt +5 -0
  68. data/lib/jax/generators/app/templates/public/index.html.tt +26 -0
  69. data/lib/jax/generators/app/templates/public/javascripts/jax.js +8726 -1
  70. data/lib/jax/generators/app/templates/public/stylesheets/%file_name%.css.tt +11 -0
  71. data/lib/jax/generators/app/templates/public/webgl_not_supported.html +1 -1
  72. data/lib/jax/generators/app/templates/spec/javascripts/support/jasmine.yml +1 -0
  73. data/lib/jax/generators/app/templates/spec/javascripts/support/spec_helpers/jax_spec_environment_helper.js +13 -5
  74. data/lib/jax/generators/app/templates/spec/javascripts/support/spec_helpers/jax_spec_helper.js +6 -10
  75. data/lib/jax/generators/commands.rb +168 -42
  76. data/lib/jax/generators/controller/controller_generator.rb +9 -2
  77. data/lib/jax/generators/interactions.rb +56 -0
  78. data/lib/jax/generators/light_source/light_source_generator.rb +1 -2
  79. data/lib/jax/generators/material/material_generator.rb +1 -2
  80. data/lib/jax/generators/model/model_generator.rb +1 -3
  81. data/lib/jax/generators/packager/package_generator.rb +32 -0
  82. data/lib/jax/generators/plugin/USAGE +4 -0
  83. data/lib/jax/generators/plugin/all.rb +113 -0
  84. data/lib/jax/generators/plugin/credentials.rb +108 -0
  85. data/lib/jax/generators/plugin/plugin_generator.rb +72 -0
  86. data/lib/jax/generators/plugin/plugin_manager.rb +254 -0
  87. data/lib/jax/generators/plugin/templates/new_plugin/app/controllers/.empty_directory +0 -0
  88. data/lib/jax/generators/plugin/templates/new_plugin/app/helpers/.empty_directory +0 -0
  89. data/lib/jax/generators/plugin/templates/new_plugin/app/models/.empty_directory +0 -0
  90. data/lib/jax/generators/plugin/templates/new_plugin/app/resources/.empty_directory +0 -0
  91. data/lib/jax/generators/plugin/templates/new_plugin/app/views/.empty_directory +0 -0
  92. data/lib/jax/generators/plugin/templates/new_plugin/config/routes.rb +3 -0
  93. data/lib/jax/generators/plugin/templates/new_plugin/init.rb +1 -0
  94. data/lib/jax/generators/plugin/templates/new_plugin/install.rb +2 -0
  95. data/lib/jax/generators/plugin/templates/new_plugin/public/.empty_directory +0 -0
  96. data/lib/jax/generators/plugin/templates/new_plugin/spec/.empty_directory +0 -0
  97. data/lib/jax/generators/plugin/templates/new_plugin/uninstall.rb +2 -0
  98. data/lib/jax/generators/script_jax_loader.rb +17 -0
  99. data/lib/jax/generators/shader/shader_generator.rb +2 -3
  100. data/lib/jax/monkeypatch/jasmine/config.rb +25 -1
  101. data/lib/jax/monkeypatch/jasmine/server.rb +1 -1
  102. data/lib/jax/packager.rb +12 -11
  103. data/lib/jax/packager/sprockets_template.rb +15 -6
  104. data/lib/jax/plugin.rb +49 -0
  105. data/lib/jax/plugin/manifest.rb +71 -0
  106. data/lib/jax/resource_compiler.rb +24 -14
  107. data/lib/jax/routes.rb +1 -0
  108. data/lib/jax/shader.rb +16 -1
  109. data/lib/jax/tasks/rake.rb +1 -1
  110. data/lib/jax/version.rb +3 -3
  111. data/spec/benchmark.htm +93 -0
  112. data/spec/fixtures/web/plugins/404.http +39 -0
  113. data/spec/fixtures/web/plugins/all.xml +106 -0
  114. data/spec/fixtures/web/plugins/author/create_account.xml.http +20 -0
  115. data/spec/fixtures/web/plugins/author/create_new_plugin.xml.http +27 -0
  116. data/spec/fixtures/web/plugins/author/login_existing_account.xml.http +19 -0
  117. data/spec/fixtures/web/plugins/author/login_not_found.xml.http +14 -0
  118. data/spec/fixtures/web/plugins/author/login_password_invalid.xml.http +12 -0
  119. data/spec/fixtures/web/plugins/clouds.xml +51 -0
  120. data/spec/fixtures/web/plugins/example.tgz.http +0 -0
  121. data/spec/fixtures/web/plugins/example.tgz.http.old +0 -0
  122. data/spec/fixtures/web/plugins/none.http +13 -0
  123. data/spec/fixtures/web/plugins/vert.xml +68 -0
  124. data/spec/fixtures/web/plugins/vertex-blob.xml +37 -0
  125. data/spec/fixtures/web/plugins/vertex-height-map.xml +44 -0
  126. data/spec/generators/app_generator_test.rb +42 -0
  127. data/spec/generators/controller_generator_test.rb +47 -0
  128. data/spec/generators/light_generator_test.rb +37 -0
  129. data/spec/generators/material_generator_test.rb +22 -0
  130. data/spec/generators/model_generator_test.rb +26 -0
  131. data/spec/generators/plugin_generator_test.rb +114 -0
  132. data/spec/generators/plugin_manager/push_test.rb +59 -0
  133. data/spec/generators/plugin_manager_test.rb +192 -0
  134. data/spec/generators/shader_generator_test.rb +38 -0
  135. data/spec/lib/jax/application_test.rb +18 -0
  136. data/spec/lib/jax/generators/plugin/credentials_test.rb +72 -0
  137. data/spec/lib/jax/packager_test.rb +87 -0
  138. data/spec/lib/jax/plugin_test.rb +27 -0
  139. data/spec/lib/jax/reloading_test.rb +23 -0
  140. data/spec/lib/jax/routes_test.rb +28 -0
  141. data/spec/lib/jax/shader_test.rb +29 -0
  142. data/spec/lib/jax/tasks/jax_rake_test.rb +85 -0
  143. data/spec/support/bases/generator_test_case.rb +108 -0
  144. data/spec/support/bases/isolated_test_case.rb +148 -0
  145. data/spec/support/fixtures_helper.rb +21 -0
  146. data/spec/support/spec_shell.rb +14 -1
  147. data/spec/test_app.rb +3 -0
  148. data/spec/test_helper.rb +55 -0
  149. metadata +200 -92
  150. data/spec/generators/app_generator_spec.rb +0 -47
  151. data/spec/generators/controller_generator_spec.rb +0 -68
  152. data/spec/generators/light_generator_spec.rb +0 -51
  153. data/spec/generators/material_generator_spec.rb +0 -35
  154. data/spec/generators/model_generator_spec.rb +0 -43
  155. data/spec/lib/jax/routes_spec.rb +0 -24
  156. data/spec/lib/jax/shader_spec.rb +0 -57
  157. data/spec/lib/jax/tasks/jax_rake_spec.rb +0 -92
  158. data/spec/spec_helper.rb +0 -11
@@ -3,8 +3,7 @@ require 'active_support/core_ext'
3
3
  module Jax
4
4
  module Generators
5
5
  module LightSource
6
- class LightSourceGenerator < Jax::Generators::Command
7
- include Thor::Actions
6
+ class LightSourceGenerator < Jax::Generators::PluggableCommand
8
7
  argument :name, :desc => "The name of this light source", :banner => "[name]"
9
8
  argument :type, :default => "point", :desc => "The light type: one of 'point', 'spot', 'directional'",
10
9
  :banner => "[point|spot|directional]"
@@ -3,8 +3,7 @@ require 'active_support/core_ext'
3
3
  module Jax
4
4
  module Generators
5
5
  module Material
6
- class MaterialGenerator < Jax::Generators::Command
7
- include Thor::Actions
6
+ class MaterialGenerator < Jax::Generators::PluggableCommand
8
7
  argument :name, :desc => "The name of this material", :banner => "[name]"
9
8
  class_option :append, :default => false, :type => :boolean
10
9
 
@@ -3,8 +3,7 @@ require 'active_support/core_ext'
3
3
  module Jax
4
4
  module Generators
5
5
  module Model
6
- class ModelGenerator < Jax::Generators::Command
7
- include Thor::Actions
6
+ class ModelGenerator < Jax::Generators::PluggableCommand
8
7
  argument :model_name
9
8
 
10
9
  def self.source_root
@@ -20,7 +19,6 @@ module Jax
20
19
  end
21
20
 
22
21
  def resources
23
- # empty_directory File.join("app", "resources", plural_name)
24
22
  create_file File.join("app", "resources", plural_name, "default.yml"), "# default attribute values\n# (these will apply to all #{plural_name})"
25
23
  end
26
24
 
@@ -0,0 +1,32 @@
1
+ require 'active_support/core_ext'
2
+
3
+ module Jax
4
+ module Generators
5
+ module Packager
6
+ class PackageGenerator < Thor::Group
7
+ include Thor::Actions
8
+
9
+ def self.source_root
10
+ File.expand_path("templates", File.dirname(__FILE__))
11
+ end
12
+
13
+ def build_package
14
+ pkg_dir = Jax.root.join("pkg")
15
+ remove_dir pkg_dir, :verbose => false
16
+
17
+ package = Jax::Packager.new pkg_dir
18
+ say "Packaging according to the following template:"
19
+ say ""
20
+ package.project.template.each { |line| say line }
21
+
22
+ package.build!
23
+
24
+ say
25
+ say_status :done, "Build complete! Package is available at: ", :green
26
+ say_status "", " #{package.pkg_path}"
27
+ say
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ Usage:
2
+ jax generate plugin NAME
3
+
4
+ Generates a new plugin with the specified name.
@@ -0,0 +1,113 @@
1
+ module Jax
2
+ module Generators
3
+ module Plugin
4
+ class ResponseError < StandardError
5
+ def initialize(message)
6
+ super("#{message} Make sure your firewall isn't injecting invalid responses.")
7
+ end
8
+ end
9
+
10
+ def rest_resource(name, accept = :xml)
11
+ url = Jax.plugin_repository_url
12
+ url.concat "/" unless url =~ /\/$/
13
+ url.concat name
14
+ RestClient::Resource.new(url, :accept => accept)
15
+ end
16
+
17
+ def search_query_rx(query)
18
+ /^#{Regexp::escape query}/i
19
+ end
20
+
21
+ def search(plugin_list, query)
22
+ plugin_list.select { |plugin, path_to_plugin|
23
+ !query || plugin =~ search_query_rx(query)
24
+ }.inject({}) do |hash, (plugin, path_to_plugin)|
25
+ hash[plugin] = path_to_plugin
26
+ hash
27
+ end
28
+ end
29
+
30
+ def installed_plugins
31
+ plugins = []
32
+ Dir.glob(Jax.root.join("vendor/plugins/*").to_s).each do |path|
33
+ if File.directory? path
34
+ plugins.push [File.basename(path), Pathname.new(path)]
35
+ end
36
+ end
37
+ plugins.sort { |a, b| a[0] <=> b[0] }
38
+ end
39
+
40
+ def load_or_infer_manifest(name, plugin_dir)
41
+ if File.file?(manifest_path = File.join(plugin_dir, "manifest.yml"))
42
+ YAML::load(File.read(manifest_path)) || { 'name' => name, 'description' => '(Description unavailable)' }
43
+ else
44
+ { 'name' => name, 'description' => '(Manifest file not found!)' }
45
+ end
46
+ end
47
+
48
+ def installed_plugin_manifests(filter_name = nil)
49
+ { 'jax_plugins' => search(installed_plugins, filter_name).collect do |name, path|
50
+ load_or_infer_manifest(name, path)
51
+ end
52
+ }
53
+ end
54
+
55
+ def matching_plugins(name = nil)
56
+ if options[:local]
57
+ hash = installed_plugin_manifests(name)
58
+ else
59
+ hash = get_remote_plugins_matching name
60
+ end
61
+
62
+ find_plugin_list(hash)
63
+ end
64
+
65
+ def find_plugin_list(hash_containing_plugin_list)
66
+ hash_containing_plugin_list['jax_plugins'] ||
67
+ hash_containing_plugin_list['nil_classes'] ||
68
+ raise(ResponseError.new("Fatal: couldn't find plugin list."))
69
+ end
70
+
71
+ def get_remote_plugins_matching(name = nil)
72
+ plugins = rest_resource("plugins")
73
+ if name
74
+ extract_hash_from_response plugins['search'][name].get
75
+ else
76
+ extract_hash_from_response plugins.get
77
+ end
78
+ end
79
+
80
+ def each_plugin(name = nil, &block)
81
+ matching_plugins(name).each &block
82
+ end
83
+
84
+ def plugin_version(details)
85
+ if options['version']
86
+ for release in details['releases']
87
+ if release['version'] && release['version'] == options['version']
88
+ return options['version']
89
+ end
90
+ end
91
+ raise "Release information for version #{options['version']} not found for plugin '#{details['name']}'!"
92
+ else
93
+ if release = details['releases'].last and release['version']
94
+ return release['version']
95
+ end
96
+ raise "Release information not found for plugin '#{details['name']}'!"
97
+ end
98
+ end
99
+
100
+ def extract_hash_from_response(response)
101
+ begin
102
+ hash = Hash.from_xml(response)
103
+ rescue
104
+ raise ResponseError.new("Fatal: response couldn't be parsed. (Maybe it wasn't valid XML?)")
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ require File.join(File.dirname(__FILE__), "credentials")
112
+ require File.join(File.dirname(__FILE__), "plugin_generator")
113
+ require File.join(File.dirname(__FILE__), "plugin_manager")
@@ -0,0 +1,108 @@
1
+ require 'rest_client'
2
+
3
+ class Jax::Generators::Plugin::Credentials
4
+ attr_reader :home, :out, :in
5
+
6
+ def api_key
7
+ @api_key ||= find_api_key
8
+ end
9
+
10
+ def initialize(options = {})
11
+ @home = home_path(options)
12
+ @out = options[:out] || $stdout || STDOUT
13
+ @in = options[:in] || $stdin || STDIN
14
+ end
15
+
16
+ def config_file
17
+ File.join(home, ".jax")
18
+ end
19
+
20
+ def home_path(options = {})
21
+ File.expand_path(options[:home] || Thor::Util.user_home)
22
+ end
23
+
24
+ def plugins
25
+ @plugins ||= RestClient::Resource.new(File.join(Jax.application.plugin_repository_url, "plugins"), :accept => :xml)
26
+ end
27
+
28
+ def authors
29
+ @authors ||= RestClient::Resource.new(File.join(Jax.application.plugin_repository_url, "authors"), :accept => :xml)
30
+ end
31
+
32
+ def profile
33
+ @profile ||= RestClient::Resource.new(File.join(Jax.application.plugin_repository_url, "profile"), :accept => :xml)
34
+ end
35
+
36
+ private
37
+ def login
38
+ profile.options[:user] = email
39
+ profile.options[:password] = password
40
+
41
+ begin
42
+ Hash.from_xml(profile.get).with_indifferent_access
43
+ rescue RestClient::RequestFailed => err # login doesn't exist
44
+ message = Hash.from_xml($!.http_body)
45
+ if message && (message = message['hash']) && (message = message['error']) && (message =~ /Login not found/i)
46
+ create_account
47
+ else
48
+ raise err
49
+ end
50
+ rescue RestClient::Unauthorized # bad password
51
+ raise "Invalid password."
52
+ end
53
+ end
54
+
55
+ def email
56
+ @email ||= begin
57
+ print "Please enter your email address: "
58
+ gets.chomp
59
+ end
60
+ end
61
+
62
+ def password
63
+ @password ||= begin
64
+ print "Please enter your password: "
65
+ gets.chomp
66
+ end
67
+ end
68
+
69
+ def create_account
70
+ print "Please confirm your password: "
71
+ confirmation = gets.chomp
72
+ raise "Password and confirmation don't match" if confirmation != password
73
+
74
+ login = email && email[/\@/] ? email[0...$~.offset(0)[0]] : email
75
+ Hash.from_xml(profile.post(:author => {
76
+ :login => login, :password => password, :password_confirmation => confirmation, :email => email
77
+ })).with_indifferent_access
78
+ rescue RestClient::RequestFailed
79
+ raise Hash.from_xml($!.http_body)['hash']['error']
80
+ end
81
+
82
+ def puts(*a)
83
+ out.puts *a
84
+ end
85
+
86
+ def print(*a)
87
+ out.print *a
88
+ end
89
+
90
+ def gets(*a)
91
+ self.in.gets(*a).to_s
92
+ end
93
+
94
+ def find_api_key
95
+ if File.file?(config_file)
96
+ yml = (YAML::load(File.read(config_file)) || {}).with_indifferent_access
97
+ if yml[:api_key]
98
+ yml[:api_key]
99
+ else
100
+ key = login[:author][:single_access_token]
101
+ yml[:api_key] = key
102
+ File.open(config_file, "w") { |f| f.print yml.to_yaml }
103
+ key
104
+ end
105
+ else login[:author][:single_access_token]
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,72 @@
1
+ require 'active_support/core_ext'
2
+ require 'rest_client'
3
+ require 'jax'
4
+
5
+ module Jax
6
+ module Generators
7
+ module Plugin
8
+ class PluginGenerator < Jax::Generators::Command
9
+ argument :name
10
+ class_option :local, :type => :boolean, :desc => "Does not connect to the plugin server for any reason",
11
+ :default => false
12
+ include Jax::Generators::Plugin
13
+ include Jax::Generators::Interactions
14
+
15
+ def check_for_remote_name_conflicts
16
+ return if options[:local]
17
+
18
+ message = catch :aborted do
19
+ begin
20
+ plugins = find_plugin_list get_remote_plugins_matching(name)
21
+ if plugins.length == 1 && (plugin = plugins.shift)['name'].downcase == name.downcase
22
+ say "A plugin named '#{name}' would conflict with an existing upstream plugin called '#{plugin['name']}'."
23
+ prompt_yn "Attempts to publish your plugin will be rejected. Are you sure you wish to proceed?"
24
+ end
25
+ rescue RestClient::Exception, Errno::ECONNREFUSED
26
+ say $!.message
27
+ say ""
28
+ say "An error occurred while checking for conflicting plugin names. If"
29
+ say "a plugin named '#{name}' already exists, you will not be able to"
30
+ say "publish your plugin until it is renamed."
31
+ prompt_yn "Do you wish to continue? "
32
+ end
33
+ nil
34
+ end
35
+ if message
36
+ say_status :aborted, message, :yellow
37
+ exit
38
+ end
39
+ end
40
+
41
+ def check_for_local_name_conflicts
42
+ if File.directory? plugin_base_directory
43
+ message = catch(:aborted) do
44
+ prompt_yn("'#{name}' conflicts with another installed plugin of the same name. Overwrite?")
45
+ FileUtils.rm_rf plugin_base_directory
46
+ say_status :remove, plugin_base_directory, :green
47
+ nil
48
+ end
49
+ if message
50
+ say_status :aborted, message, :yellow
51
+ exit
52
+ end
53
+ end
54
+ end
55
+
56
+ def create_plugin_directory
57
+ directory "new_plugin", plugin_base_directory
58
+ end
59
+
60
+ def create_manifest
61
+ Jax::Plugin::Manifest.new(name).save
62
+ say_status :create, File.join("vendor/plugins", name, 'manifest.yml')
63
+ end
64
+
65
+ private
66
+ def plugin_base_directory
67
+ Jax.root.join("vendor/plugins", name).to_s
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,254 @@
1
+ require 'active_support/core_ext'
2
+ require 'rest_client'
3
+ require 'jax'
4
+ require 'archive/tar/minitar'
5
+
6
+ module Jax
7
+ module Generators
8
+ module Plugin
9
+ class PluginManager < Thor
10
+ include Thor::Actions
11
+ include Jax::Generators::Plugin
12
+ include Jax::Generators::Interactions
13
+
14
+ desc "install NAME [NAME2...]", "Installs the named plugin from the plugin repository"
15
+ long_desc "Searches for a plugin by the specified name and installs it. The " \
16
+ "search is case-insensitive. If an exact match (other than case) is " \
17
+ "found, the matching plugin will be installed. Otherwise, a list of " \
18
+ "potential matches will be shown, and you will be prompted with a " \
19
+ "selection."
20
+ method_option :version, :type => :string, :default => false, :aliases => '-v',
21
+ :desc => "The exact version to install. If not given, the latest will be used."
22
+ def install(name, *other_names)
23
+ catch(:complete) do
24
+ message = catch(:aborted) do
25
+ list = matching_plugins(name)
26
+ if list.empty?
27
+ raise "No plugin names match or begin with the text '#{name}'."
28
+ elsif list.length == 1
29
+ if list[0]['name'] != name
30
+ prompt_yn "Plugin '#{name}' was not found, but '#{list[0]['name']}' was. Install it instead?"
31
+ end
32
+ install_plugin list[0]
33
+ else
34
+ say "No plugin was found with the name '#{name}', but the following candidates were found:"
35
+ menu list.collect { |c| c['name'] } do |selected_name, selected_index|
36
+ install_plugin list[selected_index]
37
+ end
38
+ end
39
+ throw :complete
40
+ end
41
+ say_status :aborted, message, :yellow
42
+ return # cancel any additional plugins if this one was aborted
43
+ end
44
+
45
+ install *other_names unless other_names.empty?
46
+ end
47
+
48
+ desc "uninstall NAME", "Removes the named plugin from this application"
49
+ long_desc "Removes the plugin with the given name from this application."
50
+ def uninstall(name)
51
+ catch :complete do
52
+ message = catch :aborted do
53
+ plugin_path = Jax.root.join("vendor/plugins/#{name}")
54
+ if File.exist?(plugin_path.to_s)
55
+ uninstall_plugin name, plugin_path
56
+ else
57
+ # see if it's a partial name
58
+ matches = search installed_plugins, name
59
+ throw :aborted, "Plugin '#{name}' does not seem to be installed." if matches.empty?
60
+
61
+ if matches.length == 1 && match = matches.shift
62
+ prompt_yn "Plugin '#{name}' is not installed, but '#{match[0]}' was. Delete it instead?"
63
+ uninstall_plugin *match
64
+ else
65
+ say "Plugin '#{name}' is not installed, but the following partial matches are:"
66
+ menu matches.keys.sort, :allow_all => true do |name,index|
67
+ uninstall_plugin name, matches[name]
68
+ end
69
+ end
70
+ end
71
+
72
+ throw :complete
73
+ end
74
+
75
+ say_status :aborted, message, :yellow
76
+ end
77
+ end
78
+
79
+ desc "push", "Pushes this plugin to the repository, making it available to other people"
80
+ long_desc "Releases the plugin to the plugin repository. If you do not have an " \
81
+ "account, you will be prompted to create one. The name of the plugin " \
82
+ "must be unique."
83
+ def push
84
+ if ENV['JAX_CWD'] && ENV['JAX_CWD'] =~ /^#{Regexp::escape Jax.root.join("vendor/plugins").to_s}\/?([^\/]+)(\/|$)/
85
+ plugin_name = $1
86
+ plugin_dir = Jax.root.join("vendor/plugins", plugin_name)
87
+ manifest = plugin_dir.join("manifest.yml").to_s
88
+ if File.exist? manifest
89
+ manifest = Jax::Plugin::Manifest.find(plugin_name)
90
+ if manifest.description.blank?
91
+ say "Please enter a plugin description in the manifest.yml file"
92
+ else
93
+ publish_plugin manifest
94
+ end
95
+ else
96
+ say "Plugin manifest is missing!"
97
+ say "A default manifest file will be written. Please modify "
98
+ say "this file before continuing."
99
+ say ""
100
+ Jax::Plugin::Manifest.new(plugin_name).save
101
+ say_status :created, "manifest.yml", :green
102
+ end
103
+ else
104
+ say_status :aborted, "Please run this script from within a plugin directory.", :red
105
+ end
106
+ end
107
+
108
+ desc "list [NAME]", "Lists all plugins, or searches for a plugin by the specified name"
109
+ long_desc "Lists all plugins, or searches for a plugin that starts with the specified name."
110
+ method_option :detailed, :type => :boolean, :default => false,
111
+ :desc => "Lists the plugins with detailed multiline descriptions."
112
+ method_option :local, :type => :boolean, :default => false,
113
+ :desc => "Lists only plugins that are currently installed."
114
+ def list(name = nil)
115
+ if options[:local] && matching_plugins(name).empty?
116
+ say_status :missing, "There do not seem to be any plugins installed for this application."
117
+ return
118
+ else
119
+ each_plugin(name) do |plugin|
120
+ name, description = plugin['name'], plugin['description']
121
+
122
+ if options[:detailed]
123
+ say name
124
+ say " #{description}"
125
+ say ""
126
+ else
127
+ if description.length > 60
128
+ description = description[0...57] + "..."
129
+ end
130
+ say "#{name.ljust 19} #{description}"
131
+ end
132
+ end
133
+ end
134
+ say ""
135
+ end
136
+
137
+ def self.source_root
138
+ File.expand_path("templates", File.dirname(__FILE__))
139
+ end
140
+
141
+ protected
142
+ def publish_plugin(manifest)
143
+ credentials = Jax::Generators::Plugin::Credentials.new
144
+ api_key = credentials.api_key
145
+ tmp = Jax.application.config.paths.tmp.to_a.first
146
+
147
+ file = File.join(tmp, "#{manifest.name}-#{manifest.version}.tgz")
148
+ FileUtils.mkdir_p File.dirname(file) unless File.directory?(File.dirname(file))
149
+ tar = Zlib::GzipWriter.new(File.open(file, "wb"))
150
+ Dir.chdir Jax.root.join("vendor/plugins", manifest.name).to_s do
151
+ Archive::Tar::Minitar.pack(".", tar) # closes tar
152
+ end
153
+
154
+ plugin = {
155
+ :single_access_token => api_key,
156
+ :plugin => {
157
+ :name => manifest.name,
158
+ :description => manifest.description,
159
+ :version => manifest.version,
160
+ :stream => File.new(file, "r")
161
+ }
162
+ }
163
+
164
+ begin
165
+ res = Hash.from_xml(credentials.plugins.post plugin).with_indifferent_access
166
+ if res && res[:hash] && !res[:hash][:error].blank?
167
+ say_status :error, res[:hash][:error], :red
168
+ else
169
+ say_status :done, "Plugin #{File.basename(file)} published", :green
170
+ end
171
+ rescue RestClient::RequestFailed
172
+ res = Hash.from_xml($!.http_body)
173
+ if error = res['hash'] && res['hash']['error']
174
+ say_status :error, error, :red
175
+ else
176
+ say_status :error, "A server-side error has occurred.", :red
177
+ end
178
+ end
179
+ end
180
+
181
+ def uninstall_plugin(name, plugin_path)
182
+ run_uninstall_script plugin_path
183
+ FileUtils.rm_rf plugin_path
184
+ say_status :complete, "Plugin '#{name}' has been removed.", :green
185
+ end
186
+
187
+ def install_plugin(details)
188
+ name, version = details['name'], plugin_version(details)
189
+ plugin_dir = Jax.root.join("vendor/plugins/#{name}")
190
+ overwrite plugin_dir
191
+
192
+ Dir.mktmpdir do |tmp|
193
+ tarfile = download_tgz(name, version, tmp)
194
+ untar tarfile, plugin_dir
195
+ run_install_script plugin_dir
196
+ end
197
+
198
+ save_manifest plugin_dir, details unless File.exist?(File.join(plugin_dir, "manifest.yml"))
199
+
200
+ say_status :installed, "#{plugin_dir} -v=#{version}", :green
201
+ end
202
+
203
+ def save_manifest(plugin_dir, details)
204
+ # collect necessary details
205
+ details = details.inject({}) do |hash,(k,v)|
206
+ case k.to_s
207
+ when 'name', 'description', 'version' then hash[k.to_s] = v
208
+ when 'releases' then
209
+ version = v.last['version']
210
+ hash['version'] = version if hash['version'].blank? || hash['version'] < version
211
+ end
212
+ hash
213
+ end
214
+
215
+ File.open(File.join(plugin_dir, "manifest.yml"), "w") do |f|
216
+ f.print details.to_yaml
217
+ end
218
+ end
219
+
220
+ def run_uninstall_script(plugin_dir)
221
+ run_script plugin_dir, "uninstall.rb"
222
+ end
223
+
224
+ def run_script(plugin_dir, script_filename)
225
+ script = File.join(plugin_dir, script_filename)
226
+ load script if File.exist? script
227
+ end
228
+
229
+ def run_install_script(plugin_dir)
230
+ run_script plugin_dir, "install.rb"
231
+ end
232
+
233
+ def untar(tarfile, destination)
234
+ tgz = Zlib::GzipReader.new(File.open(tarfile, "rb"))
235
+ Archive::Tar::Minitar.unpack(tgz, destination.to_s) # closes tgz
236
+ end
237
+
238
+ def download_tgz(name, version, destdir)
239
+ filename = "#{name}-#{version}.tgz"
240
+ tgz = rest_resource("plugins/#{name}.tgz").get(:params => { :version => version })
241
+ tarfile = File.join destdir, filename
242
+ File.open(tarfile, "wb") { |f| f.print tgz }
243
+ tarfile
244
+ end
245
+
246
+ class << self
247
+ def basename
248
+ "jax plugin"
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end