soca 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/HISTORY +3 -0
- data/LICENSE +20 -0
- data/README.md +89 -0
- data/Rakefile +58 -0
- data/bin/soca +5 -0
- data/lib/soca.rb +34 -0
- data/lib/soca/cli.rb +189 -0
- data/lib/soca/plugin.rb +31 -0
- data/lib/soca/plugins/compass.rb +24 -0
- data/lib/soca/plugins/jim.rb +19 -0
- data/lib/soca/pusher.rb +186 -0
- data/lib/soca/templates/Jimfile +12 -0
- data/lib/soca/templates/config.js.erb +4 -0
- data/lib/soca/templates/couchapprc.erb +10 -0
- data/lib/soca/templates/css/screen.css +1 -0
- data/lib/soca/templates/db/views/by_type/map.js +3 -0
- data/lib/soca/templates/hooks/before_build.rb +2 -0
- data/lib/soca/templates/index.html.erb +17 -0
- data/lib/soca/templates/js/app.js +12 -0
- data/lib/soca/templates/js/vendor/jquery-1.4.2.js +6240 -0
- data/lib/soca/templates/js/vendor/jquery.couch-0.11.js +668 -0
- data/lib/soca/templates/js/vendor/sammy-0.6.1.js +1809 -0
- data/lib/soca/templates/js/vendor/sammy.couch-0.1.0.js +122 -0
- data/lib/soca/templates/js/vendor/sha1.js +202 -0
- data/soca.gemspec +107 -0
- data/test/helper.rb +36 -0
- data/test/test_soca_cli.rb +120 -0
- data/test/test_soca_pusher.rb +79 -0
- data/test/testapp/.couchapprc +10 -0
- data/test/testapp/Jimfile +11 -0
- data/test/testapp/config.js +11 -0
- data/test/testapp/css/app.css +3 -0
- data/test/testapp/db/views/recent/map.js +5 -0
- data/test/testapp/hooks/before_build.rb +1 -0
- data/test/testapp/index.html +11 -0
- data/test/testapp/js/app.js +5 -0
- data/test/testapp/js/bundled.js +8544 -0
- data/test/testapp/js/vendor/jquery-1.4.2.js +6240 -0
- data/test/testapp/js/vendor/json2.js +478 -0
- data/test/testapp/js/vendor/sammy-0.5.4.js +1403 -0
- data/test/testapp/js/vendor/sammy.mustache-0.5.4.js +415 -0
- data/test/testapp/templates/index.mustache +1 -0
- metadata +205 -0
data/.document
ADDED
data/.gitignore
ADDED
data/HISTORY
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Aaron Quint
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# soca
|
2
|
+
|
3
|
+
Sammy On Couch App or Sittin' On a Couch App
|
4
|
+
|
5
|
+
## What?
|
6
|
+
|
7
|
+
A *couchapp* is method of creating applications that live inside CouchDB's
|
8
|
+
design documents. This can be a simple as an index.html and as complicated
|
9
|
+
as a full interactive JavaScript application. couchapp's inherently have a
|
10
|
+
a bunch of really cool features - easy replication and synchronization,
|
11
|
+
instant access to store and fetch data from CouchDB, and a full JS API. [Sammy.js](http://code.quirkey.com/sammy) is a perfect fit for couchapps providing a simple programmable controller layer on top of CouchDB's data.
|
12
|
+
|
13
|
+
I highly recommend reading the section on couchapps in the [CouchDB Book](http://guide.couchdb.org/editions/1/en/standalone.html).
|
14
|
+
|
15
|
+
*soca* is a simple command line tool written in ruby for building and pushing
|
16
|
+
couchapps. It is similar to and heavily inspired by the canonical couchapp
|
17
|
+
python tool, [couchapp](http://github.com/couchapp/couchapp), with a number
|
18
|
+
of key differences.
|
19
|
+
|
20
|
+
* local directories do not have to map 1-1 to the design docs directory
|
21
|
+
* lifecycle hooks for easily adding or modifying the design document with
|
22
|
+
ruby tools or plugins.
|
23
|
+
* architected around using Sammy.js, instead of Evently, which is bundled
|
24
|
+
with the python tool.
|
25
|
+
* super tiny and hackable, with the hope that people will contribute patches
|
26
|
+
and plugins.
|
27
|
+
|
28
|
+
## Why?
|
29
|
+
|
30
|
+
I'm not one to start a language war, I think python is great and the existing
|
31
|
+
couchapp tool works great for most situations. In fact I've built apps using
|
32
|
+
it. I found myself working around the design documents structure, which makes
|
33
|
+
sense when in JSON, but much less sense when mapped to the filesystem. By making a simple tool, that takes a JSON map of directories and files and
|
34
|
+
places them in their expected JSON slot, you make a new sort of couchapp.
|
35
|
+
|
36
|
+
Unlike a traditional couchapp, a soca couchapp is actually one way - you're
|
37
|
+
source directory is actually 'compiled' into its final state. This allows you
|
38
|
+
to do things you couldnt before, including bundling js files, using external
|
39
|
+
tools like [compass](http://compassstyle.org), and just generally following
|
40
|
+
your own preffered directory structure. This does mean that there is no `soca
|
41
|
+
clone` to get a couchapp out of CouchDB - though replicating works the same as
|
42
|
+
before (and is probably faster because you push only the docs you need or
|
43
|
+
use).
|
44
|
+
|
45
|
+
The bottom line is I wanted to build couchapp's with a workflow and structure
|
46
|
+
I had already established - `soca` lets me do that.
|
47
|
+
|
48
|
+
## How?
|
49
|
+
|
50
|
+
`soca` is bundled as a ruby gem so installation is easy-peasy. On a system
|
51
|
+
with ruby and ruby gems (OS X for example):
|
52
|
+
|
53
|
+
gem install soca
|
54
|
+
|
55
|
+
This will give you the `soca` bin as long as gems are in your path.
|
56
|
+
|
57
|
+
soca
|
58
|
+
|
59
|
+
Will display all the command options.
|
60
|
+
|
61
|
+
The typical workflow would be:
|
62
|
+
|
63
|
+
# Generate the app
|
64
|
+
soca generate myapp
|
65
|
+
# cd into the app
|
66
|
+
cd myapp
|
67
|
+
# edit your .couchapprc with the db url
|
68
|
+
# Do your work, editing app.js, etc
|
69
|
+
# push the app to couchdb
|
70
|
+
soca push
|
71
|
+
# open the app in a browser
|
72
|
+
soca open
|
73
|
+
|
74
|
+
Once you get it set up, you can also use
|
75
|
+
|
76
|
+
soca autopush
|
77
|
+
|
78
|
+
Which will watch the directory and push your changes automagically.
|
79
|
+
|
80
|
+
## TODO
|
81
|
+
|
82
|
+
* Better Docs
|
83
|
+
* More plugins
|
84
|
+
* More generate options (compass scaffold, etc)
|
85
|
+
* More example apps
|
86
|
+
|
87
|
+
## Copyright/License
|
88
|
+
|
89
|
+
Copyright (c) 2010 Aaron Quint under the MIT License. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
require 'lib/soca'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "soca"
|
8
|
+
gem.version = Soca::VERSION
|
9
|
+
gem.summary = %Q{Sammy on CouchApp}
|
10
|
+
gem.description = %Q{soca is a different way of writing apps for CouchDB. The structure is up to you.}
|
11
|
+
gem.email = "aaron@quirkey.com"
|
12
|
+
gem.homepage = "http://github.com/quirkey/soca"
|
13
|
+
gem.authors = ["Aaron Quint"]
|
14
|
+
gem.add_dependency 'json', '~>1.4.6'
|
15
|
+
gem.add_dependency 'typhoeus', '~>0.1.31'
|
16
|
+
gem.add_dependency 'jim', '~>0.2.3'
|
17
|
+
gem.add_dependency 'compass', '~>0.10.5'
|
18
|
+
gem.add_development_dependency "shoulda", ">= 0"
|
19
|
+
gem.add_development_dependency "yard", ">= 0"
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
Rake::TestTask.new(:test) do |test|
|
29
|
+
test.libs << 'lib' << 'test'
|
30
|
+
test.pattern = 'test/**/test_*.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
task :rcov do
|
43
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
task :test => :check_dependencies
|
48
|
+
|
49
|
+
task :default => :test
|
50
|
+
|
51
|
+
begin
|
52
|
+
require 'yard'
|
53
|
+
YARD::Rake::YardocTask.new
|
54
|
+
rescue LoadError
|
55
|
+
task :yardoc do
|
56
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
57
|
+
end
|
58
|
+
end
|
data/bin/soca
ADDED
data/lib/soca.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'typhoeus'
|
3
|
+
require 'base64'
|
4
|
+
require 'mime/types'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__))))
|
8
|
+
|
9
|
+
module Soca
|
10
|
+
VERSION = '0.1.0'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :debug
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.logger=(logger)
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.logger
|
21
|
+
@logger ||= LOGGER if defined?(LOGGER)
|
22
|
+
if !@logger
|
23
|
+
@logger = Logger.new(STDOUT)
|
24
|
+
@logger.level = Logger::ERROR
|
25
|
+
@logger.formatter = Proc.new {|s, t, n, msg| "#{msg}\n"}
|
26
|
+
@logger
|
27
|
+
end
|
28
|
+
@logger
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'soca/pusher'
|
33
|
+
require 'soca/cli'
|
34
|
+
require 'soca/plugin'
|
data/lib/soca/cli.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/actions'
|
3
|
+
|
4
|
+
module Soca
|
5
|
+
class CLI < ::Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
attr_accessor :appdir,
|
9
|
+
:config_file,
|
10
|
+
:debug
|
11
|
+
|
12
|
+
class_option "appdir",
|
13
|
+
:type => :string,
|
14
|
+
:banner => "set the application directory to work with. assumes the current directory"
|
15
|
+
|
16
|
+
class_option "config",
|
17
|
+
:aliases => '-c',
|
18
|
+
:type => :string,
|
19
|
+
:banner => "use a specific soca config.js"
|
20
|
+
|
21
|
+
class_option "debug",
|
22
|
+
:type => :boolean,
|
23
|
+
:default => false,
|
24
|
+
:aliases => '-d',
|
25
|
+
:banner => "set log level to debug"
|
26
|
+
|
27
|
+
class_option "version",
|
28
|
+
:type => :boolean,
|
29
|
+
:aliases => '-v',
|
30
|
+
:banner => "print version and exit"
|
31
|
+
|
32
|
+
default_task :help
|
33
|
+
|
34
|
+
def initialize(*)
|
35
|
+
super
|
36
|
+
if options[:version]
|
37
|
+
say "soca #{Soca::VERSION}", :red
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
self.appdir = options[:appdir] || File.expand_path(Dir.pwd)
|
41
|
+
self.config_file = options[:config]
|
42
|
+
self.debug = options[:debug]
|
43
|
+
if debug
|
44
|
+
Soca.debug = true
|
45
|
+
logger.level = Logger::DEBUG
|
46
|
+
end
|
47
|
+
self.source_paths << File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
48
|
+
end
|
49
|
+
|
50
|
+
method_option "appname",
|
51
|
+
:type => :string,
|
52
|
+
:banner => "set the name of the application for templating. defaults to the basename of the appdir"
|
53
|
+
|
54
|
+
desc 'init [APPDIR]', 'turns any directory into a soca app, generating a config.js'
|
55
|
+
def init(to = nil)
|
56
|
+
self.appdir = to if to
|
57
|
+
self.destination_root = appdir
|
58
|
+
@dir_mappings = {}
|
59
|
+
Dir[appdir + '*'].each do |filename|
|
60
|
+
basename = File.basename(filename)
|
61
|
+
@dir_mappings[basename] = "_attachments/#{basename}"
|
62
|
+
end
|
63
|
+
template('config.js.erb', 'config.js')
|
64
|
+
template('couchapprc.erb', '.couchapprc')
|
65
|
+
end
|
66
|
+
|
67
|
+
method_option "appname",
|
68
|
+
:type => :string,
|
69
|
+
:banner => "set the name of the application for templating. defaults to the basename of the appdir"
|
70
|
+
|
71
|
+
desc 'generate [APPDIR]', 'generates the basic soca app structure'
|
72
|
+
def generate(to = nil)
|
73
|
+
self.appdir = to if to
|
74
|
+
self.destination_root = appdir
|
75
|
+
|
76
|
+
directory('hooks')
|
77
|
+
directory('js')
|
78
|
+
directory('css')
|
79
|
+
directory('db')
|
80
|
+
template('Jimfile')
|
81
|
+
template('index.html.erb', 'index.html')
|
82
|
+
@dir_mappings = {
|
83
|
+
"config.js" => "",
|
84
|
+
"index.html" => "_attachments/index.html",
|
85
|
+
"css" => "_attachments/css",
|
86
|
+
"images" => "_attachments/images",
|
87
|
+
"sass" => false,
|
88
|
+
"js" => "_attachments/js",
|
89
|
+
"templates" => "_attachments/templates",
|
90
|
+
"db" => "",
|
91
|
+
"Jimfile" => false,
|
92
|
+
"hooks" => false
|
93
|
+
}
|
94
|
+
template('config.js.erb', 'config.js')
|
95
|
+
template('couchapprc.erb', '.couchapprc')
|
96
|
+
end
|
97
|
+
|
98
|
+
desc 'url [ENV]', 'outputs the app url for the ENV'
|
99
|
+
def url(env = 'default')
|
100
|
+
say pusher(env).app_url
|
101
|
+
end
|
102
|
+
|
103
|
+
desc 'open [ENV]', 'attempts to open the url for the current app in a browser'
|
104
|
+
def open(env = 'default')
|
105
|
+
`open #{pusher(env).app_url}`
|
106
|
+
end
|
107
|
+
|
108
|
+
desc 'push [ENV]', 'builds and pushes the current app to couchdb'
|
109
|
+
def push(env = 'default')
|
110
|
+
pusher(env).push!
|
111
|
+
end
|
112
|
+
|
113
|
+
desc 'build [ENV]', 'builds the app as a ruby hash and outputs it to stdout'
|
114
|
+
def build(env = 'default')
|
115
|
+
require 'pp'
|
116
|
+
pp pusher(env).build
|
117
|
+
end
|
118
|
+
|
119
|
+
desc 'compact [ENV]', 'runs a DB compact against the couchdb for ENV'
|
120
|
+
def compact(env = 'default')
|
121
|
+
pusher(env).compact!
|
122
|
+
end
|
123
|
+
|
124
|
+
desc 'json [ENV]', 'builds and then outputs the design doc JSON for the app'
|
125
|
+
def json(env = 'default')
|
126
|
+
say pusher(env).json
|
127
|
+
end
|
128
|
+
|
129
|
+
method_option '--compact', :type => :numeric, :default => 5,
|
130
|
+
:banner => 'run a compact operation every [n] pushes (default 5)'
|
131
|
+
desc 'autopush [ENV]', 'watches the current directory for changes, building and pushing to couchdb'
|
132
|
+
def autopush(env = 'default')
|
133
|
+
push = pusher(env)
|
134
|
+
files = {}
|
135
|
+
compact = options[:compact]
|
136
|
+
times = 0
|
137
|
+
loop do
|
138
|
+
changed = false
|
139
|
+
Dir.glob(push.app_dir + '**/**') do |file|
|
140
|
+
ctime = File.ctime(file).to_i
|
141
|
+
if ctime != files[file]
|
142
|
+
files[file] = ctime
|
143
|
+
changed = true
|
144
|
+
break
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
if changed
|
149
|
+
say "Running push at #{Time.now}", :yellow
|
150
|
+
begin
|
151
|
+
push.push!
|
152
|
+
rescue => e
|
153
|
+
say "Error running push #{e}", :red
|
154
|
+
end
|
155
|
+
|
156
|
+
Dir.glob(push.app_dir + '**/**') do |file|
|
157
|
+
ctime = File.ctime(file).to_i
|
158
|
+
if ctime != files[file]
|
159
|
+
files[file] = ctime
|
160
|
+
end
|
161
|
+
end
|
162
|
+
times += 1
|
163
|
+
if compact && times % compact == 0
|
164
|
+
say "pushed #{times} times, running a compact", :yellow
|
165
|
+
push.compact!
|
166
|
+
end
|
167
|
+
say "Waiting for a file change", :green
|
168
|
+
say "-------------------------"
|
169
|
+
end
|
170
|
+
|
171
|
+
sleep 1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
def appname
|
177
|
+
@appname = options[:name] || File.basename(appdir)
|
178
|
+
end
|
179
|
+
|
180
|
+
def logger
|
181
|
+
Soca.logger
|
182
|
+
end
|
183
|
+
|
184
|
+
def pusher(env)
|
185
|
+
Soca::Pusher.new(appdir, env, config_file)
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
data/lib/soca/plugin.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Soca
|
2
|
+
class Plugin
|
3
|
+
attr_reader :pusher
|
4
|
+
|
5
|
+
def self.name(plugin_name)
|
6
|
+
@@plugins ||= {}
|
7
|
+
@@plugins[plugin_name] = self
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.plugins
|
11
|
+
@@plugins ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(pusher)
|
15
|
+
@pusher = pusher
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
raise "you need to subclass plugin and provide your own logic, please"
|
20
|
+
end
|
21
|
+
|
22
|
+
def logger
|
23
|
+
Soca.logger
|
24
|
+
end
|
25
|
+
|
26
|
+
def app_dir
|
27
|
+
pusher.app_dir
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|