Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First draft

  • Loading branch information...
commit 66e2b9b5bb451a416cb2e5d046db25300e985d38 1 parent 08ff3c0
Etienne Lemay authored
1  .gitignore
View
@@ -0,0 +1 @@
+node_modules/
10 bin/skeleton
View
@@ -0,0 +1,10 @@
+#!/usr/bin/env node
+
+var path = require('path')
+var fs = require('fs')
+var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib')
+
+require('coffee-script')
+
+Skeleton = require(lib + '/skeleton')
+new Skeleton
2  lib/skeleton/helpers.coffee
View
@@ -0,0 +1,2 @@
+Array::last = ->
+ this[this.length - 1]
95 lib/skeleton/index.coffee
View
@@ -0,0 +1,95 @@
+# Node.js dependencies
+fs = require 'fs'
+path = require 'path'
+util = require 'util'
+mkdirp = require 'mkdirp'
+
+# Local dependencies
+Template = require './template'
+OptionParser = require './option_parser'
+
+# Helpers
+require './helpers'
+
+# Main class
+class Skeleton
+
+ @VERSION = '0.0.1'
+
+ @OPTIONS = [
+ ['-h', '--help', 'display this help message']
+ ['-v', '--version', 'display the version number']
+ ['-r', '--renderer', 'use specified renderer [only ejs for now]']
+ ]
+
+ # Bin command
+ constructor: ->
+ args = process.argv.splice(2)
+ options = new OptionParser(args)
+
+ if options.help
+ this.displayHelp()
+ return
+
+ if options.version
+ this.displayVersion()
+ return
+
+ this.createProject(options.appName, options) if options.appName
+
+ createProject: (appName, opts) =>
+ template = new Template(appName, opts)
+ for filename, content of template.files
+ this.write filename, "#{content}\n"
+
+ write: (path, content) ->
+ this.mkdir path, =>
+ return if path.split('/').pop() == 'empty'
+
+ fs.writeFile path, content, (err) =>
+ throw err if err
+ this.displayLine "=> Create #{path}"
+
+ mkdir: (filename, callback=null) ->
+ parts = filename.split('/')
+ parts.pop()
+ path = parts.join('/')
+
+ mkdirp path, '0755', (err) ->
+ throw err if err
+ callback() if callback
+
+ # Display messages
+ displayHelp: ->
+ rules = []
+ longest = 0
+
+ for option in Skeleton.OPTIONS
+ short = option[0]
+ long = option[1]
+ desc = option[2]
+
+ length = short.length + long.length
+ longest = length if length > longest
+
+ rules.push
+ short: short
+ long: long
+ desc: desc
+ length: length
+
+ this.displayLine '\nUsage: skeleton [options] myapp\n'
+
+ for rule in rules
+ spaces = new Array(longest - rule.length + 3).join(' ')
+ this.displayLine "#{rule.short}, #{rule.long}#{spaces}#{rule.desc}"
+
+ displayVersion: ->
+ this.displayLine "Skeleton version #{Skeleton.VERSION}"
+
+ displayLine: (line) ->
+ process.stdout.write "#{line}\n"
+
+
+# Exports
+module.exports = Skeleton
25 lib/skeleton/option_parser.coffee
View
@@ -0,0 +1,25 @@
+class OptionParser
+
+ constructor: (@args) ->
+ joinedArgs = @args.join('|')
+
+ # TODO: Make the returned object completely dynamic
+ return {
+ help: joinedArgs.search(/-h|--help/) > -1 || args.length == 0
+ version: joinedArgs.search(/-v|--version/) > -1
+ renderer: this.getOptionValue ['-r', '--renderer']
+ appName: this.getOptionValue(['-a', '--appname']) || @args.last()
+ }
+
+ # Private
+ # Returns given flags value or null
+ getOptionValue: (flags) ->
+ for flag in flags
+ index = @args.indexOf(flag)
+ return @args[index + 1] if index > -1
+
+ null
+
+
+# Exports
+module.exports = OptionParser
246 lib/skeleton/template.coffee
View
@@ -0,0 +1,246 @@
+class Template
+
+ constructor: (@appName, @opts) ->
+ @files = this.setFiles()
+
+ setFiles: ->
+ files = {}
+
+ # ./myapp
+ files["#{@appName}/.gitignore"] = """
+ node_modules/
+ """
+
+ files["#{@appName}/package.json"] = """
+ {
+ "name": "#{@appName}"
+ , "version": "0.0.1"
+ , "dependencies": {
+ "express": "3.0.x"
+ , "connect-assets": "2.1.x"
+ , "stylus": "*"
+ , "ejs": "*"
+ , "coffee-script": "*"
+ }
+ , "scripts": {
+ "start": "server.js"
+ }
+ , "engines": {
+ "node": "0.8.0"
+ }
+ }
+ """
+
+ files["#{@appName}/README.md"] = """
+ # #{@appName}
+ ***
+ App structure generated by [Skeleton](https://github.com/EtienneLem/skeleton)
+ """
+
+ files["#{@appName}/server.js"] = """
+ require("coffee-script")
+ require("./app/app.coffee")
+ """
+
+ # ./myapp/app
+ files["#{@appName}/app/app.coffee"] = """
+ # Modules
+ express = require 'express'
+ http = require 'http'
+ app = express()
+
+ # Boot setup
+ require("\#{__dirname}/../config/boot")(app)
+
+ # Configuration
+ app.configure ->
+ port = process.env.PORT || 3000
+ if process.argv.indexOf('-p') >= 0
+ port = process.argv[process.argv.indexOf('-p') + 1]
+
+ app.set 'port', port
+ app.set 'views', "\#{__dirname}/views"
+ app.set 'view engine', 'ejs'
+ app.use express.static("\#{__dirname}/../public")
+ app.use express.favicon()
+ app.use express.logger('dev')
+ app.use express.bodyParser()
+ app.use express.methodOverride()
+ app.use require('connect-assets')(src: "\#{__dirname}/assets")
+ app.use app.router
+
+ app.configure 'development', ->
+ app.use express.errorHandler()
+
+ # Routes
+ require("\#{__dirname}/routes")(app)
+
+ # Server
+ http.createServer(app).listen app.get('port'), ->
+ console.log "Express server listening on port \#{app.get 'port'} in \#{app.settings.env} mode"
+ """
+
+ # ./myapp/app/assets/css
+ files["#{@appName}/app/assets/css/styles.styl"] = """
+ // Based on <https://github.com/heliom/stylus-utils/blob/master/styles.styl-sample>
+ // @import "nib"
+
+ // Reset ---------------------------------------------------------------------
+ *
+ margin: 0; padding: 0
+ -webkit-box-sizing: border-box
+ -moz-box-sizing: border-box
+ box-sizing: border-box
+
+ // Base ----------------------------------------------------------------------
+ html
+ font-size: 62.5%
+ height: 100%
+
+ body
+ font-size: 16
+
+ body, legend, input, textarea, button
+ font-family: 'Helvetica Neue'
+ line-height: 1.4
+ color: #333
+
+ a:link, a:visited { color: deeppink }
+ a:focus, a:link:hover { color: hotpink }
+ """
+
+ # ./myapp/app/assets/js
+ files["#{@appName}/app/assets/js/scripts.coffee"] = """
+ # console.log '#{@appName}'
+ """
+
+ # ./myapp/app/controllers
+ files["#{@appName}/app/controllers/application_controller.coffee"] = """
+ class ApplicationController
+
+ # GET /
+ @index = (req, res) ->
+ res.render 'index',
+ view: 'index'
+
+
+ # Exports
+ module.exports = (app) ->
+ app.ApplicationController = ApplicationController
+ """
+
+ # ./myapp/app/helpers
+ files["#{@appName}/app/helpers/index.coffee"] = """
+ fs = require 'fs'
+
+ # Recursively require a folder’s files
+ exports.autoload = autoload = (dir, app) ->
+ fs.readdirSync(dir).forEach (file) ->
+ path = "\#{dir}/\#{file}"
+ stats = fs.lstatSync(path)
+
+ # Go through the loop again if it is a directory
+ if stats.isDirectory()
+ autoload path, app
+ else
+ require(path)?(app)
+
+ # Capitalize a string
+ # string => String
+ String::capitalize = () ->
+ this.replace /(?:^|s)S/g, (a) -> a.toUpperCase()
+
+ # Classify a string
+ # application_controller => ApplicationController
+ String::classify = (str) ->
+ classified = []
+ words = str.split('_')
+ for word in words
+ classified.push word.capitalize()
+
+ classified.join('')
+ """
+
+ # ./myapp/app/routes
+ files["#{@appName}/app/routes/index.coffee"] = """
+ module.exports = (app) ->
+ # Index
+ app.get '/', app.ApplicationController.index
+
+ # Error handling (No previous route found. Assuming it’s a 404)
+ app.get '/*', (req, res) ->
+ NotFound res
+
+ NotFound = (res) ->
+ res.render '404', status: 404, view: 'four-o-four'
+ """
+
+ # ./myapp/app/views
+ files["#{@appName}/app/views/404.ejs"] = "<h1>Nothing here…</h1>"
+
+ files["#{@appName}/app/views/index.ejs"] = """
+ <h1>This page has been generated by <a href="https://github.com/EtienneLem/skeleton">Skeleton</a></h1>
+ <p>Edit in <span>#{@appName}/views/index.ejs</span></p>
+ """
+
+ files["#{@appName}/app/views/layout.ejs"] = """
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>#{@appName}</title>
+ <%- css('styles.css') %>
+ </head>
+ <body data-view="<%= view %>">
+ <%- body %>
+ <script>
+ document.write('<script src=' +
+ ('__proto__' in {} ? 'http://zeptojs.com/zepto.min.js' : 'http://code.jquery.com/jquery-1.7.2.min.js') +
+ '.js><\/script>')
+ </script>
+ <%- js('scripts.js') %>
+ </body>
+ </html>
+ """
+
+ # ./myapp/config
+ files["#{@appName}/config/boot.coffee"] = """
+ module.exports = (app) ->
+ # Helpers
+ app.helpers = require "\#{__dirname}/../app/helpers"
+
+ # Lib
+ app.helpers.autoload "\#{__dirname}/../lib", app
+
+ # Controllers
+ app.helpers.autoload "\#{__dirname}/../app/controllers", app
+ """
+
+ # ./myapp/lib/myapp
+ files["#{@appName}/lib/#{@appName}/my_custom_class.coffee"] = """
+ # module.exports = (app) ->
+ # # Your code
+ #
+ #
+ # Or if you want this to be a class
+ #
+ # class MyCustomClass
+ #
+ # constructor: (args) ->
+ # # Your code
+ #
+ # # Exports
+ # module.exports = (app) ->
+ # app.MyCustomClass = MyCustomClass
+ #
+ # Usage: new app.MyCustomClass(args)
+ """
+
+ # ./myapp/public
+ files["#{@appName}/public/empty"] = ""
+
+ # Return the files object
+ files
+
+
+module.exports = Template
17 package.json
View
@@ -0,0 +1,17 @@
+{
+ "name": "skeleton",
+ "description": "Express 3.0 framework-less app structure generator",
+ "version": "0.0.1",
+ "author": "Etienne Lemay",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/EtienneLem/skeleton.git"
+ },
+ "engines": {"node": "*"},
+ "dependencies": {
+ "coffee-script": "1.3.x",
+ "mkdirp": "0.3.x"
+ },
+ "keywords": ["skeleton", "express", "structure"],
+ "bin": { "skeleton": "./bin/skeleton" }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.