Compare commits

...

11 Commits

Author SHA1 Message Date
5921d27f1e runit.sh update
Signed-off-by: philippe lhardy <philippe.lhardy@astrolabe.coop>
2026-03-12 14:45:17 +01:00
philippe lhardy
6095bd26ce exmples mieuxvoter 2025-10-07 13:17:33 +02:00
philippe lhardy
34154c7c4d temporary expected error : works only on my system (absolute directories) 2025-10-07 11:18:33 +02:00
philippe lhardy
23d5368f40 artlog script 2025-10-07 11:02:55 +02:00
8d011bd818 README update 2025-10-07 10:53:08 +02:00
c718ddfd7c missing sample 2025-10-07 10:52:01 +02:00
6a2ccb5d2b run docker dev nextcloud
- missing clone info
- missing setup of certificates (mkcert and so one ... )
2025-09-29 16:15:08 +02:00
ad09c3ec72 update pending 2025-09-28 09:50:55 +02:00
8d65ab03cf rename into english 2025-08-20 17:21:23 +02:00
9159d2a082 fr -> en code 2025-08-20 16:49:41 +02:00
faedb4c80c support mariadb 2025-08-20 11:32:01 +02:00
37 changed files with 1316 additions and 464 deletions

View File

@@ -29,7 +29,7 @@ C'est la description de wikipedia https://fr.wikipedia.org/wiki/Jugement_majorit
(https://en.wikipedia.org/wiki/Majority_judgment)
L'implémentaion est très basique et miminale :
L'implémentation est très basique et miminale :
|langage|code|
|-----|---------------------|
@@ -170,3 +170,12 @@ https://framagit.org/framasoft/framadate
C'est un intégration de framadate dans yunohost.
## Mieux Voter
https://app.mieuxvoter.fr/
Il est possible d'importer un csv.
https://github.com/MieuxVoter/majority-judgment-web-app
exemples sous samples/mieuxvoter

View File

@@ -1,4 +1,4 @@
# Glue to use majortiy judgement algorithm in various polls applications
# Glue to use majority judgement algorithm in various polls applications
Initial trigger of this project is implementation of this in Nextcloud poll app https://github.com/nextcloud/polls/issues/3472, project pushed by https://www.astrolabe.coop/
@@ -14,7 +14,6 @@ https://en.wikipedia.org/wiki/Majority_judgment
After some research most of implementation is done in https://github.com/MieuxVoter
Sorry for inconvenience but currently only french version is available.
LISEZMOI.md
@@ -51,3 +50,10 @@ dev done on Ubuntu 24.04.2 LTS
cd code
./check_csv.sh ../samples/nextcloud_poll_export/poll1.csv
```
```
./check_csv.sh ../samples/nextcloud_poll_export/poll1.csv >../samples/0.json
```
et tester

View File

@@ -1,4 +1,37 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2025 artlog@l0g.eu
# SPDX-License-Identifier: AGPL-3.0-or-later
#
csv_file="$1"
python3 convert_nextcloud_poll_csv.py "$csv_file" 'Très Bien' 'Bien' 'Assez Bien' 'Passable' 'Insuffisant' 'A Rejeter'
toolsdir=../lib
pushd $toolsdir >/dev/null
toolsdir=$(pwd)
source metascript.sh
popd >/dev/null
while [[ $# > 0 ]]
do
case "$1" in
*)
if [[ -z $csv_file ]]
then
csv_file="$1"
else
parsemetaarg "$1"
fi
;;
esac
shift
done
metarun=metarun
if [[ -z $csv_file ]]
then
log_error "Missing csv_file argument"
exit 1
fi
enforcefile "$csv_file" exists
$metarun python3 convert_nextcloud_poll_csv.py "$csv_file" 'Très Bien' 'Bien' 'Assez Bien' 'Passable' 'Insuffisant' 'A Rejeter'

View File

@@ -3,41 +3,41 @@ import sys
import csv
# using non modified nextcloud standard text poll
nom_mentions=['yes','maybe','','no']
mentions_name=['yes','maybe','','no']
if len(sys.argv) > 1:
csv_file=sys.argv[1]
if len(sys.argv) > 2:
nom_mentions=[]
mentions_name=[]
for i in range(2,len(sys.argv)):
nom_mentions.append(sys.argv[i])
mentions_name.append(sys.argv[i])
else:
print('missing file argument')
exit(1)
def mention_index(mention):
return nom_mentions.index(mention)
return mentions_name.index(mention)
verbose=False
mentions=len(nom_mentions)
mentions=len(mentions_name)
default_mention=mentions-1
warnings=[]
candidats={}
candidats_alias=[]
candidates={}
candidates_alias=[]
a_vote={}
votes=[]
with open(csv_file,'r') as csv_fd:
vote_reader = csv.reader(csv_fd, delimiter=',')
for row in vote_reader:
if len(candidats_alias) == 0:
if len(candidates_alias) == 0:
for i in range(2,len(row)):
candidat_alias=row[i]
candidats_alias.append(candidat_alias)
candidate_alias=row[i]
candidates_alias.append(candidate_alias)
# no other name given
if not candidat_alias in candidats:
candidats[candidat_alias]=candidat_alias
if not candidate_alias in candidates:
candidates[candidate_alias]=candidate_alias
else:
# check if not duplicated
participant=row[0]
@@ -47,20 +47,20 @@ with open(csv_file,'r') as csv_fd:
a_vote[participant]=row[1]
vote={}
for i in range(2,len(row)):
candidat_index=i-2
candidat_alias=candidats_alias[candidat_index]
candidate_index=i-2
candidate_alias=candidates_alias[candidate_index]
mention=row[i]
vote[candidat_alias]=mention_index(mention)
vote[candidate_alias]=mention_index(mention)
votes.append(vote)
default_vote={}
if verbose:
print(','.join(candidats))
print(','.join(nom_mentions))
print(','.join(candidates))
print(','.join(mentions_name))
result = {'candidats':candidats,'votants':{'decompte':len(a_vote)},'mentions':nom_mentions,'votation':{'votes':votes}}
result = {'candidates':candidates,'voters':{'count':len(a_vote)},'mentions':mentions_name,'votation':{'votes':votes}}
print(json.dumps(result))
sys.exit(0)

View File

@@ -1,124 +0,0 @@
import json
import sys
from majority_judgment import majority_judgment
verbose=False
def collect_votes(votes,candidats,default_mention,warnings):
collect={}
for vote in votes:
# missing candidat in vote picks the worse one
for candidat in candidats:
if not candidat in vote:
vote[candidat]=default_mention
for candidat,mention in vote.items():
if mention > default_mention or mention < 0:
mention = default_mention
warnings.append("index de mention invalide, soit la liste des mentions est erronnée soit la mention dans le vote est erronée")
if candidat in collect:
if mention in collect[candidat]:
collect[candidat][mention]=collect[candidat][mention]+1
else:
collect[candidat][mention]=1
else:
collect[candidat]={mention:1}
return collect
def arrange_votes_par_candidat(votes,candidats,default_mention):
vpc={}
for candidat in candidats:
vpc[candidat]=[vote[candidat] if candidat in vote else default_mention for vote in votes]
return vpc
def jugement_majoritaire(poll):
verbose=False
crosscheck=True
votants=poll['votants']['decompte']
candidats=poll['candidats']
nombre_candidats=len(candidats)
# l'ordre des mentions est de la meilleure à la pire (reject)
nom_mentions=poll['mentions']
mentions=len(nom_mentions)
default_mention=mentions-1
votes=poll["votation"]["votes"]
if verbose:
print('candidats:' + str(candidats))
print(votes)
warnings=[]
collect=collect_votes(votes,list(candidats),default_mention,warnings)
if verbose:
print(collect)
votant_median_check= votants / 2 if votants % 2 == 0 else ((votants-1) / 2) +1
votant_median=votants // 2 + votants % 2
if votant_median_check != votant_median:
print('[ERROR] le nombre median de votants semble erroné. contactez le developpeur de ce code.' + str(votant_median_check))
merite={}
mention_mediane={}
merite_pourcent={}
votes_par_candidat=arrange_votes_par_candidat(votes,candidats,mentions-1)
# cumul : du meilleur au pire
# range_mentions = range(len(mentions))
# cumul du pire au meilleur
range_mentions = range(mentions-1,-1,-1)
for candidat in list(candidats):
vote=collect[candidat]
cumul=0
m=[0 for i in range(mentions)]
mention_m=None
pourcent=[0 for i in range(mentions)]
for mention in range_mentions:
if mention in vote:
cumul=cumul+vote[mention]
pourcent[mention]= ( 100 * vote[mention] ) / votants
if cumul >= votant_median and mention_m is None:
mention_m=mention
m[mention]=cumul
if cumul < votants:
print('[ERROR] le cumul des votes doit correspondre au nombre de votants. contactez le developpeur de ce code.')
mention_mediane[candidat]=mention_m
merite[candidat]=m
merite_pourcent[candidat]=pourcent
if verbose:
print(merite)
print(mention_mediane)
# les gagnants sont ceux qui ont la mention mediane minimale ( la meilleure )
found=[]
for mention in range(0,mentions):
for candidat,merite_median in mention_mediane.items():
if merite_median == mention:
found.append({candidat:candidats[candidat]})
if len(found) > 0:
break
if verbose:
print(found)
print(nom_mentions[mention])
# expanded=expand_collect(collect,list(candidats),mentions)
if verbose:
print(votes_par_candidat)
result = {'resultat':{'mention':nom_mentions[mention],'candidats':found},'merite':merite,'profil':merite_pourcent,"warnings":warnings}
if crosscheck:
# cross check with a well known implementation
result["crosscheck"]=majority_judgment(votes_par_candidat, reverse=True)
return result

View File

@@ -0,0 +1,124 @@
import json
import sys
from majority_judgment import majority_judgment
verbose=False
def collect_votes(votes,candidates,default_mention,warnings):
collect={}
for vote in votes:
# missing candidate in vote picks the worse one
for candidate in candidates:
if not candidate in vote:
vote[candidate]=default_mention
for candidate,mention in vote.items():
if mention > default_mention or mention < 0:
mention = default_mention
warnings.append("index de mention invalide, soit la liste des mentions est erronnée soit la mention dans le vote est erronée")
if candidate in collect:
if mention in collect[candidate]:
collect[candidate][mention]=collect[candidate][mention]+1
else:
collect[candidate][mention]=1
else:
collect[candidate]={mention:1}
return collect
def arrange_votes_by_candidate(votes,candidates,default_mention):
vpc={}
for candidate in candidates:
vpc[candidate]=[vote[candidate] if candidate in vote else default_mention for vote in votes]
return vpc
def majority_judgment_run(poll):
verbose=False
crosscheck=True
voters=poll['voters']['count']
candidates=poll['candidates']
nombre_candidates=len(candidates)
# l'ordre des mentions est de la meilleure à la pire (reject)
nom_mentions=poll['mentions']
mentions=len(nom_mentions)
default_mention=mentions-1
votes=poll["votation"]["votes"]
if verbose:
print('candidates:' + str(candidates))
print(votes)
warnings=[]
collect=collect_votes(votes,list(candidates),default_mention,warnings)
if verbose:
print(collect)
voter_median_check= voters / 2 if voters % 2 == 0 else ((voters-1) / 2) +1
voter_median=voters // 2 + voters % 2
if voter_median_check != voter_median:
print('[ERROR] le nombre median de voters semble erroné. contactez le developpeur de ce code.' + str(voter_median_check))
merit={}
mention_mediane={}
merit_pourcent={}
votes_by_candidate=arrange_votes_by_candidate(votes,candidates,mentions-1)
# cumul : du meilleur au pire
# range_mentions = range(len(mentions))
# cumul du pire au meilleur
range_mentions = range(mentions-1,-1,-1)
for candidate in list(candidates):
vote=collect[candidate]
cumul=0
m=[0 for i in range(mentions)]
mention_m=None
pourcent=[0 for i in range(mentions)]
for mention in range_mentions:
if mention in vote:
cumul=cumul+vote[mention]
pourcent[mention]= ( 100 * vote[mention] ) / voters
if cumul >= voter_median and mention_m is None:
mention_m=mention
m[mention]=cumul
if cumul < voters:
print('[ERROR] cumulated votes should match voters. Contact this code developer.')
mention_mediane[candidate]=mention_m
merit[candidate]=m
merit_pourcent[candidate]=pourcent
if verbose:
print(merit)
print(mention_mediane)
# les gagnants sont ceux qui ont la mention mediane minimale ( la meilleure )
found=[]
for mention in range(0,mentions):
for candidate,merit_median in mention_mediane.items():
if merit_median == mention:
found.append({candidate:candidates[candidate]})
if len(found) > 0:
break
if verbose:
print(found)
print(nom_mentions[mention])
# expanded=expand_collect(collect,list(candidates),mentions)
if verbose:
print(votes_by_candidatee)
result = {'result':{'mention':nom_mentions[mention],'candidates':found},'merit':merit,'profil':merit_pourcent,"warnings":warnings}
if crosscheck:
# cross check with a well known implementation
result["crosscheck"]=majority_judgment(votes_by_candidate, reverse=True)
return result

View File

@@ -21,9 +21,9 @@ if ($verbose) {
var_dump($poll);
}
$votants=$poll['votants']['decompte'];
$candidats=$poll['candidats'];
$nombre_candidats=count($candidats);
$voters=$poll['voters']['count'];
$candidates=$poll['candidates'];
$nombre_candidates=count($candidates);
# l'ordre des mentions est de la meilleure à la pire (reject)
$nom_mentions=$poll['mentions'];
@@ -33,22 +33,22 @@ $default_mention=$mentions-1;
$votes=$poll["votation"]["votes"];
if ($verbose) {
print("candidats\n");
var_dump($candidats);
print("candidates\n");
var_dump($candidates);
print("votes:\n");
var_dump($votes);
}
$warnings=[];
$default_vote=[];
foreach ($candidats as $candidat => $nom_candidat ) {
foreach ($candidates as $candidat => $nom_candidat ) {
$default_vote[$candidat]=$default_mention;
}
$collect=[];
foreach ($votes as $vote) {
# missing candidat in vote is the worts one
foreach ($candidats as $candidat => $nom_candidat ) {
foreach ($candidates as $candidat => $nom_candidat ) {
if (! array_key_exists($candidat,$vote)) {
$vote[$candidat]=$default_mention;
}
@@ -78,24 +78,24 @@ if ($verbose) {
print(json_encode($collect));
}
$votant_median_check= ($votants % 2 == 0) ? intdiv($votants,2) : intdiv(($votants-1),2) + 1;
$voter_median_check= ($voters % 2 == 0) ? intdiv($voters,2) : intdiv(($voters-1),2) + 1;
$votant_median=intdiv($votants, 2) + $votants % 2;
$voter_median=intdiv($voters, 2) + $voters % 2;
if ( $votant_median_check != $votant_median ) {
print('[ERROR] le nombre median de votants (' . $votant_median_check . '/' . $votant_median . ') semble erroné. contactez le developpeur de ce code.');
if ( $voter_median_check != $voter_median ) {
print('[ERROR] le nombre median de voters (' . $voter_median_check . '/' . $voter_median . ') semble erroné. contactez le developpeur de ce code.');
}
$merite=[];
$merit=[];
$mention_mediane=[];
$merite_pourcent=[];
$merit_pourcent=[];
# cumul : du meilleur au pire
# range_mentions = range(count(mentions))
# cumul du pire au meilleur
$range_mentions = $mentions;
foreach ($candidats as $candidat => $nom_candidat) {
foreach ($candidates as $candidat => $nom_candidat) {
$vote=$collect[$candidat];
$cumul=0;
for ($i=0; $i < $range_mentions; $i ++) {
@@ -106,24 +106,24 @@ foreach ($candidats as $candidat => $nom_candidat) {
for ($mention=$range_mentions -1; $mention >=0; $mention --) {
if ( array_key_exists($mention,$vote) ) {
$cumul=$cumul+$vote[$mention];
$pourcent[$mention]= ( 100 * $vote[$mention] ) / $votants;
$pourcent[$mention]= ( 100 * $vote[$mention] ) / $voters;
}
if ($cumul >= $votant_median and $mention_m == null ) {
if ($cumul >= $voter_median and $mention_m == null ) {
$mention_m=$mention;
}
$m[$mention]=$cumul;
}
if ($cumul < $votants) {
print("[ERROR] le cumul des votes " . $cumul . " doit correspondre au nombre de votants. contactez le developpeur de ce code.");
if ($cumul < $voters) {
print("[ERROR] votes cumulated " . $cumul . " should match voters. Contact this code developer.");
}
$mention_mediane[$candidat]=$mention_m;
$merite[$candidat]=$m;
$merite_pourcent[$candidat]=$pourcent;
$merit[$candidat]=$m;
$merit_pourcent[$candidat]=$pourcent;
}
if ($verbose) {
print("\nmerite:\n");
print(json_encode($merite));
print("\nmerit:\n");
print(json_encode($merit));
print("\nmention_mediane:\n");
print(json_encode($mention_mediane));
}
@@ -132,10 +132,10 @@ if ($verbose) {
$found=[];
for ($mention = 0; $mention < $mentions; $mention ++) {
foreach ($mention_mediane as $candidat => $merite_median)
if ($merite_median == $mention) {
foreach ($mention_mediane as $candidat => $merit_median)
if ($merit_median == $mention) {
$item=[];
$item[$candidat]=$candidats[$candidat];
$item[$candidat]=$candidates[$candidat];
array_push($found,$item);
}
if (count($found) > 0) {
@@ -148,7 +148,7 @@ if ($verbose) {
print($nom_mentions[$mention]);
}
$result = ['resultat'=> ['mention'=>$nom_mentions[$mention],'candidats' => $found],'merite' => $merite,'profil' => $merite_pourcent,"warnings" => $warnings];
$result = ['result'=> ['mention'=>$nom_mentions[$mention],'candidates' => $found],'merit' => $merit,'profil' => $merit_pourcent,"warnings" => $warnings];
print(json_encode($result));

View File

@@ -1,6 +1,6 @@
import json
import sys
from jugement_majoritaire import jugement_majoritaire
from majority_judgment_method import majority_judgment_run
if len(sys.argv) > 1:
poll_file=sys.argv[1]
@@ -16,6 +16,6 @@ with open(poll_file,'r') as poll_fd:
if verbose:
print(poll)
result = jugement_majoritaire(poll)
result = majority_judgment_run(poll)
print(json.dumps(result))

View File

@@ -1,9 +1,7 @@
# contexte
Choix des noms de mentions
https://fr.wikipedia.org/wiki/Jugement_majoritaire
vs
https://en.wikipedia.org/wiki/Jugement_majoritaire

1
lib Symbolic link
View File

@@ -0,0 +1 @@
nextcloud_devenv/lib

View File

@@ -1,8 +1,16 @@
# Nextcloud Dev En
# Nextcloud Dev Environment for Poll
Two options
one created manualy with a simple podman and a local database
one fully under podman, done on nextcloud conference
# Option 1 / simple podman
Create a dev env for nextcloud
setup databse :
setup database :
```
./setupdatabase.sh
@@ -29,7 +37,7 @@ puis
./sync_poll.sh
```
il reste encore a compiler avec les dependances composer et le build npm vuejs
il reste encore a compiler avec les dépendances composer et le build npm vuejs
```
./enter_www_data.sh
@@ -63,3 +71,50 @@ utiliser la base de donnee postgres et non le SQLLite ?
./migrate_db_sqlite2postgres.sh
# Option 2 : nextcloud-docker-dev
setup
```
git clone git@github.com:juliusknorr/nextcloud-docker-dev.git
git git@github.com:nextcloud/server.git
```
check nextcloud-docker-dev.env then
```
cp nextcloud-docker-dev.env nextcloud-docker-dev.git/.env
```
https setup, local private PKI.
missing setup cert and setupcert host ... mkcert compilation + install
update-certs update-hosts
https://nextcloud-dev.l0g.eu
then will be done as sudo ( RootLess or podman limitation ? )
```
./runit.sh dryrun up
```
start
```
./runit.sh up
```
stop
```
./runit.sh down
```
Pour les mails : https://mail-dev.l0g.eu/#
un MailHog est installé !

View File

@@ -11,5 +11,13 @@ flavor=dev
database_name=nextcloud_$flavor
username=nextcloud_$flavor
# postgres
db_port=5432
db_hostname=127.0.0.1
dbtype_nextcloude=pgsql
# mariadb/mysql
db_port=3306
db_hostname=127.0.0.1
dbtype_nextcloud=mysql

View File

@@ -5,25 +5,44 @@
# [[ -f $log_functions ]] || { echo "[FATAL] Missing $log_functions" >&2 ; exit 1 ;}
# source $log_functions
metalog_color_start() {
if [[ -n $metalog_color ]]
then
echo -en "${metalog_color}"
fi
}
metalog_color_stop() {
if [[ -n $metalog_color ]]
then
echo -en "\033[0m"
fi
}
log_any()
{
priority=$1
shift
metalog_color_start
echo "[$priority] $@" >&2
metalog_color_stop
}
log_fatal()
{
local metalog_color=$metalog_color_error
log_any FATAL "$*"
}
log_error()
{
local metalog_color=$metalog_color_error
log_any ERROR "$*"
}
log_warn()
{
local metalog_color=$metalog_color_warning
log_any WARN "$*"
}
@@ -32,59 +51,36 @@ log_info()
log_any INFO "$*"
}
log_success()
{
local metalog_color=$metalog_color_success
log_info "$*"
}
log_debug()
{
local metalog_color=$metalog_color_info
[[ -n $debug ]] && log_any DEBUG "$*"
}
deferpipe()
{
cat
echo "# <previous line> | $@"
}
autoquoteargs()
{
echo -n "$1"
shift
while [[ $# > 0 ]]
do
if [[ "$1" =~ [\ \$] ]]
then
echo -n " '$1'"
else
echo -n " $1"
fi
shift
done
echo
}
echoarray()
{
declare -a arr=("${@}")
declare -i len=${#arr[@]}
# Show passed array
for ((n = 0; n < len; n++))
do
echo -en " \"${arr[$n]}\""
done
echo
}
include_source()
{
if [[ -f $1 ]]
then
source $1
else
log_fatal "Missing $1 script"
exit 1
fi
}
verbose()
{
[[ -n $verbose ]] && log_any $verbose $@
}
metalog_no_colors()
{
metalog_color_info=
metalog_color_success=
metalog_color_error=
metalog_color_warning=
}
# default colors
metalog_default_colors()
{
metalog_color_info="\033[38;5;79m"
metalog_color_success="\033[1;32m"
metalog_color_error="\033[1;31m"
metalog_color_warning="\033[1;34m"
}

View File

@@ -1,30 +1,83 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2025 artlog@l0g.eu
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# metascript to include
#
# usual way if no parameters used by outer script :
#
# source $(dirname "$0")/metascript.sh
#
# defaultmetainit $@
#
# to generate file header for caller script do :
# bash <relative_path_to>/metascript.sh header
# ex:
# bash lib/metascript.sh header
metascript_version=v1.1.0
# if called directly
if [[ "$0" =~ ^(.*)/metascript.sh$ ]]
then
prefix=${BASH_REMATCH[1]}
case $1 in
header)
cat <<EOF
#!/bin/bash
# SPDX-FileCopyrightText: 2025 artlog@l0g.eu
# SPDX-License-Identifier: AGPL-3.0-or-later
# header generated by $0 $1
# metascript_version=$metascript_version
toolsdir=$prefix
pushd \$toolsdir >/dev/null
toolsdir=\$(pwd)
source metascript.sh
popd >/dev/null
metarun=metarun
# change default to sudo if needed
# metasudo=sudo
while [[ \$# > 0 ]]
do
case "\$1" in
*)
parsemetaarg "\$1"
;;
esac
shift
done
usage
EOF
;;
version)
echo $metascript_version
;;
*)
echo "[WARNING] only header and version arguments are supported. (prefix=$prefix), this script is intended to be included not to be directly called." >&2
exit 1
esac
exit 0
fi
if [[ "$metascript_included" == "yes" ]]
then
echo "[WARNING] metascript already included ($0)" >&2
else
metascript_included=yes
# assume all tools are in lib/
toolsdir=$(dirname $(readlink -f $0))/lib
# all tools resources are relative to this directory
# at this step $(pwd) should be this of metascript.sh
# while $0 is this of caller script using metascript
# project directory
# relative
# toolsparentdir=$(realpath --relative-to "$(pwd)" $(readlink -f $0))/
# absolute
toolsparentdir=$(realpath $(readlink -f $0))/
if [[ -z $toolsdir ]]
then
# assume all tools are in lib/
toolsdir=$(dirname $(readlink -f $0))/lib
# all tools resources are relative to this directory
fi
if [[ -z $log_functions ]]
then
log_functions=$toolsdir/log_functions.sh
[[ -f $log_functions ]] || { echo "[FATAL] Missing $log_functions" >&2 ; exit 1 ;}
[[ -f $log_functions ]] || { echo "[FATAL] Missing $log_functions , toolsdir=$toolsdir script=$0" >&2 ; exit 1 ;}
source $log_functions
fi
@@ -33,28 +86,100 @@ metascript_usage()
cat <<EOF >&2
metascript commands :
help|usage help or usage of this {$0} tool
help|usage help or usage of this {$0} tool. stop after usage display.
dryrun|show|showdoc display what should/will be done
defersource= script file defining defer() non sandard function
defersource= script file defining defer() non standard function
defer= defer function to use, default is showdoc
toolsresourcesdir= where to pick resource
default to parent of script $toolsresourcesdir
mostly used with dryrun
apply default : will actual do work without defer
text_format=md set text format output to markdown
EOF
}
md_output_init() {
md_quote='```'
}
usage()
{
echo "[WARNING} no specific usage function for {$0}, to improve by developer" >&2
log_warn "no specific usage function for {$0}, to improve by developer"
echo >&2
metascript_usage
}
showinfo()
{
echo $@
echo "$@"
}
metarun()
{
$defer $metasudo "$@"
return 0
}
metasudo_auto()
{
if [[ $EUID -eq 0 ]]
then
log_info "already effective user id as root, don't require sudo"
else
metasudo=sudo
fi
}
autoquoteargs()
{
echo -n "$1"
shift
while [[ $# > 0 ]]
do
if [[ "$1" =~ [\ \$] ]]
then
echo -n " '$1'"
else
echo -n " $1"
fi
shift
done
}
echoarray()
{
declare -a arr=("${@}")
declare -i len=${#arr[@]}
# Show passed array
for ((n = 0; n < len; n++))
do
echo -en " \"${arr[$n]}\""
done
echo
}
start_quote() {
[[ -n $md_quote ]] && echo "$md_quote"
}
end_quote() {
[[ -n $md_quote ]] && echo "$md_quote"
}
deferpipe()
{
cat
echo "# <previous line> | $@"
}
include_source()
{
local script="$1"
enforce var script
source "$script"
}
showdoc()
@@ -62,12 +187,12 @@ showdoc()
if [[ $1 =~ ^tools/ ]]
then
# assumes it handles ENV_METASCRIPT_DEFER
$@
"$@"
else
echo '```'
autoquoteargs $@
start_quote
autoquoteargs "$@"
echo
echo '```'
end_quote
fi
}
@@ -77,57 +202,149 @@ redirectto()
if [[ -n $defer ]]
then
echo "Copy to $tofile"
echo '```'
start_quote
cat
echo '```'
end_quote
else
cat > $tofile
fi
}
execredirectfrom()
redirectappendto()
{
tofile=$1
shift
if [[ -n $defer ]]
then
echo '```'
autoquoteargs $@
echo ' < '"$tofile"
echo '```'
echo "Append to $tofile"
start_quote
cat
end_quote
else
$@ < $tofile
cat >> $tofile
fi
}
execredirectfrom()
{
fromfile="$1"
shift
enforcefile "$fromfile" exists
if [[ -n $defer ]]
then
start_quote
autoquoteargs "$@"
echo ' < '"$fromfile"
end_quote
else
"$@" < $fromfile
fi
}
execredirectto()
{
tofile=$1
tofile="$1"
shift
if [[ -n $defer ]]
then
echo '```'
autoquoteargs $@
start_quote
autoquoteargs "$@"
echo ' > '"$tofile"
echo '```'
end_quote
else
$@ > $tofile
"$@" > $tofile
fi
}
defer_exec_set_var()
{
tovar="$1"
shift
cat <<EOF
$md_quote
$tovar=\$($(autoquoteargs "$@"))
$md_quote
EOF
}
execredirecttoroot()
{
tofile="$1"
shift
if [[ -n $defer ]]
then
start_quote
autoquoteargs "$@"
echo ' | sudo tee '"$tofile"' >/dev/null'
end_quote
else
"$@" | sudo tee $tofile >/dev/null
fi
}
execredirectas_to()
{
while [[ $# > 0 ]]
do
case $1 in
user=*)
if [[ "$1" =~ ^user=([a-z]+[-_a-z0-9]*)$ ]]
then
asuser=${BASH_REMATCH[1]}
else
log_fatal "execredirecttouser missing user= in '$@'"
[[ -n $defer ]] || exit 1
fi
;;
chroot=*)
aschroot=${1/chroot=}
;;
*)
tofile=$1
shift
break
esac
shift
done
local command=()
if [[ -n $aschroot ]]
then
command=(sudo chroot)
if [[ -n $asuser ]]
then
# no group yet
command+=(--userspec=$asuser:$asuser)
fi
command+=($aschroot)
elif [[ -n $asuser ]]
then
command=(sudo -u $asuser)
fi
command+=(tee $tofile)
if [[ -n $defer ]]
then
start_quote
autoquoteargs "$@"
echo ' | '"${command[@]}"' >/dev/null'
end_quote
else
"$@" | "${command[@]}" >/dev/null
fi
}
pipeto()
{
if [[ -n $defer ]]
then
echo '```'
echo "$md_quote"
echo -n 'cat << EOF| '
autoquoteargs $@
autoquoteargs "$@"
echo
cat
echo "EOF"
echo '```'
echo "$md_quote"
else
cat | $@
cat | "$@"
fi
}
@@ -217,129 +434,6 @@ check_missing_dest_dir()
fi
}
sed_substitute_expr()
{
local from="$1"
shift
local to="$1"
shift
local s='/'
if [[ $from =~ [\\] ]]
then
# escape char \ should be doubled
from=${from//\\/\\\\}
fi
if [[ $from =~ $s ]]
then
# echo "[ERROR] character $s is prohibited due to sed usage" >&2
from=${from//$s/\\$s}
fi
if [[ $from =~ \[ ]]
then
from=${from//\[/\\\[}
fi
if [[ $from =~ \* ]]
then
from=${from//\*/\\\*}
fi
if [[ $from =~ ^(.*)\$$ ]]
then
from=${BASH_REMATCH[1]}'\$'
fi
if [[ $from =~ ^\^(.*)$ ]]
then
from='\^'${BASH_REMATCH[1]}
fi
if [[ $to =~ [\\] ]]
then
# escape char \ should be doubled
to=${to//\\/\\\\}
fi
if [[ $to =~ $s ]]
then
# echo "[ERROR] character $s is prohibited due to sed usage" >&2
# echo "This is a limitation of metascript.sh script, replaced by \$s" >&2
to=${to//$s/\\$s}
fi
if [[ $to =~ [\&] ]]
then
# echo "[ERROR] character & is prohibited due to sed usage" >&2
to=${to//\&/\\\&}
fi
# replace it globaly
echo "s$s$from$s$to${s}g"
}
sedreplacefromto()
{
local from="$1"
local to="$2"
shift 2
local sedexpr="$1"
execredirectto $to sed "$sedexpr" $from
shift
while [[ $# > 0 ]]
do
sedexpr="$1"
$defer sed -i "$sedexpr" $to
shift
done
}
replacefromto()
{
local from="$1"
local to="$2"
shift 2
if [[ -n $defer ]]
then
$defer "replace $@ from '$from' into '$to'"
else
local sedexpr=$(sed_substitute_expr "$1" "$2")
execredirectto $to sed "$sedexpr" $from
shift 2
while [[ $# > 0 ]]
do
sedexpr=$(sed_substitute_expr "$1" "$2")
$defer sed -i "$sedexpr" $to
shift 2
done
fi
}
sedreplacein()
{
local file=$1
shift
while [[ $# > 0 ]]
do
$defer sed -i "$1" $file
shift
done
}
replacein()
{
local infile=$1
shift
if [[ -n $defer ]]
then
$defer "replace $@ into '$infile'"
else
while [[ $# > 0 ]]
do
sedexpr=$(sed_substitute_expr "$1" "$2")
$defer sed -i "$sedexpr" $infile
shift 2
done
fi
}
parsemetaarg()
{
case $1 in
@@ -355,8 +449,15 @@ parsemetaarg()
dryrun|show|showdoc)
defer=showdoc
;;
debug)
set -ex
;;
metasudo=*)
metasudo=${1/metasudo=}
;;
help|usage)
usage
exit 0
;;
toolsresourcesdir=*)
toolsresourcesdir=${1/toolsresourcesdir=/}
@@ -364,6 +465,9 @@ parsemetaarg()
scl_enable=*)
scl_args=(scl enable ${1/scl_enable=/} --)
;;
text_format=md)
md_output_init
;;
*)
log_error "unrecognized argument '$1'"
usage
@@ -380,28 +484,82 @@ enforcearg()
if [[ -z $value ]]
then
log_error "{0} expect '$var' to be set ex $var=$default"
log_error "{$0} expect '$var' to be set ex $var=$default"
if [[ -n $defer ]]
then
[[ -z $default ]] && default="DEFAULT"
log_warn "in defer/dryrun force $var=$default"
eval "$var=$default"
else
usage
exit 1
fi
fi
}
enforce()
{
objecttype=$1
shift
case $objecttype in
file|dir)
object=$1
shift
constraint=$1
case $constraint in
exists|does_not_exist|create_if_needed|well_named)
enforce${objecttype} "$object" "$@"
;;
*)
log_error "enforce $objecttype '$object' $@"
log_fatal "constraint '$constraint' unsupported. currently known (exists|does_not_exist|create_if_needed) Please fix the code."
exit 1
;;
esac
;;
var|arg)
enforcearg "$@"
;;
user)
enforceuser "$@"
;;
*)
log_error "enforce $objecttype $@"
log_fatal "objectttype $objecttype unsupported"
exit 1
;;
esac
}
enforcefile()
{
file="$1"
constraint="$2"
local file="$1"
local constraint="$2"
if [[ $constraint == well_named ]]
then
if [[ "$file" =~ /$ ]]
then
log_error "'$file' as a file should not end with a '/'"
[[ -n $defer ]] || exit 1
fi
fi
if [[ ! -f "$file" ]]
then
if [[ $constraint = exists ]]
then
log_error "[ERROR] Missing expected $file"
case $constraint in
exists)
log_error "Missing expected $file"
[[ -n $defer ]] || exit 1
fi
;;
create_if_needed)
$metarun touch $file
;;
esac
else
if [[ $constraint = does_not_exists ]]
if [[ $constraint = does_not_exist ]]
then
log_error "[ERROR] '$file' already exists. Move it away"
log_error "'$file' already exists. Move it away"
[[ -n $defer ]] || exit 1
fi
fi
@@ -409,33 +567,53 @@ enforcefile()
enforcedir()
{
dir="$1"
constraint="$2"
local dir="$1"
local constraint="$2"
if [[ $constraint = does_not_exist ]]
if [[ $constraint == well_named ]]
then
if [[ ! "$dir" =~ /$ ]]
then
log_error "'$dir' should end with a '/'"
[[ -n $defer ]] || exit 1
fi
fi
if [[ -e $dir ]]
then
log_error "'$dir' already exists. Move it away"
case $constraint in
does_not_exist)
if [[ -d "$dir" ]]
then
log_error "'$dir' already exists"
[[ -n $defer ]] || exit 1
else
log_error "'$dir' already exists and is not a directory as expected"
[[ -n $defer ]] || exit 1
fi
fi
if [[ $constraint = exists ]]
then
;;
create_if_needed|exists)
if [[ ! -d "$dir" ]]
then
if [[ -e "$dir" ]]
log_error "'$dir' already exists and is not a directory as expected"
[[ -n $defer ]] || exit 1
fi
;;
esac
else
case $constraint in
exists)
log_error "Missing expected directory $dir"
[[ -n $defer ]] || exit 1
;;
create_if_needed)
if [[ ! -d "$dir" ]]
then
log_error "'$dir' already exists but is not a directory as expected"
[[ -n $defer ]] || exit 1
$metarun mkdir -p "$dir"
fi
log_error "[ERROR] Missing expected directory '$dir'"
[[ -n $defer ]] || exit 1
;;
esac
fi
fi
}
applymetaargs()
@@ -528,7 +706,7 @@ check_variable_in()
shift 2
local value=""
local values=$@
local values="$@"
eval value='$'"$var"
@@ -570,17 +748,222 @@ get_timestamp_second()
echo "$(date +"%Y%m%d%H%M%S")"
}
if [[ -z $ENV_METASCRIPT_RESOURCESDIR ]]
todo()
{
log_any TODO "$@"
}
get_resource_var() {
local varname="$1"
local default_value="$2"
eval value='$'"$varname"
if [[ -z $value ]]
then
echo "# ($0:metascript.sh:$LINENO) $(date)" >>$collect_context
if [[ -z $default_value ]]
then
log_error "resource $varname does not exists and no non empty default provided"
echo "# $varname=<MISSING>" >>$collect_context
exit 1
fi
log_warn "($0) Using default value '$varname'='$default_value' HARDCODED in script : should be fixed with proper default file resources.var, see traces in $collect_context"
read "$varname" <<<"$default_value"
echo "$varname=$default_value" >>$collect_context
fi
}
setup_resources_var()
{
local resource_var="$1"
enforcefile "$resource_var" exists
if [[ -f $resource_var ]]
then
while read line
do
if [[ $line =~ ^([a-zA-Z0-9_]+)=(.+)$ ]]
then
varname=${BASH_REMATCH[1]}
value=${BASH_REMATCH[2]}
read "$varname" <<<"$value"
elif [[ $line =~ ^# ]]
then
if [[ -n $defer ]]
then
$defer : "$line"
fi
else
log_warn "Invalid syntax in $resource_var '$line' does not match any expected expression"
fi
done <"$resource_var"
fi
}
# allow to check mounted points
mountpoint_get_device()
{
local mount_point="$1"
mount_point=$(readlink -f "$mount_point")
$defer awk "{ if (\$2 == \""$mount_point"\") print \$1 ;}" /proc/mounts
}
enforce_device() {
local device=$1
local constraint=$2
if [[ -n $defer ]]
then
$defer "mountpoint=$(device_get_mountpoints $device)"
else
mountpoint=$(device_get_mountpoints $device)
fi
if [[ -n $mountpoint ]] && [[ $constraint == "unmounted" ]]
then
log_error "$device seen as mounted on $mountpoint"
$defer exit 1
elif [[ -z $mountpoint ]] && [[ $constraint == "mounted" ]]
then
log_error "$device not seen as mounted"
$defer exit 1
fi
}
device_get_mountpoints()
{
local device="$1"
$defer awk "{ if (\$1 == \""$device"\") print \$2 ;}" /proc/mounts
}
mount_if_needed()
{
local mountdevice="$1"
local mountpoint="$2"
local param="$3"
if [[ -n $defer ]]
then
$defer 'device=$(mountpoint_get_device '"$mountpoint"')'
else
device=$(mountpoint_get_device "$mountpoint")
fi
enforcedir "$mountpoint" exists
# device is a device ...
# enforcefile "$mountdevice" exists
if [[ -z $device ]]
then
$metarun mount $param "$mountdevice" "$mountpoint"
elif [[ "$device" == "$mountdevice" ]]
then
log_info "$device already mounted on $mountdevice"
else
log_warn "Another device $device is mounted on $mountpoint, not $mountdevice"
fi
}
umount_if_needed()
{
local mountdevice="$1"
local mountpoint="$2"
local param="$3"
device=$(mountpoint_get_device "$mountpoint")
if [[ -n "$device" ]]
then
if [[ "$device" == "$mountdevice" ]]
then
$metarun umount "$mountpoint"
else
log_warn "Another device $device is mounted on $mountpoint, not $mountdevice"
if [[ $param == '--bind' ]]
then
log_warn "Unmouning ANYWAY ( mount --bind show root device, not mounted directory )"
$metarun umount "$mountpoint"
fi
fi
else
log_warn "no device found mounted for umount_if_needed $@"
fi
}
exec_bg()
{
if [[ -z $defer ]]
then
"$@" &
else
$defer "$@" '&'
fi
}
notice()
{
case "$1" in
git_commit)
log_warn "$1 should be $2"
;;
*)
log_fatal "unrecognized notice '$1'"
exit 2
;;
esac
}
enforceuser()
{
local expecteduser="$1"
if [[ "$USER" != "$expecteduser" ]]
then
log_error "expected user $expecteduser is not current user $USER"
exit 1
fi
}
# collect all hardcoded values.
mkdir -p ~/.artlog
collect_context=~/.artlog/collect_context.var
if [[ -z $toolsresourcesdir ]]
then
# project directory
# from current $(pwd) will follow parent dir hierarchy to find resources.var
dir="$(pwd)"
while [[ -n $dir ]] && [[ -d $dir ]] && [[ ! -f $dir/resources.var ]]
do
new_dir=$(dirname "$dir")
if [[ $new_dir == $dir ]]
then
# protect against infinite loop
break
fi
dir="$new_dir"
done
if [[ -f $dir/resources.var ]]
then
toolsresourcesdir=$dir
else
if [[ -z $ENV_METASCRIPT_RESOURCESDIR ]]
then
toolsresourcesdir=$toolsparentdir
else
else
toolsresourcesdir=$ENV_METASCRIPT_RESOURCESDIR
fi
fi
fi
# quick way to give scl patches to fill scl_arg array
if [[ -f $toolsparentdir/.scl_env ]]
resources_var=$toolsresourcesdir/resources.var
if [[ -f $resources_var ]]
then
source $toolsparentdir/.scl_env
setup_resources_var "$resources_var"
else
log_warn "No $resources_var found"
fi
# quick way to give scl patches to fill scl_arg array
if [[ -f $toolsresourcesdir/.scl_env ]]
then
source $toolsresourcesdir/.scl_env
fi
# empty defer means doit
@@ -588,6 +971,11 @@ defer=$ENV_METASCRIPT_DEFER
showinfo=showinfo
allparms=$@
allparms="$@"
applymetaargs=applymetaargs
metarun=$defer
# metascript included
fi

4
nextcloud_devenv/logs.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
nextcloud_storage=/var/wwww/http/data
sudo -u www-data tail -f $nextcloud_storage/nextcloud.log

View File

@@ -15,10 +15,16 @@ log_info "See https://docs.nextcloud.com/server/latest/admin_manual/configuratio
source ./config_db_query.sh
# convert localhost of host into container virtual brdige hostname mapping
# convert localhost of host into container virtual bridge hostname mapping
#db_hostname=host.docker.internal
#db_hostname=host.containers.internal
# host loopback mapped address
db_hostname=10.1.1.13
# dbtype_nextcloud inherited from config_db_query
$defer ./run_occ.sh db:convert-type --password="$password" --port="$db_port" --all-apps pgsql "$username" "$db_hostname" "$database_name"
$defer ./run_occ.sh db:convert-type --password="$password" --port="$db_port" --all-apps "$dbtype_nextcloud" "$username" "$db_hostname" "$database_name"
if [[ $dbtype_nextcloud == mysql ]]
then
$defer ./run_occ.sh config:system:set mysql.utf8mb4 --type boolean --value="true"
fi

View File

@@ -0,0 +1,72 @@
COMPOSE_PROJECT_NAME=master
# Default protocol to use for Nextcloud and other containers
# check the readme for details how to setup https
PROTOCOL=https
# Paths
REPO_PATH_SERVER=/home/plhardy/clients/astrolabe/nextcloud/poll/jugement_majoritaire/nextcloud_devenv/server
# Specify a path to apps which will be shared between all containers. Useful for apps that support multiple nextcloud versions
# ADDITIONAL_APPS_PATH=/home/alice/nextcloud-docker-dev/workspace/server/apps-shared
# Stable releases root directory
# STABLE_ROOT_PATH=/home/alice/nextcloud-docker-dev/workspace
# Install Nextcloud apps per default
# NEXTCLOUD_AUTOINSTALL_APPS="viewer activity"
# Retry enabling apps for a provided amount of time (can be useful when using the containers in CI)
# NEXTCLOUD_AUTOINSTALL_APPS_WAIT_TIME=0
# Blackfire configuration
# BLACKFIRE_CLIENT_ID=
# BLACKFIRE_CLIENT_TOKEN=
# BLACKFIRE_SERVER_ID=
# BLACKFIRE_SERVER_TOKEN=
# By default the published ports are only accessible at 127.0.0.1 (your localhost).
# Set this to '0.0.0.0' to make them accessible from your whole local network.
# IP_BIND=127.0.0.1
# can be used to run separate setups besides each other
# DOCKER_SUBNET=192.168.15.0/24
# PORTBASE=815
# Main dns names for ssl proxy
# This can be used to append a custom domain name to the container names
DOMAIN_SUFFIX=-dev.l0g.eu
# May be used to set the PHP version. Defaults to 7.2.
# PHP_VERSION=71
# PHP_VERSION=72
# PHP_VERSION=73
# PHP_VERSION=74
# PHP_VERSION=80
PHP_VERSION=81
# May be used to choose database (sqlite, pgsql, mysql)
SQL=mysql
# The mode of the xdebuger extention. This can be a comma separated list of
# the entries none, develop, debug, trace, and profile.
PHP_XDEBUG_MODE=develop
# Docker socket location, use it when you run rootless Docker
# Replace "1000" with the uid used by your Docker engine (default $(id -u))
# DOCKER_SOCKET=/run/user/1000/docker.sock
DOCKER_SOCKET=/var/run/podman/podman.sock
# Nextcloud AppAPI Docker Socket Proxy
# ------------------------------------
# NC_HAPROXY_PASSWORD=some_secure_password
# BIND_ADDRESS=172.17.0.1
# CERT_PATH=./data/ssl/app_api/app_api.pem
# NETWORK_MODE=host
# HAPROXY_PORT=2375
# TIMEOUT_CONNECT=10s
# TIMEOUT_CLIENT=30s
# TIMEOUT_SERVER=30s
# EX_APPS_NET=ipv4@localhost
# EX_APPS_COUNT=50
# ------------------------------------

View File

@@ -0,0 +1,6 @@
#!/bin/bash
./get_dbcontent.sh
./run_occ.sh polls:db:purge
./run_occ.sh app:enable polls
./run_occ.sh polls:db:rebuild

View File

@@ -0,0 +1,7 @@
See README.md Option 2
./runit.sh dryrun up
./runit.sh dryrun down
Please remove dryrun to actualy do it.

54
nextcloud_devenv/runit.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2025 artlog@l0g.eu
# SPDX-License-Identifier: AGPL-3.0-or-later
#
toolsdir=lib
pushd $toolsdir >/dev/null
toolsdir=$(pwd)
source metascript.sh
popd >/dev/null
usage() {
cat runit.README.md
}
while [[ $# > 0 ]]
do
case "$1" in
up|down)
action=$1
;;
*)
parsemetaarg "$1"
;;
esac
shift
done
if [[ -z $action ]]
then
log_warn "Missing arguments"
usage
exit 0
fi
metarun=metarun
metasudo=sudo
enforcedir nextcloud-docker-dev exists
pushd nextcloud-docker-dev
if [[ $action = down ]]
then
$metarun podman-compose down
fi
services=(nextcloud proxy database-mysql)
if [[ $action == up ]]
then
$metarun podman-compose --env-file $(pwd)/.env $action "${services[@]}"
fi
popd

View File

@@ -20,4 +20,14 @@ GRANT ALL PRIVILEGES ON SCHEMA public TO $username;
EOF
} | pipeto sudo -u postgres psql -f -
{
cat <<EOF
CREATE USER '$username'@'localhost' IDENTIFIED BY '$password';
CREATE DATABASE IF NOT EXISTS ${database_name} CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
GRANT ALL PRIVILEGES on ${database_name}.* to '$username'@'localhost';
EOF
} | pipeto sudo mariadb -uroot -p
log_info "created database ${database_name} with user $username"

1
samples/0.json Normal file
View File

@@ -0,0 +1 @@
{"candidates": {"A": "A", "B": "B", "C": "C"}, "voters": {"count": 4}, "mentions": ["Tr\u00e8s Bien", "Bien", "Assez Bien", "Passable", "Insuffisant", "A Rejeter"], "votation": {"votes": [{"A": 0, "B": 1, "C": 2}, {"A": 0, "B": 1, "C": 2}, {"A": 0, "B": 1, "C": 2}, {"A": 0, "B": 1, "C": 2}]}}

View File

@@ -1,5 +1,5 @@
{
"candidats":{
"candidates":{
"A":"albert",
"B":"Beatrice",
"C":"Chloé",
@@ -9,8 +9,8 @@
"G":"Gisèle",
"H":"Hugo"
},
"votants":{
"decompte":10
"voters":{
"count":10
},
"mentions":[
"Très Bien",

View File

@@ -1,5 +1,5 @@
{
"candidats":{
"candidates":{
"A":"albert",
"B":"Beatrice",
"C":"Chloé",
@@ -9,8 +9,8 @@
"G":"Gisèle",
"H":"Hugo"
},
"votants":{
"decompte":10
"voters":{
"count":10
},
"mentions":[
"Très Bien",

View File

@@ -1,5 +1,5 @@
{
"candidats":{
"candidates":{
"A":"albert",
"B":"Beatrice",
"C":"Chloé",
@@ -9,8 +9,8 @@
"G":"Gisèle",
"H":"Hugo"
},
"votants":{
"decompte":11
"voters":{
"count":11
},
"mentions":[
"Très Bien",

View File

@@ -1,5 +1,5 @@
{
"candidats":{
"candidates":{
"A":"albert",
"B":"Beatrice",
"C":"Chloé",
@@ -9,8 +9,8 @@
"G":"Gisèle",
"H":"Hugo"
},
"votants":{
"decompte":11
"voters":{
"count":11
},
"mentions":[
"Très Bien",

View File

@@ -1,5 +1,5 @@
{
"candidats":{
"candidates":{
"A":"albert",
"B":"Beatrice",
"C":"Chloé",
@@ -9,8 +9,8 @@
"G":"Gisèle",
"H":"Hugo"
},
"votants":{
"decompte":11
"voters":{
"count":11
},
"mentions":[
"Très Bien",

View File

@@ -1,25 +1,53 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2025 artlog@l0g.eu
# SPDX-License-Identifier: AGPL-3.0-or-later
#
if [[ $# > 0 ]]
then
toolsdir=../lib
pushd $toolsdir >/dev/null
toolsdir=$(pwd)
source metascript.sh
popd >/dev/null
while [[ $# > 0 ]]
do
case "$1" in
*.json)
json_file=$1
;;
*)
if [[ $1 =~ [0-9]+ ]]
then
vote=$1
fi
else
parsemetaarg "$1"
fi
;;
esac
shift
done
if [[ -z $vote ]]
if [[ -z $vote ]] && [[ -z $json_file ]]
then
echo "[ERROR] numéro de vote manquant ( ex 1 pour 1.json )"
log_error "numéro de vote manquant ( ex 1 pour 1.json )"
exit 1
fi
json_file=$vote.json
json=~/clients/artlog/artisanlogiciel.code/artlog_jsontools/build/json
if [[ -z $json_file ]]
then
json_file=$vote.json
fi
enforcefile $json_file exists
json=json
if [[ -x $json ]]
then
json_indent_file=$vote.indent.json
echo "generating $json_indent_file"
$json indent=spaces:2 -- $json_file > $json_indent_file
json=$json_file
json_file=$json_indent_file
enforcefile $json_file exists
fi
code_dir=../code/

16
samples/expected/6.stderr Normal file
View File

@@ -0,0 +1,16 @@
[WARN] No /.resources.var found
Traceback (most recent call last):
File "/home/plhardy/clients/astrolabe/nextcloud/poll/jugement_majoritaire/samples/../code/parse_sample.py", line 19, in <module>
result = majority_judgment_run(poll)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/plhardy/clients/astrolabe/nextcloud/poll/jugement_majoritaire/code/majority_judgment_method/__init__.py", line 39, in majority_judgment_run
voters=poll['voters']['count']
~~~~^^^^^^^^^^
KeyError: 'voters'
PHP Warning: Undefined array key "voters" in /home/plhardy/clients/astrolabe/nextcloud/poll/jugement_majoritaire/code/parse_sample.php on line 24
PHP Warning: Trying to access array offset on null in /home/plhardy/clients/astrolabe/nextcloud/poll/jugement_majoritaire/code/parse_sample.php on line 24
PHP Warning: Undefined array key "candidates" in /home/plhardy/clients/astrolabe/nextcloud/poll/jugement_majoritaire/code/parse_sample.php on line 25
PHP Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given in /home/plhardy/clients/astrolabe/nextcloud/poll/jugement_majoritaire/code/parse_sample.php:26
Stack trace:
#0 {main}
thrown in /home/plhardy/clients/astrolabe/nextcloud/poll/jugement_majoritaire/code/parse_sample.php on line 26

View File

@@ -0,0 +1,58 @@
{
"ref":"zdslqbbzvk",
"name":"Quel sont les mérites respectifs des participants",
"description":"{\"description\":\"\",\"randomOrder\":true}",
"candidates":[
{
"name":"A",
"description":"",
"image":"",
"id":61445
},
{
"name":"B",
"description":"",
"image":"",
"id":61446
},
{
"name":"C",
"description":"",
"image":"",
"id":61447
}
],
"grades":[
{
"name":"Excellent",
"value":4,
"id":47253
},
{
"name":"Très bien",
"value":3,
"id":47254
},
{
"name":"Bien",
"value":2,
"id":47255
},
{
"name":"Passable",
"value":1,
"id":47256
},
{
"name":"Insuffisant",
"value":0,
"id":47257
}
],
"num_voters":0,
"hide_results":false,
"force_close":true,
"date_end":"2025-10-08T08:00:00Z",
"auth_for_result":false,
"restricted":false
}

View File

@@ -0,0 +1 @@
{"ref":"zdslqbbzvk","name":"Quel sont les mérites respectifs des participants","description":"{\"description\":\"\",\"randomOrder\":true}","candidates":[{"name":"A","description":"","image":"","id":61445},{"name":"B","description":"","image":"","id":61446},{"name":"C","description":"","image":"","id":61447}],"grades":[{"name":"Excellent","value":4,"id":47253},{"name":"Très bien","value":3,"id":47254},{"name":"Bien","value":2,"id":47255},{"name":"Passable","value":1,"id":47256},{"name":"Insuffisant","value":0,"id":47257}],"num_voters":0,"hide_results":false,"force_close":true,"date_end":"2025-10-08T08:00:00Z","auth_for_result":false,"restricted":false}

View File

@@ -0,0 +1,72 @@
{
"name":"Quel sont les mérites respectifs des participants",
"description":"{\"description\":\"\",\"randomOrder\":true}",
"ref":"zdslqbbzvk",
"date_start":"2025-10-07T09:41:24.294727Z",
"date_end":"2025-10-08T08:00:00Z",
"hide_results":false,
"restricted":false,
"auth_for_result":false,
"force_close":true,
"grades":[
{
"name":"Excellent",
"value":4,
"description":"",
"election_ref":"zdslqbbzvk",
"id":47253
},
{
"name":"Très bien",
"value":3,
"description":"",
"election_ref":"zdslqbbzvk",
"id":47254
},
{
"name":"Bien",
"value":2,
"description":"",
"election_ref":"zdslqbbzvk",
"id":47255
},
{
"name":"Passable",
"value":1,
"description":"",
"election_ref":"zdslqbbzvk",
"id":47256
},
{
"name":"Insuffisant",
"value":0,
"description":"",
"election_ref":"zdslqbbzvk",
"id":47257
}
],
"candidates":[
{
"name":"A",
"description":"",
"image":"",
"election_ref":"zdslqbbzvk",
"id":61445
},
{
"name":"B",
"description":"",
"image":"",
"election_ref":"zdslqbbzvk",
"id":61446
},
{
"name":"C",
"description":"",
"image":"",
"election_ref":"zdslqbbzvk",
"id":61447
}
],
"invites":[]
}

View File

@@ -0,0 +1,2 @@
{"name":"Quel sont les mérites respectifs des participants","description":"{\"description\":\"\",\"randomOrder\":true}","ref":"zdslqbbzvk","date_start":"2025-10-07T09:41:24.294727Z","date_end":"2025-10-08T08:00:00Z","hide_results":false,"restricted":false,"auth_for_result":false,"force_close":true,"grades":[{"name":"Excellent","value":4,"description":"","election_ref":"zdslqbbzvk","id":47253},{"name":"Très bien","value":3,"description":"","election_ref":"zdslqbbzvk","id":47254},{"name":"Bien","value":2,"description":"","election_ref":"zdslqbbzvk","id":47255},{"name":"Passable","value":1,"description":"","election_ref":"zdslqbbzvk","id":47256},{"name":"Insuffisant","value":0,"description":"","election_ref":"zdslqbbzvk","id":47257}],"candidates":[{"name":"A","description":"","image":"","election_ref":"zdslqbbzvk","id":61445},{"name":"B","description":"","image":"","election_ref":"zdslqbbzvk","id":61446},{"name":"C","description":"","image":"","election_ref":"zdslqbbzvk","id":61447}],"invites":[]}

View File

@@ -0,0 +1,4 @@
"name","Excellent","Très bien","Bien","Passable","Insuffisant"
"A","0","0","3","0","0"
"B","0","1","1","1","0"
"C","1","0","2","0","0"
1 name Excellent Très bien Bien Passable Insuffisant
2 A 0 0 3 0 0
3 B 0 1 1 1 0
4 C 1 0 2 0 0

View File

@@ -1,2 +1,5 @@
Participants,Adresse e-mail,A,B,C
admin,,Très Bien,Bien,Assez Bien
user1,,Très Bien,Bien,Assez Bien
user2,,Très Bien,Bien,Assez Bien
user3,,Très Bien,Bien,Assez Bien
1 Participants Adresse e-mail A B C
2 admin Très Bien Bien Assez Bien
3 user1 Très Bien Bien Assez Bien
4 user2 Très Bien Bien Assez Bien
5 user3 Très Bien Bien Assez Bien

1
samples/poll1.csv.json Normal file
View File

@@ -0,0 +1 @@
{"candidats": {"A": "A", "B": "B", "C": "C"}, "votants": {"decompte": 1}, "mentions": ["Tr\u00e8s Bien", "Bien", "Assez Bien", "Passable", "Insuffisant", "A Rejeter"], "votation": {"votes": [{"A": 0, "B": 1, "C": 2}]}}

View File

@@ -2,7 +2,20 @@
[[ -n $VIRTUAL_ENV ]] || source ../code/bin/activate
for (( i=0;i<7;i++ ))
expect_error()
{
sample=$1
# error case on purpose
resultdir=tmp
[[ -d ${resultdir} ]] || mkdir ${resultdir}
./check.sh $sample 2>${resultdir}/$sample.stderr
diff ${resultdir}/$sample.stderr expected/$sample.stderr
}
for (( i=0;i<6;i++ ))
do
./check.sh $i
done
expect_error 6