soca 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/HISTORY +3 -0
  4. data/LICENSE +20 -0
  5. data/README.md +89 -0
  6. data/Rakefile +58 -0
  7. data/bin/soca +5 -0
  8. data/lib/soca.rb +34 -0
  9. data/lib/soca/cli.rb +189 -0
  10. data/lib/soca/plugin.rb +31 -0
  11. data/lib/soca/plugins/compass.rb +24 -0
  12. data/lib/soca/plugins/jim.rb +19 -0
  13. data/lib/soca/pusher.rb +186 -0
  14. data/lib/soca/templates/Jimfile +12 -0
  15. data/lib/soca/templates/config.js.erb +4 -0
  16. data/lib/soca/templates/couchapprc.erb +10 -0
  17. data/lib/soca/templates/css/screen.css +1 -0
  18. data/lib/soca/templates/db/views/by_type/map.js +3 -0
  19. data/lib/soca/templates/hooks/before_build.rb +2 -0
  20. data/lib/soca/templates/index.html.erb +17 -0
  21. data/lib/soca/templates/js/app.js +12 -0
  22. data/lib/soca/templates/js/vendor/jquery-1.4.2.js +6240 -0
  23. data/lib/soca/templates/js/vendor/jquery.couch-0.11.js +668 -0
  24. data/lib/soca/templates/js/vendor/sammy-0.6.1.js +1809 -0
  25. data/lib/soca/templates/js/vendor/sammy.couch-0.1.0.js +122 -0
  26. data/lib/soca/templates/js/vendor/sha1.js +202 -0
  27. data/soca.gemspec +107 -0
  28. data/test/helper.rb +36 -0
  29. data/test/test_soca_cli.rb +120 -0
  30. data/test/test_soca_pusher.rb +79 -0
  31. data/test/testapp/.couchapprc +10 -0
  32. data/test/testapp/Jimfile +11 -0
  33. data/test/testapp/config.js +11 -0
  34. data/test/testapp/css/app.css +3 -0
  35. data/test/testapp/db/views/recent/map.js +5 -0
  36. data/test/testapp/hooks/before_build.rb +1 -0
  37. data/test/testapp/index.html +11 -0
  38. data/test/testapp/js/app.js +5 -0
  39. data/test/testapp/js/bundled.js +8544 -0
  40. data/test/testapp/js/vendor/jquery-1.4.2.js +6240 -0
  41. data/test/testapp/js/vendor/json2.js +478 -0
  42. data/test/testapp/js/vendor/sammy-0.5.4.js +1403 -0
  43. data/test/testapp/js/vendor/sammy.mustache-0.5.4.js +415 -0
  44. data/test/testapp/templates/index.mustache +1 -0
  45. metadata +205 -0
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/HISTORY ADDED
@@ -0,0 +1,3 @@
1
+ == 0.1.0 [09-26-10]
2
+
3
+ - Initial Release
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.
@@ -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.
@@ -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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'soca'
4
+
5
+ Soca::CLI.start
@@ -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'
@@ -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
@@ -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