locabulary 0.3.1 → 0.5.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.
@@ -52,9 +52,10 @@ module Locabulary
52
52
 
53
53
  def initialize(attributes = {})
54
54
  attribute_names.each do |key|
55
- value = attributes.fetch(key) { attributes.fetch(key.to_s, nil) }
55
+ value = attributes[key] || attributes[key.to_s]
56
56
  send("#{key}=", value)
57
57
  end
58
+ @children = []
58
59
  end
59
60
 
60
61
  def to_h
@@ -89,9 +90,9 @@ module Locabulary
89
90
 
90
91
  def <=>(other)
91
92
  predicate_name_sort = predicate_name <=> other.predicate_name
92
- return predicate_name_sort unless predicate_name_sort == 0
93
+ return predicate_name_sort unless predicate_name_sort.zero?
93
94
  presentation_sequence_sort = presentation_sequence <=> other.presentation_sequence
94
- return presentation_sequence_sort unless presentation_sequence_sort == 0
95
+ return presentation_sequence_sort unless presentation_sequence_sort.zero?
95
96
  term_label <=> other.term_label
96
97
  end
97
98
 
@@ -100,6 +101,45 @@ module Locabulary
100
101
  def presentation_sequence
101
102
  default_presentation_sequence || SORT_SEQUENCE_FOR_NIL
102
103
  end
104
+
105
+ def children
106
+ @children.sort
107
+ end
108
+
109
+ def add_child(*input)
110
+ @children += input
111
+ end
112
+
113
+ HIERARCHY_DELIMITER = '::'.freeze
114
+ def slugs
115
+ term_label.split(HIERARCHY_DELIMITER)
116
+ end
117
+
118
+ def self.hierarchy_delimiter
119
+ HIERARCHY_DELIMITER
120
+ end
121
+
122
+ def parent_slugs
123
+ slugs[0..-2]
124
+ end
125
+
126
+ def parent_term_label
127
+ parent_slugs.join(HIERARCHY_DELIMITER)
128
+ end
129
+
130
+ def root_slug
131
+ slugs[0]
132
+ end
133
+
134
+ def selectable?
135
+ children.count.zero?
136
+ end
137
+
138
+ def selectable_label
139
+ slugs[-1]
140
+ end
141
+
142
+ alias selectable_id id
103
143
  end
104
144
  end
105
145
  end
@@ -1,8 +1,7 @@
1
- require "google/api_client"
2
1
  require "google_drive"
3
2
  require 'highline/import'
4
- require 'locabulary'
5
- require 'locabulary/items'
3
+ require 'locabulary/utility'
4
+ require 'locabulary/item'
6
5
  require 'json'
7
6
 
8
7
  module Locabulary
@@ -11,7 +10,7 @@ module Locabulary
11
10
  def initialize(document_key, predicate_name, data_fetcher = default_data_fetcher)
12
11
  @document_key = document_key
13
12
  @predicate_name = predicate_name
14
- @output_filepath = Locabulary.filename_for_predicate_name(predicate_name: predicate_name)
13
+ @output_filepath = Utility.filename_for_predicate_name(predicate_name)
15
14
  @data_fetcher = data_fetcher
16
15
  end
17
16
 
@@ -54,7 +53,7 @@ module Locabulary
54
53
 
55
54
  def convert_to_json(data)
56
55
  json_array = data.map do |row|
57
- Locabulary::Items.build(row).to_h
56
+ Locabulary::Item.build(row).to_h
58
57
  end
59
58
  @json_data = JSON.pretty_generate("predicate_name" => predicate_name, "values" => json_array)
60
59
  end
@@ -4,14 +4,14 @@ require 'dry/validation/schema'
4
4
  module Locabulary
5
5
  # Responsible for providing a defined and clear schema for each of the locabulary items.
6
6
  Schema = Dry::Validation.Schema do
7
- key(:predicate_name).required(format?: /\A[a-z_]+\Z/)
8
- key(:values).each do
9
- key(:term_label).required(:str?)
7
+ required(:predicate_name).filled(format?: /\A[a-z_]+\Z/)
8
+ required(:values).each do
9
+ required(:term_label).filled(:str?)
10
10
  optional(:description).maybe(:str?)
11
11
  optional(:grouping).maybe(:str?)
12
12
  optional(:affiliation).maybe(:str?)
13
13
  optional(:default_presentation_sequence).maybe(:int?)
14
- key(:activated_on).required(format?: /\A\d{4}-\d{2}-\d{2}\Z/)
14
+ required(:activated_on).filled(format?: /\A\d{4}-\d{2}-\d{2}\Z/)
15
15
  optional(:deactivated_on).maybe(format?: /\A\d{4}-\d{2}-\d{2}\Z/)
16
16
  end
17
17
  end
@@ -0,0 +1,42 @@
1
+ require 'date'
2
+ require 'json'
3
+ require 'locabulary/exceptions'
4
+
5
+ # :nodoc:
6
+ module Locabulary
7
+ # A service module providing methods that are common and useful for querying the
8
+ # underlying data sources.
9
+ module Utility
10
+ DATA_DIRECTORY = File.expand_path("../../../data/", __FILE__).freeze
11
+ def self.with_active_extraction_for(predicate_name, as_of)
12
+ json = JSON.parse(File.read(filename_for_predicate_name(predicate_name)))
13
+ json.fetch('values').each do |data|
14
+ yield(data) if data_is_active?(data, as_of)
15
+ end
16
+ end
17
+
18
+ def self.with_extraction_for(predicate_name)
19
+ json = JSON.parse(File.read(filename_for_predicate_name(predicate_name)))
20
+ json.fetch('values').each do |data|
21
+ yield(data)
22
+ end
23
+ end
24
+
25
+ def self.data_is_active?(data, as_of)
26
+ activated_on = Date.parse(data.fetch('activated_on'))
27
+ return false unless activated_on < as_of
28
+ deactivated_on_value = data['deactivated_on']
29
+ return true if deactivated_on_value.nil?
30
+ deactivated_on = Date.parse(deactivated_on_value)
31
+ return false unless deactivated_on >= as_of
32
+ true
33
+ end
34
+
35
+ def self.filename_for_predicate_name(predicate_name)
36
+ filename = File.join(DATA_DIRECTORY, "#{File.basename(predicate_name)}.json")
37
+ return filename if File.exist?(filename)
38
+ raise Locabulary::Exceptions::MissingPredicateNameError, "Unable to find predicate_name: #{predicate_name}"
39
+ end
40
+ end
41
+ private_constant :Utility
42
+ end
@@ -1,3 +1,3 @@
1
1
  module Locabulary
2
- VERSION = '0.3.1'.freeze
2
+ VERSION = '0.5.0'.freeze
3
3
  end
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_dependency "json", "~> 1.8"
23
23
  spec.add_dependency "dry-configurable"
24
- spec.add_dependency "hanami-utils"
24
+ spec.add_dependency "activesupport", '~>4.0'
25
25
 
26
26
  spec.add_development_dependency "dry-validation"
27
27
  spec.add_development_dependency "bundler"
@@ -30,8 +30,9 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "rake", "~> 10.0"
31
31
  spec.add_development_dependency 'google_drive'
32
32
  spec.add_development_dependency 'highline'
33
- spec.add_development_dependency "activesupport", "~>4.0"
34
33
  spec.add_development_dependency "rubocop"
35
34
  spec.add_development_dependency "simplecov"
36
35
  spec.add_development_dependency "codeclimate-test-reporter"
36
+ spec.add_development_dependency "fasterer"
37
+ spec.add_development_dependency "shoulda-matchers"
37
38
  end
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env sh
2
+ git log --reverse --pretty="format:## %s%n%n@%H%n%n%b" master..
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env ruby -wU
2
+
3
+ #*******************************************************************************
4
+ #
5
+ # CONFIGURATION OPTIONS
6
+ #
7
+ #*******************************************************************************
8
+
9
+ CONFIG_KEYS = [:REMOTE, :REPOSITORY_PATH, :STARTED_ISSUES_FILE].freeze
10
+
11
+ REPOSITORY_PATH = ENV.fetch('REPOSITORY_PATH') { File.expand_path(File.join(File.dirname(__FILE__), '../')) }
12
+ REMOTE = ENV.fetch('REMOTE', 'origin')
13
+
14
+ # TODO: Retrieve the dasherized issue from Github's API
15
+ STARTED_ISSUES_FILE = ENV.fetch('STARTED_ISSUES_FILE', '.started-issues')
16
+
17
+ #*******************************************************************************
18
+ #
19
+ # HELP OPTIONS
20
+ #
21
+ #*******************************************************************************
22
+
23
+ if ARGV.grep(/-h/i).size == 1
24
+ $stdout.puts ""
25
+ $stdout.puts "$ ./#{File.basename(__FILE__)} 123"
26
+ $stdout.puts ""
27
+ $stdout.puts "This script will close the current issue branch."
28
+ $stdout.puts ""
29
+ $stdout.puts "* Remove the started issue from the #{STARTED_ISSUES_FILE}"
30
+ $stdout.puts "* Make a commit closing the issue"
31
+ $stdout.puts ""
32
+ $stdout.puts "Note: There are steps to insure you have a clean working directory."
33
+ $stdout.puts "Note: If you have spaces in your configuration all bets are off!"
34
+ $stdout.puts ""
35
+ $stdout.puts "Current Configuration:"
36
+ CONFIG_KEYS.each do |key|
37
+ $stdout.puts "\t#{key}='#{Object.const_get(key)}'"
38
+ end
39
+ $stdout.puts ""
40
+ $stdout.puts "You can override the configuration option by adding the corresponding"
41
+ $stdout.puts "ENV variable."
42
+ $stdout.puts ""
43
+ $stdout.puts "Example:"
44
+ $stdout.puts "$ REMOTE=origin ./scripts/#{File.basename(__FILE__)}"
45
+ exit(0)
46
+ end
47
+
48
+ #*******************************************************************************
49
+ #
50
+ # GUARD
51
+ #
52
+ #*******************************************************************************
53
+
54
+ # Guard that directories exist
55
+ [:REPOSITORY_PATH].each do |key|
56
+ repository_path = Object.const_get(key)
57
+ unless File.directory?(repository_path)
58
+ $stderr.puts "Expected directory for #{key} @ #{repository_path} to exist.\n\n"
59
+ $stderr.puts "See help for details on specifying #{key}.\n\n"
60
+ $stderr.puts "$ ./#{File.basename(__FILE__)} -h"
61
+ exit!(1)
62
+ end
63
+ end
64
+
65
+ # Guard that I know what the issue number is
66
+ current_branch = `cd #{REPOSITORY_PATH} && git branch | grep '^*'`.sub(/^\*\s*/, '').strip
67
+
68
+ if current_branch =~ /^(\d+)[^\d]/
69
+ ISSUE_NUMBER = $1.to_i
70
+ else
71
+ $stderr.puts "Expected to be able to determine issue number from branch #{current_branch} name @ #{REPOSITORY_PATH}.\n\n"
72
+ $stderr.puts "See help for more information.\n\n"
73
+ $stderr.puts "$ ./#{File.basename(__FILE__)} -h"
74
+ exit!(2)
75
+ end
76
+
77
+ # Guard that we have a clean working directory
78
+ if `cd #{REPOSITORY_PATH} && git status --porcelain`.strip.size > 0
79
+ $stderr.puts "Repository @ #{REPOSITORY_PATH} did not have a clean working directory"
80
+ exit!(3)
81
+ end
82
+
83
+ # Guard that the tests all pass
84
+ unless system("cd #{REPOSITORY_PATH} && bundle exec rake")
85
+ $stderr.puts "Your tests failed. Please review the above output."
86
+ exit!(4)
87
+ end
88
+
89
+ #*******************************************************************************
90
+ #
91
+ # DO STUFF
92
+ #
93
+ #*******************************************************************************
94
+
95
+ started_issues_file_lines = File.read(File.join(REPOSITORY_PATH, STARTED_ISSUES_FILE)).split("\n")
96
+
97
+ File.open(STARTED_ISSUES_FILE, 'w+') do |file|
98
+ started_issues_file_lines.each do |line|
99
+ file.puts line unless line.to_i == ISSUE_NUMBER.to_i
100
+ end
101
+ end
102
+
103
+ `cd #{REPOSITORY_PATH}; git add #{STARTED_ISSUES_FILE}`
104
+
105
+ path_to_commit_message = File.expand_path(File.join(REPOSITORY_PATH, '../COMMIT.msg'))
106
+ begin
107
+ File.open(path_to_commit_message, 'w+') do |file|
108
+ file.puts "Closing issue #{ISSUE_NUMBER}"
109
+ file.puts ""
110
+ file.puts "Closes ##{ISSUE_NUMBER}"
111
+ file.puts ""
112
+
113
+ message = "$ ./script/#{File.basename(__FILE__)} #{ISSUE_NUMBER}"
114
+ CONFIG_KEYS.each_with_object(message) do |key, mem|
115
+ if ENV.key?(key.to_s)
116
+ mem = "#{key}=\"#{ENV[key.to_s].to_s}\" #{mem}"
117
+ end
118
+ mem
119
+ end
120
+ file.puts message
121
+ end
122
+ $stdout.puts `cd #{REPOSITORY_PATH} && git commit -F #{path_to_commit_message}`
123
+ ensure
124
+ File.unlink(path_to_commit_message) rescue true
125
+ end
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env ruby -wU
2
+
3
+ #*******************************************************************************
4
+ #
5
+ # INTENT
6
+ #
7
+ # This script codifies the "moment" that someone starts "working" on an issue.
8
+ # The goal is leveraging git to capture both the opening and closing moments
9
+ # From those moments we can compute the elapsed time for resolving an issue.
10
+ #
11
+ # The script is "close" to where the developers are working, and is hopefully
12
+ # easier to incorporate with developer workflow. Without using this script, we
13
+ # have less precise "starting moment", if at all.
14
+ #
15
+ # The result is that we can now report two time-based attributes for issues:
16
+ #
17
+ # * Elapsed time: How long an issue took to complete
18
+ # * Estimated effort: How much time we think it will take to complete; This is
19
+ # not elapsed time but instead an amount of focused time.
20
+ #
21
+ # The conjecture is given those two time-based attributes we can begin
22
+ # improving the interpretation of our Feature estimates.
23
+ #
24
+ # Features will likely be comprised of a collection of issues; The estimate for
25
+ # a feature is much less precise and I believe is equal parts "Estimated Effort"
26
+ # and "Estimated Complexity".
27
+ #
28
+ #*******************************************************************************
29
+
30
+ #*******************************************************************************
31
+ #
32
+ # CONFIGURATION OPTIONS
33
+ #
34
+ #*******************************************************************************
35
+
36
+ CONFIG_KEYS = [:REMOTE, :FROM_BRANCH, :REPOSITORY_PATH, :ISSUE_TITLE, :STARTED_ISSUES_FILE].freeze
37
+
38
+ REPOSITORY_PATH = ENV.fetch('REPOSITORY_PATH') { File.expand_path(File.join(File.dirname(__FILE__), '../')) }
39
+ REMOTE = ENV.fetch('REMOTE', 'origin')
40
+ FROM_BRANCH = ENV.fetch('FROM_BRANCH', 'master')
41
+
42
+ issue_title_fetcher = lambda do
43
+ begin
44
+ remote_url = `cd #{REPOSITORY_PATH} && git config --get remote.#{REMOTE}.url`.strip
45
+ match = remote_url.match(/(\w+)\/(\w+)(?:\.git)?\Z/)
46
+ if match
47
+ require 'open-uri'
48
+ require 'json'
49
+ owner, repository = match.captures
50
+ document = open("https://api.github.com/repos/#{owner}/#{repository}/issues/#{ISSUE_NUMBER}.json").read
51
+ json = JSON.parse(document)
52
+ json.fetch('title').gsub(/\W+/, '-')
53
+ else
54
+ 'issue-on-github'
55
+ end
56
+ rescue
57
+ 'issue-on-github'
58
+ end
59
+ end
60
+
61
+ # TODO: Retrieve the dasherized issue from Github's API
62
+ ISSUE_TITLE = ENV.fetch('ISSUE_TITLE', issue_title_fetcher)
63
+ STARTED_ISSUES_FILE = ENV.fetch('STARTED_ISSUES_FILE', '.started-issues')
64
+
65
+ #*******************************************************************************
66
+ #
67
+ # HELP OPTIONS
68
+ #
69
+ #*******************************************************************************
70
+
71
+ if ARGV.grep(/-h/i).size == 1
72
+ $stdout.puts ""
73
+ $stdout.puts "$ ./#{File.basename(__FILE__)} 123"
74
+ $stdout.puts ""
75
+ $stdout.puts "This script will create an issue branch and update the remote repository."
76
+ $stdout.puts ""
77
+ $stdout.puts "* Create a new branch for the given issue number"
78
+ $stdout.puts "* Touch and append the issue number to a tracking file"
79
+ $stdout.puts "* Write a rudimentary commit message"
80
+ $stdout.puts "* Push that commit up to #{REMOTE}"
81
+ $stdout.puts ""
82
+ $stdout.puts "Note: There are steps to insure you have a clean working directory."
83
+ $stdout.puts "Note: If you have spaces in your configuration all bets are off!"
84
+ $stdout.puts ""
85
+ $stdout.puts "Current Configuration:"
86
+ CONFIG_KEYS.each do |key|
87
+ $stdout.puts "\t#{key}='#{Object.const_get(key)}'"
88
+ end
89
+ $stdout.puts ""
90
+ $stdout.puts "You can override the configuration option by adding the corresponding"
91
+ $stdout.puts "ENV variable."
92
+ $stdout.puts ""
93
+ $stdout.puts "Example:"
94
+ $stdout.puts "$ REMOTE=origin ./scripts/#{File.basename(__FILE__)}"
95
+ exit(0)
96
+ end
97
+
98
+ #*******************************************************************************
99
+ #
100
+ # GUARD
101
+ #
102
+ #*******************************************************************************
103
+
104
+ # Guard that I have an issue number
105
+
106
+ ISSUE_NUMBER = ARGV.shift
107
+
108
+ unless ISSUE_NUMBER =~ /^\d+$/
109
+ $stderr.puts "Expected first parameter to be an issue number for REPOSITORY.\n\n"
110
+ $stderr.puts "See help for details on specifying an issue number.\n\n"
111
+ $stderr.puts "$ ./#{File.basename(__FILE__)} -h"
112
+ exit!(1)
113
+ end
114
+
115
+ # Capture the issue_title
116
+ issue_title = ISSUE_TITLE.respond_to?(:call) ? ISSUE_TITLE.call : ISSUE_TITLE
117
+
118
+ # Guard that directories exist
119
+ [:REPOSITORY_PATH].each do |key|
120
+ repository_path = Object.const_get(key)
121
+ unless File.directory?(repository_path)
122
+ $stderr.puts "Expected directory for #{key} @ #{repository_path} to exist.\n\n"
123
+ $stderr.puts "See help for details on specifying #{key}.\n\n"
124
+ $stderr.puts "$ ./#{File.basename(__FILE__)} -h"
125
+ exit!(2)
126
+ end
127
+ end
128
+
129
+ # Guard that we have a clean working directory
130
+ if `cd #{REPOSITORY_PATH} && git status --porcelain`.strip.size > 0
131
+ $stderr.puts "Repository @ #{REPOSITORY_PATH} did not have a clean working directory"
132
+ exit!(3)
133
+ end
134
+
135
+ #*******************************************************************************
136
+ #
137
+ # DO STUFF
138
+ #
139
+ #*******************************************************************************
140
+
141
+ `cd #{REPOSITORY_PATH} && git checkout #{FROM_BRANCH}`
142
+ `cd #{REPOSITORY_PATH} && git pull --rebase`
143
+
144
+ TO_BRANCH = "#{ISSUE_NUMBER}-#{issue_title.gsub(/\W+/, '-')}"
145
+ if `cd #{REPOSITORY_PATH} && git branch -l | grep '#{TO_BRANCH}$'`.strip.size > 0
146
+ $stderr.puts "ERROR: Branch #{TO_BRANCH} already exists"
147
+ exit!(4)
148
+ end
149
+
150
+ `cd #{REPOSITORY_PATH} && git checkout -b #{TO_BRANCH} && echo "#{ISSUE_NUMBER}" >> #{STARTED_ISSUES_FILE} && git add #{STARTED_ISSUES_FILE}`
151
+
152
+ path_to_commit_message = File.expand_path(File.join(REPOSITORY_PATH, '../COMMIT.msg'))
153
+ begin
154
+ File.open(path_to_commit_message, 'w+') do |file|
155
+ file.puts "Claiming issue #{ISSUE_NUMBER}"
156
+ file.puts ""
157
+ file.puts "relates to ##{ISSUE_NUMBER}"
158
+ file.puts ""
159
+
160
+ message = "$ ./script/#{File.basename(__FILE__)} #{ISSUE_NUMBER}"
161
+ CONFIG_KEYS.each_with_object(message) do |key, mem|
162
+ if ENV.key?(key.to_s)
163
+ mem = "#{key}=\"#{ENV[key.to_s].to_s}\" #{mem}"
164
+ end
165
+ mem
166
+ end
167
+ file.puts message
168
+ file.puts ""
169
+ file.puts "[skip ci]"
170
+ end
171
+ $stdout.puts `cd #{REPOSITORY_PATH} && git commit -F #{path_to_commit_message}`
172
+ ensure
173
+ File.unlink(path_to_commit_message) rescue true
174
+ end