hmx_client 0.0.5 → 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.
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