Compare commits
10 Commits
2ff413efd3
...
6095bd26ce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6095bd26ce | ||
|
|
34154c7c4d | ||
|
|
23d5368f40 | ||
| 8d011bd818 | |||
| c718ddfd7c | |||
| 6a2ccb5d2b | |||
| ad09c3ec72 | |||
| 8d65ab03cf | |||
| 9159d2a082 | |||
| faedb4c80c |
11
LISEZMOI.md
11
LISEZMOI.md
@@ -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
|
||||
10
README.md
10
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -1,4 +1,37 @@
|
||||
#!/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
|
||||
|
||||
while [[ $# > 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
*)
|
||||
if [[ -z $csv_file ]]
|
||||
then
|
||||
csv_file="$1"
|
||||
python3 convert_nextcloud_poll_csv.py "$csv_file" 'Très Bien' 'Bien' 'Assez Bien' 'Passable' 'Insuffisant' 'A Rejeter'
|
||||
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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
124
code/majority_judgment_method/__init__.py
Normal file
124
code/majority_judgment_method/__init__.py
Normal 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
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,8 +1,16 @@
|
||||
# Nextcloud Dev En
|
||||
|
||||
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
|
||||
@@ -63,3 +71,50 @@ utiliser la base de donnee postgres et non le SQLLite ?
|
||||
|
||||
./migrate_db_sqlite2postgres.sh
|
||||
|
||||
|
||||
# NEW PLAIN nextcloud 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é !
|
||||
@@ -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
|
||||
|
||||
@@ -1,30 +1,72 @@
|
||||
#!/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
|
||||
|
||||
# if called directly
|
||||
if [[ "$0" =~ ^(.*)/metascript.sh$ ]]
|
||||
then
|
||||
prefix=${BASH_REMATCH[1]}
|
||||
if [[ $1 == header ]]
|
||||
then
|
||||
cat <<EOF
|
||||
#!/bin/bash
|
||||
# SPDX-FileCopyrightText: 2025 artlog@l0g.eu
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# sample to include this script.
|
||||
toolsdir=$prefix
|
||||
pushd \$toolsdir >/dev/null
|
||||
toolsdir=\$(pwd)
|
||||
source metascript.sh
|
||||
popd >/dev/null
|
||||
|
||||
while [[ \$# > 0 ]]
|
||||
do
|
||||
case "\$1" in
|
||||
*)
|
||||
parsemetaarg "\$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
else
|
||||
echo "[WARNING] only header argument is supported. (prefix=$prefix)" >&2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$metascript_included" == "yes" ]]
|
||||
then
|
||||
log_warn "metascript already included ($0)"
|
||||
else
|
||||
|
||||
metascript_included=yes
|
||||
|
||||
|
||||
# at this step $(pwd) should be this of metascript.sh
|
||||
# while $0 is this of caller script using metascript
|
||||
|
||||
if [[ -z $toolsdir ]]
|
||||
then
|
||||
# assume all tools are in lib/
|
||||
toolsdir=$(dirname $(readlink -f $0))/lib
|
||||
# all tools resources are relative to this directory
|
||||
|
||||
# project directory
|
||||
# relative
|
||||
# toolsparentdir=$(realpath --relative-to "$(pwd)" $(readlink -f $0))/
|
||||
# absolute
|
||||
toolsparentdir=$(realpath $(readlink -f $0))/
|
||||
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
|
||||
|
||||
@@ -36,7 +78,7 @@ metascript commands :
|
||||
help|usage help or usage of this {$0} tool
|
||||
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
|
||||
@@ -54,7 +96,12 @@ usage()
|
||||
|
||||
showinfo()
|
||||
{
|
||||
echo $@
|
||||
echo "$@"
|
||||
}
|
||||
|
||||
metarun()
|
||||
{
|
||||
$defer $metasudo "$@"
|
||||
}
|
||||
|
||||
showdoc()
|
||||
@@ -62,10 +109,10 @@ showdoc()
|
||||
if [[ $1 =~ ^tools/ ]]
|
||||
then
|
||||
# assumes it handles ENV_METASCRIPT_DEFER
|
||||
$@
|
||||
"$@"
|
||||
else
|
||||
echo '```'
|
||||
autoquoteargs $@
|
||||
autoquoteargs "$@"
|
||||
echo
|
||||
echo '```'
|
||||
fi
|
||||
@@ -92,11 +139,11 @@ execredirectfrom()
|
||||
if [[ -n $defer ]]
|
||||
then
|
||||
echo '```'
|
||||
autoquoteargs $@
|
||||
autoquoteargs "$@"
|
||||
echo ' < '"$tofile"
|
||||
echo '```'
|
||||
else
|
||||
$@ < $tofile
|
||||
"$@" < $tofile
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -107,7 +154,7 @@ execredirectto()
|
||||
if [[ -n $defer ]]
|
||||
then
|
||||
echo '```'
|
||||
autoquoteargs $@
|
||||
autoquoteargs "$@"
|
||||
echo ' > '"$tofile"
|
||||
echo '```'
|
||||
else
|
||||
@@ -115,19 +162,34 @@ execredirectto()
|
||||
fi
|
||||
}
|
||||
|
||||
execredirecttoroot()
|
||||
{
|
||||
tofile=$1
|
||||
shift
|
||||
if [[ -n $defer ]]
|
||||
then
|
||||
echo '```'
|
||||
autoquoteargs "$@"
|
||||
echo ' | sudo tee '"$tofile"
|
||||
echo '```'
|
||||
else
|
||||
"$@" | sudo tee $tofile
|
||||
fi
|
||||
}
|
||||
|
||||
pipeto()
|
||||
{
|
||||
if [[ -n $defer ]]
|
||||
then
|
||||
echo '```'
|
||||
echo -n 'cat << EOF| '
|
||||
autoquoteargs $@
|
||||
autoquoteargs "$@"
|
||||
echo
|
||||
cat
|
||||
echo "EOF"
|
||||
echo '```'
|
||||
else
|
||||
cat | $@
|
||||
cat | "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -355,6 +417,9 @@ parsemetaarg()
|
||||
dryrun|show|showdoc)
|
||||
defer=showdoc
|
||||
;;
|
||||
metasudo=*)
|
||||
metasudo=${1/metasudo=}
|
||||
;;
|
||||
help|usage)
|
||||
usage
|
||||
;;
|
||||
@@ -528,7 +593,7 @@ check_variable_in()
|
||||
shift 2
|
||||
|
||||
local value=""
|
||||
local values=$@
|
||||
local values="$@"
|
||||
|
||||
eval value='$'"$var"
|
||||
|
||||
@@ -570,17 +635,148 @@ get_timestamp_second()
|
||||
echo "$(date +"%Y%m%d%H%M%S")"
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
echo "$line"
|
||||
else
|
||||
log_warn "Invalid syntax in $resource_var '$line' does not match any expected expression"
|
||||
fi
|
||||
done <"$resource_var"
|
||||
}
|
||||
|
||||
# allow to check mounted points
|
||||
mountpoint_get_device()
|
||||
{
|
||||
local mount_point="$1"
|
||||
$defer awk "{ if (\$2 == \""$mount_point"\") print \$1 ;}" /proc/mounts
|
||||
}
|
||||
|
||||
device_get_mountpoints()
|
||||
{
|
||||
local device="$1"
|
||||
$defer awk "{ if (\$1 == \""$device"\") print \$2 ;}" /proc/mounts
|
||||
}
|
||||
|
||||
mount_if_needed()
|
||||
{
|
||||
local mountpoint="$1"
|
||||
local mountdevice="$2"
|
||||
local param="$3"
|
||||
device=$(mountpoint_get_device "$mountpoint")
|
||||
|
||||
enforcedir "$mountpoint" exists
|
||||
# device is a device ...
|
||||
# enforcefile "$mountdevice" exists
|
||||
|
||||
if [[ -z $device ]]
|
||||
then
|
||||
$metarun mount $param "$mountdevice" "$device"
|
||||
elif [[ "$device" == "$mountdevice" ]]
|
||||
then
|
||||
log_info "$device already mounted on $mountdevice"
|
||||
else
|
||||
log_warn "Another device $device is mounted on $mountdevice, not $mountdevice"
|
||||
fi
|
||||
}
|
||||
|
||||
umount_if_needed()
|
||||
{
|
||||
local mountpoint="$1"
|
||||
local mountdevice="$2"
|
||||
device=$(mountpoint_get_device "$mountpoint")
|
||||
if [[ -n "$device" ]]
|
||||
then
|
||||
if [[ "$device" == "$mountpoint" ]]
|
||||
then
|
||||
$metarun umount "$mountpoint"
|
||||
else
|
||||
log_warn "Another device $device is mounted on $mountdevice, not $mountdevice"
|
||||
fi
|
||||
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
|
||||
toolsresourcesdir=$ENV_METASCRIPT_RESOURCESDIR
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
resources_var=$toolsresourcesdir/.resources.var
|
||||
if [[ -f $resources_var ]]
|
||||
then
|
||||
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 $toolsparentdir/.scl_env ]]
|
||||
if [[ -f $toolsresourcesdir/.scl_env ]]
|
||||
then
|
||||
source $toolsparentdir/.scl_env
|
||||
source $toolsresourcesdir/.scl_env
|
||||
fi
|
||||
|
||||
# empty defer means doit
|
||||
@@ -588,6 +784,11 @@ defer=$ENV_METASCRIPT_DEFER
|
||||
|
||||
showinfo=showinfo
|
||||
|
||||
allparms=$@
|
||||
allparms="$@"
|
||||
|
||||
applymetaargs=applymetaargs
|
||||
|
||||
metarun=$defer
|
||||
|
||||
# metascript included
|
||||
fi
|
||||
|
||||
4
nextcloud_devenv/logs.sh
Normal file
4
nextcloud_devenv/logs.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
nextcloud_storage=/var/wwww/http/data
|
||||
sudo -u www-data tail -f $nextcloud_storage/nextcloud.log
|
||||
@@ -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
|
||||
|
||||
72
nextcloud_devenv/nextcloud-docker-dev.env
Normal file
72
nextcloud_devenv/nextcloud-docker-dev.env
Normal 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
|
||||
# ------------------------------------
|
||||
6
nextcloud_devenv/reset_polls.sh
Executable file
6
nextcloud_devenv/reset_polls.sh
Executable 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
|
||||
43
nextcloud_devenv/runit.sh
Executable file
43
nextcloud_devenv/runit.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/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
|
||||
|
||||
while [[ $# > 0 ]]
|
||||
do
|
||||
case "$1" in
|
||||
up|down)
|
||||
action=$1
|
||||
;;
|
||||
*)
|
||||
parsemetaarg "$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
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
|
||||
@@ -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
1
samples/0.json
Normal 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}]}}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,25 +1,53 @@
|
||||
#!/bin/bash
|
||||
# SPDX-FileCopyrightText: 2025 artlog@l0g.eu
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
|
||||
if [[ $# > 0 ]]
|
||||
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
|
||||
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
|
||||
|
||||
if [[ -z $json_file ]]
|
||||
then
|
||||
json_file=$vote.json
|
||||
json=~/clients/artlog/artisanlogiciel.code/artlog_jsontools/build/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
16
samples/expected/6.stderr
Normal 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
|
||||
58
samples/mieuxvoter/cloture.indent.json
Normal file
58
samples/mieuxvoter/cloture.indent.json
Normal 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
|
||||
}
|
||||
1
samples/mieuxvoter/cloture.json
Normal file
1
samples/mieuxvoter/cloture.json
Normal 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}
|
||||
72
samples/mieuxvoter/cloture.response.indent.json
Normal file
72
samples/mieuxvoter/cloture.response.indent.json
Normal 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":[]
|
||||
}
|
||||
2
samples/mieuxvoter/cloture.response.json
Normal file
2
samples/mieuxvoter/cloture.response.json
Normal 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":[]}
|
||||
|
||||
4
samples/mieuxvoter/results-zds-lqb-bzvk.csv
Normal file
4
samples/mieuxvoter/results-zds-lqb-bzvk.csv
Normal 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,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
samples/poll1.csv.json
Normal file
1
samples/poll1.csv.json
Normal 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}]}}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user