radiant/layouts: add a single-window layout
[xonotic/netradiant.git] / gamepack-manager
1 #! /usr/bin/env bash
2
3 # get usage help this way:
4 # ./gamepack_manager -h
5
6 : "${CP:=cp -va}"
7 : "${CP_R:=cp -Rva}"
8 : "${GIT:=git}"
9 : "${SVN:=svn}"
10 : "${WGET:=wget}"
11 : "${ECHO:=echo}"
12 : "${MKDIR:=mkdir -v}"
13 : "${MKDIR_P:=mkdir -vp}"
14 : "${RM_R:=rm -vrf}"
15 : "${MV:=mv -v}"
16 : "${TAR:=tar}"
17 : "${UNZIPPER:=unzip}"
18
19 set -e
20
21 default_download_dir='build/download'
22 default_install_dir='build'
23
24 games_dir='games'
25 pack_suffix='Pack'
26
27 free_license_list='BSD GPL'
28
29 printRawDB () {
30 cat <<\EOF
31 #######################################################
32 #                                                     #
33 #  IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT  #
34 #                                                     #
35 #   Use two whitespaces or more as column separator   #
36 #                                                     #
37 #######################################################
38
39 #######################################################
40 # Obsolete packs                                      #
41 #######################################################
42
43 # Quake2World was renamed as Quetoo
44 # Other gamepacks have better version available
45
46 # OpenArena     no   unknown      zip     http://ingar.intranifty.net/files/netradiant/gamepacks/OpenArenaPack.zip
47 # Quake         no   proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/QuakePack.zip
48 # Quake2World   no   GPL          svn     svn://jdolan.dyndns.org/quake2world/trunk/gtkradiant
49 # Tremulous     no   proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/TremulousPack/branches/1.5/
50 # Unvanquished  no   unknown      zip     http://ingar.intranifty.net/gtkradiant/files/gamepacks/UnvanquishedPack.zip
51 # Warsow        no   GPL          svn     https://svn.bountysource.com/wswpack/trunk/netradiant/games/WarsowPack/
52 # Warsow        no   GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/WarsowPack.zip
53
54 #######################################################
55 # Usable packs                                        #
56 #######################################################
57
58 AlienArena      yes  GPL          svn     https://svn.code.sf.net/p/alienarena-cc/code/trunk/tools/netradiant_gamepack/AlienArenaPack
59 DarkPlaces      yes  GPL          svn     svn://svn.icculus.org/gtkradiant-gamepacks/DarkPlacesPack/branches/1.5/
60 Doom3           yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Doom3Pack/branches/1.5/
61 ET              yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/ETPack/branches/1.5/
62 Heretic2        yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Her2Pack/branches/1.5/
63 JediAcademy     no   proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/JAPack/branches/1.5/
64 Kingpin         yes  unknown      zip     http://download.kingpin.info/kingpin/editing/maps/map_editors/NetRadiant/addon/Kingpinpack.zip
65 Neverball       yes  proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/NeverballPack.zip
66 Nexuiz          yes  GPL          gitdir  git://git.icculus.org/divverent/nexuiz.git misc/netradiant-NexuizPack master
67 OpenArena       yes  GPL          git     https://github.com/NeonKnightOA/oagamepack.git
68 Osirion         yes  GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/OsirionPack.zip
69 Prey            yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/PreyPack/trunk/
70 Q3              yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q3Pack/trunk/ 29
71 Q3Rally         yes  proprietary  svn     https://svn.code.sf.net/p/q3rallysa/code/tools/radiant-config/radiant15-netradiant/
72 Quake2          yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q2Pack/branches/1.5/
73 Quake4          yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q4Pack/branches/1.5/
74 Quake           yes  GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/Quake1Pack.zip
75 Quetoo          yes  GPL          svn     svn://svn.icculus.org/gtkradiant-gamepacks/QuetooPack/branches/1.5/
76 SmokinGuns      yes  unknown      git     https://github.com/smokin-guns/smokinguns-mapeditor-support.git
77 Tremulous       yes  proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/TremulousPack.zip
78 TurtleArena     yes  proprietary  git     https://github.com/Turtle-Arena/turtle-arena-radiant-pack.git
79 UFOAI           yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/UFOAIPack/branches/1.5/
80 Unvanquished    yes  BSD          git     https://github.com/Unvanquished/unvanquished-mapeditor-support.git
81 Warsow          yes  GPL          git     https://github.com/Warsow/NetRadiantPack.git
82 Wolf            yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/WolfPack/branches/1.5/
83 WoP             yes  proprietary  git     https://github.com/PadWorld-Entertainment/wop-mapeditor-support.git
84 Xonotic         yes  GPL          git     https://gitlab.com/xonotic/netradiant-xonoticpack.git
85 EOF
86 }
87
88 if command -v gsed >/dev/null
89 then
90         SED=gsed
91 elif sed --help >/dev/null 2>&1
92 then
93         SED=sed
94 else
95         printf 'ERROR: GNU sed is missing\n' >&2
96         exit 1
97 fi
98
99 printRealPath ()
100 {
101         if command -v grealpath >/dev/null
102         then
103                 grealpath "${1}"
104         elif command -v realpath >/dev/null
105         then
106                 realpath "${1}"
107         elif command -v greadlink >/dev/null
108         then
109                 # test greadlink first as greadlink has the feature on macos
110                 # but readlink only has it on linux, note that it's probably
111                 # the same on bsd
112                 # note: (g)readlink requires the file to be create first
113                 greadlink -f "${1}"
114         elif command -v readlink >/dev/null
115         then
116                 # --help and -f options are GNU readlink things
117                 if readlink --help >/dev/null 2>&1
118                 then
119                         readlink -f "${1}"
120                 else
121                         if ! python -c "import os; print(os.path.realpath('${1}'))"
122                         then
123                                 printf 'ERROR: GNU realpath or other way to compute real path of a file is missing\n' >&2
124                                 exit 1
125                         fi
126                 fi
127         fi
128 }
129
130 sanitizeDB () {
131         ${SED} -e 's/#.*//;s/[ \t][ \t][ \t]*/\t/g;s/^[ \t]*//;s/[ \t]*$//' \
132         | grep -v '^$'
133 }
134
135 inList () {
136         [ "$(grep "^${1}$")" = "${1}" ]
137 }
138
139 printList () {
140         echo "${1}" \
141         | tr ' ' '\n' \
142         | grep -v '^$' \
143         | sort -u
144 }
145
146 dedupeList () {
147         printList "${1}" \
148         | tr '\n' ' ' \
149         | ${SED} -e 's/ $//'
150 }
151
152 printGamePackDB () {
153         printRawDB \
154         | sanitizeDB
155 }
156
157 printLicenseList () {
158         printGamePackDB \
159         | awk '{ print $3 }' \
160         | sort -u
161 }
162
163 printNameList () {
164         printGamePackDB \
165         | awk '{ print $1 }' \
166         | sort -u
167 }
168
169 printNameListByLicense () {
170         local arg_license_list
171         local license_list
172         local license
173
174         arg_license_list="${1}"
175         license_list=''
176
177         for license in ${arg_license_list}
178         do
179                 case "${license}" in
180                         'none')
181                                 break
182                                 ;;
183                         'all')
184                                 license_list="$(printLicenseList)"
185                                 break
186                                 ;;
187                         'free')
188                                 license_list="${license_list} ${free_license_list}"
189                                 ;;
190                         *)
191                                 if printLicenseList | inList "${license}"
192                                 then
193                                         license_list="${license_list} ${license}"
194                                 else
195                                         printError "unknown license: ${license}"
196                                 fi
197                                 ;;
198                 esac
199         done
200
201         license_list="$(dedupeList "${license_list}")"
202
203         for license in ${license_list}
204         do
205                 printGamePackDB \
206                 | awk '$3 == "'"${license}"'"' \
207                 | awk '{ print $1 }'
208         done | sort -u
209 }
210
211 printNameListByName () {
212         local argname_list
213         local name_list
214         local name
215
216         argname_list="${1}"
217         name_list=''
218
219         for name in ${argname_list}
220         do
221                 case "${name}" in
222                         'none')
223                                 break
224                                 ;;
225                         'all')
226                                 name_list="$(printNameList)"
227                                 break
228                                 ;;
229                         'validated')
230                                 name_list="${name_list} validated"
231                                 ;;
232                         *)
233                                 if printNameList | inList "${name}"
234                                 then
235                                         name_list="${name_list} ${name}"
236                                 else
237                                         printError "unknown name: ${name}"
238                                 fi
239                                 ;;
240                 esac
241         done
242
243         name_list="$(dedupeList "${name_list}")"
244
245         for name in ${name_list}
246         do
247                 if [ "${name}" = 'validated' ]
248                 then
249                         printGamePackDB \
250                         | awk '$2 == "yes"' \
251                         | awk '{ print $1 }'
252                 fi
253
254                 printGamePackDB \
255                 | awk '$1 == "'"${name}"'"' \
256                 | awk '{ print $1 }'
257         done
258 }
259
260 printPackLine () {
261         local name
262
263         name="${1}"
264
265         printGamePackDB \
266         | awk '$1 == "'"${name}"'"'
267 }
268
269 getValue () {
270         local name
271         local key
272
273         name="${1}"
274         key="${2}"
275
276         printPackLine "${name}" \
277         | awk '{ print $'"${key}"' }'
278 }
279
280 downloadExtraUrls ()
281 {
282         if [ -f 'extra-urls.txt' ]
283         then
284                 while IFS='     ' read -r extra_file extra_url
285                 do
286                         (
287                                 ${WGET} -O "${extra_file}" "${extra_url}"
288                         ) </dev/null
289                 done < 'extra-urls.txt'
290         fi
291 }
292
293 downloadPack () {
294         local download_dir
295         local name
296         local license
297         local source_type
298         local source_url
299         local pack
300         local reference
301         local subdir
302         local branch
303
304         download_dir="${1}"
305         name="${2}"
306
307         validation="$(getValue "${name}" 2)"
308         license="$(getValue "${name}" 3)"
309         source_type="$(getValue "${name}" 4)"
310         source_url="$(getValue "${name}" 5)"
311
312         pack="${name}${pack_suffix}"
313
314         ${MKDIR_P} "${download_dir}"
315
316         (
317                 cd "${download_dir}"
318
319                 ${ECHO} ''
320                 ${ECHO} "Available pack: ${pack}"
321                 ${ECHO} "  License: ${license}"
322                 ${ECHO} "  Download via ${source_type} from ${source_url}"
323                 ${ECHO} ''
324
325                 if [ -d "${download_dir}/${pack}" ]
326                 then
327                         ${ECHO} "Updating ${name}…"
328                 else
329                         ${ECHO} "Downloading ${pack}…"
330                 fi
331
332                 case "${source_type}" in
333                         'svn')
334                                 reference="$(getValue "${name}" 6)"
335                                 if [ -z "${reference}" ]
336                                 then
337                                         reference='HEAD'
338                                 fi
339
340                                 if [ -d "${pack}" ]
341                                 then
342                                         if [ -d "${pack}/.git" ]
343                                         then
344                                                 (
345                                                         cd "${pack}"
346                                                         ${GIT} svn fetch
347                                                 )
348                                         else
349                                                 ${SVN} update -r"${reference}" "${pack}"
350                                         fi
351                                 else
352                                         ${SVN} checkout -r"${reference}" "${source_url}" "${pack}" \
353                                         || ${GIT} svn clone "${source_url}" "${pack}"
354                                 fi
355                                 ;;
356                         'zip')
357                                 ${RM_R} 'zipdownload'
358                                 ${MKDIR} 'zipdownload'
359                                 (
360                                         cd 'zipdownload'
361                                         ${WGET} "${source_url}"
362                                         ${UNZIPPER} './'*.zip
363                                 )
364                                 ${RM_R} "${pack}"
365                                 ${MKDIR} "${pack}"
366                                 if [ -d 'zipdownload/games' ]
367                                 then
368                                         ${MV} 'zipdownload/'* "${pack}/"
369                                 else
370                                         ${MV} 'zipdownload/'*'/'* "${pack}/"
371                                 fi
372                                 ${RM_R} 'zipdownload'
373                                 ;;
374                         'gitdir')
375                                 local subdir="$(getValue "${name}" 6)"
376                                 local branch="$(getValue "${name}" 7)"
377                                 ${RM_R} "${pack}"
378                                 ${GIT} archive --remote="${source_url}" --prefix="${pack}/" "${branch}":"${subdir}" \
379                                 | ${TAR} xvf -
380                                 ;;
381                         'git')
382                                 if [ -d "${pack}" ]
383                                 then
384                                         (
385                                                 cd "${pack}"
386                                                 ${GIT} pull
387                                         )
388                                 else
389                                         ${GIT} clone "${source_url}" "${pack}"
390                                 fi
391                                 ;;
392                 esac
393
394                 if [ -d "${pack}" ]
395                 then
396                         (
397                                 cd "${pack}"
398                                 downloadExtraUrls
399                         )
400                 fi
401
402         )
403 }
404
405 downloadPackList () {
406         local download_dir
407         local name_list
408
409         download_dir="${1}"
410         name_list="${2}"
411
412         for name in ${name_list}
413         do
414                 if printNameList | inList "${name}"
415                 then
416                         downloadPack "${download_dir}" "${name}"
417                 else
418                         printError "unknown name: ${name}"
419                 fi
420         done
421 }
422
423 installPack () {
424         local download_dir
425         local install_dir
426         local name
427         local pack
428         local path
429         local game_file
430         local game_dir
431
432         download_dir="${1}"
433         install_dir="${2}"
434         name="${3}"
435
436         pack="${name}${pack_suffix}"
437
438         ${MKDIR_P} "${install_dir}/${games_dir}"
439
440         # Some per-game workaround for malformed gamepack
441         case "${name}" in
442                 'JediAcademy')
443                         pack="${pack}/Tools"
444                         ;;
445                 'Prey'|'Q3')
446                         pack="${pack}/tools"
447                         ;;
448                 'Wolf')
449                         pack="${pack}/bin"
450                         ;;
451         esac
452
453         if [ -d "${download_dir}/${pack}/build/netradiant" ]
454         then
455                 # mkeditorpacks-based gamepack
456                 pack="${pack}/build/netradiant"
457         elif [ -d "${download_dir}/${pack}/netradiant" ]
458         then
459                 # other layout
460                 pack="${pack}/netradiant"
461         fi
462
463         path="${download_dir}/${pack}"
464
465         for game_file in "${path}/${games_dir}/"*'.game'
466         do
467                 if [ x"${game_file}" != x"${path}/"*'.game' ]
468                 then
469                         ${CP} "${game_file}" "${real_install_dir}/${games_dir}/"
470                 fi
471         done
472
473         for game_dir in "${path}/"*'.game'
474         do
475                 if [ x"${game_dir}" != x"${path}/"*'.game' ]
476                 then
477                         ${CP_R} "${game_dir}" "${real_install_dir}/"
478                 fi
479         done
480 }
481
482 installPackList () {
483         local download_dir
484         local install_dir
485         local name_list
486
487         download_dir="${1}"
488         install_dir="${2}"
489         name_list="${3}"
490
491         for name in ${name_list}
492         do
493                 if printNameList | inList "${name}"
494                 then
495                         installPack "${download_dir}" "${install_dir}" "${name}"
496                 else
497                         printError "unknown name: ${name}"
498                 fi
499         done
500 }
501
502 printError () {
503         printf 'ERROR: %s\n' "${1}" >&2
504         exit 1
505 }
506
507 printHelp () {
508         local tab
509         local prog_name
510
511         tab="$(printf '\t')"
512         prog_name='gamepack-manager'
513
514         cat <<-EOF
515         Usage: ${prog_name} [OPTION] [SELECTION <ARGUMENTS>] [ACTION]
516
517         OPTIONS:
518         ${tab}-dd, --download-dir DIRNAME
519         ${tab}${tab}store downloaded games to DIRNAME (default: ${default_download_dir})
520
521         ${tab}-id, --install-dir DIRNAME
522         ${tab}${tab}store installed games to DIRNAME (default: ${default_install_dir})
523
524         SELECTIONS:
525         ${tab}-n, --name NAMES…
526         ${tab}${tab}select games by name (default: none)
527         ${tab}${tab}special keyword: validated, all, none
528         ${tab}${tab}available games:
529         $(printNameList | ${SED} -e 's/^/\t\t\t/')
530
531         ${tab}-l, --license LICENSES…
532         ${tab}${tab}select games by license (default: none)
533         ${tab}${tab}special keyword: free, all, none
534         ${tab}${tab}available licenses:
535         $(printLicenseList | ${SED} -e 's/^/\t\t\t/')
536
537         ACTIONS:
538         ${tab}-ln, --list-names
539         ${tab}${tab}list all game names
540
541         ${tab}-ll, --list-licenses
542         ${tab}${tab}list all game licenses
543
544         ${tab}-ls, --list-selected
545         ${tab}${tab}list selected games
546
547         ${tab}-d, --download
548         ${tab}${tab}download selected games
549
550         ${tab}-i, --install
551         ${tab}${tab}install selected games
552
553         ${tab}-h, --help
554         ${tab}${tab}print this help
555
556         Examples:
557         ${tab}${prog_name} --license GPL BSD --list-selected
558         ${tab}${prog_name} --license GPL BSD --download --install
559
560         ${tab}${prog_name} --name validated --list-selected
561         ${tab}${prog_name} --name validated --download --install
562
563         EOF
564
565         exit
566 }
567
568 option_list=''
569
570 list_selected='false'
571 list_licenses='false'
572 list_names='false'
573
574 download_packs='false'
575 install_packs='false'
576
577 mkdir_download='false'
578 mkdir_install='false'
579
580 by_license='false'
581 by_name='false'
582
583 arg_type=''
584 selected_list=''
585 license_list=''
586 name_list=''
587 install_dir=''
588
589 while ! [ -z "${1}" ]
590 do
591
592         if printList "${option_list}" | inList "${1}"
593         then
594                 printError "option called more than once: ${1}"
595         fi
596
597         if echo "${@}" | tr ' ' '\n' | inList '--help'
598         then
599                 printHelp
600         elif echo "${@}" | tr ' ' '\n' | inList '-h'
601         then
602                 printHelp
603         fi
604
605         case "${1}" in
606                 '--list-names'|'-ln')
607                         arg_type=''
608                         list_names='true'
609                         option_list="${option_list} ${1}"
610                         ;;
611                 '--list-licenses'|'-ll')
612                         arg_type=''
613                         list_licenses='true'
614                         option_list="${option_list} ${1}"
615                         ;;
616                 '--list-selected'|'-ls')
617                         arg_type=''
618                         list_selected='true'
619                         option_list="${option_list} ${1}"
620                         ;;
621                 '--download'|'-d')
622                         arg_type=''
623                         download_packs='true'
624                         mkdir_download='true'
625                         option_list="${option_list} ${1}"
626                         ;;
627                 '--install'|'-i')
628                         arg_type=''
629                         install_packs='true'
630                         mkdir_download='true'
631                         mkdir_install='true'
632                         option_list="${option_list} ${1}"
633                         ;;
634                 '--license'|'-l')
635                         by_license='true'
636                         arg_type='pack-license'
637                         option_list="${option_list} ${1}"
638                         ;;
639                 '--name'|'-n')
640                         by_name='true'
641                         arg_type='pack-name'
642                         option_list="${option_list} ${1}"
643                         ;;
644                 '--download-dir'|'-dd')
645                         arg_type='download-dir'
646                         option_list="${option_list} ${1}"
647                         ;;
648                 '--install-dir'|'-id')
649                         arg_type='install-dir'
650                         option_list="${option_list} ${1}"
651                         ;;
652                 '-'*)
653                         printError "unknown option: ${1}"
654                         ;;
655                 *)
656                         case "${arg_type}" in
657                                 'pack-name')
658                                         name_list="${name_list} ${1}"
659                                         ;;
660                                 'pack-license')
661                                         license_list="${license_list} ${1}"
662                                         ;;
663                                 'download-dir')
664                                         if [ -z "${download_dir}" ]
665                                         then
666                                                 download_dir="${1}"
667                                         else
668                                                 printError "more than one download dir: ${1}"
669                                         fi
670                                         ;;
671                                 'install-dir')
672                                         if [ -z "${install_dir}" ]
673                                         then
674                                                 install_dir="${1}"
675                                         else
676                                                 printError "more than one install dir: ${1}"
677                                         fi
678                                         ;;
679                                 *)
680                                         printError "misplaced argument: ${1}"
681                                         ;;
682                         esac
683                         ;;
684         esac
685
686         shift
687 done
688
689 # compatibility with legacy Makefile
690 if [ "${DOWNLOAD_GAMEPACKS}" = 'yes' ]
691 then
692         if ! [ -z "${DOWNLOADDIR}" ]
693         then
694                 download_dir="${DOWNLOADDIR}"
695         fi
696
697         if ! [ -z "${INSTALLDIR}" ]
698         then
699                 install_dir="${INSTALLDIR}"
700         fi
701
702         license_list='free'
703         by_license='true'
704
705         download_packs='true'
706         mkdir_download='true'
707
708         install_packs='true'
709         mkdir_install='true'
710 fi
711
712 if [ -z "${download_dir}" ]
713 then
714         download_dir="${default_download_dir}"
715 fi
716
717 if [ -z "${install_dir}" ]
718 then
719         install_dir="${default_install_dir}"
720 fi
721
722 if "${by_name}"
723 then
724         selected_list="${selected_list} $(printNameListByName "${name_list}")"
725 fi
726
727 if "${by_license}"
728 then
729         selected_list="${selected_list} $(printNameListByLicense "${license_list}")"
730 fi
731
732 selected_list="$(dedupeList "${selected_list}")"
733
734 if "${mkdir_download}"
735 then
736         ${MKDIR_P} "${download_dir}"
737         real_download_dir="$(printRealPath "${download_dir}")"
738 fi
739
740 if "${mkdir_install}"
741 then
742         ${MKDIR_P} "${install_dir}"
743         real_install_dir="$(printRealPath "${install_dir}")"
744 fi
745
746 if "${list_names}"
747 then
748         printNameList
749 fi
750
751 if "${list_licenses}"
752 then
753         printLicenseList
754 fi
755
756 if "${list_selected}"
757 then
758         printList "${selected_list}"
759 fi
760
761 if "${download_packs}"
762 then
763         downloadPackList "${real_download_dir}" "${selected_list}"
764 fi
765
766 if "${install_packs}"
767 then
768         installPackList "${real_download_dir}" "${real_install_dir}" "${selected_list}"
769 fi
770
771 #EOF