make-archive
Version:
produce mountable archives with ease (OSX only, sorry)
262 lines (229 loc) • 12.6 kB
text/coffeescript
############################################################################################################
njs_path = require 'path'
njs_os = require 'os'
njs_spawn = ( require 'child_process' ).spawn
#...........................................................................................................
### TAINT temporary solution; should unify the two ###
njs_fs = require 'fs-extra'
FS = require './FS'
#...........................................................................................................
TRM = require 'coffeenode-trm'
rpr = TRM.rpr.bind TRM
badge = 'make-archive'
log = TRM.get_logger 'plain', badge
info = TRM.get_logger 'info', badge
alert = TRM.get_logger 'alert', badge
debug = TRM.get_logger 'debug', badge
warn = TRM.get_logger 'warn', badge
urge = TRM.get_logger 'urge', badge
whisper = TRM.get_logger 'whisper', badge
help = TRM.get_logger 'help', badge
echo = TRM.echo.bind TRM
#...........................................................................................................
ruler = ( new Array 108 ).join '─'
fat_ruler = ( new Array 108 ).join '━'
ASYNC = require 'async'
#...........................................................................................................
host_name = njs_os.hostname()
host_name = host_name.replace '.fritz.box', ''
project_locators = process.argv[ 2 ... ]
#...........................................................................................................
options = require '../options'
#-----------------------------------------------------------------------------------------------------------
@main = ->
throw new Error "must give at least one project locator" if project_locators.length is 0
#.........................................................................................................
backup_homes = []
primary_tasks = []
[ primary_output_home
secondary_output_homes... ] = options[ 'output-homes' ]
@validate_route_is_folder primary_output_home
#.........................................................................................................
info()
whisper fat_ruler
info 'COFFEENODE Backup Utility'
whisper ruler
#.........................................................................................................
for backup_home in secondary_output_homes
backup_homes.push backup_home if is_folder = FS.is_folder backup_home
help 'backup folder: ', backup_home, TRM.truth is_folder
#.........................................................................................................
for project_locator in project_locators
project_locator = ( FS.resolve_route project_locator ).replace /\/+$/, ''
@validate_route_is_folder project_locator
#.......................................................................................................
project_name = njs_path.basename project_locator
project_home = njs_path.dirname project_locator
#.......................................................................................................
@validate_name project_name
#.......................................................................................................
timestamp = FS.get_timestamp_of_newest_object_in_folder project_locator
archive_name = "(#{host_name})_#{project_name}_#{timestamp}.dmg"
archive_home = njs_path.join primary_output_home, project_name
archive_locator = njs_path.join archive_home, archive_name
@ensure_route_is_folder archive_home
archive_exists = FS.exists archive_locator
whisper ruler
help 'project-name: ', project_name
help 'project-locator: ', project_locator
help 'archive-locator: ', archive_locator
help 'archive-exists: ', TRM.truth archive_exists
#.......................................................................................................
continue if archive_exists
command_and_arguments = @get_archiving_command_and_arguments project_locator, archive_locator
#.......................................................................................................
do ( project_locator, archive_name, archive_locator, command_and_arguments ) =>
primary_tasks.push ( primary_handler ) =>
@spawn command_and_arguments..., ( error ) =>
return primary_handler error if error?
return primary_handler null if backup_homes.length is 0
secondary_tasks = []
for backup_home in backup_homes
do ( backup_home ) =>
secondary_tasks.push ( secondary_handler ) =>
backup_locator = njs_path.join backup_home, archive_name
njs_fs.copy archive_locator, backup_locator, ( error ) =>
help "copied #{archive_locator} to #{backup_home}" unless error?
secondary_handler error
ASYNC.parallelLimit secondary_tasks, 5, ( error ) =>
return primary_handler error
#.........................................................................................................
ASYNC.series primary_tasks, ( error, results ) ->
throw error if error?
whisper fat_ruler
help 'All tasks have finished.'
#===========================================================================================================
# VALIDATORS
#-----------------------------------------------------------------------------------------------------------
@validate_route = ( route ) ->
throw new Error "expected a text as route" unless ( Object::toString.call route ) is '[object String]'
throw new Error "expected a non-empty text as route" if route.length is 0
throw new Error "illegal route: #{rpr route}" unless ( route.match /[\s:"'\\<>|]/ ) is null
return null
#-----------------------------------------------------------------------------------------------------------
@validate_name = ( name ) ->
throw new Error "expected a text as name" unless ( Object::toString.call name ) is '[object String]'
throw new Error "expected a non-empty text as name" if name.length is 0
throw new Error "illegal name: #{rpr name}" unless ( name.match /[\s:"'\\<>|\/]/ ) is null
return null
#-----------------------------------------------------------------------------------------------------------
@validate_route_is_folder = ( route ) ->
@validate_route route
unless FS.is_folder route
@complain "route #{rpr route} must point to existing folder" unless options[ 'create-folder' ] ? true
return null
#-----------------------------------------------------------------------------------------------------------
@ensure_route_is_folder = ( route ) ->
@validate_route route
unless FS.is_folder route
@complain "route #{rpr route} must point to existing folder" unless options[ 'create-folder' ] ? true
@complain "route #{rpr route} points to existing file" if FS.exists route
FS.mkdirp route
help "created folder at #{route}"
return null
#-----------------------------------------------------------------------------------------------------------
@validate_route_doesnt_exist = ( route ) ->
@validate_route route
throw new Error "route #{rpr route} already exists" if FS.exists route
return null
#-----------------------------------------------------------------------------------------------------------
@validate_archive_format = ( format ) ->
throw new Error "expected a text as format" unless ( Object::toString.call format ) is '[object String]'
switch format
when 'UDRW', 'UDRO', 'UDCO', 'UDZO', 'UDBZ', 'UFBI', 'UDTO', 'UDxx', 'UDSP', 'UDSB' then null
else
throw new Error "unknown format #{rpr format}"
return null
#-----------------------------------------------------------------------------------------------------------
@complain = ( message ) ->
warn message
process.exit()
# #===========================================================================================================
# # BASH
# #-----------------------------------------------------------------------------------------------------------
# BASH = {}
# #-----------------------------------------------------------------------------------------------------------
# BASH.escape = ( route ) ->
# return route.replace /([^-a-zA-Z0-9\/_.])/g, '\\$1'
#===========================================================================================================
# HELPERS
#-----------------------------------------------------------------------------------------------------------
@get_archiving_command_and_arguments = ( project_locator, archive_locator ) ->
### thx to http://qntm.org/bash
###
volume_name = ( njs_path.basename archive_locator ).replace /\..*$/, ''
archive_format = options[ 'archive-format']
@validate_archive_format archive_format
# volume_name = BASH.escape volume_name
# project_locator = BASH.escape project_locator
# archive_locator = BASH.escape archive_locator
# return [ "/usr/bin/env", ['hdiutil', 'help', ] ]
# return "hdiutil create -format UDZO -srcfolder #{project_locator} -volname #{volume_name} #{archive_locator}"
command = 'hdiutil'
arguments_ = [
'create'
'-format'
archive_format
'-srcfolder'
project_locator
'-volname'
volume_name
archive_locator ]
return [ command, arguments_, ]
#-----------------------------------------------------------------------------------------------------------
@spawn = ( command, arguments_, handler ) ->
whisper fat_ruler
whisper command + ' ' + arguments_.join ' '
R = njs_spawn command, arguments_
#.........................................................................................................
R.on 'error', ( error ) =>
handler error
#.........................................................................................................
R.stderr.on 'data', ( error ) =>
handler error
#.........................................................................................................
R.stdout.on 'data', ( data_buffer ) =>
### TAINT strictly speaking, this could cause encoding errors: ###
text = ( data_buffer.toString 'utf-8' ).trim()
help text if text.length > 0
#.........................................................................................................
R.on 'close', ( code ) =>
# echo 'OK'
handler null, null
#.........................................................................................................
return R
###
#===========================================================================================================
.d8888b. 888 8888888
d88P Y88b 888 888
888 888 888 888
888 888 888
888 888 888
888 888 888 888
Y88b d88P 888 888
"Y8888P" 88888888 8888888
#===========================================================================================================
###
#-----------------------------------------------------------------------------------------------------------
@cli = ->
docopt = ( require 'coffeenode-docopt' ).docopt
version = ( require '../package.json' )[ 'version' ]
filename = ( require 'path' ).basename __filename
usage = """
Usage: #{filename} <project-locators>...
Options:
-h, --help
-v, --version
"""
#.........................................................................................................
cli_options = docopt usage, version: version, help: ( left, collected ) ->
# urge left
# help collected
help '\n' + usage
#.........................................................................................................
# debug cli_options
settings = {}
settings[ 'project-locators' ] = cli_options[ '<project-locators>' ]
@main()
#-----------------------------------------------------------------------------------------------------------
@cli() unless module.parent?