From 9698a9e8126fa42292d2e8bd104bbfc137e702ce Mon Sep 17 00:00:00 2001 From: Matt Colyer Date: Wed, 18 Sep 2013 15:02:36 -0700 Subject: [PATCH 1/7] Add `apm init -p package-name` for generating packages --- spec/apm-cli-spec.coffee | 29 ++++++ src/apm-cli.coffee | 1 + src/fs.coffee | 3 + src/init.coffee | 95 +++++++++++++++++++ .../keymaps/__package-name__.cson.template | 3 + .../lib/__package-name__-view.coffee.template | 25 +++++ .../lib/__package-name__.coffee.template | 13 +++ templates/package/package.cson | 2 + .../__package-name__-spec.coffee.template | 5 + ...__package-name__-view-spec.coffee.template | 21 ++++ .../stylesheets/__package-name__.css.template | 2 + 11 files changed, 199 insertions(+) create mode 100644 src/init.coffee create mode 100644 templates/package/keymaps/__package-name__.cson.template create mode 100644 templates/package/lib/__package-name__-view.coffee.template create mode 100644 templates/package/lib/__package-name__.coffee.template create mode 100644 templates/package/package.cson create mode 100644 templates/package/spec/__package-name__-spec.coffee.template create mode 100644 templates/package/spec/__package-name__-view-spec.coffee.template create mode 100644 templates/package/stylesheets/__package-name__.css.template diff --git a/spec/apm-cli-spec.coffee b/spec/apm-cli-spec.coffee index 3577d1ef9..a4ce588d8 100644 --- a/spec/apm-cli-spec.coffee +++ b/spec/apm-cli-spec.coffee @@ -508,3 +508,32 @@ describe 'apm command line interface', -> expect(fs.existsSync(repoPath)).toBeTruthy() expect(fs.existsSync(linkedRepoPath)).toBeTruthy() expect(fs.realpathSync(linkedRepoPath)).toBe fs.realpathSync(repoPath) + + describe "apm init", -> + [packagePath] = [] + + beforeEach -> + currentDir = temp.mkdirSync('apm-init-') + spyOn(process, 'cwd').andReturn(currentDir) + packagePath = path.join(currentDir, 'fake-package') + + describe "when creating a package", -> + it "generates the proper file structure", -> + callback = jasmine.createSpy('callback') + apm.run(['init', '--package', 'fake-package'], callback) + + waitsFor 'waiting for develop to complete', -> + callback.callCount is 1 + + runs -> + expect(fs.existsSync(packagePath)).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'keymaps'))).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'keymaps', 'fake-package.cson'))).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'lib'))).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'lib', 'fake-package-view.coffee'))).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'lib', 'fake-package.coffee'))).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'spec', 'fake-package-view-spec.coffee'))).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'spec', 'fake-package-spec.coffee'))).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'stylesheets', 'fake-package.css'))).toBeTruthy() + expect(fs.existsSync(path.join(packagePath, 'package.cson'))).toBeTruthy() + diff --git a/src/apm-cli.coffee b/src/apm-cli.coffee index 67fe91724..30651477c 100644 --- a/src/apm-cli.coffee +++ b/src/apm-cli.coffee @@ -16,6 +16,7 @@ commandClasses = [ require './uninstaller' require './unlinker' require './updater' + require './init' ] commands = {} diff --git a/src/fs.coffee b/src/fs.coffee index 0b3484294..466f8bfde 100644 --- a/src/fs.coffee +++ b/src/fs.coffee @@ -33,6 +33,9 @@ fsAdditions = else [] + listRecursive: (directoryPath) -> + wrench.readdirSyncRecursive(directoryPath) + rm: (pathToRemove) -> rimraf.sync(pathToRemove) diff --git a/src/init.coffee b/src/init.coffee new file mode 100644 index 000000000..3b505144b --- /dev/null +++ b/src/init.coffee @@ -0,0 +1,95 @@ +path = require 'path' + +async = require 'async' +optimist = require 'optimist' + +Command = require './command' +config = require './config' +fs = require './fs' +Installer = require './installer' + +module.exports = +class Generator extends Command + @commandNames: ['init'] + + constructor: -> + @atomNpmPath = require.resolve('npm/bin/npm-cli') + + parseOptions: (argv) -> + options = optimist(argv) + + options.usage """ + Usage: apm init -p + + Generates code scaffolding for either a theme or package depending + on option selected. + """ + options.alias('p', 'package').describe('package', 'Generates a basic package') + options.alias('h', 'help').describe('help', 'Print this usage message') + + showHelp: (argv) -> @parseOptions(argv).showHelp() + + run: (options) -> + {callback} = options + options = @parseOptions(options.commandArgs) + if options.argv.package? + packagePath = path.join(process.cwd(), options.argv.package) + @generatePackage(packagePath) + + callback() + + generatePackage: (packagePath) -> + templatePath = path.resolve(__dirname, '..', 'templates', 'package') + packageName = path.basename(packagePath) + + fs.mkdir(packagePath) + + for childPath in fs.listRecursive(templatePath) + templateChildPath = path.resolve(__dirname, '..', 'templates', 'package', childPath) + relativePath = templateChildPath.replace(templatePath, "") + relativePath = relativePath.replace(/^\//, '') + relativePath = relativePath.replace(/\.template$/, '') + relativePath = @replacePackageNamePlaceholders(relativePath, packageName) + + sourcePath = path.join(packagePath, relativePath) + if fs.isDirectory(templateChildPath) + fs.mkdir(sourcePath) + else if fs.isFile(templateChildPath) + fs.mkdir(path.dirname(sourcePath)) + contents = fs.readFileSync(templateChildPath).toString() + content = @replacePackageNamePlaceholders(contents, packageName) + fs.writeFileSync(sourcePath, content) + + replacePackageNamePlaceholders: (string, packageName) -> + placeholderRegex = /__(?:(package-name)|([pP]ackageName)|(package_name))__/g + string = string.replace placeholderRegex, (match, dash, camel, underscore) => + if dash + @dasherize(packageName) + else if camel + if /[a-z]/.test(camel[0]) + packageName = packageName[0].toLowerCase() + packageName[1...] + else if /[A-Z]/.test(camel[0]) + packageName = packageName[0].toUpperCase() + packageName[1...] + @camelize(packageName) + + else if underscore + @underscore(packageName) + + dasherize: (string) -> + string = string[0].toLowerCase() + string[1..] + string.replace /([A-Z])|(_)/g, (m, letter, underscore) -> + if letter + "-" + letter.toLowerCase() + else + "-" + + camelize: (string) -> + string.replace /[_-]+(\w)/g, (m) -> m[1].toUpperCase() + + underscore: (string) -> + string = string[0].toLowerCase() + string[1..] + string.replace /([A-Z])|(-)/g, (m, letter, dash) -> + if letter + "_" + letter.toLowerCase() + else + "_" diff --git a/templates/package/keymaps/__package-name__.cson.template b/templates/package/keymaps/__package-name__.cson.template new file mode 100644 index 000000000..b3213a6a9 --- /dev/null +++ b/templates/package/keymaps/__package-name__.cson.template @@ -0,0 +1,3 @@ +# DOCUMENT: link to keymap documentation +'body': + 'meta-alt-ctrl-o': '__package-name__:toggle' diff --git a/templates/package/lib/__package-name__-view.coffee.template b/templates/package/lib/__package-name__-view.coffee.template new file mode 100644 index 000000000..215b8fdbf --- /dev/null +++ b/templates/package/lib/__package-name__-view.coffee.template @@ -0,0 +1,25 @@ +{$$, View} = require 'space-pen' + +module.exports = +class __PackageName__View extends View + @content: -> + @div class: '__package-name__ overlay from-top', => + @div "The __PackageName__ package is Alive! It's ALIVE!", class: "message" + + initialize: (serializeState) -> + rootView.command "__package-name__:toggle", => @toggle() + + # Returns an object that can be retrieved when package is activated + serialize: -> + + # Tear down any state and detach + destroy: -> + @detach() + + toggle: -> + console.log "__PackageName__View was toggled!" + if @hasParent() + @detach() + else + rootView.append(this) + diff --git a/templates/package/lib/__package-name__.coffee.template b/templates/package/lib/__package-name__.coffee.template new file mode 100644 index 000000000..7592196fe --- /dev/null +++ b/templates/package/lib/__package-name__.coffee.template @@ -0,0 +1,13 @@ +__PackageName__View = require './__package-name__-view' + +module.exports = + __packageName__View: null + + activate: (state) -> + @__packageName__View = new __PackageName__View(state.__packageName__ViewState) + + deactivate: -> + @__packageName__View.destroy() + + serialize: -> + __packageName__ViewState: @__packageName__View.serialize() diff --git a/templates/package/package.cson b/templates/package/package.cson new file mode 100644 index 000000000..7da6f6611 --- /dev/null +++ b/templates/package/package.cson @@ -0,0 +1,2 @@ +'main': './lib/__package-name__' +'activationEvents': ['__package-name__:toggle'] diff --git a/templates/package/spec/__package-name__-spec.coffee.template b/templates/package/spec/__package-name__-spec.coffee.template new file mode 100644 index 000000000..f0d203f4a --- /dev/null +++ b/templates/package/spec/__package-name__-spec.coffee.template @@ -0,0 +1,5 @@ +__PackageName__ = require '../lib/__package-name__' + +describe "__PackageName__", -> + it "has one valid test", -> + expect("life").toBe "easy" diff --git a/templates/package/spec/__package-name__-view-spec.coffee.template b/templates/package/spec/__package-name__-view-spec.coffee.template new file mode 100644 index 000000000..a9008665c --- /dev/null +++ b/templates/package/spec/__package-name__-view-spec.coffee.template @@ -0,0 +1,21 @@ +__PackageName__View = require '../lib/__package-name__-view' +RootView = require 'root-view' + +# This spec is focused because it starts with an `f`. Remove the `f` +# to unfocus the spec. +# +# Press meta-alt-ctrl-s to run the specs +fdescribe "__PackageName__View", -> + __packageName__ = null + + beforeEach -> + window.rootView = new RootView + __packageName__ = atom.activatePackage('__packageName__', immediate: true) + + describe "when the __package-name__:toggle event is triggered", -> + it "attaches and then detaches the view", -> + expect(rootView.find('.__package-name__')).not.toExist() + rootView.trigger '__package-name__:toggle' + expect(rootView.find('.__package-name__')).toExist() + rootView.trigger '__package-name__:toggle' + expect(rootView.find('.__package-name__')).not.toExist() diff --git a/templates/package/stylesheets/__package-name__.css.template b/templates/package/stylesheets/__package-name__.css.template new file mode 100644 index 000000000..f6fe86ba8 --- /dev/null +++ b/templates/package/stylesheets/__package-name__.css.template @@ -0,0 +1,2 @@ +.__package-name__ { +} From 5bc073366a7cefff4ee1624b2272810e54c3f08d Mon Sep 17 00:00:00 2001 From: Matt Colyer Date: Wed, 18 Sep 2013 15:07:46 -0700 Subject: [PATCH 2/7] Sort command requires alphabetically. --- src/apm-cli.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apm-cli.coffee b/src/apm-cli.coffee index 30651477c..d1e224a53 100644 --- a/src/apm-cli.coffee +++ b/src/apm-cli.coffee @@ -7,6 +7,7 @@ commandClasses = [ require './cleaner' require './developer' require './fetcher' + require './init' require './installer' require './link-lister' require './linker' @@ -16,7 +17,6 @@ commandClasses = [ require './uninstaller' require './unlinker' require './updater' - require './init' ] commands = {} From 94240ef7c81150a89186be86776d863330a0d183 Mon Sep 17 00:00:00 2001 From: Matt Colyer Date: Wed, 18 Sep 2013 15:08:24 -0700 Subject: [PATCH 3/7] Correct spec comment --- spec/apm-cli-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/apm-cli-spec.coffee b/spec/apm-cli-spec.coffee index a4ce588d8..0fc4ceb98 100644 --- a/spec/apm-cli-spec.coffee +++ b/spec/apm-cli-spec.coffee @@ -522,7 +522,7 @@ describe 'apm command line interface', -> callback = jasmine.createSpy('callback') apm.run(['init', '--package', 'fake-package'], callback) - waitsFor 'waiting for develop to complete', -> + waitsFor 'waiting for init to complete', -> callback.callCount is 1 runs -> From 9a1e5df902491ebdf61720c22e8dd5240a8387e3 Mon Sep 17 00:00:00 2001 From: Matt Colyer Date: Wed, 18 Sep 2013 15:10:03 -0700 Subject: [PATCH 4/7] Remove unused requires/variables --- src/init.coffee | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/init.coffee b/src/init.coffee index 3b505144b..644e116e7 100644 --- a/src/init.coffee +++ b/src/init.coffee @@ -1,20 +1,14 @@ path = require 'path' -async = require 'async' optimist = require 'optimist' Command = require './command' -config = require './config' fs = require './fs' -Installer = require './installer' module.exports = class Generator extends Command @commandNames: ['init'] - constructor: -> - @atomNpmPath = require.resolve('npm/bin/npm-cli') - parseOptions: (argv) -> options = optimist(argv) From 6423cea4a92ea9640b0deb0e1c4abfbf6cfe18f4 Mon Sep 17 00:00:00 2001 From: Matt Colyer Date: Wed, 18 Sep 2013 16:05:58 -0700 Subject: [PATCH 5/7] Throw an error if a type isn't specified --- src/init.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/init.coffee b/src/init.coffee index 644e116e7..5affdedd4 100644 --- a/src/init.coffee +++ b/src/init.coffee @@ -29,8 +29,9 @@ class Generator extends Command if options.argv.package? packagePath = path.join(process.cwd(), options.argv.package) @generatePackage(packagePath) - - callback() + callback() + else + callback('Error: You must specify either --project or --theme to `apm init`') generatePackage: (packagePath) -> templatePath = path.resolve(__dirname, '..', 'templates', 'package') From eada453f4e2e4aab7f92c5d0ef9c9363ae17db9d Mon Sep 17 00:00:00 2001 From: Matt Colyer Date: Wed, 18 Sep 2013 16:10:06 -0700 Subject: [PATCH 6/7] Accept absolute paths as well as relative paths --- src/init.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init.coffee b/src/init.coffee index 5affdedd4..2b30275a3 100644 --- a/src/init.coffee +++ b/src/init.coffee @@ -27,7 +27,7 @@ class Generator extends Command {callback} = options options = @parseOptions(options.commandArgs) if options.argv.package? - packagePath = path.join(process.cwd(), options.argv.package) + packagePath = path.resolve(options.argv.package) @generatePackage(packagePath) callback() else From af5b90141cfc637e945396d19eb279614a4b3524 Mon Sep 17 00:00:00 2001 From: Matt Colyer Date: Wed, 18 Sep 2013 17:05:26 -0700 Subject: [PATCH 7/7] Implement `apm init --theme ` --- spec/apm-cli-spec.coffee | 19 ++++++++++++++++++- src/init.coffee | 20 ++++++++++++++------ templates/theme/README.md | 3 +++ templates/theme/index.less | 1 + templates/theme/package.json | 19 +++++++++++++++++++ templates/theme/stylesheets/base.less | 9 +++++++++ 6 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 templates/theme/README.md create mode 100644 templates/theme/index.less create mode 100644 templates/theme/package.json create mode 100644 templates/theme/stylesheets/base.less diff --git a/spec/apm-cli-spec.coffee b/spec/apm-cli-spec.coffee index 0fc4ceb98..ee34b7f10 100644 --- a/spec/apm-cli-spec.coffee +++ b/spec/apm-cli-spec.coffee @@ -510,12 +510,13 @@ describe 'apm command line interface', -> expect(fs.realpathSync(linkedRepoPath)).toBe fs.realpathSync(repoPath) describe "apm init", -> - [packagePath] = [] + [packagePath, themePath] = [] beforeEach -> currentDir = temp.mkdirSync('apm-init-') spyOn(process, 'cwd').andReturn(currentDir) packagePath = path.join(currentDir, 'fake-package') + themePath = path.join(currentDir, 'fake-theme') describe "when creating a package", -> it "generates the proper file structure", -> @@ -537,3 +538,19 @@ describe 'apm command line interface', -> expect(fs.existsSync(path.join(packagePath, 'stylesheets', 'fake-package.css'))).toBeTruthy() expect(fs.existsSync(path.join(packagePath, 'package.cson'))).toBeTruthy() + describe "when creating a theme", -> + fit "generates the proper file structure", -> + callback = jasmine.createSpy('callback') + apm.run(['init', '--theme', 'fake-theme'], callback) + + waitsFor 'waiting for init to complete', -> + callback.callCount is 1 + + runs -> + expect(fs.existsSync(themePath)).toBeTruthy() + expect(fs.existsSync(path.join(themePath, 'stylesheets'))).toBeTruthy() + expect(fs.existsSync(path.join(themePath, 'stylesheets', 'base.less'))).toBeTruthy() + expect(fs.existsSync(path.join(themePath, 'index.less'))).toBeTruthy() + expect(fs.existsSync(path.join(themePath, 'README.md'))).toBeTruthy() + expect(fs.existsSync(path.join(themePath, 'package.json'))).toBeTruthy() + diff --git a/src/init.coffee b/src/init.coffee index 2b30275a3..76c4d303f 100644 --- a/src/init.coffee +++ b/src/init.coffee @@ -13,12 +13,15 @@ class Generator extends Command options = optimist(argv) options.usage """ - Usage: apm init -p + Usage: + apm init -p + apm init -t Generates code scaffolding for either a theme or package depending on option selected. """ options.alias('p', 'package').describe('package', 'Generates a basic package') + options.alias('t', 'theme').describe('theme', 'Generates a basic theme') options.alias('h', 'help').describe('help', 'Print this usage message') showHelp: (argv) -> @parseOptions(argv).showHelp() @@ -28,19 +31,24 @@ class Generator extends Command options = @parseOptions(options.commandArgs) if options.argv.package? packagePath = path.resolve(options.argv.package) - @generatePackage(packagePath) + templatePath = path.resolve(__dirname, '..', 'templates', 'package') + @generateFromTemplate(packagePath, templatePath) + callback() + else if options.argv.theme? + themePath = path.resolve(options.argv.theme) + templatePath = path.resolve(__dirname, '..', 'templates', 'theme') + @generateFromTemplate(themePath, templatePath) callback() else - callback('Error: You must specify either --project or --theme to `apm init`') + callback('Error: You must specify either --package or --theme to `apm init`') - generatePackage: (packagePath) -> - templatePath = path.resolve(__dirname, '..', 'templates', 'package') + generateFromTemplate: (packagePath, templatePath) -> packageName = path.basename(packagePath) fs.mkdir(packagePath) for childPath in fs.listRecursive(templatePath) - templateChildPath = path.resolve(__dirname, '..', 'templates', 'package', childPath) + templateChildPath = path.resolve(templatePath, childPath) relativePath = templateChildPath.replace(templatePath, "") relativePath = relativePath.replace(/^\//, '') relativePath = relativePath.replace(/\.template$/, '') diff --git a/templates/theme/README.md b/templates/theme/README.md new file mode 100644 index 000000000..a7af3918a --- /dev/null +++ b/templates/theme/README.md @@ -0,0 +1,3 @@ +## __package-name__ Theme + +A short description of your theme. diff --git a/templates/theme/index.less b/templates/theme/index.less new file mode 100644 index 000000000..d8509617f --- /dev/null +++ b/templates/theme/index.less @@ -0,0 +1 @@ +@import "./stylesheets/base.less"; diff --git a/templates/theme/package.json b/templates/theme/package.json new file mode 100644 index 000000000..ca38a89d2 --- /dev/null +++ b/templates/theme/package.json @@ -0,0 +1,19 @@ +{ + "name": "__package-name__", + "theme": true, + "version": "0.0.0", + "description": "A short description of your theme", + "repository": { + "type": "git", + "url": "https://github.com/your/repo.git" + }, + "bugs": { + "url": "https://github.com/your/repo/issues" + }, + "engines": { + "atom": ">26.0" + }, + "publishConfig": { + "registry": "https://atom.iriscouch.com/registry/_design/app/_rewrite" + } +} diff --git a/templates/theme/stylesheets/base.less b/templates/theme/stylesheets/base.less new file mode 100644 index 000000000..e4058cd52 --- /dev/null +++ b/templates/theme/stylesheets/base.less @@ -0,0 +1,9 @@ +// The ui-variables file is provided by base themes provided by Atom. +// +// See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less +// for a full listing of what's available. +@import "ui-variables"; + +.editor { + color: fade(@text-color, 20%); +}