2024-02-19 20:37:09 +01:00
#!/usr/bin/env nodejsscript
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true */ /* global echo, $, pipe, s, fetch, cyclicLoop */
//TODO: save options!?
$ . is _fatal = true ;
const dirs = { vim _root : $ . xdg . home ` .vim ` } ;
Object . assign ( dirs , {
pack : dirs . vim _root + "/pack/" ,
bundle : dirs . vim _root + "/bundle/" ,
one _files : dirs . vim _root + "/bundle/__one_files/plugin/" } ) ;
const file _one _file = dirs . bundle + "one_file.json" ;
const runResToArr = pipe ( s . $ ( ) . run , ( { stderr , stdout } ) => stdout + stderr , o => o . split ( "\n" ) ) ;
const css = echo . css `
. code { color : yellow ; }
. code : : before , . code : : after { content : "\`" ; }
. url { color : lightblue ; }
. bold { color : magenta ; }
` ;
$ . api ( )
. version ( "2023-04-18" )
. describe ( [
"Utility for managing vim plugins “native” way. It uses two types:" ,
` - “old” way ( ${ f ( "bundle" , css . code ) } ): inspiration from ${ f ( "https://shapeshed.com/vim-packages/" , css . url ) } ` ,
` - vim8 native package managing ( ${ f ( "pack" , css . code ) } ): see for example ${ f ( "https://shapeshed.com/vim-packages/" , css . url ) } `
] )
. command ( "path <type>" , [ "Prints paths for given package type" ,
"Use (‘ S’ is alias for this script name):" ,
"- " + f ( "cd `S path bundle`" , css . code ) ,
"- " + f ( "cd `S path one_files`" , css . code ) ,
"- " + f ( "cd `S path pack`" , css . code )
] )
. action ( function ( type ) { echo ( dirs [ type ] ) ; $ . exit ( 0 ) ; } )
. command ( "clone <type> <url>" , [ "Add/install new package." ,
` Use ${ f ( "bundle" , css . code ) } / ${ f ( "pack" , css . code ) } to specify the package ${ f ( "type" , css . code ) } . ` ,
` The ${ f ( "url" , css . url ) } should be a URL to the script itself or url of the git repository or github repository in the form of ${ f ( "username/reponame" , css . url ) } . `
] )
. alias ( "C" )
. option ( "--target, -t [target]" , ` In case of ${ f ( "pack" , css . code ) } type, specify the target sub-directory (typically/defaults ${ f ( "start" , css . code ) } ). ` )
. option ( "--branch, -b [branch]" , ` In case of ${ f ( "git" , css . bold ) } repository, specify the branch if it is different from default one. ` )
. action ( function ( type , url , options ) {
switch ( type ) {
case "bundle" : return addBundle ( url ) ;
case "pack" : return addPack ( url , options ) ;
}
echo ( "Nothing todo, check given arguments (compare to `--help`):" , { type , url , options } ) ;
$ . exit ( 1 ) ;
} )
. command ( "remove <type> <path>" , [ "Remove/uninstall package." ,
` As ${ f ( "type" , css . bold ) } / ${ f ( "path" , css . bold ) } use output printed by ${ f ( "list" , css . code ) } command. `
] )
. alias ( "R" ) . alias ( "rm" )
. action ( function ( type , path ) {
switch ( type ) {
case "bundle" : return removeBundle ( path ) ;
case "pack" : return removePack ( path ) ;
}
echo ( "Nothing todo, check given arguments (compare to `--help`):" , { type , path } ) ;
$ . exit ( 1 ) ;
} )
. command ( "list" , "List all plugins paths/url/… (based on option)." )
. alias ( "L" ) . alias ( "ls" )
. option ( "--type, -t [type]" , ` Defaults to list of paths ( ${ f ( "paths" , css . code ) } ). Use ${ f ( "repos" , css . code ) } to show plugins origin. ` )
. example ( "list" )
. example ( "list --type paths" )
. example ( "list --type repos" )
. action ( actionList )
. command ( "export" , "List all plugins in the form that can be imported by this Utility." )
. action ( actionList . bind ( null , { type : "json" } ) )
. command ( "status" , "Loops through all installed plugins and shows overall status." )
. alias ( "S" )
. action ( actionStatus )
. command ( "pull" , "Loops through all installed plugins and updates them." )
. alias ( "P" ) . alias ( "update" )
. action ( actionUpdate )
. parse ( ) ;
function removePack ( path ) {
s . cd ( dirs . pack ) ;
s . $ ( "-V" ) . rm ( "-rf" , path ) ;
const root = dirs . pack + path . split ( "/" ) [ 0 ] ;
const { code , stdout } = s . $ ( ) . find ( root + "/*/*" ) ;
if ( ! code ) echo ( stdout ) ;
else if ( s . test ( "-d" , root ) )
s . $ ( "-V" ) . rm ( "-rf" , root ) ;
$ . exit ( 0 ) ;
}
function removeBundle ( path ) {
const is _onefile = dirs . one _files . endsWith ( path . split ( "/" ) . slice ( 0 , 2 ) . join ( "/" ) + "/" ) ;
const name = path . slice ( path . lastIndexOf ( "/" ) + 1 ) ;
s . cd ( dirs . bundle ) ;
if ( is _onefile ) {
s . rm ( "-f" , path ) ;
pipe ( s . cat , JSON . parse , f => f . filter ( u => ! u . endsWith ( name ) ) , JSON . stringify , s . echo )
( file _one _file ) . to ( file _one _file ) ;
} else {
s . run ` git submodule deinit -f ${ path } ` ;
s . run ` git rm ${ path } ` ;
s . rm ( "-rf" , ".git/modules/" + path ) ;
}
const type = is _onefile ? "file" : "repo" ;
s . run ` git commit --all -m "Remove ${ type } : ${ name } " ` ;
$ . exit ( 0 ) ;
}
async function addBundle ( url ) {
const is _onefile = url . endsWith ( ".vim" ) ;
if ( ! is _onefile )
url = gitUrl ( url ) ;
const name = url . split ( /[\/\.]/g ) . at ( is _onefile ? - 2 : - 1 ) ;
s . cd ( dirs . bundle ) ;
if ( is _onefile ) {
const file = await fetch ( url ) . then ( r => r . text ( ) ) ;
s . echo ( file ) . to ( dirs . one _files + name + ".vim" ) ;
const log = new Set ( s . cat ( file _one _file ) . xargs ( JSON . parse ) ) ;
log . add ( url ) ;
s . echo ( JSON . stringify ( [ ... log ] ) ) . to ( file _one _file ) ;
} else {
s . run ` git submodule init ` ;
s . run ` git submodule add ${ url } ` ;
}
s . run ` git add . ` ;
const type = is _onefile ? "file" : "repo" ;
s . run ` git commit -m "Added ${ type } : ${ name } " ` ;
$ . exit ( 0 ) ;
}
/** @param {string} url @param {{ target: "start", branch?: string }} options */
function addPack ( url , { target = "start" , branch } ) {
url = gitUrl ( url ) ;
const author = url . split ( /[:\/]/ ) . at ( - 2 ) ;
const path = dirs . pack + author + "/" + target ;
s . mkdir ( "-p" , path ) ;
s . cd ( path ) ;
branch = ! branch ? "" : ` -b ${ branch } ` ;
s . run ` git clone ${ branch } --single-branch ${ url } --depth 1 ` ;
$ . exit ( 0 ) ;
}
function gitUrl ( url _candidate ) {
if ( url _candidate . endsWith ( ".git" ) )
return url _candidate ;
return "git@github.com:" + url _candidate ;
}
async function actionUpdate ( ) {
const css = echo . css `
. success { color : lightgreen ; }
. success : : before { content : "✓ " ; }
` ;
updateRepo ( dirs . bundle , getBundle ( ) ) ;
const todo = getOneFilesUrls ( ) ;
const progress = echoProgress ( todo . length , "Downloaded one-file plugins" ) ;
await Promise . all ( todo . map ( function ( url , i ) {
return fetch ( url ) . then ( r => {
progress . update ( i , url ) ;
return r . text ( ) ;
} ) . then ( f => s . echo ( f ) . to ( dirs . one _files + fileName ( url ) ) ) ;
} ) ) ;
echo ( "One-file plugin(s) updated." ) ;
2024-03-06 09:31:10 +01:00
try {
s . cd ( dirs . bundle ) . $ ( ) . run ` git commit -m "Update" ` ;
} catch ( e ) {
echo ( e ? . message ) ;
}
2024-02-19 20:37:09 +01:00
updateRepo ( dirs . pack , getPack ( ) ) ;
$ . exit ( 0 ) ;
function updateRepo ( dir , paths ) {
echo ( dir ) ;
const progress = echoProgress ( paths . length , "Pulling" ) ;
const todo = paths . map ( function ( p , i ) {
progress . update ( i , p ) ;
return pull ( p ) ;
} ) . filter ( isUpToDate ) ;
if ( ! todo . length )
return echo ( "%cAll up-to-date!" , css . success ) ;
todo . forEach ( ( [ p , result ] ) => echo ( "%c" + p + "\n" , css . success , result . join ( "\n" ) ) ) ;
}
function pull ( p ) {
s . cd ( p ) ;
return [ p , runResToArr ( "git pull" ) ] ;
}
function isUpToDate ( [ _ , result ] ) {
return result [ 0 ] === " Already up-to-date." ;
}
}
function actionList ( { type = "paths" } ) {
if ( "paths" === type ) {
echo ( "%cbundle" , css . bold , dirs . bundle ) ;
getOneFiles ( ) . forEach ( echoPath ) ;
getBundle ( ) . forEach ( echoPath ) ;
echo ( "%cpack" , css . bold , dirs . pack ) ;
getPack ( ) . forEach ( echoPath ) ;
$ . exit ( 0 ) ;
}
const progress = echoProgress ( 3 , "Collecting plugins urls" ) ;
progress . update ( 0 , dirs . bundle ) ;
const urls _bundle = getBundle ( ) . map ( getRepo ) ;
progress . update ( 1 , dirs . bundle ) ;
const urls _onefiles = getOneFilesUrls ( ) ;
progress . update ( 2 , dirs . pack ) ;
const urls _pack = getPack ( ) . map ( getRepo ) ;
if ( "repos" === type ) {
const echoUrl = pipe (
u => u . replace ( "git@github.com:" , "https://github.com/" ) ,
u => f ( u , css . url ) ,
echo
)
echo ( "%cbundle" , css . bold , dirs . bundle ) ;
urls _bundle . forEach ( echoUrl ) ;
echo ( "%cbundle" , css . bold , dirs . one _files ) ;
urls _onefiles . forEach ( echoUrl ) ;
echo ( "%cpack" , css . bold , dirs . pack ) ;
urls _pack . forEach ( echoUrl ) ;
}
if ( "json" === type ) { //TODO: save options!?
const o = { } ;
o . bundle = urls _bundle ;
o . one _files = urls _onefiles ;
o . pack = urls _pack ;
echo ( JSON . stringify ( o ) ) ;
}
$ . exit ( 0 ) ;
function getRepo ( p ) { s . cd ( p ) ; return runResToArr ( "git remote -v" ) [ 0 ] . split ( /\s+/g ) [ 1 ] ; }
}
function actionStatus ( ) {
const css = echo . css `
. success { color : lightgreen ; }
. success : : before { content : "✓ " ; }
` ;
check ( dirs . bundle , getBundle ( ) ) ;
echo ( "Onefiles plugins are not supported yet" ) ;
check ( dirs . pack , getPack ( ) ) ;
$ . exit ( 0 ) ;
function check ( root , repos ) {
echo ( root ) ;
const progress = echoProgress ( repos . length ) ;
const results = repos . flatMap ( function ( p , i ) {
progress . update ( i , p ) ;
s . cd ( p ) ;
const result = runResToArr ( "git fetch --dry-run --verbose" )
. filter ( l => ! l ? false : l . startsWith ( "From" ) || ( ! l . startsWith ( " = [up-to-date]" ) && ! l . startsWith ( "POST" ) ) ) ;
if ( result . length === 1 ) return [ ] ;
return [ [ p , result . join ( "\n" ) ] ] ;
} ) ;
if ( ! results . length )
return echo ( "%cup-to-date" , css . success ) ;
results . forEach ( ( [ p , l ] ) => {
echoPath ( p ) ;
echo ( l ) ;
} ) ;
}
}
import { relative } from 'node:path' ;
function echoPath ( path ) { return echo ( formatPath ( path ) ) ; }
function formatPath ( path ) {
const type = path . startsWith ( dirs . bundle ) ? "bundle" : "pack" ;
return echo . format ( "%c" + relative ( dirs [ type ] , path ) , "color:lightblue" ) ;
}
import { stdout } from 'node:process' ;
function echoProgress ( length , message _start = "Working" ) {
if ( typeof stdout . isTTY === "undefined" ) return { update ( ) { } } ;
const css = echo . css `
. progress { color : lightblue ; }
` ;
echo . use ( "-R" , ` ${ message _start } (%c0/ ${ length } %c) ` , css . progress ) ;
return {
update ( i , status ) {
const s = status ? ` : ${ status } ` : "" ;
return echo . use ( "-R" , ` ${ message _start } (%c ${ i + 1 } / ${ length } %c) ${ s } ` , css . progress ) ;
}
} ;
}
function getPack ( ) { return s . ls ( dirs . pack ) . flatMap ( f => s . find ( dirs . pack + f + "/start/*/" ) [ 0 ] ) . filter ( Boolean ) ; }
function getBundle ( ) { return s . cd ( dirs . bundle ) . grep ( "path" , ".gitmodules" ) . split ( "\n" ) . filter ( Boolean ) . map ( l => dirs . bundle + l . split ( " = " ) [ 1 ] ) ; }
function getOneFiles ( ) { return s . find ( dirs . one _files + "*" ) ; }
function getOneFilesUrls ( ) { return s . cat ( file _one _file ) . xargs ( JSON . parse ) ; }
function fileName ( url ) { return url . split ( "/" ) . pop ( ) ; }
/** Quick formating of one piece of text. */
function f ( text , css ) { return echo . format ( "%c" + text , css ) ; }