hmx_client 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/bin/hmx CHANGED
@@ -1,4 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
+ begin
3
+ require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /mingw32/
4
+ rescue Exception => e
5
+ puts e.message
6
+ end
7
+
8
+ require 'cli-colorize'
2
9
 
3
10
  lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
11
  $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
@@ -10,32 +17,17 @@ require 'hmx_client'
10
17
  include HmxClient
11
18
 
12
19
  $stdout.sync = true
20
+ $folder = nil
21
+ $file = nil
22
+ $outFile = nil
13
23
  $debug = false
14
24
 
15
- options = {
16
- :api => 'http://localhost:9999',
17
- :user => 'amkimian',
18
- :password => 'password'
19
- }
20
-
21
- ARGV.options do |o|
22
- o.on("-a apiUrl", "--api") { |api| options[:api] = api }
23
- o.on("-u user", "--user") { |user| options[:user] = user }
24
- o.on("-d", "--debug") { $debug = true }
25
- o.parse!
26
- end
27
-
28
25
  begin
29
26
  args = ARGV.dup
30
27
  ARGV.clear
31
- h = Client.new(args, options).run!
32
- puts "Hello mum" if $debug
33
- rescue Client::CommandInvalid
34
- abort File.read(__FILE__).split('__END__').last
28
+ command = args.shift.strip rescue 'help'
29
+ HmxClient::Command.load
30
+ HmxClient::Command.run(command, args)
31
+ rescue HmxException => info
32
+ abort "General HMX Exception - #{info.msg['message']}"
35
33
  end
36
-
37
- __END__
38
- Usage: hmx [OPTIONS] command
39
-
40
- hmx test alan
41
-
data/lib/hmx/client.rb CHANGED
@@ -4,9 +4,8 @@ module HmxClient
4
4
  class Client
5
5
  FILE = File.expand_path("~/.hmxConfig")
6
6
 
7
- def initialize(args, options)
7
+ def initialize(args)
8
8
  @args = args
9
- @options = options
10
9
  loadConfig!
11
10
  end
12
11
 
@@ -14,13 +13,17 @@ module HmxClient
14
13
 
15
14
  def loadConfig!
16
15
  # Load the config from the save file
17
- @config = {}
18
- @config = Marshal.load(File.read(FILE)) if File.exist?(FILE)
16
+ @config = if File.exist?(FILE)
17
+ File.open(FILE) { |file| Marshal.load(file) }
18
+ else
19
+ {}
20
+ end
21
+ RestClient.proxy = @config[:proxy] if @config.has_key?(:proxy)
19
22
  end
20
23
  def storeConfig(keyName, keyValue)
21
24
  # Update the config hashMap and persist it
22
25
  @config[keyName] = keyValue
23
- File.open(FILE, 'w+') { |f| f.write(Marshal.dump(@config)) }
26
+ File.open(FILE, 'w+') { |f| Marshal.dump(@config, f) }
24
27
  end
25
28
  def run!
26
29
  command = @args.shift || @options[:command]
@@ -46,33 +49,119 @@ module HmxClient
46
49
  storeConfig(:api, @args.shift)
47
50
  when "partition"
48
51
  storeConfig(:partition, @args.shift)
52
+ when "proxy"
53
+ proxy = @args.shift
54
+ storeConfig(:proxy, proxy)
55
+ RestClient.proxy = proxy
49
56
  else
50
57
  abort "Unknown config command"
51
58
  end
52
59
  end
60
+ def genout(content)
61
+ puts content
62
+ File.open($outFile, 'w') { | f | puts "And writing to #{$outFile} "
63
+ f.write(content) } unless $outFile.nil?
64
+ end
53
65
  def get
54
66
  h = getapi
55
- puts h.getData(@args.shift)
67
+ genout JSON.pretty_generate(h.getData(@args))
56
68
  end
57
69
  def getData
58
70
  h = getapi
59
- puts h.getContent(@args.shift)
71
+ genout h.getContent(@args)
60
72
  end
61
73
  def putSimpleData
62
74
  h = getapi
63
- puts h.putSimpleData(@args.shift, @args.shift)
75
+ genout JSON.pretty_generate(h.putSimpleData(@args))
76
+ end
77
+ def put
78
+ h = getapi
79
+ genout JSON.pretty_generate(h.putData(@args))
64
80
  end
65
81
  def query
66
82
  h = getapi
67
- puts h.query(@args.shift, nil)
68
- end
69
- def test
70
- case check = @args.shift
71
- when "alan"
72
- puts "Test Alan complete"
73
- else
74
- abort "Unknown test command"
75
- end
83
+ genout h.query(@args.shift, nil)
84
+ end
85
+ def view
86
+ h = getapi
87
+ viewData = h.runView(@args.shift, JSON.parse(@args.shift))
88
+ resp = ''
89
+ viewData.each { | line |
90
+ line.each { | cell | resp = resp + "%20.20s\t" % cell }
91
+ resp = resp + "\n"
92
+ }
93
+ genout resp
94
+ end
95
+ # Take all of the documents for a type and put it into the folder passed, one per file
96
+ def dumpType
97
+ h = getapi
98
+ displayNames = h.query(@args.shift, nil)
99
+ puts displayNames
100
+ displayNames.each { | displayName |
101
+ fileName = $folder + "/" + displayName
102
+ puts "FileName will be #{ fileName } "
103
+ Dir.mkdir(File.dirname(fileName)) if !Dir.exist?(File.dirname(fileName))
104
+ puts "DisplayName is #{ displayName } "
105
+ File.open(fileName, 'w') { |f| f.write(h.getData([displayName])) } }
106
+ end
107
+ # Take all of the documents in a folder for a type (the opposite of dumpType above)
108
+ # and simply put them back in
109
+ def loadType
110
+ h = getapi
111
+ typeName = @args.shift
112
+ folderName = $folder + "/" + typeName
113
+ Dir.foreach(folderName) { | f |
114
+ puts "Working with #{ f }"
115
+ fullFile = folderName + "/" + f
116
+ if (File.file?(fullFile))
117
+ content = File.open(fullFile) { | h |
118
+ c = '';
119
+ while(line = h.gets)
120
+ c = c + line
121
+ end
122
+ c
123
+ }
124
+ puts "Content is #{ content } "
125
+ h.putData(JSON.parse(content))
126
+ end
127
+ }
128
+ end
129
+ def deleteData
130
+ h = getapi
131
+ h.deleteData(@args.shift)
132
+ end
133
+ def getTypes
134
+ h = getapi
135
+ genout JSON.pretty_generate(h.getTypes)
136
+ end
137
+ def getType
138
+ h = getapi
139
+ genout JSON.pretty_generate(h.getType(@args.shift))
140
+ end
141
+ def getFn
142
+ h = getapi
143
+ genout h.getFn(@args)
144
+ end
145
+ def putFn
146
+ h = getapi
147
+ genout h.putFn(@args)
148
+ end
149
+ # Add a user, given a name (more can be added through a low level api)
150
+ def user
151
+ h = getapi
152
+ case command = @args.shift
153
+ when "add"
154
+ username = @args.shift
155
+ password = Digest::MD5::hexdigest(@args.shift)
156
+
157
+ user = { "MXUser" => { "hashPassword" => password , "apiKey" => false, "userName" => username, "fullName" => username }}
158
+ genout JSON.pretty_generate(h.putSimpleData([ "sys.user/#{ username}", JSON.generate(user)]))
159
+ when "delete"
160
+ username = @args.shift
161
+ h.deleteData("sys.user/#{username}")
162
+ when "list"
163
+ puts h.query("sys.user", nil)
164
+ end
76
165
  end
77
166
  end
78
167
  end
@@ -0,0 +1,194 @@
1
+ require "fileutils"
2
+ require "hmx/command"
3
+ require 'cli-colorize'
4
+ require 'hmx/helpers'
5
+
6
+ class HmxClient::Command::Base
7
+ include HmxClient::Helpers
8
+ include CLIColorize
9
+
10
+ def self.namespace
11
+ self.to_s.split("::").last.downcase
12
+ end
13
+
14
+ attr_reader :args
15
+ attr_reader :options
16
+
17
+ FILE = File.expand_path("~/.hmxConfig")
18
+
19
+ def initialize(args=[], options={})
20
+ @args = args
21
+ @options = options
22
+ loadConfig!
23
+ end
24
+
25
+ def loadConfig!
26
+ # Load the config from the save file
27
+ @config = if File.exist?(FILE)
28
+ File.open(FILE) { |file| Marshal.load(file) }
29
+ else
30
+ {}
31
+ end
32
+ RestClient.proxy = @config[:proxy] if @config.has_key?(:proxy)
33
+ end
34
+ def storeConfig(keyName, keyValue)
35
+ # Update the config hashMap and persist it
36
+ if (keyName == :password)
37
+ keyValue = Digest::MD5.hexdigest(keyValue)
38
+ end
39
+ @config[keyName] = keyValue
40
+ writeConfig
41
+ end
42
+ def removeConfig(keyName)
43
+ @config.delete keyName
44
+ writeConfig
45
+ end
46
+ def writeConfig
47
+ File.open(FILE, 'w+') { |f| Marshal.dump(@config, f) }
48
+ end
49
+ def hmx
50
+ if @hmx.nil?
51
+ @hmx = Hmx.new
52
+ @hmx.login(@config)
53
+ writeConfig
54
+ end
55
+ @hmx
56
+ end
57
+
58
+ protected
59
+
60
+ def self.inherited(klass)
61
+ help = extract_help_from_caller(caller.first)
62
+ HmxClient::Command.register_namespace(
63
+ :name => klass.namespace,
64
+ :description => help.split("\n").first
65
+ )
66
+ end
67
+
68
+ def self.method_added(method)
69
+ return if self == HmxClient::Command::Base
70
+ return if private_method_defined?(method)
71
+ return if protected_method_defined?(method)
72
+
73
+ help = extract_help_from_caller(caller.first)
74
+ resolved_method = (method.to_s == "index") ? nil : method.to_s
75
+ default_command = [ self.namespace, resolved_method ].compact.join(":")
76
+ command = extract_command(help) || default_command
77
+ banner = extract_banner(help) || command
78
+ permute = !banner.index("*")
79
+ banner.gsub!("*", "")
80
+
81
+ HmxClient::Command.register_command(
82
+ :klass => self,
83
+ :method => method,
84
+ :namespace => self.namespace,
85
+ :command => command,
86
+ :banner => banner,
87
+ :help => help,
88
+ :summary => extract_summary(help),
89
+ :description => extract_description(help),
90
+ :options => extract_options(help),
91
+ :permute => permute
92
+ )
93
+ end
94
+
95
+ def self.alias_command(new, old)
96
+ raise "no such command: #{old}" unless HmxClient::Command.commands[old]
97
+ HmxClient::Command.command_aliases[new] = old
98
+ end
99
+
100
+ #
101
+ # Parse the caller format and identify the file and line number as identified
102
+ # in : http://www.ruby-doc.org/core/classes/Kernel.html#M001397. This will
103
+ # look for a colon followed by a digit as the delimiter. The biggest
104
+ # complication is windows paths, which have a color after the drive letter.
105
+ # This regex will match paths as anything from the beginning to a colon
106
+ # directly followed by a number (the line number).
107
+ #
108
+ # Examples of the caller format :
109
+ # * c:/Ruby192/lib/.../lib/heroku/command/addons.rb:8:in `<module:Command>'
110
+ # * c:/Ruby192/lib/.../heroku-2.0.1/lib/heroku/command/pg.rb:96:in `<class:Pg>'
111
+ # * /Users/ph7/...../xray-1.1/lib/xray/thread_dump_signal_handler.rb:9
112
+ #
113
+ def self.extract_help_from_caller(line)
114
+ # pull out of the caller the information for the file path and line number
115
+ if line =~ /^(.+?):(\d+)/
116
+ return extract_help($1, $2)
117
+ end
118
+ raise "unable to extract help from caller: #{line}"
119
+ end
120
+
121
+ def self.extract_help(file, line)
122
+ buffer = []
123
+ lines = File.read(file).split("\n")
124
+
125
+ catch(:done) do
126
+ (line.to_i-2).downto(0) do |i|
127
+ case lines[i].strip[0..0]
128
+ when "", "#" then buffer << lines[i]
129
+ else throw(:done)
130
+ end
131
+ end
132
+ end
133
+
134
+ buffer.map! do |line|
135
+ line.strip.gsub(/^#/, "")
136
+ end
137
+
138
+ buffer.reverse.join("\n").strip
139
+ end
140
+
141
+ def self.extract_command(help)
142
+ extract_banner(help).to_s.split(" ").first
143
+ end
144
+
145
+ def self.extract_banner(help)
146
+ help.split("\n").first
147
+ end
148
+
149
+ def self.extract_summary(help)
150
+ extract_description(help).split("\n").first
151
+ end
152
+
153
+ def self.extract_description(help)
154
+ lines = help.split("\n").map { |l| l.strip }
155
+ lines.shift
156
+ lines.reject do |line|
157
+ line =~ /^-(.+)#(.+)/
158
+ end.join("\n").strip
159
+ end
160
+
161
+ def self.extract_options(help)
162
+ help.split("\n").map { |l| l.strip }.select do |line|
163
+ line =~ /^-(.+)#(.+)/
164
+ end.inject({}) do |hash, line|
165
+ description = line.split("#", 2).last.strip
166
+ long = line.match(/--([A-Za-z\- ]+)/)[1].strip
167
+ short = line.match(/-([A-Za-z ])/)[1].strip
168
+ hash.update(long.split(" ").first => { :desc => description, :short => short, :long => long })
169
+ end
170
+ end
171
+
172
+ def extract_option(name, default=true)
173
+ key = name.gsub("--", "").to_sym
174
+ return unless options[key]
175
+ value = options[key] || default
176
+ block_given? ? yield(value) : value
177
+ end
178
+
179
+ def confirm_mismatch?
180
+ options[:confirm] && (options[:confirm] != options[:app])
181
+ end
182
+
183
+ def dout(msg)
184
+ if (!HmxClient::Command.fileOut.nil?)
185
+ realFile = File.expand_path(HmxClient::Command.fileOut)
186
+ File.open(realFile, 'w') { | f |
187
+ display("Writing to #{ realFile }")
188
+ f.write(msg) }
189
+ end
190
+ puts colorize(msg, :yellow)
191
+ #display(green(msg))
192
+ end
193
+ end
194
+
@@ -0,0 +1,18 @@
1
+ require "hmx/command/base"
2
+ require 'date'
3
+
4
+ module HmxClient::Command
5
+
6
+ # Bootstrap a default configuration
7
+ #
8
+ class Bootstrap < Base
9
+
10
+ # bootstrap
11
+ #
12
+ # Bootstrap a default configuration into this partition
13
+ def index
14
+ hmx.bootstrap
15
+ puts "Done."
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,77 @@
1
+ require "hmx/command/base"
2
+ require 'date'
3
+
4
+ module HmxClient::Command
5
+
6
+ # Clone a partition or setup a partition from a clone
7
+ #
8
+ class Clone < Base
9
+
10
+ # clone
11
+ #
12
+ # Create a clone file for the current partition
13
+ def index
14
+ # Clone the type information
15
+ # Clone these built in types (i.e. the content)
16
+ # Clone other types as defined by the command line
17
+ extraTypes = args
18
+ types = [ 'sys.user', 'sys.config', 'sys.ent', 'sys.egroup', 'sys.trigger', 'sys.view' ]
19
+ extraTypes.each { | et | types << et }
20
+ types.each { | t | dumpTypeAndData(t) }
21
+ end
22
+
23
+ # clone:load
24
+ #
25
+ # Load a file that was created using hmx clone
26
+ # The file basically contains a series of types (documents separated by << >>)
27
+ # and data (separated by [[ ]])
28
+ def load
29
+ # We define the file as being the input file on the command line (--file-in)
30
+ if (HmxClient::Command.fileIn.nil?)
31
+ raise CommandFailed, "Usage: hmx clone:load --file-in=<filename>"
32
+ end
33
+ realFile = File.expand_path(HmxClient::Command.fileIn)
34
+ File.open(realFile, 'r') { | f |
35
+ currentDoc = ''
36
+ isType = false
37
+ while(line = f.gets)
38
+ if (line[0] == '#')
39
+ elsif (line[0] == '<' && line[1] == '<')
40
+ currentDoc = ''
41
+ isType = true
42
+ elsif (line[0] == '>' && line[1] == '>')
43
+ if isType
44
+ print '.'
45
+ hmx.updateType(JSON.parse(currentDoc))
46
+ end
47
+ currentDoc = ''
48
+ elsif (line[0] == '[' && line[1] == '[')
49
+ currentDoc = ''
50
+ isType = false
51
+ elsif (line[0] == ']' && line[1] == ']')
52
+ if (!isType)
53
+ print 'x'
54
+ hmx.putData([currentDoc])
55
+ end
56
+ currentDoc = ''
57
+ else
58
+ currentDoc = currentDoc + line
59
+ end
60
+ end
61
+ }
62
+ puts ""
63
+ puts "Done"
64
+ end
65
+
66
+ protected
67
+ def dumpTypeAndData(typeName)
68
+ puts "####### #{ typeName } "
69
+ puts "<<"
70
+ puts JSON.generate(hmx.getType(typeName))
71
+ puts ">>"
72
+ puts "####### Data for #{ typeName } "
73
+ displayNames = hmx.query(typeName, nil)
74
+ displayNames.each { | d | puts("[["); puts JSON.generate(hmx.getData([d])); puts("]]") }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,67 @@
1
+ require "hmx/command/base"
2
+
3
+ module HmxClient::Command
4
+
5
+ # Manage app config vars
6
+ #
7
+ class Config < Base
8
+
9
+ # config
10
+ #
11
+ # display the config vars for an app
12
+ #
13
+ # -s, --shell # output config vars in shell format
14
+ #
15
+ def index
16
+ display_vars(@config, :long => true, :shell => false)
17
+ end
18
+
19
+ # config:add KEY1=VALUE1 ...
20
+ #
21
+ # add one or more config vars
22
+ #
23
+ def add
24
+ unless args.size > 0 and args.all? { |a| a.include?('=') }
25
+ raise CommandFailed, "Usage: hmx config:add <key>=<value> [<key2>=<value2> ...]"
26
+ end
27
+
28
+ args.inject({}) do |vars, arg|
29
+ key, value = arg.split('=', 2)
30
+ storeConfig(key.to_sym,value)
31
+ end
32
+
33
+ display_vars(@config, :indent => 2)
34
+ end
35
+
36
+ # config:remove KEY1 [KEY2 ...]
37
+ #
38
+ # remove a config var
39
+ #
40
+ def remove
41
+ raise CommandFailed, "Usage: hmx config:remove KEY1 [KEY2 ...]" if args.empty?
42
+
43
+ args.each do |key|
44
+ removeConfig(key.to_sym)
45
+ display_vars(@config, :indent => 2)
46
+ end
47
+ end
48
+
49
+ protected
50
+ def display_vars(vars, options={})
51
+ max_length = vars.map { |v| v[0].to_s.size }.max
52
+ vars.keys.sort.each do |key|
53
+ if options[:shell]
54
+ display "#{key}=#{vars[key]}"
55
+ else
56
+ spaces = ' ' * (max_length - key.to_s.size)
57
+ display "#{' ' * (options[:indent] || 0)}#{key}#{spaces} => #{format(vars[key], options)}"
58
+ end
59
+ end
60
+ end
61
+
62
+ def format(value, options)
63
+ return value if options[:long] || value.to_s.size < 36
64
+ value[0, 16] + '...' + value[-16, 16]
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,67 @@
1
+ require "hmx/command/base"
2
+
3
+ module HmxClient::Command
4
+
5
+ # Dump data from a type in hmx and load it back again
6
+ #
7
+ class Dump < Base
8
+
9
+ # dump
10
+ #
11
+ # Dump data from hmx into the local filesystem. Used
12
+ # as a preface to a backup, or a change then restore
13
+ #
14
+ # <typeName> <folderPath>
15
+ def index
16
+ unless args.size > 0
17
+ raise CommandFailed, "Usage: hmx dump <typeName> <folderPath>"
18
+ end
19
+ # The filenames will come from the name of the type and their displayName, appended onto
20
+ # the folder path given. We will also attempt to create the folder path and the type
21
+ # sub folder
22
+ typeName = args.shift
23
+ rootFolder = args.shift
24
+
25
+ Dir.mkdir(rootFolder) if !Dir.exist?(rootFolder)
26
+ Dir.mkdir(rootFolder + "/" + typeName) if !Dir.exist?(rootFolder + "/" + typeName)
27
+ displayNames = hmx.query(typeName, nil)
28
+ displayNames.each { | displayName |
29
+ fileName = rootFolder + "/" + displayName
30
+ display "Writing to #{ fileName }"
31
+ File.open(fileName, 'w') { | f | f.write(JSON.pretty_generate(hmx.getData([displayName]))) }
32
+ }
33
+ end
34
+
35
+ # dump:load
36
+ #
37
+ # Load data that has previously been dumped using the main dump command
38
+ #
39
+ # <typeName> <folderPath>
40
+ #
41
+ # The real path for the files is in the folder formed by concatenating the typename to the rootFolder
42
+ def load
43
+ unless args.size > 0
44
+ raise CommandFailed, "Usage: hmx dump:load <typeName> <folderPath>"
45
+ end
46
+ typeName = args.shift
47
+ rootFolder = args.shift
48
+
49
+ folderName = rootFolder + "/" + typeName
50
+ Dir.foreach(folderName) { | f |
51
+ display "Loading #{ f } "
52
+ fullFile = folderName + "/" + f
53
+ if (File.file?(fullFile))
54
+ content = File.open(fullFile) { | h |
55
+ c = '';
56
+ while(line = h.gets)
57
+ c = c + line
58
+ end
59
+ c
60
+ }
61
+ puts "Content is #{ content } "
62
+ hmx.putData([content])
63
+ end
64
+ }
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,43 @@
1
+ require "hmx/command/base"
2
+
3
+ module HmxClient::Command
4
+
5
+ # Load and Save functions in hmx
6
+ #
7
+ class Fn < Base
8
+
9
+ # fn
10
+ #
11
+ # Manipulate HMX functions
12
+ #
13
+ #
14
+ def index
15
+ unless args.size > 0
16
+ raise CommandFailed, "Usage: hmx fn <fnName>"
17
+ end
18
+ dout hmx.getFn([args.shift])
19
+ end
20
+
21
+ # fn:list
22
+ #
23
+ # List all functions in HMX
24
+ #
25
+ def list
26
+ fns = hmx.query("fn", nil)
27
+ fns.each { | f |
28
+ display f.rpartition('/')[2]
29
+ }
30
+ end
31
+
32
+ # fn:put
33
+ #
34
+ # Create or update a function
35
+ #
36
+ def put
37
+ unless args.size > 0
38
+ raise CommandFailed, "Usage: hmx fn:put <fnName> <f:function>"
39
+ end
40
+ dout hmx.putFn(args)
41
+ end
42
+ end
43
+ end