techcor 0.0.1

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 (71) hide show
  1. data/Gemfile +22 -0
  2. data/Gemfile.lock +96 -0
  3. data/README.md +27 -0
  4. data/Rakefile +45 -0
  5. data/bin/tc +6 -0
  6. data/config/cucumber.yml +2 -0
  7. data/config/mongoid.yml +29 -0
  8. data/config/mongoid.yml.sample +29 -0
  9. data/db/seed.rb +37 -0
  10. data/features/add_metric.feature +9 -0
  11. data/features/add_project.feature +9 -0
  12. data/features/console_interface.feature +58 -0
  13. data/features/describe_project.feature +25 -0
  14. data/features/edit_property.feature +9 -0
  15. data/features/list_projects.feature +69 -0
  16. data/features/property_history.feature +51 -0
  17. data/features/rake.feature +11 -0
  18. data/features/seeds.feature +9 -0
  19. data/features/step_definitions/add_project.rb +22 -0
  20. data/features/step_definitions/console_interface.rb +7 -0
  21. data/features/step_definitions/list_projects.rb +3 -0
  22. data/features/step_definitions/property_history.rb +4 -0
  23. data/features/step_definitions/rake.rb +3 -0
  24. data/features/support/env.rb +9 -0
  25. data/lib/commands/add_metric.rb +17 -0
  26. data/lib/commands/add_project.rb +5 -0
  27. data/lib/commands/describe_project.rb +26 -0
  28. data/lib/commands/edit_property.rb +9 -0
  29. data/lib/commands/gli/add.rb +12 -0
  30. data/lib/commands/gli/add_metric.rb +23 -0
  31. data/lib/commands/gli/describe.rb +16 -0
  32. data/lib/commands/gli/edit_property.rb +22 -0
  33. data/lib/commands/gli/history.rb +19 -0
  34. data/lib/commands/gli/hooks.rb +8 -0
  35. data/lib/commands/gli/list.rb +14 -0
  36. data/lib/commands/gli/program.rb +5 -0
  37. data/lib/commands/list_projects.rb +32 -0
  38. data/lib/commands/view_history.rb +37 -0
  39. data/lib/console_formatter.rb +18 -0
  40. data/lib/gli_interface.rb +15 -0
  41. data/lib/metrics/boolean_metric.rb +7 -0
  42. data/lib/metrics/metric.rb +24 -0
  43. data/lib/metrics/number_metric.rb +7 -0
  44. data/lib/metrics/string_metric.rb +7 -0
  45. data/lib/project.rb +15 -0
  46. data/lib/project_catalog.rb +34 -0
  47. data/lib/property_value.rb +3 -0
  48. data/lib/storage/metric_mongo.rb +11 -0
  49. data/lib/storage/project_mongo.rb +9 -0
  50. data/lib/storage/property_value_mongo.rb +10 -0
  51. data/lib/tc/version.rb +9 -0
  52. data/lib/tc.rb +27 -0
  53. data/spec/integration/mongo_spec.rb +19 -0
  54. data/spec/lib/commands/add_metric_spec.rb +31 -0
  55. data/spec/lib/commands/add_project_spec.rb +10 -0
  56. data/spec/lib/commands/describe_project_spec.rb +42 -0
  57. data/spec/lib/commands/edit_property_spec.rb +19 -0
  58. data/spec/lib/commands/list_projects_spec.rb +47 -0
  59. data/spec/lib/commands/view_history_spec.rb +64 -0
  60. data/spec/lib/console_formatter_spec.rb +29 -0
  61. data/spec/lib/metrics/boolean_metric_spec.rb +9 -0
  62. data/spec/lib/metrics/metric_spec.rb +26 -0
  63. data/spec/lib/metrics/number_metric_spec.rb +8 -0
  64. data/spec/lib/metrics/string_metric_spec.rb +7 -0
  65. data/spec/lib/project_catalog_spec.rb +23 -0
  66. data/spec/lib/project_spec.rb +45 -0
  67. data/spec/lib/storage/metric_mongo_spec.rb +12 -0
  68. data/spec/lib/storage/project_mongo_spec.rb +11 -0
  69. data/spec/lib/storage/property_value_mongo_spec.rb +11 -0
  70. data/spec/spec_helper.rb +8 -0
  71. metadata +313 -0
@@ -0,0 +1,26 @@
1
+ class DescribeProject < Struct.new :project_name, :date_format
2
+ def call formatter = formatter, records = records
3
+ formatter.present records
4
+ end
5
+
6
+ def project project_name = project_name
7
+ Project.find_by name: project_name
8
+ end
9
+
10
+ def records project = project
11
+ project.metrics
12
+ end
13
+
14
+ def formatter format = format
15
+ ConsoleFormatter.new format
16
+ end
17
+
18
+ def format date_format = date_format
19
+ {
20
+ 'Metric' => 'name',
21
+ 'Value' => 'value',
22
+ 'Changed at' => "last_updated_at.strftime('#{date_format}')",
23
+ 'Changed by' => "last_updated_by"
24
+ }
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ class EditProperty < Struct.new :project_name, :metric_name, :value, :author
2
+ def call project = project, metric_name = metric_name, value = value, author = author
3
+ project.edit_property(metric_name, value, author)
4
+ end
5
+
6
+ def project project_name = project_name
7
+ Project.find_by(name: project_name)
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ class TcCli
2
+ desc "Adds new project"
3
+ long_desc "Allows to create new project"
4
+ arg_name '{project_name}'
5
+ command :add do |command|
6
+ command.action do |global, options, args|
7
+ name = args.first
8
+ puts "Project #{name} was successfully added." if AddProject.new(name).call
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,23 @@
1
+ class TcCli
2
+ desc "Adds new metric to the project"
3
+ long_desc "Allows to add new metric to the project"
4
+ arg_name '{metric_name}'
5
+ command :add_metric do |command|
6
+ command.arg_name 'project name'
7
+ command.desc 'name of the project, to which you want to add metric'
8
+ command.flag :pn, :project_name
9
+
10
+ command.arg_name 'metric type'
11
+ command.desc 'type of the metric you want to add (string, number, boolean)'
12
+ command.default_value "string"
13
+ command.flag :mt, :metric_type
14
+
15
+ command.action do |global, options, args|
16
+ project_name = options[:project_name]
17
+ metric_type = options[:metric_type]
18
+ metric_name = args.first
19
+ puts "Metric #{metric_name} with type #{metric_type} was successfully added to project #{project_name}." if AddMetric.new(project_name, metric_type, metric_name).call
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,16 @@
1
+ class TcCli
2
+ desc "Describes current state of the project"
3
+ long_desc "Displays current metrics of the project, date and author of last modification"
4
+ arg_name '{project_name}'
5
+ command :describe do |command|
6
+ command.arg_name 'date format'
7
+ command.desc 'ruby date format to display time of change'
8
+ command.default_value "%d-%m-%Y"
9
+ command.flag :df, :date_format
10
+
11
+ command.action do |global, options, args|
12
+ puts DescribeProject.new(args.first, options[:date_format]).call
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,22 @@
1
+ class TcCli
2
+ desc "Edits one metric of the project"
3
+ long_desc "Allows to edit one of the project's metrics"
4
+ arg_name '{value}'
5
+ command :edit_property do |command|
6
+ command.arg_name 'project name'
7
+ command.desc 'name of the project, property of which you want to edit'
8
+ command.flag :pn, :project_name
9
+
10
+ command.arg_name 'metric name'
11
+ command.desc 'name of the metric, which you want to edit'
12
+ command.flag :mn, :metric_name
13
+
14
+ command.action do |global, options, args|
15
+ project_name = options[:project_name]
16
+ metric_name = options[:metric_name]
17
+ value = args.first
18
+ puts "Metric #{metric_name} in project #{project_name} now has value #{value}." if EditProperty.new(project_name, metric_name, value, `whoami`).call
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,19 @@
1
+ class TcCli
2
+ desc "Displays property history"
3
+ long_desc "Displays one or more properties history (list of values over time)"
4
+ arg_name '[{property}],[{another_property}],[...]'
5
+ command :history do |command|
6
+ command.arg_name 'project name'
7
+ command.desc 'name of the project, property of which you want to edit'
8
+ command.flag :pn, :project_name
9
+
10
+ command.arg_name 'date format'
11
+ command.desc 'ruby date format to display time of change'
12
+ command.default_value "%d-%m-%Y"
13
+ command.flag :df, :date_format
14
+
15
+ command.action do |global, options, args|
16
+ puts ViewHistory.new(options[:project_name], options[:date_format], args.first.try(:split, ',')).call
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ class TcCli
2
+ pre do
3
+ ENV['RACK_ENV'] ||= 'production'
4
+ require 'hirb'
5
+ require 'tc'
6
+ true
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ class TcCli
2
+ desc "List projects from catalog"
3
+ long_desc "Lists projects from catalog matching specified criteria"
4
+ arg_name '{criteria}'
5
+ command :list do |command|
6
+ command.arg_name 'table format'
7
+ command.desc 'ruby code for table format'
8
+ command.flag :fm, :format
9
+
10
+ command.action do |global, options, args|
11
+ puts ListProjects.new(options[:format], args.first).call
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class TcCli
2
+ program_desc 'Command Line Interface for Technical Coordination'
3
+ version '0.0.1'
4
+
5
+ end
@@ -0,0 +1,32 @@
1
+ class ListProjects < Struct.new :format, :criteria
2
+ def call formatter = formatter, projects = projects
3
+ formatter.present projects
4
+ end
5
+
6
+ def projects catalog = ProjectCatalog.load, criteria = criteria
7
+ catalog.projects criteria
8
+ end
9
+
10
+ def formatter(format = format.present? ? eval(format) : default_format)
11
+ ConsoleFormatter.new format
12
+ end
13
+
14
+ def default_format projects = projects
15
+ {'Name' => 'name'}.merge list_properties_format properties
16
+ end
17
+
18
+ private
19
+
20
+ def list_properties_format properties
21
+ Hash[properties.map { |p| [p, "property('#{p}').try(:value)"] }]
22
+ end
23
+
24
+ def properties
25
+ metrics.collect(&:name).flatten
26
+ end
27
+
28
+ def metrics
29
+ projects.collect(&:metrics).flatten
30
+ end
31
+
32
+ end
@@ -0,0 +1,37 @@
1
+ class ViewHistory < Struct.new :project_name, :date_format, :properties
2
+ def call formatter = formatter, records = records
3
+ formatter.present records
4
+ end
5
+
6
+ def project project_name = project_name
7
+ Project.find_by name: project_name
8
+ end
9
+
10
+ def records metrics = metrics
11
+ metrics.collect(&:values).flatten
12
+ end
13
+
14
+ def formatter format = format
15
+ ConsoleFormatter.new format
16
+ end
17
+
18
+ def properties project = project
19
+ self[:properties].present? ? self[:properties] : project.metrics.collect(&:name)
20
+ end
21
+
22
+ def metrics project = project, properties = properties
23
+ properties.map do |property|
24
+ project.property property
25
+ end.compact
26
+ end
27
+
28
+ def format date_format = date_format, metrics = metrics
29
+ {'Date' => "created_at.strftime('#{date_format}')"}.merge list_metrics_format(metrics)
30
+ end
31
+
32
+ private
33
+
34
+ def list_metrics_format metrics
35
+ Hash[metrics.map { |m| [m.name, "metric.name == '#{m.name}' ? value : ''"] }]
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ class ConsoleFormatter < Struct.new :format
2
+
3
+ def present records, format = format
4
+ Hirb::Helpers::AutoTable.render(render_each(records), fields: format.keys, resize: false)
5
+ end
6
+
7
+ def render_each records
8
+ records.map { |record|
9
+ render_record record
10
+ }
11
+ end
12
+
13
+ def render_record record, format = format
14
+ format.merge(format) do |_, expression|
15
+ record.instance_eval expression
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require 'gli'
2
+ ENV["COLUMNS"] = "1000"
3
+ ENV["LINES"] = "1000"
4
+
5
+ $:.unshift File.expand_path(File.dirname(File.realpath(__FILE__)))
6
+
7
+ class TcCli
8
+ extend GLI::App
9
+ commands_from 'commands/gli'
10
+
11
+ def self.exec argv
12
+ commands[:help] = GLI::Commands::Help.new(self)
13
+ run(argv)
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ class BooleanMetric < Metric
2
+
3
+ def convert value
4
+ !!value
5
+ end
6
+
7
+ end
@@ -0,0 +1,24 @@
1
+ class Metric
2
+
3
+ def edit value, user
4
+ values << PropertyValue.new(value: convert(value), created_by: user)
5
+ self
6
+ end
7
+
8
+ def value
9
+ values.last.try :value
10
+ end
11
+
12
+ def last_updated_by
13
+ values.last.try :created_by
14
+ end
15
+
16
+ def last_updated_at
17
+ values.last.try :created_at
18
+ end
19
+
20
+ def convert value
21
+ value
22
+ end
23
+
24
+ end
@@ -0,0 +1,7 @@
1
+ class NumberMetric < Metric
2
+
3
+ def convert value
4
+ value.to_f
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ class StringMetric < Metric
2
+
3
+ def convert value
4
+ value.to_s
5
+ end
6
+
7
+ end
data/lib/project.rb ADDED
@@ -0,0 +1,15 @@
1
+ class Project
2
+ def add_metric metric
3
+ metrics << metric
4
+ self
5
+ end
6
+
7
+ def edit_property name, value, user
8
+ property(name).edit value, user
9
+ self
10
+ end
11
+
12
+ def property name
13
+ metrics.find_by(name: name)
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ class ProjectCatalog
2
+
3
+ def initialize
4
+ @projects = []
5
+ end
6
+
7
+ def projects criteria = nil
8
+ result = @projects.sort_by(&:name)
9
+
10
+ return result if criteria.blank?
11
+ result.find_all { |project|
12
+ project.instance_eval criteria
13
+ }
14
+ end
15
+
16
+ def add_project project
17
+ @projects << project
18
+ self
19
+ end
20
+
21
+ def save
22
+ @projects.all? &:save
23
+ self
24
+ end
25
+
26
+ def load
27
+ @projects = Project.all
28
+ self
29
+ end
30
+
31
+ def self.load
32
+ self.new.load
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ class PropertyValue
2
+
3
+ end
@@ -0,0 +1,11 @@
1
+ class Metric
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ field :name
6
+ validates :name, uniqueness: true
7
+
8
+ embedded_in :project
9
+
10
+ embeds_many :values, class_name: 'PropertyValue', cascade_callbacks: true
11
+ end
@@ -0,0 +1,9 @@
1
+ class Project
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ field :name
6
+ validates :name, uniqueness: true
7
+
8
+ embeds_many :metrics, cascade_callbacks: true
9
+ end
@@ -0,0 +1,10 @@
1
+ class PropertyValue
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ field :value
6
+ field :created_by
7
+ validates :created_by, presence: true
8
+
9
+ embedded_in :metric
10
+ end
data/lib/tc/version.rb ADDED
@@ -0,0 +1,9 @@
1
+ class TC
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ PATCH = 1
6
+
7
+ STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
+ end
9
+ end
data/lib/tc.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'hirb'
2
+ require 'mongoid'
3
+
4
+ config_folder = File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../config')
5
+ `cp #{config_folder}/mongoid.yml.sample #{config_folder}/mongoid.yml` unless File.exists? "#{config_folder}/mongoid.yml"
6
+
7
+ Mongoid.load!("#{config_folder}/mongoid.yml")
8
+
9
+ $: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
10
+
11
+ require 'project'
12
+ require 'property_value'
13
+ require 'project_catalog'
14
+ require 'console_formatter'
15
+ require 'metrics/metric'
16
+ require 'metrics/number_metric'
17
+ require 'metrics/string_metric'
18
+ require 'metrics/boolean_metric'
19
+ require 'commands/list_projects'
20
+ require 'commands/add_project'
21
+ require 'commands/add_metric'
22
+ require 'commands/edit_property'
23
+ require 'commands/view_history'
24
+ require 'commands/describe_project'
25
+ require 'storage/metric_mongo'
26
+ require 'storage/project_mongo'
27
+ require 'storage/property_value_mongo'
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProjectCatalog do
4
+
5
+ before { Project.delete_all }
6
+
7
+ subject {
8
+ ProjectCatalog.new.
9
+ add_project(Project.new(name: '1').add_metric(NumberMetric.new(name: 'length')).edit_property('length', 3, 'user')).
10
+ add_project(Project.new(name: '2').add_metric(NumberMetric.new(name: 'length')).edit_property('length', 2, 'user')).
11
+ add_project(Project.new(name: '3').add_metric(NumberMetric.new(name: 'length')).edit_property('length', 1, 'user'))
12
+ }
13
+
14
+ it 'loads list of saved projects' do
15
+ subject.save
16
+
17
+ ProjectCatalog.load.projects.should have_exactly(3).items
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe AddMetric do
4
+ it 'adds metric to the project' do
5
+ result = stub(:result)
6
+ metric = stub(:metric)
7
+ project = stub(:project).tap { |p| p.should_receive(:add_metric).with(metric) { result } }
8
+ subject.call(project, metric)
9
+ end
10
+
11
+ it 'find project' do
12
+ result = stub(:result)
13
+ project_name = stub(:project_name)
14
+ Project.should_receive(:find_by).with(name: project_name) { result }
15
+ subject.project(project_name).should == result
16
+ end
17
+
18
+ it 'creates appropriate metric class' do
19
+ subject.metric_class(:string).should == StringMetric
20
+ subject.metric_class(:number).should == NumberMetric
21
+ subject.metric_class(nil).should == Metric
22
+ end
23
+
24
+ it 'creates metric' do
25
+ result = stub(:result)
26
+ name = stub(:name)
27
+ metric_class = stub(:metric_class).tap { |mc| mc.should_receive(:new).with(name: name) { result } }
28
+
29
+ subject.metric(metric_class, name).should == result
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe AddProject do
4
+ it 'adds new project to the database' do
5
+ name = stub(:name)
6
+ project = stub(:project)
7
+ Project.should_receive(:create).with(name: name) { project }
8
+ subject.call(name).should == project
9
+ end
10
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe DescribeProject do
4
+ it 'presents properties to table view' do
5
+ result = stub(:result)
6
+
7
+ records = stub(:records)
8
+ formatter = stub(:formatter).tap { |f| f.should_receive(:present).with(records) { result } }
9
+
10
+ subject.call(formatter, records).should == result
11
+ end
12
+
13
+ it 'passes format to console formatter' do
14
+ format = stub(:format)
15
+ ConsoleFormatter.should_receive(:new).with(format)
16
+
17
+ subject.formatter(format)
18
+ end
19
+
20
+ it 'collects metrics from project' do
21
+ metrics = stub(:metrics)
22
+ project = stub(:project, :metrics => metrics)
23
+ subject.records(project).should == metrics
24
+ end
25
+
26
+ it 'constructs format for console formatter' do
27
+ subject.format("DATE_FORMAT").should == {
28
+ 'Metric' => 'name',
29
+ 'Value' => 'value',
30
+ 'Changed at' => "last_updated_at.strftime('DATE_FORMAT')",
31
+ 'Changed by' => "last_updated_by"
32
+ }
33
+ end
34
+
35
+ it 'finds project by its name' do
36
+ name, result = stub(:name), stub(:result)
37
+ Project.should_receive(:find_by).with(name: name) { result }
38
+
39
+ subject.project(name).should == result
40
+ end
41
+
42
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe EditProperty do
4
+ it 'edits property of the project' do
5
+ result = stub(:result)
6
+ metric_name = stub(:metric_name)
7
+ value = stub(:value)
8
+ author = stub(:author)
9
+ project = stub(:project).tap { |p| p.should_receive(:edit_property).with(metric_name, value, author) { result } }
10
+ subject.call(project, metric_name, value, author).should == result
11
+ end
12
+
13
+ it 'find project' do
14
+ result = stub(:result)
15
+ project_name = stub(:project_name)
16
+ Project.should_receive(:find_by).with(name: project_name) { result }
17
+ subject.project(project_name).should == result
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe ListProjects do
4
+ it 'passes format to console formatter' do
5
+ format = '42'
6
+ ConsoleFormatter.should_receive(:new).with(eval(format))
7
+
8
+ ListProjects.new(format).formatter
9
+ end
10
+
11
+ it 'gets list of projects from catalog according to criteria' do
12
+ result = stub(:result)
13
+
14
+ criteria = stub(:criteria)
15
+ catalog = stub(:catalog).tap { |c| c.should_receive(:projects).with(criteria) { result } }
16
+
17
+ subject.projects(catalog, criteria).should == result
18
+ end
19
+
20
+ it 'presents projects to table view' do
21
+ result = stub(:result)
22
+
23
+ projects = stub(:projects)
24
+ formatter = stub(:formatter).tap { |f| f.should_receive(:present).with(projects) { result } }
25
+
26
+ subject.call(formatter, projects).should == result
27
+ end
28
+
29
+ it 'uses default format if no format string was specified' do
30
+ result = stub(:result)
31
+
32
+ command = ListProjects.new(nil)
33
+ command.should_receive(:default_format) { result }
34
+
35
+ command.formatter
36
+ end
37
+
38
+ it 'builds default format as list of all metrics for all projects' do
39
+ subject.stub(:projects => [
40
+ stub(:metrics => [stub(:name => 'metric1'), stub(:name => 'metric2')]),
41
+ stub(:metrics => [stub(:name => 'metric3')])])
42
+ subject.default_format.should == {'Name' => 'name',
43
+ 'metric1' => "property('metric1').try(:value)",
44
+ 'metric2' => "property('metric2').try(:value)",
45
+ 'metric3' => "property('metric3').try(:value)"}
46
+ end
47
+ end