protopack 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .#*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in protopack.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 conanite
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # Protopack
2
+
3
+ Protopack knows how to load object definitions (prototypes) from packages of yml files and apply
4
+ them to your object repository. It's built to work seamslessly with ActiveRecord but doesn't depend
5
+ on any component of Rails.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'protopack'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install protopack
20
+
21
+ ## Usage
22
+
23
+ Store package items in a directory structure such as this:
24
+
25
+ config
26
+ |- packages
27
+ |- this-package
28
+ | |- package-config.yml
29
+ | |- this-first-item.yml
30
+ | |- this-second-item.yml
31
+ | |- this-third-item.yml
32
+ |- that-package
33
+ |- package-config.yml
34
+ |- that-first-item.yml
35
+ |- that-second-item.yml
36
+ |- that-third-item.yml
37
+
38
+ Each "item" file stores attributes of the prototypes you want to be able to create.
39
+
40
+ Protopack::Package distinguishes between items that already exist, and items that are missing.
41
+
42
+ In order to add only missing items to your repository, call
43
+
44
+ package.apply_missing
45
+
46
+ In order to apply all package items, overwriting existing items in your repository and adding any
47
+ missing items, call
48
+
49
+ package.apply_all
50
+
51
+ Protopack expects your models to implement a class method #existence to provide the interface for
52
+ querying and creating objects. The expected interface is the same as that returned by Rails'
53
+ ActiveRelation, so it works seamlessly with ActiveRecord.
54
+
55
+ exists = YourModel.existence(attributes)
56
+
57
+ exists.empty? # => boolean
58
+
59
+ exists.create!(attributes) # => create an instance using the given attributes
60
+
61
+ exists.first.update_attributes(attributes) # => overwrite attributes of an existing instance
62
+
63
+
64
+ ### package-config.yml
65
+
66
+ Each package requires a package-config.yml file, with at least a "name" key. You can add whatever
67
+ other keys you like that may be useful to your application for selecting and presenting packages.
68
+
69
+ Example package-config.yml:
70
+
71
+ ---
72
+ name: my-widget-package
73
+ title:
74
+ en: A sample package of prototypes
75
+ fr: Un package exemplaire plain de prototypes
76
+ description:
77
+ en: Widgets to set up the widgeting process. Remember to enscarfle the widget feature if you install this package.
78
+ fr: Machins pour initialiser le processus de machination. Une fois installé, il faut impérativement enscarfler la case machinations.
79
+ prerequisites:
80
+ - my-other-package
81
+ updated: 2013-03-15
82
+
83
+
84
+ ### Item files
85
+
86
+ Protopack::Package looks for item files in the same directory as the package-config.yml file. Item
87
+ file names must match the glob pattern "*item*.yml". This pattern allows you give locale-specific
88
+ names to items, if l10n is a concern: "widget-item.fr.yml", for example.
89
+
90
+ Each *-item.yml contains at least three keys: "id", "type" and "attributes". Protopack calls #constantize
91
+ on the "type" value in order to get a reference to the target class. The "attributes" are then
92
+ passed to your #existence method as described above.
93
+
94
+ You can add other keys that you might find useful for selecting and presenting items, for example, a
95
+ "locale" key to show only items in the current user's locale.
96
+
97
+ Protopack does no pre- or post-processing. Instead, define attributes on your models that will know
98
+ what to do, perhaps in conjunction with a :before_save or :after_save handler.
99
+
100
+ Example item file:
101
+
102
+ ---
103
+ id: example-widget
104
+ description: "This is a kind of widget that's good for widgeting"
105
+ locale: en
106
+ type: PublicWidget
107
+ attributes:
108
+ name: good-widget
109
+ lang: en
110
+ size: 42
111
+ next-widget-name: better-widget
112
+ permissions: widget-admin, widget-user
113
+
114
+
115
+
116
+ ## Security
117
+
118
+ Packages are executable code. Don't let end-users provide packages or package items. Protopack allows
119
+ package authors call any method on any class.
120
+
121
+ ## Contributing
122
+
123
+ 1. Fork it
124
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
125
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
126
+ 4. Push to the branch (`git push origin my-new-feature`)
127
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,59 @@
1
+ class Protopack::Package
2
+ attr_reader :config
3
+
4
+ def method_missing m, *args
5
+ config.send m, *args
6
+ end
7
+
8
+ def initialize cfg
9
+ @config = (cfg.is_a?(Hashie::Mash) ? cfg : Hashie::Mash.new(cfg))
10
+ end
11
+
12
+ def items
13
+ config.items.map { |item_file|
14
+ Protopack::PackageItem.new(Hashie::Mash.new(YAML.load(File.read(item_file))))
15
+ }
16
+ end
17
+
18
+ def item id
19
+ items.each do |item|
20
+ return item if (item.id == id)
21
+ end
22
+ end
23
+
24
+ def apply_missing
25
+ items.each do |item|
26
+ item.apply! if item.missing?
27
+ end
28
+ end
29
+
30
+ def apply_all
31
+ items.each &:apply!
32
+ end
33
+
34
+ def self.config_root= root
35
+ @@config_root = root
36
+ end
37
+
38
+ def self.config_root
39
+ @@config_root
40
+ end
41
+
42
+ def self.all
43
+ Dir.glob("#{config_root}/*/package-config.yml").map { |pkg_cfg|
44
+ cfg = Hashie::Mash.new(YAML.load(File.read(pkg_cfg)))
45
+ root = File.dirname pkg_cfg
46
+ cfg["items"] = Dir.glob("#{root}/*item*.yml")
47
+ cfg["root"] = root
48
+ new cfg
49
+ }
50
+ end
51
+
52
+ def self.find name
53
+ root = "#{config_root}/#{name}"
54
+ cfg = Hashie::Mash.new(YAML.load(File.read("#{root}/package-config.yml")))
55
+ cfg["items"] = Dir.glob("#{root}/*item*.yml")
56
+ cfg["root"] = root
57
+ new cfg
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ class Protopack::PackageItem
2
+ attr_reader :config
3
+
4
+ def method_missing m, *args
5
+ config.send m, *args
6
+ end
7
+
8
+ def initialize cfg
9
+ @config = (cfg.is_a?(Hashie::Mash) ? cfg : Hashie::Mash.new(cfg))
10
+ end
11
+
12
+ def name
13
+ attributes.name
14
+ end
15
+
16
+ def target_class
17
+ Kernel.const_get type
18
+ end
19
+
20
+ def missing?
21
+ existence.empty?
22
+ end
23
+
24
+ def existence
25
+ target_class.existence attributes
26
+ end
27
+
28
+ def apply!
29
+ factory = existence
30
+ if factory.empty?
31
+ factory.create! attributes
32
+ else
33
+ factory.first.update_attributes attributes
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,3 @@
1
+ module Protopack
2
+ VERSION = "0.0.1"
3
+ end
data/lib/protopack.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "hashie"
2
+ require "protopack/version"
3
+ require "protopack/package"
4
+ require "protopack/package_item"
5
+
6
+ module Protopack
7
+ # Your code goes here...
8
+ end
data/protopack.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'protopack/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "protopack"
8
+ gem.version = Protopack::VERSION
9
+ gem.authors = ["conanite"]
10
+ gem.email = ["conan@conandalton.net"]
11
+ gem.description = %q{Create objects from object definitions stored as files}
12
+ gem.summary = %q{Store packages of object prototypes on-disk as YML; this gem allows you scan each package for missing items and apply them to your repository.}
13
+ gem.homepage = ""
14
+
15
+ gem.add_dependency 'hashie'
16
+ gem.add_development_dependency 'rspec', '~> 2.9'
17
+
18
+ gem.files = `git ls-files`.split($/)
19
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
+ gem.require_paths = ["lib"]
22
+ end
@@ -0,0 +1,64 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require File.expand_path('../../spec_helper', __FILE__)
4
+
5
+ describe Protopack::Package do
6
+
7
+ before {
8
+ Protopack::Package.config_root = File.expand_path('../packages', __FILE__)
9
+ Widget.destroy_all
10
+ }
11
+
12
+ it "should find all the packages" do
13
+ Protopack::Package.all.map(&:name).join(" ").should == "advanced-widgets standard-widgets"
14
+ end
15
+
16
+ it "should find a given package" do
17
+ p = Protopack::Package.find("standard-widgets")
18
+ p.name.should == "standard-widgets"
19
+
20
+ p.title.en.should == "Standard Widgets"
21
+ p.title.fr.should == "Widgets standards"
22
+
23
+ p.description.en.should == "Use these widgets for everyday widgeting"
24
+ p.description.fr.should == "Ces widgets sont utilisables pour votre widgeting quotidien"
25
+
26
+ p.authors.should == %w{ baz titi Z }
27
+
28
+ p.updated.should == Date.parse("2013-03-15")
29
+ end
30
+
31
+ it "should install all items from a package" do
32
+ p = Protopack::Package.find("standard-widgets")
33
+ p.apply_all
34
+
35
+ Widget.all.map(&:colour).should == %w{ blue green red}
36
+ end
37
+
38
+ it "should install all items from a package, overwriting existing items" do
39
+ Widget.new :colour => "blue"
40
+ Widget.new :colour => "green"
41
+
42
+ p = Protopack::Package.find("standard-widgets")
43
+ p.apply_all
44
+
45
+ Widget.all.map(&:colour).should == %w{ blue green red}
46
+ Widget.all[0].height.should == 'elephant'
47
+ Widget.all[1].height.should == 'zebra'
48
+ Widget.all[2].height.should == 'tiger'
49
+ end
50
+
51
+ it "should install only missing items from a package, not overwriting existing items" do
52
+ Widget.new :colour => "blue"
53
+ Widget.new :colour => "green"
54
+
55
+ p = Protopack::Package.find("standard-widgets")
56
+ p.apply_missing
57
+
58
+ Widget.all.map(&:colour).should == %w{ blue green red}
59
+ Widget.all[0].height.should == nil
60
+ Widget.all[1].height.should == nil
61
+ Widget.all[2].height.should == 'tiger'
62
+ end
63
+
64
+ end
@@ -0,0 +1,6 @@
1
+ name: lavender-widget
2
+ type: Widget
3
+ attributes:
4
+ colour: lavender
5
+ height: trapeze
6
+ density: bittersweet
@@ -0,0 +1,6 @@
1
+ name: magenta-widget
2
+ type: Widget
3
+ attributes:
4
+ colour: magenta
5
+ height: hippopotamus
6
+ density: unavailable
@@ -0,0 +1,3 @@
1
+ ---
2
+ name: advanced-widgets
3
+
@@ -0,0 +1,6 @@
1
+ id: blue-widget
2
+ type: Widget
3
+ attributes:
4
+ colour: blue
5
+ height: elephant
6
+ density: difficult
@@ -0,0 +1,6 @@
1
+ id: green-widget
2
+ type: Widget
3
+ attributes:
4
+ colour: green
5
+ height: zebra
6
+ density: obtuse
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: standard-widgets
3
+ title:
4
+ en: Standard Widgets
5
+ fr: Widgets standards
6
+ description:
7
+ en: Use these widgets for everyday widgeting
8
+ fr: Ces widgets sont utilisables pour votre widgeting quotidien
9
+ authors:
10
+ - baz
11
+ - titi
12
+ - Z
13
+ updated: 2013-03-15
14
+
@@ -0,0 +1,6 @@
1
+ id: red-widget
2
+ type: Widget
3
+ attributes:
4
+ colour: red
5
+ height: tiger
6
+ density: abstract
@@ -0,0 +1,75 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+
4
+ Bundler.setup
5
+ Bundler.require
6
+ require File.expand_path('../../lib/protopack', __FILE__)
7
+
8
+ # This file was generated by the `rspec --init` command. Conventionally, all
9
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
10
+ # Require this file using `require "spec_helper"` to ensure that it is only
11
+ # loaded once.
12
+ #
13
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
14
+ RSpec.configure do |config|
15
+ config.treat_symbols_as_metadata_keys_with_true_values = true
16
+ config.run_all_when_everything_filtered = true
17
+ config.filter_run :focus
18
+
19
+ # Run specs in random order to surface order dependencies. If you find an
20
+ # order dependency and want to debug it, you can fix the order by providing
21
+ # the seed, which is printed after each run.
22
+ # --seed 1234
23
+ config.order = 'random'
24
+ end
25
+
26
+ class Widget
27
+ @@widgets = []
28
+
29
+ def self.all
30
+ @@widgets
31
+ end
32
+
33
+ def self.destroy_all
34
+ @@widgets = []
35
+ end
36
+
37
+ def initialize attrs
38
+ @attrs = attrs.is_a?(Hash) ? Hashie::Mash.new(attrs) : attrs
39
+ @@widgets << self
40
+ end
41
+
42
+ def method_missing m, *args
43
+ @attrs.send m, *args
44
+ end
45
+
46
+ def update_attributes attrs
47
+ @attrs = attrs
48
+ end
49
+
50
+ def self.existence attrs
51
+ WidgetRepository.new attrs.colour
52
+ end
53
+ end
54
+
55
+ class WidgetRepository
56
+ def initialize name
57
+ @name = name
58
+ end
59
+
60
+ def create! attributes
61
+ Widget.new(attributes)
62
+ end
63
+
64
+ def matches
65
+ Widget.all.select { |w| w.colour == @name }
66
+ end
67
+
68
+ def empty?
69
+ matches.empty?
70
+ end
71
+
72
+ def first
73
+ matches.first
74
+ end
75
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protopack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - conanite
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: hashie
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.9'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.9'
46
+ description: Create objects from object definitions stored as files
47
+ email:
48
+ - conan@conandalton.net
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - .rspec
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - lib/protopack.rb
60
+ - lib/protopack/package.rb
61
+ - lib/protopack/package_item.rb
62
+ - lib/protopack/version.rb
63
+ - protopack.gemspec
64
+ - spec/protopack/package_spec.rb
65
+ - spec/protopack/packages/advanced-widgets/lavender-widget-item.yml
66
+ - spec/protopack/packages/advanced-widgets/magenta-widget-item.yml
67
+ - spec/protopack/packages/advanced-widgets/package-config.yml
68
+ - spec/protopack/packages/standard-widgets/blue-widget-item.yml
69
+ - spec/protopack/packages/standard-widgets/green-widget-item.yml
70
+ - spec/protopack/packages/standard-widgets/package-config.yml
71
+ - spec/protopack/packages/standard-widgets/red-widget-item.yml
72
+ - spec/spec_helper.rb
73
+ homepage: ''
74
+ licenses: []
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.24
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Store packages of object prototypes on-disk as YML; this gem allows you scan
97
+ each package for missing items and apply them to your repository.
98
+ test_files:
99
+ - spec/protopack/package_spec.rb
100
+ - spec/protopack/packages/advanced-widgets/lavender-widget-item.yml
101
+ - spec/protopack/packages/advanced-widgets/magenta-widget-item.yml
102
+ - spec/protopack/packages/advanced-widgets/package-config.yml
103
+ - spec/protopack/packages/standard-widgets/blue-widget-item.yml
104
+ - spec/protopack/packages/standard-widgets/green-widget-item.yml
105
+ - spec/protopack/packages/standard-widgets/package-config.yml
106
+ - spec/protopack/packages/standard-widgets/red-widget-item.yml
107
+ - spec/spec_helper.rb