change_agent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b77277230a8527bd86be1e4caa5500834b9aef1b
4
+ data.tar.gz: 43b6adf0ecc54612020d28b9050970cd3279b4c6
5
+ SHA512:
6
+ metadata.gz: 308dc37a2ce06801aac82ee1bde0113ee23181bc40c3cf0093f72bf03b3c4c47a4f8672f53b4e032d25c596e217428b1590da20b530054ab35b36f2dbe73a3e0
7
+ data.tar.gz: 26a15963bff1938fdd7418169dfd9bdf0b7e731ba17b527032800e87a209e12340850cb96fe8bd7969071d1b53f69670c7e770344919d997db11b3ea9f3ff721
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in change_agent.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ben Balter
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,68 @@
1
+ # Change Agent
2
+
3
+ *A Git-backed key-value store, for tracking changes to documents and other files over time.*
4
+
5
+ ### A git-backed key value store sounds like a terrible idea. Why would you do that?
6
+
7
+ Let's say you're building a scraper to see when Members of Congress post press releases to their websites and to track how those press releases change over time. You could build a purpose-built application, store each revision in a database, and then build an interface to view all the known press releases and compare their history. Stop the insanity!
8
+
9
+ But wait. What if I told you you could just commit each press release to Git, and let Git/GitHub do the heavy lifting. Without writing a single line of HTML you can browse all the press releases, see when they were changed, diff exactly how they were changed, and you've got hosting baked in.
10
+
11
+ Having built the first app more times then I'd like to admit, I thought I'd make a Gem to facilitate building lightweight apps that use Git to track changes to scraped documents (or whatever you want, really).
12
+
13
+ ## Okay, I'm sold. How do I use it?
14
+
15
+ ChangeAgent writes values to the file system based on a given key, and immediately commits the file to Git, providing you with both a snapshot and a timestamp for every change.
16
+
17
+ ### Basic usage
18
+
19
+ ```ruby
20
+ change_agent = ChangeAgent.init "path/to/repo"
21
+ => #<ChangeAgent::Client repo="path/to/repo">
22
+
23
+ change_agent.set "foo", "bar"
24
+ => #<ChangeAgent::Document key="foo">
25
+
26
+ change_agent.get "foo"
27
+ => "bar"
28
+ ```
29
+
30
+ ### Namespaced usage
31
+
32
+ Keys (files) are intended to be namespaced when logically grouped. In the above example, if you were storing congressional press releases, you might store Rep. Balter's Nov 26th press release on puppies as "balter/2014/11/26/puppies.html", or just "balter/2014-11-26-puppies.txt" or even just "balter/puppies".
33
+
34
+ ```ruby
35
+ change_agent.set "foo/bar", "baz"
36
+ => #<ChangeAgent::Document key="foo">
37
+
38
+ change_agent.get "foo/bar"
39
+ => "baz"
40
+ ```
41
+
42
+ It's really up to you, but you'll get performance and usability bumps the more you namespace. I'd recommend thinking about what you want the Git repo to look like when browse, and work backwards from there.
43
+
44
+ ## Project status
45
+
46
+ Initial proof of concept
47
+
48
+ ## Installation
49
+
50
+ Add this line to your application's Gemfile:
51
+
52
+ gem 'change_agent'
53
+
54
+ And then execute:
55
+
56
+ $ bundle
57
+
58
+ Or install it yourself as:
59
+
60
+ $ gem install change_agent
61
+
62
+ ## Contributing
63
+
64
+ 1. Fork it ( https://github.com/[my-github-username]/change_agent/fork )
65
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
66
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
67
+ 4. Push to the branch (`git push origin my-new-feature`)
68
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require "bundler/gem_tasks"
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_change_agent*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ desc "Open console with Change Agent loaded"
12
+ task :console do
13
+ exec "pry -r ./lib/change_agent.rb"
14
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'change_agent/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "change_agent"
8
+ spec.version = ChangeAgent::VERSION
9
+ spec.authors = ["Ben Balter"]
10
+ spec.email = ["ben.balter@github.com"]
11
+ spec.summary = %q{A Git-backed key-value store, for tracking changes to documents and other files over time.}
12
+ spec.homepage = "https://github.com/benbalter/change-agent"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "git"
21
+ spec.add_dependency "rugged"
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "pry"
25
+ spec.add_development_dependency('shoulda', '~> 3.5')
26
+ end
@@ -0,0 +1,12 @@
1
+ require_relative "change_agent/version"
2
+ require_relative "change_agent/document"
3
+ require_relative "change_agent/client"
4
+ require "git"
5
+
6
+ module ChangeAgent
7
+
8
+ def self.init(directory=nil)
9
+ Client.new(directory)
10
+ end
11
+
12
+ end
@@ -0,0 +1,37 @@
1
+ module ChangeAgent
2
+ class Client
3
+
4
+ attr_accessor :directory
5
+
6
+ def initialize(directory=nil)
7
+ @directory = File.expand_path(directory || Dir.pwd)
8
+ end
9
+
10
+ def git
11
+ @git ||= Git.init directory
12
+ end
13
+
14
+ def set(key, value)
15
+ document = Document.new(key, self)
16
+ document.contents = value
17
+ document.write
18
+ document
19
+ end
20
+
21
+ def get(key)
22
+ get_document(key).contents
23
+ end
24
+
25
+ def get_document(key)
26
+ Document.new(key, self)
27
+ end
28
+
29
+ def delete(key)
30
+ Document.new(key, self).delete
31
+ end
32
+
33
+ def inspect
34
+ "#<ChangeAgent::Client repo=\"#{directory}\">"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ module ChangeAgent
2
+ class Document
3
+
4
+ attr_accessor :key
5
+ attr_writer :contents
6
+
7
+ def initialize(key, client_or_directory=nil)
8
+ @key = key
9
+ if client_or_directory.class == ChangeAgent::Client
10
+ @client = client_or_directory
11
+ else
12
+ @client = ChangeAgent::Client.new(client_or_directory)
13
+ end
14
+ end
15
+
16
+ def git
17
+ @client.git
18
+ end
19
+
20
+ # base dir for repo
21
+ def base_dir
22
+ @client.directory
23
+ end
24
+
25
+ # directory containing file
26
+ def directory
27
+ @directory ||= File.dirname(path)
28
+ end
29
+
30
+ def path
31
+ File.expand_path key, base_dir
32
+ end
33
+
34
+ def exists?
35
+ File.exists? path
36
+ end
37
+
38
+ def contents
39
+ @contents ||= File.open(path).read
40
+ rescue Errno::ENOENT
41
+ nil
42
+ end
43
+
44
+ def write
45
+ FileUtils.mkdir_p directory unless Dir.exists? directory
46
+ File.write(path, contents)
47
+ commit
48
+ end
49
+
50
+ def commit
51
+ git.add(path)
52
+ git.commit "Updating #{path}"
53
+ end
54
+
55
+ def delete
56
+ git.remove(path)
57
+ end
58
+
59
+ def inspect
60
+ "#<ChangeAgent::Document key=\"#{key}\">"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module ChangeAgent
2
+ VERSION = "0.0.1"
3
+ end
data/script/bootstrap ADDED
@@ -0,0 +1 @@
1
+ bundle install
data/script/cibuild ADDED
@@ -0,0 +1 @@
1
+ bundle exec rake test
data/script/console ADDED
@@ -0,0 +1 @@
1
+ bundle exec rake console
data/script/release ADDED
@@ -0,0 +1,38 @@
1
+ #!/bin/sh
2
+ # Tag and push a release.
3
+
4
+ set -e
5
+
6
+ # Make sure we're in the project root.
7
+
8
+ cd $(dirname "$0")/..
9
+
10
+ # Build a new gem archive.
11
+
12
+ rm -rf change_agent-*.gem
13
+ gem build -q change_agent.gemspec
14
+
15
+ # Make sure we're on the master branch.
16
+
17
+ (git branch | grep -q '* master') || {
18
+ echo "Only release from the master branch."
19
+ exit 1
20
+ }
21
+
22
+ # Figure out what version we're releasing.
23
+
24
+ tag=v`ls change_agent-*.gem | sed 's/^change_agent-\(.*\)\.gem$/\1/'`
25
+
26
+ # Make sure we haven't released this version before.
27
+
28
+ git fetch -t origin
29
+
30
+ (git tag -l | grep -q "$tag") && {
31
+ echo "Whoops, there's already a '${tag}' tag."
32
+ exit 1
33
+ }
34
+
35
+ # Tag it and bag it.
36
+
37
+ gem push change_agent-*.gem && git tag "$tag" &&
38
+ git push origin master && git push origin "$tag"
data/test/helper.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'minitest/autorun'
4
+ require 'shoulda'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+
8
+ def tempdir
9
+ File.expand_path "../tmp", File.dirname(__FILE__)
10
+ end
11
+
12
+ def init_tempdir
13
+ FileUtils.rm_rf tempdir
14
+ FileUtils.mkdir tempdir
15
+ end
16
+
17
+ require 'change_agent'
@@ -0,0 +1,12 @@
1
+ require 'helper'
2
+
3
+ class TestChangeAgent < Minitest::Test
4
+
5
+ def setup
6
+ init_tempdir
7
+ end
8
+
9
+ should "return a ChangeAgent::Client" do
10
+ assert_equal ChangeAgent::Client, ChangeAgent.init(tempdir).class
11
+ end
12
+ end
@@ -0,0 +1,64 @@
1
+ require 'helper'
2
+
3
+ class TestChangeAgentClient < Minitest::Test
4
+
5
+ def setup
6
+ init_tempdir
7
+ @client = ChangeAgent::Client.new(tempdir)
8
+ end
9
+
10
+ should "set the directory on init" do
11
+ assert_equal tempdir, @client.directory
12
+ end
13
+
14
+ should "default to the pwd" do
15
+ assert_equal Dir.pwd, ChangeAgent::Client.new.directory
16
+ end
17
+
18
+ should "init the git object" do
19
+ assert_equal Git::Base, @client.git.class
20
+ expected = File.expand_path ".git", tempdir
21
+ assert_equal expected, @client.git.repo.path
22
+ end
23
+
24
+ should "store a value" do
25
+ @client.set "foo", "bar"
26
+ file = File.expand_path "foo", tempdir
27
+ assert File.exists? file
28
+ assert_equal "bar", File.open(file).read
29
+ end
30
+
31
+ should "store a namespaced value" do
32
+ @client.set "foo/bar", "baz"
33
+ file = File.expand_path "foo/bar", tempdir
34
+ assert File.exists? file
35
+ assert_equal "baz", File.open(file).read
36
+ end
37
+
38
+ should "retrieve a file's value" do
39
+ file = File.expand_path "foo", tempdir
40
+ File.write file, "bar"
41
+ assert_equal "bar", @client.get("foo")
42
+ end
43
+
44
+ should "retrive a namespaced file's value" do
45
+ file = File.expand_path "foo/bar", tempdir
46
+ FileUtils.mkdir_p File.dirname(file)
47
+ File.write file, "baz"
48
+ assert_equal "baz", @client.get("foo/bar")
49
+ end
50
+
51
+ should "round trip a value" do
52
+ @client.set "foo", "bar"
53
+ assert_equal "bar", @client.get("foo")
54
+ end
55
+
56
+ should "round trip a namespaced value" do
57
+ @client.set "foo/bar", "baz"
58
+ assert_equal "baz", @client.get("foo/bar")
59
+ end
60
+
61
+ should "not err on an unknown value" do
62
+ refute @client.get "does/not/exist"
63
+ end
64
+ end
@@ -0,0 +1,79 @@
1
+ require 'helper'
2
+
3
+ class TestChangeAgentDocument < Minitest::Test
4
+
5
+ def setup
6
+ init_tempdir
7
+ @document = ChangeAgent::Document.new("foo", tempdir)
8
+ @namespaced_document = ChangeAgent::Document.new("bar/foo", tempdir)
9
+ end
10
+
11
+ should "store the document key on init" do
12
+ assert_equal "foo", @document.key
13
+ end
14
+
15
+ should "accept a client if passed" do
16
+ doc = ChangeAgent::Document.new("foo", ChangeAgent::Client.new)
17
+ assert_equal ChangeAgent::Client, doc.instance_variable_get("@client").class
18
+ end
19
+
20
+ should "build a client from a directory" do
21
+ assert_equal ChangeAgent::Client, @document.instance_variable_get("@client").class
22
+ end
23
+
24
+ should "expose the git client" do
25
+ assert_equal Git::Base, @document.git.class
26
+ end
27
+
28
+ should "calcuate the base_dir" do
29
+ assert_equal tempdir, @document.base_dir
30
+ end
31
+
32
+ should "calcuate the directory" do
33
+ assert_equal tempdir, @document.directory
34
+
35
+ expected = File.expand_path "bar", tempdir
36
+ assert_equal expected, @namespaced_document.directory
37
+ end
38
+
39
+ should "know the file path" do
40
+ expected = File.expand_path "foo", tempdir
41
+ assert_equal expected, @document.path
42
+
43
+ expected = File.expand_path "bar/foo", tempdir
44
+ assert_equal expected, @namespaced_document.path
45
+ end
46
+
47
+ should "know if a file exists" do
48
+ refute @document.exists?
49
+ FileUtils.touch @document.path
50
+ assert @document.exists?
51
+ end
52
+
53
+ should "read a file's contents" do
54
+ File.write @document.path, "bar"
55
+ assert_equal "bar", @document.contents
56
+ end
57
+
58
+ should "write a file's contents" do
59
+ @document.contents = "bar"
60
+ assert_equal "bar", @document.contents
61
+ @document.write
62
+ assert_equal "bar", File.open(@document.path).read
63
+ end
64
+
65
+ should "commit the document to the repo" do
66
+ @document.contents = "bar"
67
+ @document.write
68
+ assert_equal 1, @document.git.log.count
69
+ assert_equal "Updating #{@document.path}", @document.git.log.first.message
70
+ end
71
+
72
+ should "delete the document" do
73
+ @document.contents = "bar"
74
+ @document.write
75
+ assert File.exists? @document.path
76
+ @document.delete
77
+ refute File.exists? @document.path
78
+ end
79
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: change_agent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben Balter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: git
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rugged
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: shoulda
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.5'
97
+ description:
98
+ email:
99
+ - ben.balter@github.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - change_agent.gemspec
110
+ - lib/change_agent.rb
111
+ - lib/change_agent/client.rb
112
+ - lib/change_agent/document.rb
113
+ - lib/change_agent/version.rb
114
+ - script/bootstrap
115
+ - script/cibuild
116
+ - script/console
117
+ - script/release
118
+ - test/helper.rb
119
+ - test/test_change_agent.rb
120
+ - test/test_change_agent_client.rb
121
+ - test/test_change_agent_document.rb
122
+ homepage: https://github.com/benbalter/change-agent
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.2.0
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: A Git-backed key-value store, for tracking changes to documents and other
146
+ files over time.
147
+ test_files:
148
+ - test/helper.rb
149
+ - test/test_change_agent.rb
150
+ - test/test_change_agent_client.rb
151
+ - test/test_change_agent_document.rb