astroboa-cli 0.3.0

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.
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require "astroboa-cli/command"
4
+
5
+ module AstroboaCLI
6
+
7
+ class CLI
8
+ def self.start(*args)
9
+ command = args.shift.strip rescue "help"
10
+ AstroboaCLI::Command.load
11
+ AstroboaCLI::Command.run(command, args)
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,173 @@
1
+ # encoding: UTF-8
2
+
3
+ # This file contains modifications of work covered by the following copyright and
4
+ # permission notice:
5
+ #
6
+ # The MIT License (MIT)
7
+ #
8
+ # Copyright © Heroku 2008 - 2012
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # "Software"), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+
30
+ require 'logger'
31
+
32
+ module AstroboaCLI::Command
33
+
34
+ class Base
35
+ include AstroboaCLI::Util
36
+
37
+ def self.namespace
38
+ self.to_s.split("::").last.downcase
39
+ end
40
+
41
+ attr_reader :args
42
+ attr_reader :options
43
+ attr_reader :log
44
+ attr_reader :log_file
45
+
46
+ def initialize(args=[], options={})
47
+ @args = args
48
+ @options = options
49
+
50
+ @log_file = '/tmp/astroboa-cli-log.txt'
51
+ @log = Logger.new(@log_file)
52
+ @log.level = Logger::INFO
53
+
54
+ # Check if the proper version of ruby is running
55
+ ruby_ok?
56
+ end
57
+
58
+ protected
59
+ def self.inherited(klass)
60
+ return if klass == AstroboaCLI::Command::Base
61
+
62
+ help = extract_help_from_caller(caller.first)
63
+
64
+ AstroboaCLI::Command.register_namespace(
65
+ :name => klass.namespace,
66
+ :description => help.split("\n").first,
67
+ :long_description => help.split("\n")
68
+ )
69
+ end
70
+
71
+ def self.method_added(method)
72
+ return if self == AstroboaCLI::Command::Base
73
+ return if private_method_defined?(method)
74
+ return if protected_method_defined?(method)
75
+
76
+ help = extract_help_from_caller(caller.first)
77
+
78
+ resolved_method = (method.to_s == "default") ? nil : method.to_s
79
+ command = [ self.namespace, resolved_method ].compact.join(":")
80
+ banner = extract_banner(help) || command
81
+ permute = !banner.index("*")
82
+ banner.gsub!("*", "")
83
+
84
+ AstroboaCLI::Command.register_command(
85
+ :klass => self,
86
+ :method => method,
87
+ :namespace => self.namespace,
88
+ :command => command,
89
+ :banner => banner,
90
+ :help => help,
91
+ :summary => extract_summary(help),
92
+ :description => extract_description(help),
93
+ :options => extract_options(help),
94
+ :permute => permute
95
+ )
96
+ end
97
+
98
+ # Parse the caller format and identify the file and line number as identified
99
+ # in : http://www.ruby-doc.org/core/classes/Kernel.html#M001397. This will
100
+ # look for a colon followed by a digit as the delimiter. The biggest
101
+ # complication is windows paths, which have a color after the drive letter.
102
+ # This regex will match paths as anything from the beginning to a colon
103
+ # directly followed by a number (the line number).
104
+ #
105
+ # Examples of the caller format :
106
+ # * c:/Ruby192/lib/.../lib/astroboa-cli/command/server.rb:8:in `<module:Command>'
107
+ #
108
+ def self.extract_help_from_caller(line)
109
+ # pull out of the caller the information for the file path and line number
110
+ if line =~ /^(.+?):(\d+)/
111
+ return extract_help($1, $2)
112
+ end
113
+ raise "unable to extract help from caller: #{line}"
114
+ end
115
+
116
+ def self.extract_help(file, line)
117
+ buffer = []
118
+ lines = File.read(file).split("\n")
119
+
120
+ catch(:done) do
121
+ (line.to_i-2).downto(0) do |i|
122
+ case lines[i].strip[0..0]
123
+ when "", "#" then buffer << lines[i]
124
+ else throw(:done)
125
+ end
126
+ end
127
+ end
128
+
129
+ buffer.map! do |line|
130
+ line.strip.gsub(/^#/, "")
131
+ end
132
+
133
+ buffer.reverse.join("\n").strip
134
+ end
135
+
136
+ def self.extract_banner(help)
137
+ help.split("\n").first
138
+ end
139
+
140
+ def self.extract_summary(help)
141
+ extract_description(help).split("\n").first
142
+ end
143
+
144
+ def self.extract_description(help)
145
+ lines = help.split("\n").map { |l| l.strip }
146
+ lines.shift
147
+ lines.reject do |line|
148
+ line =~ /^-(.+)#(.+)/
149
+ end.join("\n").strip
150
+ end
151
+
152
+ def self.extract_options(help)
153
+ help.split("\n").map { |l| l.strip }.select do |line|
154
+ line =~ /^-(.+)#(.+)/
155
+ end.inject({}) do |hash, line|
156
+ description = line.split("#", 2).last.strip
157
+ long = line.match(/--([A-Za-z_\- ]+)/)[1].strip
158
+ short = line.match(/-([A-Za-z ])/)[1].strip
159
+ hash.update(long.split(" ").first => { :desc => description, :short => short, :long => long })
160
+ end
161
+ end
162
+
163
+ def extract_option(name, default=true)
164
+ key = name.gsub("--", "").to_sym
165
+ return unless options[key]
166
+ value = options[key] || default
167
+ block_given? ? yield(value) : value
168
+ end
169
+
170
+
171
+ end # class base
172
+
173
+ end # Module AstroboaCLI::Command
@@ -0,0 +1,95 @@
1
+ # encoding: utf-8
2
+
3
+ require "astroboa-cli/command/base"
4
+
5
+ # display available commands and help
6
+ #
7
+ class AstroboaCLI::Command::Help < AstroboaCLI::Command::Base
8
+
9
+ # help [COMMAND]
10
+ #
11
+ # list available commands or display help for a specific command
12
+ #
13
+ def default
14
+ if command = args.shift
15
+ help_for_command(command)
16
+ else
17
+ help_for_root
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def subcommands_in_namespace(name)
24
+ AstroboaCLI::Command.commands.values.select do |command|
25
+ command[:namespace] == name && command[:command] != name
26
+ end
27
+ end
28
+
29
+ def help_for_root
30
+ puts "Usage: astroboa-cli COMMAND [command-specific-options]"
31
+ puts
32
+ puts "To get help about a top command, type \"astroboa-cli help command\""
33
+ puts "Available top commands:"
34
+ summary_for_namespaces
35
+ puts
36
+ end
37
+
38
+ def summary_for_namespaces
39
+ size = longest(AstroboaCLI::Command.namespaces.values.map { |namespace| namespace[:name] })
40
+ AstroboaCLI::Command.namespaces.values.sort_by {|namespace| namespace[:name]}.each do |namespace|
41
+ name = namespace[:name]
42
+ puts " %-#{size}s # %s" % [ name, namespace[:description] ]
43
+ end
44
+ end
45
+
46
+ def help_for_subcommands_in_namespace(namespace)
47
+ subcommands = subcommands_in_namespace(namespace)
48
+
49
+ unless subcommands.empty?
50
+ size = longest(subcommands.map { |c| c[:banner] })
51
+ subcommands.sort_by { |c| c[:banner].to_s }.each do |command|
52
+ next if command[:help] =~ /DEPRECATED/
53
+ puts " %-#{size}s # %s" % [ command[:banner], command[:summary] ]
54
+ end
55
+ end
56
+ end
57
+
58
+ def help_for_command(name)
59
+ puts AstroboaCLI::Command.namespaces[name][:long_description] if AstroboaCLI::Command.namespaces[name]
60
+ puts
61
+ command = AstroboaCLI::Command.commands[name]
62
+
63
+ if command
64
+ puts "Usage: astroboa-cli #{command[:banner]}"
65
+
66
+ if command[:help].strip.length > 0
67
+ command[:help].split("\n")[1..-1].each do |line|
68
+ if line =~ /#/
69
+ option_parts = line.split('#')
70
+ puts "#{option_parts[0]}"
71
+ option_parts[1..-1].each do |sentence|
72
+ puts "\t #{sentence}"
73
+ end
74
+ puts
75
+ elsif
76
+ puts line
77
+ end
78
+ end
79
+
80
+ puts
81
+ end
82
+ end
83
+
84
+ # if there are sub commands inform the user
85
+ if subcommands_in_namespace(name).size > 0
86
+ puts "This top command supports the following sub-commands, type \"astroboa-cli help SUB-COMMAND\" to get help:"
87
+ puts
88
+ help_for_subcommands_in_namespace(name)
89
+ puts
90
+ elsif command.nil? # if there are not subcommands and the top command itself does not exist display an error
91
+ error "#{name} is not an astroboa command. See 'astroboa-cli help'."
92
+ end
93
+ end
94
+
95
+ end
@@ -0,0 +1,181 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'nokogiri'
4
+ require 'fileutils'
5
+
6
+ # Manage your domain models
7
+ #
8
+ # Astroboa facilitates a DOMAIN DRIVEN design of applications.
9
+ # The core of your application(s) is the DOMAIN MODEL, a graph of ENTITIES that describe
10
+ # the type of information your application(s) will create, consume and search.
11
+ #
12
+ # In order to store your application(s) data you follow three simple steps:
13
+ # 1) you create an astroboa repository (astroboa-cli repository:create)
14
+ # 2) you create your domain model by:
15
+ # - using the Astroboa Entity Definition DSL which is very close to ActiveRecord::Schema definitions of ruby on rails
16
+ # - directy writing an XML Schema for each entity (or a single schema to include all entity definitions)
17
+ # 3) you "associate" your domain model with the repository you just created (astroboa-cli model:associate)
18
+ #
19
+ # One of the best features of astroboa is its programming-language agnostic and dynamic domain model that can be shared between applications.
20
+ # You do not need to create model classes for your applications. Astroboa uses the domain model that you define once and
21
+ # dynamically creates the appropriate objects for your app.
22
+ # Additionally you can "model-as-you-go", that is you may define new entities
23
+ # or update existing ones at any time during development or production.
24
+ # Astroboa will automatically update the APIs and the generated object instances to the updated domain model.
25
+ #
26
+ class AstroboaCLI::Command::Model < AstroboaCLI::Command::Base
27
+
28
+ # model:associate REPOSITORY MODEL_DIR
29
+ #
30
+ # This command allows you to associate a repository with a domain model.
31
+ # After the association is done your repository can store entities that follow the domain model.
32
+ #
33
+ # It is recommended to use this command only on new repositories because it does not cope with model updates
34
+ # (it will cause instant performace decrease because it resets the whole repository schema and most important
35
+ # it may render your data inaccessible if the updated model contain changes to property names or property value cardinality)
36
+ # If you change a domain model that has been already associated with a repository use 'astroboa-cli model:propagate_updates'
37
+ #
38
+ # If you specify the 'MODEL_DIR' (i.e. where your models are stored) then your DSL model definition is expected to be
39
+ # in 'MODEL_DIR/dsl' and your XML Schemas to be in 'MODEL_DIR/xsd'
40
+ # If you do not specify the 'MODEL_DIR' then domain model is expected to be found inside current directory in 'model/dsl' and 'model/xsd'
41
+ #
42
+ def associate
43
+
44
+ if repository = args.shift
45
+ repository = repository.strip
46
+ else
47
+ error "Please specify the repository name. Usage: model:associate REPOSITORY MODEL_DIR"
48
+ end
49
+
50
+ server_configuration = get_server_configuration
51
+
52
+ error "Repository '#{repository}' does not exist or it is not properly configured (use astroboa-cli repository:list to see available repositories)" unless repository?(server_configuration, repository)
53
+
54
+ if model_dir = args.shift
55
+ model_dir = model_dir.strip
56
+ else
57
+ model_dir = File.join(Dir.getwd, 'model')
58
+ end
59
+
60
+ error <<-MSG unless Dir.exists? model_dir
61
+ Directory #{model_dir} does not exist.
62
+ If you specify the 'MODEL_DIR' then your DSL model definition is expected to be
63
+ in 'MODEL_DIR/dsl' and your XML Schemas to be in 'MODEL_DIR/xsd'
64
+ If you do not specify the 'MODEL_DIR' then domain model is expected to be found inside current directory in 'model/dsl' and 'model/xsd'
65
+ MSG
66
+
67
+ astroboa_dir = server_configuration['install_dir']
68
+
69
+ display "Looking for XML Schemas..."
70
+ xsd_dir = File.join model_dir, 'xsd'
71
+ model_contains_xsd = Dir.exists?(xsd_dir) && Dir.entries(xsd_dir) != [".", ".."]
72
+
73
+ if model_contains_xsd
74
+ display "Found XML Schemas in '#{xsd_dir}'"
75
+ display "Validating XML Schemas..."
76
+
77
+ tmp_dir = File.join(astroboa_dir, 'tmp')
78
+
79
+ FileUtils.rm_r tmp_dir if Dir.exists? tmp_dir
80
+
81
+ FileUtils.mkdir_p tmp_dir
82
+ FileUtils.cp_r File.join(astroboa_dir, 'schemas'), tmp_dir
83
+
84
+ tmp_schema_dir = File.join(tmp_dir, 'schemas')
85
+ FileUtils.cp_r Dir.glob(File.join(xsd_dir, '*.xsd')), tmp_schema_dir
86
+
87
+ # Validate schemas in domain model
88
+ Dir[File.join(xsd_dir, '*.xsd')].each do |schema_path|
89
+ schema_file = schema_path.split('/').last
90
+
91
+ display
92
+ display '-------------------------------------------------'
93
+ display "Validating XML Schema: #{schema_file}"
94
+ error "Please correct the schema file '#{schema_file}' and run the command again" unless domain_model_valid? schema_file, tmp_schema_dir
95
+ end
96
+
97
+ # copy schemas to repository schemas directory
98
+ repository_schema_dir = File.join(server_configuration['repos_dir'], repository, 'astroboa_schemata')
99
+ FileUtils.cp_r Dir.glob(File.join(xsd_dir, '*.xsd')), repository_schema_dir
100
+
101
+ display
102
+ display '-------------------------------------------------'
103
+ display '-------------------------------------------------'
104
+ display "Repository '#{repository}' associated to XML Schemas in '#{xsd_dir}': OK"
105
+ else
106
+ display "No XML Schemas Found"
107
+ end
108
+
109
+
110
+ end
111
+
112
+ # model:graph DOMAIN_MODEL
113
+ #
114
+ # Draws a graphic representation of the domain model
115
+ def graph
116
+
117
+ end
118
+
119
+ # model:view PATH
120
+ #
121
+ # Displays the model of an entity or entity property
122
+ def view
123
+
124
+ end
125
+
126
+ # model:list DOMAIN_MODEL
127
+ #
128
+ # Displays information about the domain models and their entities
129
+ def list
130
+
131
+ end
132
+
133
+ private
134
+
135
+ def domain_model_valid? domain_model_file, schemas_dir
136
+ Dir.chdir(schemas_dir) do
137
+
138
+ # first make sure that Domain model is a well formed XML Doc and if not show errors
139
+ domain_model_doc = Nokogiri::XML(File.read(domain_model_file))
140
+ unless domain_model_doc.errors.empty?
141
+ puts "#{domain_model_file} is not a well formed XML Document"
142
+ puts domain_model_doc.errors
143
+ return false
144
+ else
145
+ puts "Check if domain model is a well formed XML Document: OK"
146
+ end
147
+
148
+ # Then check if domain model is a valid XML Schema, i.e. validate it against the XML Schema schema
149
+ xml_schema_grammar = File.read('XMLSchema.xsd')
150
+ xml_schema_validator = Nokogiri::XML::Schema(xml_schema_grammar)
151
+
152
+ errors = xml_schema_validator.validate(domain_model_doc)
153
+
154
+ unless errors.empty?
155
+ puts "Check if domain model is a valid XML Schema: Not valid XML Schema"
156
+ errors.each do |error|
157
+ puts error.message
158
+ end
159
+ return false
160
+ else
161
+ puts "Check if domain model is a valid XML Schema: OK"
162
+ end
163
+
164
+ # Finally check if domain model is valid against its dependencies to astroboa schemas and external user-provided schemas,
165
+ # i.e. it properly loads and uses all its external schema dependencies
166
+ begin
167
+ external_schemas_validator = Nokogiri::XML::Schema(File.read(domain_model_file))
168
+ puts 'Check if domain model properly loads and uses external schemas (i.e. astroboa model schemas + user defined schemas): OK'
169
+ true
170
+ rescue Exception => e
171
+ puts 'Check if domain model is properly loading and using external schemas (i.e. astroboa model schemas + user defined schemas): Errors found!'
172
+ puts external_schemas_validator.errors if external_schemas_validator
173
+ puts e.message
174
+ false
175
+ end
176
+
177
+ end
178
+
179
+ end
180
+
181
+ end