Skip to content
Browse files

create new application script

  • Loading branch information...
1 parent f90212a commit 5caf1b5ab8b0698c20146b8de23416819959f901 @cif committed
Showing with 27,532 additions and 94 deletions.
  1. BIN .DS_Store
  2. +42 −0 app/coffee/controllers/application.coffee
  3. 0 app/coffee/controllers/controller.shell.coffee
  4. BIN app/public/favicon.ico
  5. +83 −0 app/stylus/_mixins.styl
  6. +109 −0 app/stylus/application.styl
  7. +1 −0 app/templates/default/create.hb
  8. +1 −0 app/templates/default/edit.hb
  9. +1 −0 app/templates/default/list.hb
  10. +1 −0 app/templates/default/view.hb
  11. +4 −4 core/{ → client}/calendar.coffee
  12. 0 core/{ → client}/collection.coffee
  13. +30 −12 core/{ → client}/controller.coffee
  14. +8 −7 core/{ → client}/form.coffee
  15. +30 −25 core/{ → client}/helpers.coffee
  16. +22 −12 core/{ → client}/list.coffee
  17. 0 core/{ → client}/model.coffee
  18. +7 −0 core/{ → client}/notifications.coffee
  19. 0 core/{ → client}/sync.coffee
  20. 0 core/{ → client}/view.coffee
  21. +5 −0 core/server/adapters/mongo.coffee
  22. +6 −0 core/server/adapters/mysql.coffee
  23. +22 −0 core/server/model.coffee
  24. +10 −0 core/server/responder.coffee
  25. +4 −0 node/bouncer.js
  26. +45 −0 node/builder.js
  27. +383 −0 node/burner.js
  28. +51 −0 node/compressor.js
  29. +131 −34 node/flint.js
  30. +657 −0 node/inflector.js
  31. +1 −0 node/server.js
  32. +1 −0 node_modules/.bin/node-supervisor
  33. +1 −0 node_modules/.bin/supervisor
  34. +1 −0 node_modules/supervisor
  35. BIN {app → }/public/apple-touch-icon.png
  36. +3,324 −0 public/application.css
  37. BIN public/favicon.ico
  38. 0 {app → }/public/fonts/museosans-300-webfont.eot
  39. 0 {app → }/public/fonts/museosans-300-webfont.svg
  40. 0 {app → }/public/fonts/museosans-300-webfont.ttf
  41. 0 {app → }/public/fonts/museosans-300-webfont.woff
  42. BIN public/fonts/museosans_700-webfont.eot
  43. +241 −0 public/fonts/museosans_700-webfont.svg
  44. BIN public/fonts/museosans_700-webfont.ttf
  45. BIN public/fonts/museosans_700-webfont.woff
  46. +19 −0 public/index.html
  47. +21 −0 public/index_develop.html
  48. +9,549 −0 public/javascript/application.js
  49. +6,811 −0 public/javascript/dependencies.js
  50. +1 −0 public/javascript/production.js
  51. +5,769 −0 public/javascript/templates.js
  52. +54 −0 service/flint.js
  53. +86 −0 service/responders.js
View
BIN .DS_Store
Binary file not shown.
View
42 app/coffee/controllers/application.coffee
@@ -0,0 +1,42 @@
+
+class Application extends Backbone.Router
+
+ # routes:
+ # unless your application is light on routes, you'll probably them in other controllers
+
+ initialize: =>
+
+ # handy variables for mobile and online / offline
+ @isTouch = navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/)
+ @isOnline = navigator.onLine
+
+ # initialize storage helper, overrides backbone.sync, local, socket, ajax helper etc.
+ @sync = new Flint.Sync
+
+ # notifications are great!
+ @notifications = new Flint.Notifications @
+
+ # initialize helpers
+ @helpers = new views.Helpers
+
+ # YOUR APP CONTROLLERS HERE.
+ # be sure to pass @ so that Flint.Controllers can register themselves for binding/unbinding switches
+ @controllers = []
+
+ # return the app
+ this
+
+
+ #
+ # register controllers that require delegation space, classes that you register must implement @undelegate!
+ #
+ register: (controller) =>
+ @controllers.push(controller)
+
+ #
+ # undelegates events on controller views, controllers call this method when they being switched to.
+ #
+ undelegate: =>
+ _.each @controllers, (controller) =>
+ controller.undelegate()
+
View
0 app/coffee/controllers/controller.shell.coffee
No changes.
View
BIN app/public/favicon.ico
Binary file not shown.
View
83 app/stylus/_mixins.styl
@@ -0,0 +1,83 @@
+
+/* variables */
+
+
+/* mixins */
+
+rounded()
+ -webkit-border-radius arguments
+ -moz-border-radius arguments
+ border-radius arguments
+
+box-shadow()
+ box-shadow arguments
+ -moz-box-shadow arguments
+ -webkit-box-shadow arguments
+
+transform()
+ -webkit-transform arguments
+ -moz-transform arguments
+ -ms-transform arguments
+ -o-transform arguments
+ transform arguments
+
+gradient(startcolor=#fff, endcolor=#000, startx=left, starty=top, endx=left, endy=bottom)
+ background-image linear-gradient(startx starty, startcolor 0%, endcolor 100%)
+ background-image -o-linear-gradient(startx starty, startcolor 0%, endcolor 100%)
+ background-image -moz-linear-gradient(startx starty, startcolor 0%, endcolor 100%)
+ background-image -webkit-linear-gradient(startx starty, startcolor 0%, endcolor 100%)
+ background-image -ms-linear-gradient(startx starty, startcolor 0%, endcolor 100%)
+ background-image -webkit-gradient(linear,
+ startx starty,
+ endx endy,
+ color-stop(0, startcolor),
+ color-stop(1, endcolor)
+ )
+
+radial(startcolor=#fff, endcolor=#000, xlocation=center, ylocation=center, innersize=10px, outtersize=50px, shape=circle)
+ background-image -moz-radial-gradient(xlocation, shape, startcolor innersize, endcolor outtersize)
+ background-image -webkit-radial-gradient(xlocation ylocation, shape, startcolor innersize, endcolor outtersize)
+ background-image -o-radial-gradient(xlocation ylocation, shape, startcolor innersize, endcolor outtersize)
+ background-image -ms-radial-gradient(xlocation ylocation, shape, startcolor innersize, endcolor outtersize)
+ background-image radial-gradient(xlocation ylocation, shape, startcolor innersize, endcolor outtersize)
+ filter none
+
+repeating_gradient(angle= 0deg, startcolor=#000, startwidth=11px, endcolor=#fff, endwidth=11px, finalwidth=22px)
+ background-image -webkit-repeating-linear-gradient(angle, startcolor, startcolor startwidth, endcolor endwidth, endcolor finalwidth)
+ background-image -moz-repeating-linear-gradient(angle, startcolor, startcolor startwidth, endcolort endwidth, endcolor finalwidth)
+ background-image -o-repeating-linear-gradient(angle, startcolor, startcolor startwidth, endcolor endwidth, endcolor finalwidth)
+ background-image repeating-linear-gradient(angle, startcolor, startcolor startwidth, endcolor endwidth, endcolor finalwidth)
+
+
+transition(property=all, duration=.25s, ease=ease-out, delay=now)
+ -webkit-transition property duration ease
+ -moz-transition property duration ease
+ -o-transition property duration ease
+ -ms-transition property duration ease
+ -webkit-transition-delay delay
+
+disable-selection()
+ -webkit-touch-callout none
+ -webkit-user-select none
+ -khtml-user-select none
+ -moz-user-select none
+ -ms-user-select none
+ user-select none
+ cursor default
+
+animation(name, duration=1s, iteration=infinite, direction=normal, delay=0)
+ animation-name name
+ -moz-animation-name name
+ -webkit-animation-name name
+ animation-duration duration
+ -moz-animation-duration duration
+ -webkit-animation-duration duration
+ animation-iteration-count iteration
+ -webkit-animation-iteration-count iteration
+ -moz-animation-iteration-count iteration
+ animation-direction direction
+ -webkit-animation-direction direction
+ -moz-animation-direction direction
+ animation-delay delay
+ -webkit-animation-delay delay
+ -moz-animation-delay delay
View
109 app/stylus/application.styl
@@ -0,0 +1,109 @@
+
+@import '_mixins.styl'
+
+body, html
+ margin 0 auto
+ padding 0
+ background rgba(0,0,0,0.9) url('https://s3.amazonaws.com/flybook-asset/desktops/flyfish-lighter.jpg') center center no-repeat fixed
+ background-size cover
+ font default_font
+ color text_color
+ text-shadow 1px 1px text_shadow_color
+ line-height 25px
+ min-width 800px
+ min-height 650px
+ width 100%
+
+a
+ color link_color
+ text-decoration none
+a:hover
+ text-decoration underline
+
+p
+ margin 5px 0
+ clear both
+
+.small
+ font-size 11px
+ line-height 16px
+
+.highlight
+ font-size 12px
+ line-hight 16px
+ padding 10px
+ background highlight_color
+
+.error-msg
+ color #cc0000
+
+h1
+ font-size 21px
+ float left
+ font-family 'Museo700Regular', serif
+ font-weight normal
+ letter-spacing 0
+ margin 10px 0 5px 0
+ padding 11px 0 5px 0
+ text-shadow none
+ color 333
+
+h2
+ font-size 16px
+ font-family 'Helvetica', sans-serif
+ font-weight normal
+ padding 10px 0 5px 0
+ margin 0
+ text-shadow none
+ clear both
+
+h3
+ clear both
+ font-size 12px
+ font-family 'MuseoSans700Regular', serif
+ text-transform uppercase
+ letter-spacing 1px
+ font-weight normal
+ border-bottom 1px solid #ddd
+ color #777
+ margin 0 0 4px 0
+ padding 4px 0 0 0
+
+h5
+ margin 0
+
+
+h6
+ font-size 11px
+ text-transform uppercase
+ padding 5px 0
+ margin 0
+ font-weight normal
+
+#app
+ transition()
+ position absolute
+ top 40px
+ width 450px
+ z-index 1
+ min-height 200px
+ left -560px
+ padding 0 20px 40px 20px
+ margin 0 0 15px 0
+ box-shadow 1px 1px 4px rgba(0,0,0,0.2)
+ border-radius 0 0 8px 0
+ background #fff
+
+#dropdown
+ position fixed
+ top 0
+ z-index 3
+ width 100%
+ height 0
+ background #fff
+ transition()
+ box-shadow 0 1px 1px rgba(0,0,0,0.2)
+ .loader
+ position relative
+ top 80px
+
View
1 app/templates/default/create.hb
@@ -0,0 +1 @@
+<h1>Default Create</h1>
View
1 app/templates/default/edit.hb
@@ -0,0 +1 @@
+<h1>Default Edit</h1>
View
1 app/templates/default/list.hb
@@ -0,0 +1 @@
+<h1>Default List View</h1>
View
1 app/templates/default/view.hb
@@ -0,0 +1 @@
+<h1>Default View</h1>
View
8 core/calendar.coffee → core/client/calendar.coffee
@@ -61,8 +61,8 @@ class Calendar extends Backbone.View
# renders using the month() template
#
render: (year=false, month=false) ->
- @year = year if year
- @month = month if month
+ @year = year if year or year is 0 or year is '0'
+ @month = month if month or month is 0 or month is '0'
data =
month_name: @month_labels[@month]
day_labels: @day_labels
@@ -104,7 +104,7 @@ class Calendar extends Backbone.View
# renders the next month
#
next_month: ->
- if (@month + 1) is 12
+ if (parseInt(@month) + 1) is 12
@year++
@month = 0
else
@@ -117,7 +117,7 @@ class Calendar extends Backbone.View
# renders the previous month
#
previous_month: ->
- if @month is '0' or @month is 0
+ if parseInt(@month) is 0
@year--
@month = 11
else
View
0 core/collection.coffee → core/client/collection.coffee
File renamed without changes.
View
42 core/controller.coffee → core/client/controller.coffee
@@ -11,11 +11,10 @@ class Controller extends Backbone.Router
# Specify a template path for each CRUD action.
# There is also an optional help_template property
- template_create : 'forms/create'
- template_edit : 'forms/edit'
- template_view : 'forms/view'
- template_list : 'forms/list'
- template_help : false
+ template_create : 'default/create'
+ template_edit : 'default/edit'
+ template_view : 'default/view'
+ template_list : 'default/list'
# Specify the collection and model classes if the exist.
# Note that these are strings and not references to the classes themselves.
@@ -94,7 +93,7 @@ class Controller extends Backbone.Router
@app = app
# Call @init
- @init(app)
+ @init.apply(@, arguments)
# Return the instance
this
@@ -105,7 +104,7 @@ class Controller extends Backbone.Router
# Binds the application components to common events which include:
- # creation, editing, sorting, saving, deleting, leaving the view (cancled)
+ # creation, editing, sorting, saving, deleting, leaving the view (canceled)
bind: =>
_bind: =>
@bind()
@@ -137,12 +136,13 @@ class Controller extends Backbone.Router
@form.off 'delete saved canceled'
@form.model.off 'error'
@.off 'saved deleted sorted destroyed delete_undone sort_undone destroy_error'
+
# fetch()
# is a helper method that will fetch the list from the server
# without having to rewrite this routine each time you want to make sure the collection is there
- fetch: (callback) =>
- if @list.collection.length > 0
+ fetch: (callback, refresh=false) =>
+ if @list.collection.length > 0 and !refresh
return callback @list.collection
else
@list.collection.fetch
@@ -171,7 +171,22 @@ class Controller extends Backbone.Router
model.fetch
silent: true
success: =>
- callback model
+ if callback
+ callback model
+
+ # grab(id)
+ # is the synchronous version of get - will return false if the collection is empty,
+ # or try to find the object via Collection.get()
+ grab: (id) =>
+ if @list.collection.length is 0
+ model = false
+ else
+ model = @list.collection.get(id)
+ model
+
+ # refresh() simply calls fetch with refresh argument set to true, it will grab the collection from the data store
+ refresh: (callback) =>
+ @fetch callback, true
# create() is the c in your crud. it will render a new model instance into your form
# using defaults to populate any values specified.
@@ -190,6 +205,7 @@ class Controller extends Backbone.Router
message = _tmpl(model.attributes)
@app.notifications.notify(message) unless not @app.notifications or not @messages.created
@edit model.id
+ @trigger 'returned', model
# handle errors resulting from save and remove the model from the collection
,error: (model, message) =>
@app.notifications.error(message) unless not @app.notifications
@@ -210,11 +226,13 @@ class Controller extends Backbone.Router
# as a result Flint.Sync becomes aware of this change.
saved: (model) =>
@trigger 'saved', model
+ @app.notifications.notify('Saving...') unless not @app.notifications
model.save null, success: =>
_tmpl = tmpl_compile(@messages.saved)
message = _tmpl(model.attributes)
@app.notifications.notify(message) unless not @app.notifications or not @messages.saved
-
+ @trigger 'returned', model
+
# deleted() is recieved from @list or @form and behaves differently than you might expect.
# Instead of blowing the model away, it retains a copy and will prompt undo
deleted: (model, collection, options) =>
@@ -261,7 +279,7 @@ class Controller extends Backbone.Router
# Update is a handy method to overide which gets all the events broadcast by this class that
# by default, it will simply re-render the list view
update: =>
- @list.render @template_list
+ @list.render()
# Handle any errors recived from validation or other
error: (object, error) =>
View
15 core/form.coffee → core/client/form.coffee
@@ -17,11 +17,11 @@ class Form extends Backbone.View
'submit form' : 'nosubmit'
# initialize() is called by Backbone.View automaticallly, there was not a need to overwrite the contructor in this case
- initialize: (options) ->
+ initialize: ->
# extend additional events by the subclass
- @events = _.extend({}, @_events, @events)
+ @events = _.extend {}, @_events, @events
# call init for any other task, useful if you have to create life-cycle instances in a form class.
- @init()
+ @init.apply @, arguments
this
init: =>
@@ -34,7 +34,7 @@ class Form extends Backbone.View
if model
@model = model
@data.model = @model
- @data = _.extend({}, @data, @model.attributes)
+ @data = _.extend {}, @data, @model.attributes
# call before() using the actual markup replacement routine as a callback
@before =>
@@ -51,7 +51,8 @@ class Form extends Backbone.View
after: ->
- # changed() is called from the events property on form inputs.
+ # changed() is called from the events property on form inputs.
+ # this will broadcast two events, changed and changed:[property] if you want to listen to a specific property's change
changed: (e) ->
# stop propegation and get the target
e.stopPropagation()
@@ -83,7 +84,7 @@ class Form extends Backbone.View
# because the controller is listening for collection added event we need not broadcast anything
save: ->
@collection.add(@model)
-
+
# done() is called when a DOM element with class="done" is clicked. it is used for saving changes made to a model
# done() can be called with an optional silent argument which avoids the 'saved' event from firing
done: (silent=false) =>
@@ -94,7 +95,7 @@ class Form extends Backbone.View
# this method will unrender the view and trigger a 'canceled' event
# cancel() can be called with an option silent argument that will avoid the event from firing.
cancel: (silent=false) =>
- @trigger 'canceled' unless silent
+ @trigger 'canceled', @model unless silent and !_.isObject(silent)
$(@el).empty()
# save() is called when a DOM element with class="delete" is clicked. it removes a model from the collection
View
55 core/helpers.coffee → core/client/helpers.coffee
@@ -4,33 +4,33 @@ class Helpers
constructor: ->
# register class methods with handlebars
- Handlebars.registerHelper('eq', @eq)
- Handlebars.registerHelper('check_role', @check_role)
+ Handlebars.registerHelper 'eq', @eq
+ Handlebars.registerHelper 'check_role', @check_role
- Handlebars.registerHelper('link', @link)
- Handlebars.registerHelper('link_nohref', @link_nohref)
- Handlebars.registerHelper('list', @list)
- Handlebars.registerHelper('filtered_list', @filtered_list)
+ Handlebars.registerHelper 'link', @link
+ Handlebars.registerHelper 'link_nohref', @link_nohref
+ Handlebars.registerHelper 'list', @list
+ Handlebars.registerHelper 'filtered_list', @filtered_list
- Handlebars.registerHelper('input', @input)
- Handlebars.registerHelper('text_field', @text_field)
- Handlebars.registerHelper('password', @password)
- Handlebars.registerHelper('select', @select)
- Handlebars.registerHelper('select_range', @select_range)
- Handlebars.registerHelper('radio', @radio)
- Handlebars.registerHelper('checkbox', @checkbox)
- Handlebars.registerHelper('text_area', @text_area)
+ Handlebars.registerHelper 'input', @input
+ Handlebars.registerHelper 'text_field', @text_field
+ Handlebars.registerHelper 'password', @password
+ Handlebars.registerHelper 'select', @select
+ Handlebars.registerHelper 'select_range', @select_range
+ Handlebars.registerHelper 'radio', @radio
+ Handlebars.registerHelper 'checkbox', @checkbox
+ Handlebars.registerHelper 'text_area', @text_area
-
- Handlebars.registerHelper('month_grid', @month_grid)
- Handlebars.registerHelper('date_today', @date_today)
- Handlebars.registerHelper('sql_to_slash', @sql_to_slash)
-
- Handlebars.registerHelper('dollar', @dollar)
- Handlebars.registerHelper('random', @random)
- Handlebars.registerHelper('sum', @sum)
- Handlebars.registerHelper('truncate', @truncate)
- Handlebars.registerHelper('repeater', @repeater)
+ Handlebars.registerHelper 'month_grid', @month_grid
+ Handlebars.registerHelper 'date_today', @date_today
+ Handlebars.registerHelper 'sql_to_slash', @sql_to_slash
+ Handlebars.registerHelper 'date_format', @date_format # requires momentjs lib
+ Handlebars.registerHelper 'twenty_four_to_twelve', @twenty_four_to_twelve
+ Handlebars.registerHelper 'dollar', @dollar
+ Handlebars.registerHelper 'random', @random
+ Handlebars.registerHelper 'sum', @sum
+ Handlebars.registerHelper 'truncate', @truncate
+ Handlebars.registerHelper 'repeater', @repeater
@initialize()
@@ -278,6 +278,11 @@ class Helpers
sp[2] = sp[2].substr(0, sp[2].indexOf(' '))
new Date(sp[0], sp[1]-1, sp[2])
+ date_format: (date, format) =>
+ if !date or date is '' or date is '0000-00-00' or date is '0000-00-00 00:00:00'
+ return 'N/A'
+ moment(date).format(format)
+
sql_to_slash: (sql) =>
if !sql or sql is '0000-00-00' or sql is ''
return ''
@@ -325,7 +330,7 @@ class Helpers
sum
truncate: (str, length) ->
- if str.length > length
+ if str and str.length > length
return str.substr(0, length) + '...'
else
return str
View
34 core/list.coffee → core/client/list.coffee
@@ -27,11 +27,18 @@ class List extends Backbone.View
@template = template if template
@data = data if data
@before()
-
- if @template and @data
+
+ # default data is simply the collection models as template variable 'items'.
+ # if the collection needs to be marshaled more than once (particuarly sorted)
+ # you should do so in a custom before() method
+ if !@data
+ @data =
+ items: @collection.models
+
+ if @template
$(@el).html tmpl[@template](@data)
else if console and console.log
- console.log('WARNING Flint.List: @template or @data is undefined, unable to render view.')
+ console.log('WARNING Flint.List: @template is undefined, unable to render view.')
# make sortable
if @sortable
@@ -101,20 +108,23 @@ class List extends Backbone.View
# sortable handler
#
sorted: =>
- console.log('sorting.')
@serialized = []
order = 0
_.each @sortable.find('li'), (item, index) =>
id = item.getAttribute('id')
- last_order = @collection.get(id).get('sort_order')
- @collection.get(id).set 'sort_order', index, silent:true
- @collection.get(id).set 'order_before_sort', last_order, silent:true
- @serialized.push({
- id:id,
- sort_order:index
- })
-
+ model = @collection.get(id)
+ if model
+ last_order = model.get('sort_order')
+ @collection.get(id).set 'sort_order', index, silent:true
+ @collection.get(id).set 'order_before_sort', last_order, silent:true
+ @serialized.push
+ id: id
+ sort_order: index
+
+ # sort the collection and update default data. custom before() methods should override @data setting.
@collection.sort()
+ @data =
+ items: @collection.models
@trigger 'sort', @serialized
#
View
0 core/model.coffee → core/client/model.coffee
File renamed without changes.
View
7 core/notifications.coffee → core/client/notifications.coffee
@@ -37,6 +37,13 @@ class Notifications extends Backbone.View
@render(message, save, 'OK', 'Cancel')
@callback = save
@undo = ->
+
+ yes_or_no: (message, save) ->
+ $(@el).attr('class','notice')
+ .css({top:0})
+ @render(message, save, 'Yes', 'No')
+ @callback = save
+ @undo = ->
dismiss: (undo) =>
$(@el).css({top:'-100px'})
View
0 core/sync.coffee → core/client/sync.coffee
File renamed without changes.
View
0 core/view.coffee → core/client/view.coffee
File renamed without changes.
View
5 core/server/adapters/mongo.coffee
@@ -0,0 +1,5 @@
+
+class Mongo
+
+ constructor: ->
+
View
6 core/server/adapters/mysql.coffee
@@ -0,0 +1,6 @@
+
+class MySql
+
+ constructor: ->
+
+
View
22 core/server/model.coffee
@@ -0,0 +1,22 @@
+#
+# server side flint model
+#
+
+class Model
+
+ constructor: ->
+
+
+ # create
+ post: =>
+
+ #read
+ get: =>
+
+ # update
+ put: =>
+
+ # delete
+ destroy: =>
+
+
View
10 core/server/responder.coffee
@@ -0,0 +1,10 @@
+#
+# resonds to requests from the client
+#
+class Responder
+
+ constructor: (adapter) ->
+
+
+
+
View
4 node/bouncer.js
@@ -0,0 +1,4 @@
+
+// checks wristband colors !
+
+
View
45 node/builder.js
@@ -0,0 +1,45 @@
+
+// handles file system operations that create a new application and shell components
+fs = require('fs');
+cmd = require('child_process').exec;
+
+(function(){
+
+ var color = {
+ red : '\u001b[31m',
+ green: '\u001b[32m',
+ yellow: '\u001b[33m',
+ reset: '\u001b[0m'
+ }
+
+ exports.new = function(name){
+
+ try {
+
+ console.log(color.yellow + '[builder] Building new app "'+name+'"' + color.reset);
+ // make the new directory
+ var dir = './' + name
+ fs.mkdirSync(dir)
+
+ // copy primary application directories
+ cmd('cp -R ' + __dirname + '/../app/' + ' ' + dir + '/app/')
+ cmd('cp -R ' + __dirname + '/../public/' + ' ' + dir + '/public/')
+ cmd('cp -R ' + __dirname + '/../service/' + ' ' + dir + '/service/')
+ cmd('cp -R ' + __dirname + '/../flint.js' + ' ' + dir + '/flint.js')
+
+ // delete collection, controller and model shells
+ cmd('rm ' + __dirname + '/../app/coffee/collections/_collection.shell.coffee')
+ cmd('rm ' + __dirname + '/../app/coffee/controlers/controller.shell.coffee')
+ cmd('rm ' + __dirname + '/../app/coffee/models/model.shell.coffee')
+ cmd('rm ' + __dirname + '/../app/coffee/views/git.empty')
+
+ } catch (e){
+
+ console.log(color.red + '[builder] Error creating new filnt app: ' + color.reset);
+ console.log(e.message);
+
+ }
+
+ }
+
+})()
View
383 node/burner.js
@@ -0,0 +1,383 @@
+
+// burner currently replicates WAY too much of brewer's functionality.
+// unfortunately, the responders that are compiled needs flint.js to be part of the same file due to coffeescript protoyping.
+
+coffeescript = require('coffee-script');
+fs = require('fs');
+
+(function(){
+
+ //colored output
+ var color = {
+ red : '\u001b[31m',
+ green: '\u001b[32m',
+ yellow: '\u001b[33m',
+ reset: '\u001b[0m'
+ }
+
+ //options and helper functions scoped globally in simple namespaced object
+ var coffee = {
+
+ in: './app/coffee/',
+ out: './public/javascript/application.js',
+ scripts: [],
+ directories: [],
+ silent: false,
+ export: false
+
+ }
+
+ //watches a file for changes
+ var fileHasChanged = function(event, filename){
+
+ if(!coffee.silent)
+ console.log(color.yellow + '[burner] change detected to file, recompiling' + color.reset);
+ compileTemplates();
+
+ };
+
+ // for when folders (non recusrive mode is being used)
+ var folderHasChanged = function(event, filename){
+ if(!coffee.silent)
+ console.log(color.yellow + '[burner] new or removed file detected, recompiling' + color.reset);
+ readAndCompileFolders()
+
+ }
+
+ // watches a directory for changes
+ var directoryHasChanged = function(event, filename){
+ if(!coffee.silent)
+ console.log(color.yellow + '[burner] new or removed file detected, recompiling' + color.reset);
+ readAndCompile(coffee.in, coffee.watch);
+
+ };
+
+ var watchers = []
+ var unwatchAll = function(){
+
+ for(var w = 0; w < watchers.length; w++){
+ watchers[w].close();
+ }
+
+ }
+
+ //compiles to output destination
+ var compileTemplates = function(){
+
+ out = []
+
+ // the one difference is here. we need to get flint.js
+ // and we're assuming it is in the same location.. tisk tisk.
+ if(!coffee.build)
+ out.push( fs.readFileSync('service/flint.js', 'utf8') + '\n' );
+
+ try {
+
+ //console.log(coffee.directories)
+
+ // scope all the directories as objects for the begining of the file
+ for(var d = 0; d < coffee.directories.length; d++){
+ object = coffee.directories[d].namespace.replace(/\//g,'.');
+ out.push(object + ' = {}\n');
+ }
+
+ // create the flint object if we're building the core .js file
+ if(coffee.build){
+ out.push('Flint = {}\n');
+ }
+
+ // add the __ methods required for class inheritance
+ out.push('\n');
+ out.push('__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n');
+ out.push('__hasProp = {}.hasOwnProperty,\n');
+ out.push('__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };\n');
+
+ // compile all the coffee scripts
+ out.push('\n');
+
+ for(var s = 0; s < coffee.scripts.length; s++){
+
+ data = fs.readFileSync(coffee.scripts[s].file, 'utf8');
+ compiled = coffeescript.compile(data, {bare:true})
+
+ // crop off var Classname; line
+ compiled = compiled.substr(compiled.indexOf('\n\n'), compiled.length);
+
+ // trim so we can rescope objects based on the directory structure
+ compiled = compiled.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+
+ // space depending on folder defined or not
+ if(coffee.scripts[s].namespace.indexOf('/') > 0){
+ spaced = coffee.scripts[s].namespace.substr(0, coffee.scripts[s].namespace.lastIndexOf('/')).replace(/\//g,'.')
+ } else {
+ spaced = coffee.scripts[s].namespace
+
+ }
+
+
+ // flint gets uppercased because it is special
+ if(spaced == '' || coffee.build)
+ spaced = 'Flint'
+
+ // in case you left a file empty... oops!
+ // leaving a file empty will also reverse the entire order of recursive compilation ... no bueno.
+ if(compiled != '' && compiled != '\n')
+ out.push(spaced + '.' + compiled + '\n\n')
+
+ }
+
+ // see if we need our holder objects to export for require() purposes
+ if ( coffee.export ) {
+
+ for(var d = 0; d < coffee.directories.length; d++){
+ object = coffee.directories[d].namespace.replace(/\//g,'.');
+ out.push('exports.' + object + ' = ' + object + '\n');
+ }
+
+ // create the flint object if we're building the core .js file
+ if(coffee.build){
+ out.push('exports.Flint = Flint\n');
+ }
+
+ }
+
+ } catch (e) {
+
+ //ignore deleted file errors
+ if(e.message.toString().indexOf('no such file') <= 0){
+ console.log(color.red + '[burner] ERROR, coffeescript compile error:' + color.reset);
+ console.log(e.message);
+ }
+
+ }
+
+
+ //write the output file
+ try {
+
+ fs.writeFileSync(coffee.out, out.join(''), 'utf8');
+ if(!coffee.silent)
+ console.log(color.green + '[burner] compiled coffeescript sources to ' + coffee.out + color.reset);
+
+ } catch(e){
+
+ console.log(color.red + '[burner] ERROR! destination file or directory does not exist.' + color.reset + '\n:' + e);
+
+ }
+
+
+ };
+
+ // recursively reads in directory files and namespaces
+ var recurseCoffeeDirectory = function(dir, done) {
+
+ // stores results
+ var results = []
+
+ // read the directory
+ fs.readdir(dir, function(err, list) {
+
+ if (err) return done(err);
+ var pending = list.length;
+ if (!pending) return done(null, results);
+
+ // walk it
+ list.forEach( function(file) {
+
+ file = dir + '/' + file;
+
+ // determine if file or directory
+ fs.stat(file, function(err, stat) {
+
+ if (stat && stat.isDirectory()) {
+
+ // push as directory
+ results.push('directory:' + file);
+ recurseCoffeeDirectory(file, function(err, res) {
+ results = results.concat(res);
+ if (!--pending) done(null, results);
+
+ });
+
+ } else {
+
+ // push as file
+ results.push('file:' + file);
+ if (!--pending) done(null, results);
+
+ }
+
+ });
+ });
+ });
+
+ };
+
+ var readAndCompile = function(dir, watch){
+
+ coffee.scripts = []
+ coffee.directories = []
+
+ // unwatch all the files
+ unwatchAll();
+
+ // watch the directory itself
+ if(watch)
+ watchers.push( fs.watch(dir, directoryHasChanged) )
+
+ //read files in the directory
+ recurseCoffeeDirectory(dir, function(err, files) {
+
+ try {
+
+ if(err)
+ throw new Error('directory does not exists: ' + dir)
+
+ // sort the files to ensure the order went right, as readdir is async.
+ files.sort();
+
+ source_dir = coffee.in.toString();
+ for(var f = 0; f < files.length; f++){
+
+ // determine whether or not it's a file or directory.
+ info = files[f].split(':')
+
+ if( info[0] == 'file' && info[1].indexOf('.coffee') > 0 ){
+
+ // watch the file
+ if(watch) {
+
+ watchers.push(fs.watch(info[1], fileHasChanged))
+
+ }
+
+ // get the template namespace by using the extra directory
+ file_and_dir = info[1].substr( (info[1].indexOf(dir) + dir.length) + 1, info[1].length);
+ namespaced = file_and_dir.replace(/\.coffee/,'');
+
+ // push object for the compiler to work with
+ coffee.scripts.push({
+ namespace: namespaced,
+ file: info[1]
+ })
+
+
+ } else if ( info[0] == 'directory') {
+
+ namespaced = info[1].substr( (info[1].indexOf(source_dir) + source_dir.length) + 1, info[1].length);
+ coffee.directories.push({
+ namespace: namespaced,
+ directory: info[0]
+ })
+
+ // watch the directory for new / removed files
+ if(watch){
+
+ watchers.push(fs.watch(info[1], directoryHasChanged))
+
+ }
+
+ }
+
+ }
+
+ compileTemplates();
+
+ } catch (e){
+
+ console.log(color.red + '[burner] ERROR! check to be sure your directories exist. ' + color.reset);
+ console.log(e);
+
+ if(coffee.help)
+ console.log(coffee.help)
+
+ }
+
+ });
+
+ }
+
+ var readAndCompileFolders = function(folders, watch) {
+
+
+ coffee.scripts = []
+ coffee.directories = []
+
+ // unwatch all the files
+ unwatchAll();
+
+ for(var f = 0; f < folders.length; f++){
+
+ folder = folders[f];
+
+ Object.keys(folder).forEach(function(namespace) {
+ var dir = coffee.base + folder[namespace]
+
+ // create directory namespaced
+ coffee.directories.push({
+ namespace: namespace,
+ directory: dir
+ })
+
+ // watch the directory
+ if(watch)
+ watchers.push( fs.watch(dir, folderHasChanged) )
+
+ var files = fs.readdirSync(dir)
+ for(var i = 0; i < files.length; i++){
+
+ if( files[i].indexOf('.coffee') > 0){
+
+ if(watch)
+ watchers.push( fs.watch(dir + '/' + files[i], fileHasChanged) );
+
+ coffee.scripts.push({
+ namespace: namespace,
+ file: dir + '/' + files[i]
+ });
+
+ }
+
+ }
+
+ })
+
+ }
+
+ compileTemplates();
+
+ }
+
+
+// export functions for module use
+exports.configure = function(config){ coffee = config; }
+exports.watch = function(){
+
+
+ coffee.watch = true;
+ if(typeof coffee.in == 'string') {
+
+ console.log(color.yellow + '[burner] watching coffee directory ' + coffee.in + color.reset);
+ readAndCompile(coffee.base + coffee.in, true);
+
+ } else {
+
+ console.log(color.yellow + '[burner] watching '+ coffee.in.length + ' coffee folder(s) ' + color.reset)
+ readAndCompileFolders(coffee.in, true);
+ }
+
+}
+
+exports.compile = function(){
+ coffee.watch = false;
+ if(typeof coffee.in == 'string')
+ readAndCompile(coffee.base + coffee.in, false);
+ else
+ readAndCompileFolders(coffee.in, false);
+}
+
+
+})()
+
+
+
View
51 node/compressor.js
@@ -0,0 +1,51 @@
+// compresses javascript for deployment
+
+uglify = require('uglify-js');
+
+(function(){
+
+ //colored output
+ var color = {
+ red : '\u001b[31m',
+ green: '\u001b[32m',
+ reset: '\u001b[0m'
+ }
+
+ var sources = {}
+ exports.configure = function(config){ sources = config; }
+
+ exports.deploy = function(to_file){
+
+ console.log(color.green + '[compressor] compressing javascript, this could take a moment...' + color.reset)
+
+ try {
+
+ // concat and minify all the javascript output
+ deps = fs.readFileSync(sources.depends)
+ tmpl = fs.readFileSync(sources.plates)
+ app = fs.readFileSync(sources.scripts)
+
+ output = deps + '\n' + tmpl + '\n' + app
+
+ // compress the file
+ var ast = uglify.parser.parse(output);
+ ast = uglify.uglify.ast_mangle(ast);
+ ast = uglify.uglify.ast_squeeze(ast);
+ output = uglify.uglify.gen_code(ast);
+
+ // write the file
+ fs.writeFileSync(to_file, output, 'utf8')
+
+ // done!
+ console.log(color.green + '[compressor] compiled and minifed deploy target file: ' + to_file + color.reset)
+
+ } catch(e){
+
+ console.log(color.red + '[compressor] ERROR: problem compressing javasript: ' + color.reset)
+ console.log(e)
+
+ }
+
+ }
+
+})()
View
165 node/flint.js
@@ -1,44 +1,50 @@
-
// flint command line tool
+
// require the things we need
optimist = require('optimist');
path = require('path');
-brewer = require('./brewer');
-styler = require('./styler');
-depend = require('./dependency');
-plater = require('./plater');
-
// get optimist options
argv = optimist
- .usage('\nflint.\nFull stack coffeescript development \n\nInstructions: ')
+ .usage('\nflint.\nFull stack coffeescript development \n\Usage:')
+ .alias('n','new')
+ .describe('n','Creates a new flint application of specified name')
+ .alias('s','server')
+ .describe('s','Starts the express server on port specified by the configuration')
.alias('w','watch')
- .describe('w','Watches and compiles coffee, stylus, templates and dependencies')
+ .describe('w','Watches and compiles coffee, stylus, templates, dependencies as well as responders running the server.')
+ .alias('q','quiet')
+ .describe('q','Watch modifier. Watches files but only outputs errors, change and compile messages are silenced')
+ .default('q', false)
.alias('c','compile')
.describe('c','Compiles coffee, stylus, handlebars, templates and dependencies')
+ .alias('d','deploy')
+ .describe('d','Packages and minifies all javascript into a single production target')
.alias('f','file')
- .describe('f','Path to the flint configuration file')
- .default('f','flint.js')
+ .describe('f','Specify a configuration file to work with.')
+ .default('f','./flint.js')
.alias('b','build')
- .describe('b','Rebuilds the core flint javacript library')
+ .describe('b','Compiles the core flint libraries as flint.js to the build targets')
.argv
// load the configuration file and configure our tools
try {
+
+ // load the configuration and the base dir
+ cwd = process.cwd();
+ flint = require(cwd + '/' + argv.file)
+ base = path.resolve(path.dirname(argv.file)) + '/'
// compile and watch generally do the same things, but watch will also watch the files
- if(argv.compile || argv.watch) {
+ if(argv.compile || argv.watch || argv.deploy) {
- cwd = process.cwd();
- flint = require(cwd + '/' + argv.file)
- base = path.dirname(argv.file) + '/'
-
- // dependencies - todo, people are going to want to specify a load order for these
+ // dependencies, dealing with load order kind of sucks right now.
depencency = {}
depencency.in = base + flint.config.dependencies
depencency.out = base + flint.config.compile_dependencies_to
+ depend = require('./dependency');
depend.on(depencency)
// coffee destinations and template engine
@@ -46,12 +52,16 @@ try {
maker.in = flint.config.coffeescript
maker.out = base + flint.config.compile_coffee_to
maker.base = base
+ maker.silent = argv.quiet
+ brewer = require('./brewer');
brewer.configure(maker)
// stylus
artist = {}
artist.in = base + flint.config.stylus
artist.out = base + flint.config.compile_stylus_to
+ artist.silent = argv.quiet
+ styler = require('./styler');
styler.configure(artist)
// templates
@@ -59,8 +69,28 @@ try {
plates.in = base + flint.config.templates
plates.out = base + flint.config.compile_templates_to
plates.engine = flint.config.template_engine
+ plates.silent = argv.quiet
+ plater = require('./plater');
plater.configure(plates)
+ // see if we are interested in the server side of flint and set up burner to do the work.
+ if( argv.server ){
+
+ burn = {}
+ burn.in = flint.config.server_resources
+ burn.out = base + flint.config.compile_resources_to
+ burn.base = base
+ burn.export = true
+ burn.silent = argv.quiet
+ burner = require('./burner')
+ burner.configure(burn)
+ if ( argv.watch )
+ burner.watch()
+ if ( argv.compile )
+ burner.compile()
+
+ }
+
if ( argv.compile ) {
// compile it up
@@ -73,51 +103,118 @@ try {
if ( argv.watch ) {
- // compile it up
+ // watch and compile
depend.watch()
brewer.watch()
styler.watch()
- plater.watch()
+ plater.watch()
}
- // todo - deploy goes here.
+ if (argv.deploy) {
+
+ // deploy all client side javascript to a single minified file.
+ compressor = require('./compressor');
+ dest = base + flint.config.deploy_javascript_to
+ squeeze = {}
+ squeeze.depends = depencency.out
+ squeeze.plates = plates.out
+ squeeze.scripts = maker.out
+ compressor.configure(squeeze)
+ compressor.deploy(dest)
+
+ }
+
+ }
+
+ // start the express server on the specified port
+ if ( argv.server || argv.run){
+
+ // if we aren't running the live show, get a new brewer instance ready to watch the back end.
+ if ( !argv.run)
+ burner = require('./brewer')
+
+ // set base for the server config
+ flint.config.base = base
+
+ // fire up the server
+ server = require('./server')
+ server.configure(flint.config)
+ server.start(!argv.run, argv.watch)
+
+ }
+ // build tools.
+ // create a new application
+ if (argv.new || argv.generate){
+
+ builder = require('./builder');
+
+ if(argv.new){
+
+ builder.new(argv.new);
+
+ } else {
+
+ // generators
+
+ }
+
}
- // this uses brewer to rebuild the vendor/flint.js file from the ./core library files as Flint.Class ..
+ // this uses brewer to rebuild flint.js file from the ./core library coffeescript
if( argv.build ) {
+
+ brewer = require('./brewer');
if(argv.file){
- cwd = process.cwd();
- flint = require(cwd + '/' + argv.file)
- base = path.dirname(argv.file) + '/'
- dest = base + flint.config.build_target + '/flint.js'
+ client_dest = flint.config.client_build_target ? base + flint.config.client_build_target : './app/vendor/flint.js'
+ server_dest = flint.config.server_build_target ? base + flint.config.server_build_target : './service/flint.js'
} else {
- dest = './app/vendor/flint.js'
-
+ client_dest = './app/vendor/flint.js'
+ server_dest = './service/flint.js'
+
}
-
+
+ // brew the core/client coffee and output to the destination file.
coffee_maker = {}
coffee_maker.base = ''
- coffee_maker.in = __dirname + '/../core/'
- coffee_maker.out = dest;
+ coffee_maker.in = __dirname + '/../core/client/'
+ coffee_maker.out = client_dest;
coffee_maker.build = true
coffee_maker.template_engine = false
brewer.configure(coffee_maker)
brewer.compile()
+
+ // brew the core/server coffee and output to destiation file
+ burner = require('./burner')
+ back_burner = {}
+ back_burner.base = ''
+ back_burner.in = __dirname + '/../core/server/'
+ back_burner.out = server_dest;
+ back_burner.build = true
+ back_burner.export = true
+ back_burner.template_engine = false
+ burner.configure(back_burner)
+ burner.compile()
}
+ if(!argv.build && !argv.compile && !argv.watch && !argv.server && !argv.deploy && !argv.new && !argv.generate){
+
+ console.log(optimist.help())
+
+ }
+
+
} catch (e) {
- console.log('[flint] configuration file missing or currupted: ' + argv.file + '\n')
+ console.log('[flint] command line error:')
console.log(e)
- console.log(optimist.help())
-
-
+ console.log('[flint] configuration file: ' + argv.file)
+
}
View
657 node/inflector.js
@@ -0,0 +1,657 @@
+/*
+Copyright (c) 2010 Ryan Schuft (ryan.schuft@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+ This code is based in part on the work done in Ruby to support
+ infection as part of Ruby on Rails in the ActiveSupport's Inflector
+ and Inflections classes. It was initally ported to Javascript by
+ Ryan Schuft (ryan.schuft@gmail.com) in 2007.
+
+ The code is available at http://code.google.com/p/inflection-js/
+
+ The basic usage is:
+ 1. Include this script on your web page.
+ 2. Call functions on any String object in Javascript
+
+ Currently implemented functions:
+
+ String.pluralize(plural) == String
+ renders a singular English language noun into its plural form
+ normal results can be overridden by passing in an alternative
+
+ String.singularize(singular) == String
+ renders a plural English language noun into its singular form
+ normal results can be overridden by passing in an alterative
+
+ String.camelize(lowFirstLetter) == String
+ renders a lower case underscored word into camel case
+ the first letter of the result will be upper case unless you pass true
+ also translates "/" into "::" (underscore does the opposite)
+
+ String.underscore() == String
+ renders a camel cased word into words seperated by underscores
+ also translates "::" back into "/" (camelize does the opposite)
+
+ String.humanize(lowFirstLetter) == String
+ renders a lower case and underscored word into human readable form
+ defaults to making the first letter capitalized unless you pass true
+
+ String.capitalize() == String
+ renders all characters to lower case and then makes the first upper
+
+ String.dasherize() == String
+ renders all underbars and spaces as dashes
+
+ String.titleize() == String
+ renders words into title casing (as for book titles)
+
+ String.demodulize() == String
+ renders class names that are prepended by modules into just the class
+
+ String.tableize() == String
+ renders camel cased singular words into their underscored plural form
+
+ String.classify() == String
+ renders an underscored plural word into its camel cased singular form
+
+ String.foreign_key(dropIdUbar) == String
+ renders a class name (camel cased singular noun) into a foreign key
+ defaults to seperating the class from the id with an underbar unless
+ you pass true
+
+ String.ordinalize() == String
+ renders all numbers found in the string into their sequence like "22nd"
+*/
+
+/*
+ This sets up a container for some constants in its own namespace
+ We use the window (if available) to enable dynamic loading of this script
+ Window won't necessarily exist for non-browsers.
+
+if (window && !window.InflectionJS)
+{
+ window.InflectionJS = null;
+}
+*/
+
+/*
+ This sets up some constants for later use
+ This should use the window namespace variable if available
+*/
+InflectionJS =
+{
+ /*
+ This is a list of nouns that use the same form for both singular and plural.
+ This list should remain entirely in lower case to correctly match Strings.
+ */
+ uncountable_words: [
+ 'equipment', 'information', 'rice', 'money', 'species', 'series',
+ 'fish', 'sheep', 'moose', 'deer', 'news'
+ ],
+
+ /*
+ These rules translate from the singular form of a noun to its plural form.
+ */
+ plural_rules: [
+ [new RegExp('(m)an$', 'gi'), '$1en'],
+ [new RegExp('(pe)rson$', 'gi'), '$1ople'],
+ [new RegExp('(child)$', 'gi'), '$1ren'],
+ [new RegExp('^(ox)$', 'gi'), '$1en'],
+ [new RegExp('(ax|test)is$', 'gi'), '$1es'],
+ [new RegExp('(octop|vir)us$', 'gi'), '$1i'],
+ [new RegExp('(alias|status)$', 'gi'), '$1es'],
+ [new RegExp('(bu)s$', 'gi'), '$1ses'],
+ [new RegExp('(buffal|tomat|potat)o$', 'gi'), '$1oes'],
+ [new RegExp('([ti])um$', 'gi'), '$1a'],
+ [new RegExp('sis$', 'gi'), 'ses'],
+ [new RegExp('(?:([^f])fe|([lr])f)$', 'gi'), '$1$2ves'],
+ [new RegExp('(hive)$', 'gi'), '$1s'],
+ [new RegExp('([^aeiouy]|qu)y$', 'gi'), '$1ies'],
+ [new RegExp('(x|ch|ss|sh)$', 'gi'), '$1es'],
+ [new RegExp('(matr|vert|ind)ix|ex$', 'gi'), '$1ices'],
+ [new RegExp('([m|l])ouse$', 'gi'), '$1ice'],
+ [new RegExp('(quiz)$', 'gi'), '$1zes'],
+ [new RegExp('s$', 'gi'), 's'],
+ [new RegExp('$', 'gi'), 's']
+ ],
+
+ /*
+ These rules translate from the plural form of a noun to its singular form.
+ */
+ singular_rules: [
+ [new RegExp('(m)en$', 'gi'), '$1an'],
+ [new RegExp('(pe)ople$', 'gi'), '$1rson'],
+ [new RegExp('(child)ren$', 'gi'), '$1'],
+ [new RegExp('([ti])a$', 'gi'), '$1um'],
+ [new RegExp('((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$','gi'), '$1$2sis'],
+ [new RegExp('(hive)s$', 'gi'), '$1'],
+ [new RegExp('(tive)s$', 'gi'), '$1'],
+ [new RegExp('(curve)s$', 'gi'), '$1'],
+ [new RegExp('([lr])ves$', 'gi'), '$1f'],
+ [new RegExp('([^fo])ves$', 'gi'), '$1fe'],
+ [new RegExp('([^aeiouy]|qu)ies$', 'gi'), '$1y'],
+ [new RegExp('(s)eries$', 'gi'), '$1eries'],
+ [new RegExp('(m)ovies$', 'gi'), '$1ovie'],
+ [new RegExp('(x|ch|ss|sh)es$', 'gi'), '$1'],
+ [new RegExp('([m|l])ice$', 'gi'), '$1ouse'],
+ [new RegExp('(bus)es$', 'gi'), '$1'],
+ [new RegExp('(o)es$', 'gi'), '$1'],
+ [new RegExp('(shoe)s$', 'gi'), '$1'],
+ [new RegExp('(cris|ax|test)es$', 'gi'), '$1is'],
+ [new RegExp('(octop|vir)i$', 'gi'), '$1us'],
+ [new RegExp('(alias|status)es$', 'gi'), '$1'],
+ [new RegExp('^(ox)en', 'gi'), '$1'],
+ [new RegExp('(vert|ind)ices$', 'gi'), '$1ex'],
+ [new RegExp('(matr)ices$', 'gi'), '$1ix'],
+ [new RegExp('(quiz)zes$', 'gi'), '$1'],
+ [new RegExp('s$', 'gi'), '']
+ ],
+
+ /*
+ This is a list of words that should not be capitalized for title case
+ */
+ non_titlecased_words: [
+ 'and', 'or', 'nor', 'a', 'an', 'the', 'so', 'but', 'to', 'of', 'at',
+ 'by', 'from', 'into', 'on', 'onto', 'off', 'out', 'in', 'over',
+ 'with', 'for'
+ ],
+
+ /*
+ These are regular expressions used for converting between String formats
+ */
+ id_suffix: new RegExp('(_ids|_id)$', 'g'),
+ underbar: new RegExp('_', 'g'),
+ space_or_underbar: new RegExp('[\ _]', 'g'),
+ uppercase: new RegExp('([A-Z])', 'g'),
+ underbar_prefix: new RegExp('^_'),
+
+ /*
+ This is a helper method that applies rules based replacement to a String
+ Signature:
+ InflectionJS.apply_rules(str, rules, skip, override) == String
+ Arguments:
+ str - String - String to modify and return based on the passed rules
+ rules - Array: [RegExp, String] - Regexp to match paired with String to use for replacement
+ skip - Array: [String] - Strings to skip if they match
+ override - String (optional) - String to return as though this method succeeded (used to conform to APIs)
+ Returns:
+ String - passed String modified by passed rules
+ Examples:
+ InflectionJS.apply_rules("cows", InflectionJs.singular_rules) === 'cow'
+ */
+ apply_rules: function(str, rules, skip, override)
+ {
+ if (override)
+ {
+ str = override;
+ }
+ else
+ {
+ var ignore = (skip.indexOf(str.toLowerCase()) > -1);
+ if (!ignore)
+ {
+ for (var x = 0; x < rules.length; x++)
+ {
+ if (str.match(rules[x][0]))
+ {
+ str = str.replace(rules[x][0], rules[x][1]);
+ break;
+ }
+ }
+ }
+ }
+ return str;
+ }
+};
+
+/*
+ This lets us detect if an Array contains a given element
+ Signature:
+ Array.indexOf(item, fromIndex, compareFunc) == Integer
+ Arguments:
+ item - Object - object to locate in the Array
+ fromIndex - Integer (optional) - starts checking from this position in the Array
+ compareFunc - Function (optional) - function used to compare Array item vs passed item
+ Returns:
+ Integer - index position in the Array of the passed item
+ Examples:
+ ['hi','there'].indexOf("guys") === -1
+ ['hi','there'].indexOf("hi") === 0
+*/
+if (!Array.prototype.indexOf)
+{
+ Array.prototype.indexOf = function(item, fromIndex, compareFunc)
+ {
+ if (!fromIndex)
+ {
+ fromIndex = -1;
+ }
+ var index = -1;
+ for (var i = fromIndex; i < this.length; i++)
+ {
+ if (this[i] === item || compareFunc && compareFunc(this[i], item))
+ {
+ index = i;
+ break;
+ }
+ }
+ return index;
+ };
+}
+
+/*
+ You can override this list for all Strings or just one depending on if you
+ set the new values on prototype or on a given String instance.
+*/
+if (!String.prototype._uncountable_words)
+{
+ String.prototype._uncountable_words = InflectionJS.uncountable_words;
+}
+
+/*
+ You can override this list for all Strings or just one depending on if you
+ set the new values on prototype or on a given String instance.
+*/
+if (!String.prototype._plural_rules)
+{
+ String.prototype._plural_rules = InflectionJS.plural_rules;
+}
+
+/*
+ You can override this list for all Strings or just one depending on if you
+ set the new values on prototype or on a given String instance.
+*/
+if (!String.prototype._singular_rules)
+{
+ String.prototype._singular_rules = InflectionJS.singular_rules;
+}
+
+/*
+ You can override this list for all Strings or just one depending on if you
+ set the new values on prototype or on a given String instance.
+*/
+if (!String.prototype._non_titlecased_words)
+{
+ String.prototype._non_titlecased_words = InflectionJS.non_titlecased_words;
+}
+
+/*
+ This function adds plurilization support to every String object
+ Signature:
+ String.pluralize(plural) == String
+ Arguments:
+ plural - String (optional) - overrides normal output with said String
+ Returns:
+ String - singular English language nouns are returned in plural form
+ Examples:
+ "person".pluralize() == "people"
+ "octopus".pluralize() == "octopi"
+ "Hat".pluralize() == "Hats"
+ "person".pluralize("guys") == "guys"
+*/
+if (!String.prototype.pluralize)
+{
+ String.prototype.pluralize = function(plural)
+ {
+ return InflectionJS.apply_rules(
+ this,
+ this._plural_rules,
+ this._uncountable_words,
+ plural
+ );
+ };
+}
+
+/*
+ This function adds singularization support to every String object
+ Signature:
+ String.singularize(singular) == String
+ Arguments:
+ singular - String (optional) - overrides normal output with said String
+ Returns:
+ String - plural English language nouns are returned in singular form
+ Examples:
+ "people".singularize() == "person"
+ "octopi".singularize() == "octopus"
+ "Hats".singularize() == "Hat"
+ "guys".singularize("person") == "person"
+*/
+if (!String.prototype.singularize)
+{
+ String.prototype.singularize = function(singular)
+ {
+ return InflectionJS.apply_rules(
+ this,
+ this._singular_rules,
+ this._uncountable_words,
+ singular
+ );
+ };
+}
+
+/*
+ This function adds camelization support to every String object
+ Signature:
+ String.camelize(lowFirstLetter) == String
+ Arguments:
+ lowFirstLetter - boolean (optional) - default is to capitalize the first
+ letter of the results... passing true will lowercase it
+ Returns:
+ String - lower case underscored words will be returned in camel case
+ additionally '/' is translated to '::'
+ Examples:
+ "message_properties".camelize() == "MessageProperties"
+ "message_properties".camelize(true) == "messageProperties"
+*/
+if (!String.prototype.camelize)
+{
+ String.prototype.camelize = function(lowFirstLetter)
+ {
+ var str = this.toLowerCase();
+ var str_path = str.split('/');
+ for (var i = 0; i < str_path.length; i++)
+ {
+ var str_arr = str_path[i].split('_');
+ var initX = ((lowFirstLetter && i + 1 === str_path.length) ? (1) : (0));
+ for (var x = initX; x < str_arr.length; x++)
+ {
+ str_arr[x] = str_arr[x].charAt(0).toUpperCase() + str_arr[x].substring(1);
+ }
+ str_path[i] = str_arr.join('');
+ }
+ str = str_path.join('::');
+ return str;
+ };
+}
+
+/*
+ This function adds underscore support to every String object
+ Signature:
+ String.underscore() == String
+ Arguments:
+ N/A
+ Returns:
+ String - camel cased words are returned as lower cased and underscored
+ additionally '::' is translated to '/'
+ Examples:
+ "MessageProperties".camelize() == "message_properties"
+ "messageProperties".underscore() == "message_properties"
+*/
+if (!String.prototype.underscore)
+{
+ String.prototype.underscore = function()
+ {
+ var str = this;
+ var str_path = str.split('::');
+ for (var i = 0; i < str_path.length; i++)
+ {
+ str_path[i] = str_path[i].replace(InflectionJS.uppercase, '_$1');
+ str_path[i] = str_path[i].replace(InflectionJS.underbar_prefix, '');
+ }
+ str = str_path.join('/').toLowerCase();
+ return str;
+ };
+}
+
+/*
+ This function adds humanize support to every String object
+ Signature:
+ String.humanize(lowFirstLetter) == String
+ Arguments:
+ lowFirstLetter - boolean (optional) - default is to capitalize the first
+ letter of the results... passing true will lowercase it
+ Returns:
+ String - lower case underscored words will be returned in humanized form
+ Examples:
+ "message_properties".humanize() == "Message properties"
+ "message_properties".humanize(true) == "message properties"
+*/
+if (!String.prototype.humanize)
+{
+ String.prototype.humanize = function(lowFirstLetter)
+ {
+ var str = this.toLowerCase();
+ str = str.replace(InflectionJS.id_suffix, '');
+ str = str.replace(InflectionJS.underbar, ' ');
+ if (!lowFirstLetter)
+ {
+ str = str.capitalize();
+ }
+ return str;
+ };
+}
+
+/*
+ This function adds capitalization support to every String object
+ Signature:
+ String.capitalize() == String
+ Arguments:
+ N/A
+ Returns:
+ String - all characters will be lower case and the first will be upper
+ Examples:
+ "message_properties".capitalize() == "Message_properties"
+ "message properties".capitalize() == "Message properties"
+*/
+if (!String.prototype.capitalize)
+{
+ String.prototype.capitalize = function()
+ {
+ var str = this.toLowerCase();
+ str = str.substring(0, 1).toUpperCase() + str.substring(1);
+ return str;
+ };
+}
+
+/*
+ This function adds dasherization support to every String object
+ Signature:
+ String.dasherize() == String
+ Arguments:
+ N/A
+ Returns:
+ String - replaces all spaces or underbars with dashes
+ Examples:
+ "message_properties".capitalize() == "message-properties"
+ "Message Properties".capitalize() == "Message-Properties"
+*/
+if (!String.prototype.dasherize)
+{
+ String.prototype.dasherize = function()
+ {
+ var str = this;
+ str = str.replace(InflectionJS.space_or_underbar, '-');
+ return str;
+ };
+}
+
+/*
+ This function adds titleize support to every String object
+ Signature:
+ String.titleize() == String
+ Arguments:
+ N/A
+ Returns:
+ String - capitalizes words as you would for a book title
+ Examples:
+ "message_properties".titleize() == "Message Properties"
+ "message properties to keep".titleize() == "Message Properties to Keep"
+*/
+if (!String.prototype.titleize)
+{
+ String.prototype.titleize = function()
+ {
+ var str = this.toLowerCase();
+ str = str.replace(InflectionJS.underbar, ' ');
+ var str_arr = str.split(' ');
+ for (var x = 0; x < str_arr.length; x++)
+ {
+ var d = str_arr[x].split('-');
+ for (var i = 0; i < d.length; i++)
+ {
+ if (this._non_titlecased_words.indexOf(d[i].toLowerCase()) < 0)
+ {
+ d[i] = d[i].capitalize();
+ }
+ }
+ str_arr[x] = d.join('-');
+ }
+ str = str_arr.join(' ');
+ str = str.substring(0, 1).toUpperCase() + str.substring(1);
+ return str;
+ };
+}
+
+/*
+ This function adds demodulize support to every String object
+ Signature:
+ String.demodulize() == String
+ Arguments:
+ N/A
+ Returns:
+ String - removes module names leaving only class names (Ruby style)
+ Examples:
+ "Message::Bus::Properties".demodulize() == "Properties"
+*/
+if (!String.prototype.demodulize)
+{
+ String.prototype.demodulize = function()
+ {
+ var str = this;
+ var str_arr = str.split('::');
+ str = str_arr[str_arr.length - 1];
+ return str;
+ };
+}
+
+/*
+ This function adds tableize support to every String object
+ Signature:
+ String.tableize() == String
+ Arguments:
+ N/A
+ Returns:
+ String - renders camel cased words into their underscored plural form
+ Examples:
+ "MessageBusProperty".tableize() == "message_bus_properties"
+*/
+if (!String.prototype.tableize)
+{
+ String.prototype.tableize = function()
+ {
+ var str = this;
+ str = str.underscore().pluralize();
+ return str;
+ };
+}
+
+/*
+ This function adds classification support to every String object
+ Signature:
+ String.classify() == String
+ Arguments:
+ N/A
+ Returns:
+ String - underscored plural nouns become the camel cased singular form
+ Examples:
+ "message_bus_properties".classify() == "MessageBusProperty"
+*/
+if (!String.prototype.classify)
+{
+ String.prototype.classify = function()
+ {
+ var str = this;
+ str = str.camelize().singularize();
+ return str;
+ };
+}
+
+/*
+ This function adds foreign key support to every String object
+ Signature:
+ String.foreign_key(dropIdUbar) == String
+ Arguments:
+ dropIdUbar - boolean (optional) - default is to seperate id with an
+ underbar at the end of the class name, you can pass true to skip it
+ Returns:
+ String - camel cased singular class names become underscored with id
+ Examples:
+ "MessageBusProperty".foreign_key() == "message_bus_property_id"
+ "MessageBusProperty".foreign_key(true) == "message_bus_propertyid"
+*/
+if (!String.prototype.foreign_key)
+{
+ String.prototype.foreign_key = function(dropIdUbar)
+ {
+ var str = this;
+ str = str.demodulize().underscore() + ((dropIdUbar) ? ('') : ('_')) + 'id';
+ return str;
+ };
+}
+
+/*
+ This function adds ordinalize support to every String object
+ Signature:
+ String.ordinalize() == String
+ Arguments:
+ N/A
+ Returns:
+ String - renders all found numbers their sequence like "22nd"
+ Examples:
+ "the 1 pitch".ordinalize() == "the 1st pitch"
+*/
+if (!String.prototype.ordinalize)
+{
+ String.prototype.ordinalize = function()
+ {
+ var str = this;
+ var str_arr = str.split(' ');
+ for (var x = 0; x < str_arr.length; x++)
+ {
+ var i = parseInt(str_arr[x]);
+ if (i === NaN)
+ {
+ var ltd = str_arr[x].substring(str_arr[x].length - 2);
+ var ld = str_arr[x].substring(str_arr[x].length - 1);
+ var suf = "th";
+ if (ltd != "11" && ltd != "12" && ltd != "13")
+ {
+ if (ld === "1")
+ {
+ suf = "st";
+ }
+ else if (ld === "2")
+ {
+ suf = "nd";
+ }
+ else if (ld === "3")
+ {
+ suf = "rd";
+ }
+ }
+ str_arr[x] += suf;