code:
#!/bin/sh

#########################
# Name: src_rebrand.sh
# Main: jadedctrl
# Lisc: ISC
# Desc: Rebranding OBSD base
#       sources for use in
#       LBSD.
#########################

# Usage: src_rebrand.sh $SRC_DIR

. ./libdeblob.sh

PATCH_DIR=/tmp/src_rebrand

if [ -e $PATCH_DIR ]
then
        self_destruct_sequence $PATCH_DIR
else
        mkdir $PATCH_DIR
fi

if test -z $1
then
        SRC_DIR=/usr/src
else
        SRC_DIR=$1
fi

arch_list="amd64 i386"

# Rebranding amd64 images

rep "export OBSD=\"OpenBSD/\$ARCH \$VNAME\"" "export OBSD=\"LibertyBSD/\$ARCH \$VNAME\"" distrib/miniroot/dot.profile

#iso_list="alpha amd64 hppa i386 sgi sparc sparc64 vax"
iso_list="amd64 i386"
for arch in $(echo $iso_list)
do
	rep "OpenBSD \${OSREV} ${arch} Install CD" "LibertyBSD \${OSREV} ${arch} Install CD" distrib/$arch/iso/Makefile
	rep "Copyright (c) `date +%Y` Theo de Raadt, The OpenBSD project" "Copyright (c) `date +%Y` The *OpenBSD* and LibertyBSD projects" distrib/$arch/iso/Makefile
	rep "Theo de Raadt <deraadt@openbsd.org>" "Riley Baird <riley@openmailbox.org>" distrib/$arch/iso/Makefile
	rep "OpenBSD/\${MACHINE}   \${OSREV} Install CD" "LibertyBSD/\${MACHINE} \${OSREV} Install CD" distrib/$arch/iso/Makefile
done

#cdfs_list="alpha amd64 i386 loongson sgi sparc sparc64 vax"
cdfs_list="amd64 i386"
for arch in $(echo $cdfs_list)
do
	rep "OpenBSD \${OSREV} ${arch} bootonly CD" "LibertyBSD \${OSREV} ${arch} bootonly CD" distrib/$arch/cdfs/Makefile
	rep "Copyright (c) `date +%Y` Theo de Raadt, The OpenBSD project" "Copyright (c) `date +%Y` The *OpenBSD* and LibertyBSD projects" distrib/$arch/cdfs/Makefile
	rep "Theo de Raadt <deraadt@openbsd.org>" "Riley Baird <riley@openmailbox.org>" distrib/$arch/cdfs/Makefile
	rep "OpenBSD/${arch}   \${OSREV} boot-only CD" "LibertyBSD/${arch} \${OSREV} boot CD" distrib/$arch/cdfs/Makefile
done

# Distrib changes for all archs
for arch in $(echo $arch_list)
do
	rep "${arch}-openbsd" "${arch}-libertybsd" distrib/sets/lists/base/md.$arch
	lineadd "./usr/bin/uname" "./usr/bin/uname-obsd" distrib/sets/lists/base/md.$arch
	rep "You will not be able to boot OpenBSD from \${1}." "You will not be able to boot LibertyBSD from \${1}." distrib/$arch/common/install.md
done

dir_list="lib/libiberty lib/libobjc lib/libstdc++ share usr.bin/binutils usr.bin/binutils-2.17 usr.bin/gcc usr.bin/texinfo ../usr.sbin/bind ../usr.sbin/unbound"
for dir in $dir_list
do
	rep "UNAME_SYSTEM=\`(uname -s) 2>/dev/null\`" "UNAME_SYSTEM=\`(echo OpenBSD) 2>/dev/null\`" gnu/${dir}/config.guess
done

lineadd "openbsd) osname=openbsd" "$(space 22) osvers=\"$3\"\n$(space 22) ;;\n$(space 14) libertybsd) osname=libertybsd\n$(space 22) osvers=\"$3\"\n$(space 22) ;;" gnu/usr.bin/perl/Configure
#rep "openbsd) osname=openbsd" "libertybsd) osname=libertybsd" gnu/usr.bin/perl/Configure
rep "interix|dragonfly|bitrig" "libertybsd|interix|dragonfly|bitrig" gnu/usr.bin/perl/Configure
rep "dragonfly\*|bitrig*" "libertybsd\*|dragonfly\*|bitrig\*" gnu/usr.bin/perl/Makefile.SH
rep "-openbsd" "-libertybsd" gnu/usr.bin/perl/Makefile.bsd-wrapper
filecp gnu/usr.bin/perl/hints/openbsd.sh gnu/usr.bin/perl/hints/libertybsd.sh

rep "#define DMESG_START \"OpenBSD \"" "#define DMESG_START \"LibertyBSD \"" usr.bin/sendbug/sendbug.c
rep "bugs@openbsd.org" "bugs@libertybsd.net" usr.bin/sendbug/sendbug.c

filecp files/motd etc/motd
filecp files/root.mail etc/root/root.mail
filecp files/install.sub distrib/miniroot/install.sub

apply


code:
#!/bin/sh

#########################
# Name: sys_deblob.sh
# Main: jadedctrl
# Lisc: ISC
# Desc: Delobbing OBSD kernel
#       sources for use in
#       LBSD.
#########################

# Usage: sys_deblob.sh $SRC_DIR

. ./libdeblob.sh

PATCH_DIR=/tmp/sys_deblob

if [ -e $PATCH_DIR ]
then
        self_destruct_sequence $PATCH_DIR
	mkdir $PATCH_DIR
else
        mkdir $PATCH_DIR
fi

if test -z $1
then
        SRC_DIR=/usr/src/sys
else
        SRC_DIR=$1
fi

#arch_list="alpha amd64 armish armv7 hppa i386 landisk loongson luna88k macppc miniroot octeon sgi socppc sparc sparc64 vax zaurus"
arch_list="amd64 i386"

for arch in $arch_list
do
	linedel "kue\*" arch/${arch}/conf/GENERIC
	linedel "rum\*" arch/${arch}/conf/GENERIC
	linedel "zyd\*" arch/${arch}/conf/GENERIC
	linedel "uvideo\?" arch/${arch}/conf/GENERIC
	linedel "udl\*" arch/${arch}/conf/GENERIC
	linedel "udl?" arch/${arch}/conf/GENERIC
	linedel "ips\*" arch/${arch}/conf/GENERIC
	linedel "siop\*" arch/${arch}/conf/GENERIC
	linedel "adw" arch/${arch}/conf/GENERIC
	linedel "fxp" arch/${arch}/conf/GENERIC
	linedel "myx" arch/${arch}/conf/GENERIC
	linedel "bnx" arch/${arch}/conf/GENERIC
	linedel "thtc" arch/${arch}/conf/GENERIC
	linedel "ral\*" arch/${arch}/conf/GENERIC
	linedel "yds" arch/${arch}/conf/GENERIC

	linedel "fxp\*" arch/${arch}/conf/RAMDISK

	linedel "kue\*" arch/${arch}/conf/RAMDISK_CD
	linedel "rum\*" arch/${arch}/conf/RAMDISK_CD
	linedel "zyd\*" arch/${arch}/conf/RAMDISK_CD
	linedel "ips\*" arch/${arch}/conf/RAMDISK_CD
	linedel "siop\*" arch/${arch}/conf/RAMDISK_CD
	linedel "adw\*" arch/${arch}/conf/RAMDISK_CD
	linedel "fxp\*" arch/${arch}/conf/RAMDISK_CD
	linedel "bnx\*" arch/${arch}/conf/RAMDISK_CD
	linedel "ral\*" arch/${arch}/conf/RAMDISK_CD
done

filedel "dev/microcode/adw"
filedel dev/microcode/afb
filedel dev/microcode/atmel
filedel dev/microcode/bnx
filedel dev/microcode/bwi
filedel dev/microcode/cirruslogic
filedel dev/microcode/cyclades
filedel dev/microcode/esa
filedel dev/microcode/fxp
filedel dev/microcode/ises
filedel dev/microcode/kue
filedel dev/microcode/myx
filedel dev/microcode/neomagic
filedel dev/microcode/ral
filedel dev/microcode/rum
filedel dev/microcode/siop
filedel dev/microcode/symbol
filedel dev/microcode/tht
filedel dev/microcode/tigon
filedel dev/microcode/tusb3410
filedel dev/microcode/typhoon
filedel dev/microcode/udl
filedel dev/microcode/yds
filedel dev/microcode/zydas

linedel "SUBDIR=" dev/microcode/Makefile
linedel "symbol tigon tht" dev/microcode/Makefile

filedel dev/pci/adv_pci.c
filedel dev/pci/adw_pci.c
filedel dev/pci/esa.c
filedel dev/pci/esareg.h
filedel dev/pci/esavar.h

linedel "# AdvanSys 1200A, 1200B, and ULTRA SCSI controllers" dev/pci/files.pci
linedel "# device declaration in sys/conf/files" dev/pci/files.pci
linedel "adv_pci" dev/pci/files.pci
linedel "adw_pci" dev/pci/files.pci
linedel "# AdvanSys ULTRA WIDE SCSI controllers" dev/pci/files.pci
linedel "# device declaration in sys/conf/files" dev/pci/files.pci
linedel "# ESS Maestro3" dev/pci/files.pci
linedel "esa" dev/pci/files.pci
linedel "# Yamaha YMF Audio" dev/pci/files.pci
linedel "yds" dev/pci/files.pci
linedel "# NeoMagic 256AV and 256ZX" dev/pci/files.pci
linedel "neo" dev/pci/files.pci
linedel "# Intel EtherExpress PRO 10/100B" dev/pci/files.pci
linedel "fxp_pci" dev/pci/files.pci
linedel "# Tehuti Networks 10Gb Ethernet" dev/pci/files.pci
linedel "thtc" dev/pci/files.pci
linedel "# Myricom Myri-10G Ethernet" dev/pci/files.pci
linedel "myx" dev/pci/files.pci
linedel "# Broadcom BCM570[68] gigabit ethernet" dev/pci/files.pci
linedel "bnx" dev/pci/files.pci
linedel "# Ralink RT2500 PCI/Mini-PCI" dev/pci/files.pci
linedel "ral_pci" dev/pci/files.pci

apply

repo_name: pecg/LibertyBSD

license: unknown


code:
#!/bin/sh

#########################
# Name: sys_rebrand.sh
# Main: jadedctrl
# Lisc: ISC
# Desc: Rebranding OBSD kernel
#       sources for use in
#       LBSD.
#########################

# Usage: sys_deblob.sh $SRC_DIR

. ./libdeblob.sh

PATCH_DIR=/tmp/sys_rebrand

if [ -e $PATCH_DIR ]
then
	self_destruct_sequence $PATCH_DIR
else
        mkdir $PATCH_DIR
fi

if test -z $1
then
        SRC_DIR=/usr/src/sys
else
        SRC_DIR=$1
fi

#arch_list="alpha amd64 armish armv7 hppa i386 landisk loongson luna88k macppc miniroot octeon sgi socppc sparc sparc64 vax zaurus"
arch_list="amd64 i386"

for arch in $arch_list
do
	rep "no OpenBSD" "no LibertyBSD" arch/${arch}/stand/libsa/biosdev.c
done

rep "ost=\"OpenBSD\"" "ost=\"LibertyBSD\"" conf/newvers.sh
rep ">> OpenBSD/\" MACHINE" ">> LibertyBSD/\" MACHINE" stand/boot/boot.c

apply


code:
#!/bin/sh

#########################
# Name: xenocara_rebrand.sh
# Main: jadedctrl
# Lisc: ISC
# Desc: Rebranding xenocara
#       sources for use in
#       LBSD.
#########################

PATCH_DIR=/tmp/xenocara_rebrand

if [ -e $PATCH_DIR ]
then
        self_destruct_sequence $PATCH_DIR
else
        mkdir $PATCH_DIR
fi

if test -z $1
then
        SRC_DIR=/usr/xenocara
else
        SRC_DIR=$1
fi

. ./libdeblob.sh

rep "OpenBSD __osrelease__" "LibertyBSD __osrelease" app/fvwm/sample.fvwmrc/system.fvwmrc
rep "\*:OpenBSD:\*:)" "\*:LibertyBSD:\*:)" app/twm/config.guess

filecp files/LibertyBSD_15bpp.xpm app/xdm/config/LibertyBSD_15bpp.xpm
filecp files/LibertyBSD_1bpp.xpm app/xdm/config/LibertyBSD_1bpp.xpm
filecp files/LibertyBSD_4bpp.xpm app/xdm/config/LibertyBSD_4bpp.xpm
filecp files/LibertyBSD_8bpp.xpm app/xdm/config/LibertyBSD_8bpp.xpm

rep "BITMAPDIR/\*\*//OpenBSD" "BITMAPDIR/**//LibertyBSD" app/xdm/config/Xresources.cpp
rep "--with-color-pixmap=OpenBSD" "--with-color-pixmap=LibertyBSD" app/xdm/Makefile.bsd-wrapper
rep "--with-bw-pixmap=OpenBSD" "--with-bw-pixmap=LibertyBSD" app/xdm/Makefile.bsd-wrapper
rep "/config/OpenBSD_" "/config/LibertyBSD_" app/xdm/Makefile.bsd-wrapper

rep "/pixmaps/OpenBSD" "/pixmaps/LibertyBSD" distrib/sets/lists/xshare/mi

lineadd "CONFIG_SITE=$(CONFIG_SITE)" "build_alias=\${arch}-unknown-openbsd6.1" app/xlockmore/Makefile.bsd-wrapper
lineadd "\${CONFIGURE_ENV} PATH=\$(XENOCARA_PATH)" "build_alias=\${arch}-unknown-openbsd6.1" share/mk/bsd.xorg.mk

apply


code:
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

repo_name: steinarb/frontend

path: .editorconfig

license: mit


code:
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
- package-ecosystem: maven
  directory: "/"
  schedule:
    interval: daily
  ignore:
    # ignore Maven APIs/SPIs
    - dependency-name: org.apache.maven:*
- package-ecosystem: "github-actions"
  directory: "/"
  schedule:
    interval: "daily"


code:
name: frontend-karaf-demo CI build

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up OpenJDK 21
      uses: actions/setup-java@v4
      with:
        java-version: '21'
        distribution: 'temurin'
        cache: maven
    - name: Add ssh key
      run: |
          mkdir -p /home/runner/.m2
          cp .github/workflows/settings.xml /home/runner/.m2
          mkdir -p /home/runner/.ssh
          ssh-keyscan maven.bang.priv.no >> /home/runner/.ssh/known_hosts
          echo "${{ secrets.MAVEN_SSH_KEY }}" > /home/runner/.ssh/github_actions
          chmod 600 /home/runner/.ssh/github_actions
    - name: Build with Maven
      run: mvn -B org.jacoco:jacoco-maven-plugin:prepare-agent install coveralls:report sonar:sonar --file pom.xml --define repoToken=${{ secrets.COVERALLS_REPO_SECRET }} -Dsonar.projectKey=steinarb_frontend-karaf-demo -Dsonar.organization=steinarb -Dsonar.host.url=https://sonarcloud.io
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    - name: Deploy snapshot to maven repo
      run: mvn -B deploy -DskipTests=true
      env:
        REPO_USER: ${{ secrets.REPO_USER }}


code:
<settings>
    <servers>
        <server>
            <id>ssh-maven-travis</id>
            <username>${env.REPO_USER}</username>
            <privateKey>/home/runner/.ssh/github_actions</privateKey>
            <configuration>
                <strictHostKeyChecking>no</strictHostKeyChecking>
                <preferredAuthentications>publickey,password</preferredAuthentications>
                <interactive>false</interactive>
            </configuration>
        </server>
    </servers>
</settings>


code:
*.lock
*.log
/.classpath
/.project
/.settings/
.idea/
*.iml
node_modules/
target/

path: .gitignore


code:
* Testing the frontend-maven-plugin

This is a maven project that is a testbed for delivering [[https://reactjs.org][react.js]] frontends from apache karaf.

The [[https://github.com/steinarb/frontend-karaf-demo/blob/master/pom.xml#L138][maven plugin config]]:
 - uses [[https://github.com/eirslett/frontend-maven-plugin][frontend-maven-plugin]] to [[https://webpack.js.org][webpack]] a  [[https://reactjs.org][react.js]] application into a self-contained bundle.js file that can be served as a static resource. Note that this doesn't need a node.js installed when building, nor will it use any node.js/npm installed locally on the build machine.
   The [[https://github.com/eirslett/frontend-maven-plugin][frontend-maven-plugin]] will download and upack its own node.js, and use this node.js to download the npm dependencies and create the bundle.js
 - uses [[http://felix.apache.org/components/bundle-plugin/][maven-bundle-plugin]] to create two DS OSGi components that expose the Servlet service that is plugged into an OSGi web whiteboard
   1. The DS component ReactServlet serves the static resources index.html and bundle.js on the path "/frontend-karaf-demo"
   2. The DS component IncrementerServlet receives POST requests on  the path "/frontend-karaf-demo/api/increment", parses the POSTed JSON into a bean, increments the value property of the bean, and returns the bean as a JSON object (ie. the servlet is stateless and just increments the value of the POSTed object)
 - uses [[https://karaf.apache.org/manual/latest/#_using_the_karaf_maven_plugin][karaf-maven-plugin]] to create an [[https://karaf.apache.org/manual/latest/#_features_repositories_2][apache karaf feature repository]] that loads the servlets and ensures that all runtime requirements are in place (the pax web whiteboard extender, the jackson JSON parser). The feature repository is attached to the maven bundle artifact

The react application uses the following features:
 - [[https://reactjs.org][react.js]] to render the application's page
 - [[https://redux.js.org][redux]] to hold the application's state
 - [[https://redux-toolkit.js.org][Redux Toolkit]] to create redux actions and reducers (if a reducer is longer than a single line per action your redux state is probably organized wrong)
 - [[https://redux-saga.js.org][redux-saga]] and [[https://github.com/axios/axios][axios]] for REST API communication and asynchronous update of the application's state
 - [[https://reacttraining.com/react-router/][react-router]] to route between different pages (however this requires hardcoding the ReactServlet's path into the react app, and requires the ReactServlet to return the top level index.html from all routes)

The react application follows the structure I've found clear and easy to work with:
 - Avoid embedding binaries like images or fonts in the bundle.js, it's better to load them from a web resource
 - Avoid embedding large CSS files like e.g. bootstrap in bundle.js, again: it's better to load them from a web resource
 - The build script and package.json in the top directory with the application in a subdirectory named =src/=
 - Inside =src/=
   - On the top, index.js that enters the application and sets up and starts redux and saga
   - actiontypes.js containing all of the redux actions in the applcation, created using redux toolkit's "[[https://redux-toolkit.js.org/api/createAction][createAction()]]"
   - Subdirectory =components/= containing all of the visible UI components of the frontend app
     - Note in particular Counter.js which demonstrates both displaying data from redux and dispatching actions with redux
   - Subdirectory =reducers/= containing all of the reducers (created with redux toolkit "[[https://redux-toolkit.js.org/api/createReducer#usage-with-the-map-object-notation][createReducer()]] using the map notation, and using "computed syntax" (i.e. square brackets on property identifiers), to be able to use the createAction() actions directly
   - Subdirectory =sagas/= containing the sagas that handle asynchronous REST API communication (a REST operation is triggered by dispatching a redux action that is picked up by the saga, and both successful and failed REST responses results in redux actions, that in turn are picked up by reducers and stored in the reduc state

** Status

[[https://github.com/steinarb/frontend-karaf-demo/actions/workflows/frontend-karaf-demo-maven-ci-build.yml][file:https://github.com/steinarb/frontend-karaf-demo/actions/workflows/frontend-karaf-demo-maven-ci-build.yml/badge.svg]]
[[https://coveralls.io/github/steinarb/frontend-karaf-demo][file:https://coveralls.io/repos/github/steinarb/frontend-karaf-demo/badge.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=alert_status#.svg]]
[[https://maven-badges.herokuapp.com/maven-central/no.priv.bang.demos/frontend-karaf-demo][file:https://maven-badges.herokuapp.com/maven-central/no.priv.bang.demos/frontend-karaf-demo/badge.svg]]

[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/images/project_badges/sonarcloud-white.svg]]

[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=sqale_index#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=coverage#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=ncloc#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=code_smells#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=sqale_rating#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=security_rating#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=bugs#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=vulnerabilities#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=duplicated_lines_density#.svg]]
[[https://sonarcloud.io/summary/new_code?id=steinarb_frontend-karaf-demo][file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_frontend-karaf-demo&metric=reliability_rating#.svg]]

*** Branches

| branch        | karaf version | snapshot version | OSGi version |
|---------------+---------------+------------------+--------------|
| master        |         4.3.x | 2.0.0-SNAPSHOT   | OSGi 7       |
| release/1.1.0 |        4.2.11 | 1.1.0-SNAPSHOT   | OSGi 6       |

** Installation on apache karaf

Installation steps:
 1. Clone this project and build it:
    #+BEGIN_EXAMPLE
      mkdir -p ~/git
      cd ~/git/
      git clone https://github.com/steinarb/frontend-karaf-demo.git
      cd ~/git/frontend-karaf-demo/
      mvn clean install
    #+END_EXAMPLE
 2. Install apache karaf (e.g. [[https://karaf.apache.org/manual/latest/quick-start.html][according to the karaf quick start guide]] or by [[https://steinar.bang.priv.no/2018/01/23/packaging-karaf-with-native-debian-packaging-tools/][installing the .deb package on debian/ubuntu/raspbian]]), and start karaf
 3. From the karaf console (either from the karaf started above, or from an SSH session into a running karaf), install the application with the following commands:
    #+BEGIN_EXAMPLE
      feature:repo-add mvn:no.priv.bang.demos/frontend-karaf-demo/LATEST/xml/features
      feature:install frontend-karaf-demo
    #+END_EXAMPLE
 4. Open a web browser on http://localhost:8181/frontend-karaf-demo/ and use the "+" and "-" buttons to increment and decrement the counter
    - If using Chrome or Chromium it's a good idea to press Ctrl-Shift-i to open the developer tools, where it is possible to watch error messages in the console, and set breakpoints and debug the JavaScript code
    - It's also a good idea to visit the [[https://chrome.google.com/webstore/category/extensions][Chrome Web Store]] and install:
      1. [[https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi][React Developer Tools]] from Facebook
         - The "React" tab in chrome devtools gives a view of the react application tree, rather than the DOM, where it is possible to examine the react components' props and state, it's also possible to click on a part of the application and navigate to the corret place in the react application tree
      2. [[https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd][Redux DevTools]] from remotedevio
         - The "Redux" tab in chrome devtools lets you examine the redux store of the application

** LICENSE

This maven project is licensed under Apache v2.0.

See the file LICENSE for details.


code:
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>no.priv.bang.demos</groupId>
    <artifactId>frontend-karaf-demo</artifactId>
    <version>2.0.0-SNAPSHOT</version>

    <name>React app test</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.bundle.plugin.version>6.0.0</maven.bundle.plugin.version>
        <karaf.version>4.4.7</karaf.version>
        <karaf-feature-name>${project.artifactId}</karaf-feature-name>
        <Bundle-SymbolicName>no.priv.bang.demos.${project.artifactId}</Bundle-SymbolicName>
        <frontend.maven.plugin.version>1.15.1</frontend.maven.plugin.version>
        <nodejs.version>v20.18.0</nodejs.version>
        <jersey-feature.version>1.9.10</jersey-feature.version>
        <bang-servlet.version>2.0.2</bang-servlet.version>
        <osgi-service-adapters.version>1.2.0</osgi-service-adapters.version>
        <junit.jupiter.version>5.13.1</junit.jupiter.version>
        <assertj.version>3.27.3</assertj.version>
        <mockito.version>5.18.0</mockito.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.apache.karaf</groupId>
                <artifactId>karaf-bom</artifactId>
                <version>${karaf.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>no.priv.bang.karaf</groupId>
                <artifactId>jersey-feature-bom</artifactId>
                <version>${jersey-feature.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>no.priv.bang.servlet</groupId>
                <artifactId>servlet-bom</artifactId>
                <version>${bang-servlet.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>no.priv.bang.osgi.service.adapters</groupId>
                <artifactId>adapters-bom</artifactId>
                <version>${osgi-service-adapters.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>${junit.jupiter.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.service.component.annotations</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.osgi.service.adapters</groupId>
            <artifactId>adapters.logservice</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.osgi.service.adapters</groupId>
            <artifactId>adapters.logservice</artifactId>
            <type>xml</type>
            <classifier>features</classifier>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.osgi.service.adapters</groupId>
            <artifactId>service-mocks</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.ops4j.pax.web</groupId>
            <artifactId>pax-web-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.ops4j.pax.web</groupId>
            <artifactId>pax-web-extender-whiteboard</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.servlet</groupId>
            <artifactId>servlet.frontend</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>no.priv.bang.servlet</groupId>
            <artifactId>servlet.frontend</artifactId>
            <type>xml</type>
            <classifier>features</classifier>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>${assertj.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.mockrunner</groupId>
            <artifactId>mockrunner-servlet</artifactId>
            <version>2.0.7</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>${frontend.maven.plugin.version}</version>
                <configuration>
                    <nodeVersion>${nodejs.version}</nodeVersion>
                    <workingDirectory>src/main/frontend</workingDirectory>
                    <installDirectory>target</installDirectory>
                </configuration>
                <executions>
                    <execution>
                        <id>install node and npm</id>
                        <goals>
                            <goal>install-node-and-npm</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>npm install</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>vite build</id>
                        <goals>
                            <goal>npx</goal>
                        </goals>
                        <configuration>
                            <arguments>vite build</arguments>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.5.0</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>src/main/frontend</directory>
                            <includes>
                                <include>**/*.log</include>
                            </includes>
                            <followSymlinks>false</followSymlinks>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.14.0</version>
                <configuration>
                    <release>17</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.3.1</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.11.2</version>
                <configuration>
                    <show>private</show>
                </configuration>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.5.3</version>
                <configuration>
                    <useSystemClassLoader>false</useSystemClassLoader>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.sonarsource.scanner.maven</groupId>
                <artifactId>sonar-maven-plugin</artifactId>
                <version>5.1.0.4751</version>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.13</version>
                <executions>
                    <execution>
                        <id>report</id>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <phase>verify</phase>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.eluder.coveralls</groupId>
                <artifactId>coveralls-maven-plugin</artifactId>
                <version>4.3.0</version>
                <dependencies>
                    <dependency>
                        <groupId>jakarta.xml.bind</groupId>
                        <artifactId>jakarta.xml.bind-api</artifactId>
                        <version>2.3.3</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <jacocoReports>
                        <jacocoReport>${project.basedir}/target/site/jacoco/jacoco.xml</jacocoReport>
                    </jacocoReports>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>${maven.bundle.plugin.version}</version>
                <configuration>
                    <supportedProjectTypes>
                        <supportedProjectType>jar</supportedProjectType>
                        <supportedProjectType>bundle</supportedProjectType>
                        <supportedProjectType>war</supportedProjectType>
                    </supportedProjectTypes>
                    <instructions>
                        <Bundle-SymbolicName>${Bundle-SymbolicName}</Bundle-SymbolicName>
                        <Import-Package>
                            org.osgi.service.http.context;version="[1,2)",
                            org.osgi.service.log;version="[1.4,2)",
                            *
                        </Import-Package>
                        <Export-Package>!*</Export-Package>
                        <Include-Resource>/=target/classes/</Include-Resource>
                        <_removeheaders>
                            Private-Package,
                            Include-Resource,
                            Embed-Dependency,
                            Embed-Transitive
                        </_removeheaders>
                        <!-- Enable processing of OSGI DS component annotations -->
                        <_dsannotations>*</_dsannotations>
                        <!-- Enable processing of OSGI metatype annotations -->
                        <_metatypeannotations>*</_metatypeannotations>
                    </instructions>
                </configuration>
                <executions>
                    <execution>
                        <id>manifest</id>
                        <goals>
                            <goal>manifest</goal>
                        </goals>
                        <configuration>
                            <supportIncrementalBuild>true</supportIncrementalBuild>
                        </configuration>
                    </execution>
                    <execution>
                        <id>bundle</id>
                        <goals>
                            <goal>bundle</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>karaf-maven-plugin</artifactId>
                <version>${karaf.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <startLevel>80</startLevel>
                    <includeTransitiveDependency>true</includeTransitiveDependency>
                    <aggregateFeatures>false</aggregateFeatures>
                    <includeProjectArtifact>true</includeProjectArtifact>
                    <primaryFeatureName>${karaf-feature-name}</primaryFeatureName>
                </configuration>
                <executions>
                    <execution>
                        <id>generate-features-file</id>
                        <phase>package</phase>
                        <goals>
                            <goal>features-generate-descriptor</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <pluginManagement>
            <plugins>
                <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>org.apache.karaf.tooling</groupId>
                                        <artifactId>karaf-maven-plugin</artifactId>
                                        <versionRange>[4.4.3,)</versionRange>
                                        <goals>
                                            <goal>features-generate-descriptor</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <ignore></ignore>
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <extensions>
            <extension>
                <groupId>org.apache.maven.wagon</groupId>
                <artifactId>wagon-ssh</artifactId>
                <version>3.5.3</version>
            </extension>
        </extensions>
    </build>

    <distributionManagement>
        <repository>
            <id>ssh-maven-travis</id>
            <url>sftp://maven.bang.priv.no/repository</url>
        </repository>
        <snapshotRepository>
            <id>ssh-maven-travis</id>
            <url>sftp://maven.bang.priv.no/repository</url>
        </snapshotRepository>
    </distributionManagement>
</project>


code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<features>
    <feature name="${karaf-feature-name}">
        <feature>scr</feature>
        <feature>http-whiteboard</feature>
    </feature>
</features>


code:
#!/bin/sh
PATH="$PWD/node/":$PATH
node "node/node_modules/npm/bin/npm-cli.js" "$@"

path: src/main/frontend/npm


code:
@echo off
setlocal
set PATH=%~dp0node/;%PATH%
node node/node_modules/npm/bin/npm-cli.js %*
@echo on


frontend-karaf-demo

type: opencollective

deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.

deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.

deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.

deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.

deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.

deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.

deprecated: Use @eslint/config-array instead

type: github

deprecated: Use @eslint/object-schema instead

acorn: bin/acorn

type: tidelift

license: MIT

esbuild: bin/esbuild

resolve: bin/resolve

deprecated: Glob versions prior to v9 are no longer supported

deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.

jsesc: bin/jsesc

type: patreon

type: consulting

regjsparser: bin/parser

deprecated: Rimraf versions prior to v4 are no longer supported

rollup: dist/bin/rollup

terser: bin/terser

license: ISC

tsc: bin/tsc

tsserver: bin/tsserver

node-which: bin/node-which


code:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
    reducerPath: 'api',
    baseQuery: (...args) => {
        const api = args[1];
        const basename = api.getState().basename;
        return fetchBaseQuery({ baseUrl: basename + '/api' })(...args);
    },
    endpoints: (builder) => ({
        postIncrement: builder.mutation({ query: body => ({ url: '/increment', method: 'POST', body }) }),
        postDecrement: builder.mutation({ query: body => ({ url: '/increment', method: 'POST', body: { ...body, delta: -body.delta } }) }),
    }),
});

console.log(api);

export const {
    usePostIncrementMutation,
    usePostDecrementMutation,
} = api;


code:
import React from 'react';

function About() {
    return (

About the karaf react demo

This is a testbed for delivering react.js frontends from apache karaf.

    );
}

export default About;


code:
import React from 'react';
import { Routes, Route, NavLink } from 'react-router';
import { HistoryRouter as Router } from "redux-first-history/rr6";
import { configureStore, Tuple } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { createReduxHistoryContext } from "redux-first-history";
import { createBrowserHistory } from 'history';
import createRootReducer from '../reducers';
import { api } from '../api';
import Home from './Home';
import About from './About';
import Counter from './Counter';

const baseUrl = Array.from(document.scripts).map(s => s.src).filter(src => src.includes('assets/'))[0].replace(/\/assets\/.*/, '');
const basename = new URL(baseUrl).pathname.replace(/\/$/, '');
const {
    createReduxHistory,
    routerMiddleware,
    routerReducer
} = createReduxHistoryContext({ history: createBrowserHistory(), basename });
const store = configureStore({
    reducer: createRootReducer(routerReducer, basename),
    middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(routerMiddleware).concat(api.middleware),
});
const history = createReduxHistory(store);

function App() {
    return (
        <Provider store={store}>
            <Router history={history} basename={basename}>

                        <NavLink to="/"><button>Home</button></NavLink>
                        <NavLink to="/counter"><button>Counter</button></NavLink>
                        <NavLink to="/about"><button>About</button></NavLink>

                    <Routes>
                        <Route exact path="/" element={<Home/>} />
                        <Route path="/counter" element={<Counter/>} />
                        <Route path="/about" element={<About/>} />
                    </Routes>

            </Router>
        </Provider>
    );
}

export default App;


code:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { setDelta } from '../reducers/deltaSlice';
import { usePostIncrementMutation, usePostDecrementMutation } from '../api';

export default function Counter() {
    const delta = useSelector(state => state.delta);
    const value = useSelector(state => state.counter);
    const dispatch = useDispatch();
    const [ postIncrement ] = usePostIncrementMutation();
    const onPlusButtonClicked = async () => await postIncrement({ value, delta });
    const [ postDecrement ] = usePostDecrementMutation();
    const onMinusButtonClicked = async () => await postDecrement({ value, delta });

    return (

Counting high and low

                Increment step:
                <input id="delta" type="text" value={delta} onChange={e => dispatch(setDelta(e.target.value))} />

                {value}
                <button onClick={onPlusButtonClicked}>+</button>
                <button onClick={onMinusButtonClicked}>-</button>

    );
}


code:
import React from 'react';

function Home() {
    return (

This is home

    );
}

export default Home;


code:
import "regenerator-runtime/runtime";
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './components/App';

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
    <App />,
);


code:
import { createReducer } from '@reduxjs/toolkit';
import { api } from '../api';

export default createReducer(0, builder => {
    builder
        .addMatcher(api.endpoints.postIncrement.matchRejected, (state, action) => action.payload)
        .addMatcher(api.endpoints.postDecrement.matchRejected, (state, action) => action.payload);
});


code:
import { createReducer } from '@reduxjs/toolkit';
import { api } from '../api';

export default createReducer(0, builder => {
    builder
        .addMatcher(api.endpoints.postIncrement.matchFulfilled, (state, action) => action.payload.value)
        .addMatcher(api.endpoints.postDecrement.matchFulfilled, (state, action) => action.payload.value);
});


code:
import { createSlice } from '@reduxjs/toolkit';

export const deltaSlice = createSlice({
    name: 'delta',
    initialState: 1,
    reducers: {
        setDelta: (_, action) => parseInt(action.payload) || 0,
    },
});

export const { setDelta } = deltaSlice.actions;
export default deltaSlice.reducer;


code:
import { combineReducers } from 'redux';
import { createReducer } from '@reduxjs/toolkit';
import { api } from '../api';
import apiErrors from './apiErrorsReducer';
import delta from './deltaSlice';
import counter from './counterReducer';

export default (routerReducer, basename) => combineReducers({
    router: routerReducer,
    [api.reducerPath]: api.reducer,
    apiErrors,
    delta,
    counter,
    basename: createReducer(basename, (builder) => builder),
});


code:
import { defineConfig } from 'vite';
import eslintPlugin from "@nabla/vite-plugin-eslint";
import path from 'path';
import fs from 'fs';
import { parse } from '@babel/parser';
import traverse from '@babel/traverse';
import * as t from "@babel/types";

export default defineConfig({
    plugins: [eslintPlugin(), exportRoutesPlugin()],
    build: {
        minify: false,
        sourcemap: true,
        manifest: true,
        rollupOptions: {
            // overwrite default .html entry
            input: 'src/index.js',
            output: {
                entryFileNames: `assets/[name].js`,
                chunkFileNames: `assets/[name].js`,
                assetFileNames: `assets/[name].[ext]`
            }
        },
        // Relative to the root
        outDir: '../../../target/classes',
    },
    // Treat .js files as jsx
    esbuild: {
        include: /\.js$/,
        exclude: [],
        loader: 'jsx',
    },
});

function exportRoutesPlugin() {
    const routePaths = new Set();

    return {
        name: 'export-routes',

        async transform(src, id) {
            if (!id.includes('node_modules') && !id.includes('commonjsHelpers') && id.includes('.js')) {
                // This is a rollup plugin that runs after esbuild, so code in src has already been processed
                // from JSX to plain JS (i.e. the JSX stuff is gone...).
                // Have to read the raw file from disk to find the JSX tags.
                fs.readFile(id, 'utf-8', (err, data) => {
                    const ast = parse(data, {
                        sourceType: 'module',
                        plugins: ['jsx'],
                    });

                    traverse(ast, {
                        enter(path) {
                            if (t.isJSXElement(path.node)) {
                                const elementName = path.node.openingElement.name.name;
                                if (elementName === 'Route') {
                                    path.node.openingElement.attributes.forEach((attribute) => {
                                        if (attribute.name.name === 'path' && attribute.value.value) {
                                            routePaths.add(attribute.value.value);
                                        }
                                    });
                                }
                            }
                        }
                    });
                });
            }
        },

        generateBundle(options, bundle) {
            const outputDirectory = options.dir || 'dist';
            const assetsDirectory = path.join(outputDirectory, 'assets');
            fs.mkdirSync(assetsDirectory, { recursive: true });
            const filePath = path.join(assetsDirectory, 'routes.txt');
            const fileContent = Array.from(routePaths).join('\n');
            fs.writeFileSync(filePath, fileContent);
        },
    };
};


code:
package no.priv.bang.demos.frontendkarafdemo;

public class ApplicationConstants {

    static final String APPLICATION_PATH = "/frontend-karaf-demo"; // NOSONAR Configuration for this value would be overkill for a demo

    private ApplicationConstants() {}
}


code:
package no.priv.bang.demos.frontendkarafdemo;

public record Counter(int value, int delta) {

    public static Builder with() {
        return new Builder();
    }

    public static class Builder {
        private int value = 0;
        private int delta = 0;

        private Builder() {}

        public Counter build() {
            return new Counter(value, delta);
        }

        public Builder value(int value) {
            this.value = value;
            return this;
        }

        public Builder delta(int delta) {
            this.delta = delta;
            return this;
        }

    }

}


code:
package no.priv.bang.demos.frontendkarafdemo;

public record Error(int status, String message) {

    public static Builder with() {
        return new Builder();
    }

    public static class Builder {
        int status = -1;
        String message = "";

        private Builder() {}

        public Error build() {
            return new Error(status, message);
        }

        public Builder status(int status) {
            this.status = status;
            return this;
        }

        public Builder message(String message) {
            this.message = message;
            return this;
        }

    }
}


code:
package no.priv.bang.demos.frontendkarafdemo;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.http.context.ServletContextHelper;
import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardContext;
import static no.priv.bang.demos.frontendkarafdemo.ApplicationConstants.*;

@Component(service=ServletContextHelper.class, immediate=true)
@HttpWhiteboardContext(name = "frontend-demo", path = APPLICATION_PATH)
public class FrontendDemoServletContextHelper extends ServletContextHelper {}


code:
package no.priv.bang.demos.frontendkarafdemo;

import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardContextSelect;
import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern;
import org.osgi.service.log.LogService;

import com.fasterxml.jackson.databind.ObjectMapper;

import no.priv.bang.osgi.service.adapters.logservice.LoggerAdapter;

import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.*;

@Component(service={Servlet.class})
@HttpWhiteboardContextSelect("(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=frontend-demo)")
@HttpWhiteboardServletPattern("/api/increment")
public class IncrementerServlet extends HttpServlet {
    private static final long serialVersionUID = 103245292629129445L;
    static final ObjectMapper mapper = new ObjectMapper();
    final LoggerAdapter logger = new LoggerAdapter(getClass());

    @Reference
    public void setLogService(LogService logservice) {
        this.logger.setLogService(logservice);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json");
        try {
            try(var postBody = request.getInputStream()) {
                var incrementedCounter = increment(mapper.readValue(postBody, Counter.class));
                response.setStatus(HttpServletResponse.SC_OK);
                try(var responseBody = response.getWriter()) {
                    mapper.writeValue(responseBody, incrementedCounter);
                }
            }
        } catch (Exception e) {
            logger.error("Failed to increment the counter value", e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            try(var responseBody = response.getWriter()) {
                mapper.writeValue(responseBody, Error.with().status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).message(e.getMessage()).build());
            } catch (Exception e2) {
                // Swallow exception quietly and just return the error code
            }
        }
    }

    Counter increment(Counter counter) {
        var delta = counter.delta();
        return Counter.with()
            .value(counter.value() + delta)
            .delta(delta)
            .build();
    }

}


code:
package no.priv.bang.demos.frontendkarafdemo;

import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME;

import javax.servlet.Servlet;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardContextSelect;
import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName;
import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern;
import org.osgi.service.log.LogService;

import no.priv.bang.servlet.frontend.FrontendServlet;

@Component(service={Servlet.class})
@HttpWhiteboardContextSelect("(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=frontend-demo)")
@HttpWhiteboardServletName("frontend-demo")
@HttpWhiteboardServletPattern("/*")
public class ReactServlet extends FrontendServlet {
    private static final long serialVersionUID = 250817058831319271L;

    public ReactServlet() {
        super(ReactServlet.class);
    }

    @Override
    @Reference
    public void setLogService(LogService logservice) {
        super.setLogService(logservice);
    }

}


code:
        <title>Frontend Karaf Test</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>

        This webpage requires javascript in the browser!


code:
package no.priv.bang.demos.frontendkarafdemo;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import com.mockrunner.mock.web.MockHttpServletRequest;
import com.mockrunner.mock.web.MockHttpServletResponse;

import no.priv.bang.osgi.service.mocks.logservice.MockLogService;

class IncrementerServletTest {

    @Test
    void doPostIncrement() throws Exception {
        var value = Counter.with().value(10).delta(1).build();
        var valueAsJson = IncrementerServlet.mapper.writeValueAsString(value);
        var request = new MockHttpServletRequest()
            .setMethod("POST")
            .setRequestURI("http://localhost:8181/frontend-karaf-demo/api/increment")
            .setPathInfo("/")
            .setBodyContent(valueAsJson);
        var response = new MockHttpServletResponse();

        var servlet = new IncrementerServlet();
        servlet.service(request, response);

        assertEquals("application/json", response.getContentType());
        assertEquals(200, response.getStatus());

        var incrementedValue = IncrementerServlet.mapper.readValue(response.getOutputStreamContent(), Counter.class);
        assertThat(incrementedValue.value()).isGreaterThan(value.value());
    }

    @Test
    void doPostDecrement() throws Exception {
        var value = Counter.with().value(10).delta(-1).build();
        var valueAsJson = IncrementerServlet.mapper.writeValueAsString(value);
        var request = new MockHttpServletRequest()
            .setMethod("POST")
            .setRequestURI("http://localhost:8181/frontend-karaf-demo/api/increment")
            .setPathInfo("/")
            .setBodyContent(valueAsJson);
        var response = new MockHttpServletResponse();

        var servlet = new IncrementerServlet();
        servlet.service(request, response);

        assertEquals("application/json", response.getContentType());
        assertEquals(200, response.getStatus());

        var incrementedValue = IncrementerServlet.mapper.readValue(response.getOutputStreamContent(), Counter.class);
        assertThat(incrementedValue.value()).isLessThan(value.value());
    }

    @Test
    void doPostNotJson() throws Exception {
        var logservice = new MockLogService();
        var valueNotJson = "<this>is <not>json</not></this>";
        var request = new MockHttpServletRequest()
            .setMethod("POST")
            .setRequestURI("http://localhost:8181/frontend-karaf-demo/api/increment")
            .setPathInfo("/")
            .setBodyContent(valueNotJson);
        var response = new MockHttpServletResponse();

        var servlet = new IncrementerServlet();
        servlet.setLogService(logservice);
        servlet.service(request, response);

        assertEquals("application/json", response.getContentType());
        assertEquals(500, response.getStatus());

        var incrementedValue = IncrementerServlet.mapper.readValue(response.getOutputStreamContent(), Error.class);
        assertEquals(500,incrementedValue.status());
        assertThat(incrementedValue.message()).startsWith("Unexpected character");
    }

    @Test
    void doPostIncrementWithWrongNameForValueProperty() throws Exception {
        var logservice = new MockLogService();
        var valueAsJson = "{ \"currentValue\": 0, \"delta\": -1 }";
        var request = new MockHttpServletRequest()
            .setMethod("POST")
            .setRequestURI("http://localhost:8181/frontend-karaf-demo/api/increment")
            .setPathInfo("/")
            .setBodyContent(valueAsJson);
        var response = new MockHttpServletResponse();

        var servlet = new IncrementerServlet();
        servlet.setLogService(logservice);
        servlet.service(request, response);

        assertEquals("application/json", response.getContentType());
        assertEquals(500, response.getStatus());
        assertThat(logservice.getLogmessages()).isNotEmpty();
    }

}


code:
package no.priv.bang.demos.frontendkarafdemo;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.io.IOException;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import com.mockrunner.mock.web.MockHttpServletRequest;
import com.mockrunner.mock.web.MockHttpServletResponse;

import no.priv.bang.osgi.service.mocks.logservice.MockLogService;

class ReactServletTest {

    @Test
    void testDoGetSuccess() throws Exception {
        var logservice = new MockLogService();
        var request = new MockHttpServletRequest()
            .setMethod("GET")
            .setRequestURL("http://localhost:8181/frontend-karaf-demo")
            .setRequestURI("http://localhost:8181/frontend-karaf-demo/")
            .setPathInfo("/");
        var response = new MockHttpServletResponse();

        var servlet = new ReactServlet();
        servlet.setLogService(logservice);

        servlet.service(request, response);

        assertEquals("text/html", response.getContentType());
        assertEquals(200, response.getStatus());
        assertThat(response.getOutputStreamContent()).isNotEmpty();
    }

    @Test
    void testDoGetAddTrailingSlash() throws Exception {
        var logservice = new MockLogService();
        var request = new MockHttpServletRequest()
            .setMethod("GET")
            .setRequestURL("http://localhost:8181/frontend-karaf-demo")
            .setRequestURI("http://localhost:8181/frontend-karaf-demo")
            .setServletPath("/frontend-karaf-demo");
        var response = new MockHttpServletResponse();

        var servlet = new ReactServlet();
        servlet.setLogService(logservice);

        servlet.service(request, response);

        assertEquals(302, response.getStatus());
    }

    @Test
    void testDoGetResponseThrowsIOException() throws Exception {
        var logservice = new MockLogService();
        var request = mock(HttpServletRequest.class);
        when(request.getMethod()).thenReturn("GET");
        when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8181/frontend-karaf-demo"));
        when(request.getRequestURI()).thenReturn("http://localhost:8181/frontend-karaf-demo/");
        when(request.getPathInfo()).thenReturn("/");
        var response = spy(MockHttpServletResponse.class);
        var streamThrowingIOException = mock(ServletOutputStream.class);
        doThrow(IOException.class).when(streamThrowingIOException).write(anyInt());
        when(response.getOutputStream()).thenReturn(streamThrowingIOException);

        var servlet = new ReactServlet();
        servlet.setLogService(logservice);

        servlet.service(request, response);

        assertEquals(500, response.getStatus());
    }

    @Test
    void testDoGetResponseStreamMethodThrowsIOException() throws Exception {
        var logservice = new MockLogService();
        var request = new MockHttpServletRequest()
            .setMethod("GET")
            .setRequestURL("http://localhost:8181/frontend-karaf-demo")
            .setRequestURI("http://localhost:8181/frontend-karaf-demo/")
            .setPathInfo("/");
        var response = spy(MockHttpServletResponse.class);
        when(response.getOutputStream()).thenThrow(IOException.class);

        var servlet = new ReactServlet();
        servlet.setLogService(logservice);

        servlet.service(request, response);

        assertEquals(500, response.getStatus());
    }

    @Test
    void testDoGetResourceNotFound() throws Exception {
        var logservice = new MockLogService();
        var request = new MockHttpServletRequest()
            .setMethod("GET")
            .setRequestURL("http://localhost:8181/frontend-karaf-demo/static/nosuchname.png")
            .setRequestURI("http://localhost:8181/frontend-karaf-demo/static/nosuchname.png")
            .setPathInfo("/static/nosuchname.png");
        var response = new MockHttpServletResponse();

        var servlet = new ReactServlet();
        servlet.setLogService(logservice);

        servlet.service(request, response);

        assertEquals(404, response.getErrorCode());
    }

    @Test
    void testServletContext() {
        var contextHelper = new FrontendDemoServletContextHelper();
        assertNull(contextHelper.getMimeType(null));
    }

}


code:
avatar/
files/
file/
local/
logs/
log/
run/
config.php
.htaccess
httpd.conf
dataobject.ini
*.bak
*.orig
*.rej
TODO.rym
config-*.php
good-config.php
*.mo
/vendor/

repo_name: andremichelle/gnu


code:
## Code of Conduct

### Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

### Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

### Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

### Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

### Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at mattl@gnu.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

### Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

## The Code of Conflict

GNU social has a high submission standard and we want to keep quality code in the
codebase and bad code out of it. As such your code will be closely scrutinized,
and you might take this criticism personally. Please understand that this is
meant to keep the standards of the codebase up, and isn't meant personally. All
the same, this isn't an excuse for poor behaviour, and a reviewer shouldn't be
misbehaving towards submitters.

If however, anyone feels personally abused, threatened, or otherwise
uncomfortable due to this process, that is not acceptable.  If so, please
contact the project team at mattl@gnu.org, and they will work to resolve the issue
to the best of their ability.

As a reviewer of code, please strive to keep things civil and focused on the
technical issues involved.  We are all humans, and frustrations can be high on
both sides of the process.  Try to keep in mind the immortal words of Bill and
Ted, "Be excellent to each other."


code:
# Contributing to GNU social

First of all, if you're reading this intending to contribute to GNU social,
thanks! Free software development only happens when people like you take an
interest in giving back to the software they themselves use, and their
community.

When contributing to this repository, please first discuss the change you wish to
make via issue, email, or any other method with the owners of this repository before
making a change.

There's a few files you should read before going forward with a merge request
or a patch submission.  They detail what this file touches on in brief.  They
are:

* `DOCUMENTATION/DEVELOPERS/CONTRIBUTING/coding_standards.md`: How your code should be structured and formatted to be
  accepted into the GNU social codebase.
* `/DOCUMENTATION/DEVELOPERS/CONTRIBUTING/merge_request_checklist.md`: A quick checklist to review before submission.

## Merge Request Process

1. Ensure you strip any trailing spaces off and checked the file with php-cs-fixer
2. Increase the version numbers in any examples files and the README.md to the new version that this
   Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
   do not have permission to do that, you may request the second reviewer to merge it for you.

## Coding Standards

Since we will be expected to maintain your code once it's submitted, we ask you
to adhere to certain coding standards that make it easier for us to do so. If
code doesn't follow them, it will be rejected, so please read up on these.

## Bug Reports

Please report bugs to the issue tracker at
<https://notabug.org/diogo/gnu-social/issues> Avoid assigning the labels
yourself, as these are for the development team to assign priority and area of
coverage to a subject. Please only submit something here if you are certain it
is a bug or represents a feature enhancement that we do not presently have. If
you are uncertain whether it's a bug, please feel free to ask
at #social IRC channel on freenode.net https://www.freenode.net/.

When reporting a bug, please try to include as much information as possible,
including the environment being run on (if it's a common LAMP stack just give
us version numbers of the main stack components, that's fine), and the specific
error you get. If you do not get a client-facing error, please check the PHP
error_log and ensure there isn't something silently reported there, as well as
the GNU social log. Try to include steps to reproduce the error as well, as if
we cannot reproduce the error, we can't fix it!

It is perfectly acceptable to reference the archive page of a discussion on the
mailing list for the bug report, by the way, as long as it includes all the
information we need for a bug report.

## Submitting Feature Requests / Enhancement Requests

Social media is constantly evolving, and we welcome ideas about how we can
change and evolve GNU social to keep it the excellent piece of software that it
is. However, there are a few things we ask you do when submitting feature
requests:

1. Understand that since we have a limited amount of developers and these people
contribute in their free time, we may prioritize things differently than you
value them. Oftentimes this is because certain requests involve less changes
to the existing codebase than others, and therefore this makes them easier
to add.
2. Please search the existing feature requests and enhancements to see if a
similar request exists. If one does but you have different ideas about how
to do it or what it should entail, please add a comment to the existing idea
rather than create a new one for your "version" of it. Duplicate submissions
mean we spend more time maintaining the tracker and less time actually
working on the codebase!
3. When outlining the way that you see something working, don't be afraid to be
as detailed as possible! We may not implement it exactly as you describe for
any variety of reasons, but the more concrete and fleshed out an idea is, the
easier it is for us to know what you want and be able to implement it in a
sane and secure fashion.
4. When describing a possible new idea and its mechanisms of operation, the key
words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the issue submission
are to be interpreted as described in RFC 2119.
<https://tools.ietf.org/html/rfc2119>

Finally, and just as a call back to the first point, realize just because we
might not rush to implement something, doesn't mean that we don't want to
implement it! We would rather take the time to do something right the first
time, then hurriedly apply a new idea, or a fix, only to have to patch it later.

## Branch of Code Submissions

Unless you've been specifically directed otherwise, all submissions of code
should be against the `nightly` branch, so make sure any modifications are based
on Nightly.

## Copyright / Licensing

You acknowledge that by submitting code to GNU social, you are licensing it under
the GNU AGPLv3 unless there is an extenuating circumstance where it would be
licensed differently (such as modifications to an external library we include
such as Stomp).

You also acknowledge that unless you assign a copyright explicitly, it will be
assumed to be assigned to GNU social.

Thanks for considering submission, and happy hacking!


code:
Credits for GNU social
======================
The following is an incomplete list of developers
who've worked on GNU social, or its predecessors
StatusNet and Free Social. Apologies for any
oversight; please let mattl@gnu.org know if
anyone's been overlooked in error.

Current team
------------
* Matt Lee
* Mikael Nordfeldth
* Diogo Cordeiro
* Bruno Casteleiro
* Miguel Dantas
* Alexei Sorokin

Additional Contributors
-----------------------
* Ciaran Gultnieks
* Michael Landers
* Ori Avtalion
* Garret Buell
* Mike Cochrane
* Matthew Gregg
* Sean Murphy
* Leslie Michael Orchard
* Eric Helgeson
* Ken Sedgwick
* Brian Hendrickson
* Tobias Diekershoff
* Dan Moore
* Fil
* Jeff Mitchell
* Brenda Wallace
* Jeffery To
* Federico Marani
* mEDI
* Brett Taylor
* Brigitte Schuster
* Craig Andrews
* Donald Robertson
* Deb Nicholson
* Ian Denhart
* Steven DuBois
* Blaine Cook
* Henry Story
* Melvin Carvalho
* chimo
* Akio
* Maiyannah Bishop
* Bob Mottram
* David Yip
* Neil E Hodges
* Moonman
* Normandy
* Verius
* Alexei Sorokin
* Daniel Supernault

Credits for StatusNet
--------------
Leads
* Evan Prodromou
* Zach Copley

Team
* Earle Martin
* Marie-Claude Doyon
* Sarven Capadisli
* Robin Millette
* Brion Vibber
* James Walker
* Samantha Doherty
* Florian Biree
* Erik Stambaugh
* 'drry'
* Gina Haeussge
* Tryggvi Björgvinsson
* Adrian Lang
* Ori Avtalion
* Meitar Moscovitz
* Ken Sheppardson
* Simon Waters, Surevine
* Joshua Judson Rosen (rozzin)

Translators
-----------
* Siebrand Mazeland
* Tiago 'gouki' Faria
* TranslateWiki.net

A special thanks to the thousands of people who
have tried out GNU social, told their friends, and
built the fediverse network to what it is today.

License help from
-----------------
* Bradley M. Kuhn


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Description of this file.
 *
 * @package   samples
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace samples;

defined('GNUSOCIAL') || die();

require_once(__DIR__ . DIRECTORY_SEPARATOR . 'SampleHandler.php');

/**
 * Description of this class.
 *
 * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */
class MySampleClass
{
    /**
     * Constructor for the sample class.
     *
     * @param string $dummy_word just because.
     * @param int $result another just because.
     */
    public function __construct(string $dummy_word = '', int $result = null)
    {
        global $demo;
        $this->niceWorld();
    }

    /**
     * How cool is this function.
     *
     * @return string
     */
    public function niceWorld() : string
    {
        return 'hello, world.';
    }
}


code:
GNU social Coding Style
===========================

Please comply with [PSR-2](https://www.php-fig.org/psr/psr-2/) and the following standard when working on GNU social
if you want your patches accepted and modules included in supported releases.

If you see code which doesn't comply with the below, please fix it :)

Strings
-------------------------------------------------------------------------------
Use `'` instead of `"` for strings, where substitutions aren't required.
This is a performance issue, and prevents a lot of inconsistent coding styles.
When using substitutions, use curly braces around your variables - like so:

    $var = "my_var: {$my_var}";

Comments and Documentation
-------------------------------------------------------------------------------
Comments go on the line ABOVE the code, NOT to the right of the code, unless it is very short.
All functions and methods are to be documented using PhpDocumentor - https://docs.phpdoc.org/guides/

File Headers
-------------------------------------------------------------------------------
File headers follow a consistent format, as such:

    // This file is part of GNU social - https://www.gnu.org/software/social
    //
    // GNU social is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Affero General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // GNU social is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Affero General Public License for more details.
    //
    // You should have received a copy of the GNU Affero General Public License
    // along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

    /**
     * Description of this file.
     *
     * @package   samples
     * @author    Diogo Cordeiro <diogo@fc.up.pt>
     * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
     * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
     */

Please use it.

A few notes:

*  The description of the file doesn't have to be exhaustive.  Rather it's
   meant to be a short summary of what's in this file and what it does.  Try
   to keep it to 1-5 lines.  You can get more in-depth when documenting
   individual functions!

*  You'll probably see files with multiple authors, this is by
   design - many people contributed to GNU social or its forebears!  If you
   are modifying an existing file, APPEND your own author line, and update
   the copyright year if needed. Do not replace existing ones.

You may find `boilerplate.php` useful when creating a new file from scratch.

Paragraph spacing
-------------------------------------------------------------------------------
Where-ever possible, try to keep the lines to 80 characters.  Don't
sacrifice readability for it though - if it makes more sense to have it in
one longer line, and it's more easily read that way, that's fine.

With assignments, avoid breaking them down into multiple lines unless
neccesary, except for enumerations and arrays.

'If' statements format
-------------------------------------------------------------------------------
Use switch statements where many else if's are going to be used. Switch/case is faster

    if ($var == 'example') {
        echo 'This is only an example';
    } else {
        echo 'This is not a test.  This is the real thing';
    }

Do NOT make if statements like this:

    if ($var == 'example'){ echo 'An example'; }

    OR this

    if($var = 'example')
            echo "An {$var}";

Associative arrays
-------------------------------------------------------------------------------
Always use `[]` instead of `array()`. Associative arrays must be written in the
following manner:

    $array = [
        'var' => 'value',
        'var2' => 'value2'
    ];

Note that spaces are preferred around the '=>'.

A note about shorthands
-------------------------------------------------------------------------------
Some short hands are evil:

- Use the long format for `<?php`.  Do NOT use `<?`.
- Use the long format for `<?php` echo. Do NOT use `<?=`.

Naming conventions
-------------------------------------------------------------------------------
Respect PSR2 first.

- Classes use PascalCase (e.g. MyClass).
- Functions/Methods use camelCase (e.g. myFunction).
- Variables use snake_case (e.g. my_variable).

A note on variable names, etc. It must be possible to understand what is meant
without neccesarialy seeing it in context, because the code that calls something
might not always make it clear.

So if you have something like:

    $notice->post($contents);

Well I can easily tell what you're doing there because the names are straight-
forward and clear.

Something like this:

    foo->bar();

Is much less clear.

Also, whereever possible, avoid ambiguous terms.  For example, don't use text
as a term for a variable.  Call back to "contents" above.

Comparisons
-------------------------------------------------------------------------------
Always use symbol based comparison operators (&&, ||) instead of text based
operators (AND, OR) as they are evaluated in different orders and at different
speeds.  This is will prevent any confusion or strange results.

Use English
-------------------------------------------------------------------------------
All variables, classes, methods, functions and comments must be in English.
Bad english is easier to work with than having to babelfish code to work out
how it works.

Encoding
-------------------------------------------------------------------------------
Files should be in UTF-8 encoding with UNIX line endings.

No ending tag
-------------------------------------------------------------------------------
Files should not end with an ending php tag "?>".  Any whitespace after the
closing tag is sent to the browser and cause errors, so don't include them.

Nesting Functions
-------------------------------------------------------------------------------
Avoid, if at all possible.  When not possible, document the living daylights
out of why you're nesting it.  It's not always avoidable, but PHP 5 has a lot
of obscure problems that come up with using nested functions.

If you must use a nested function, be sure to have robust error-handling.
This is a must and submissions including nested functions that do not have
robust error handling will be rejected and you'll be asked to add it.

Scoping
-------------------------------------------------------------------------------
Properly enforcing scope of functions is something many PHP programmers don't
do, but should.

In general:
*  Variables unique to a class should be protected and use interfacing to
   change them.  This allows for input validation and making sure we don't have
   injection, especially when something's exposed to the API, that any program
   can use, and not all of them are going to be be safe and trusted.

*  Variables not unique to a class should be validated prior to every call,
   which is why it's generally not a good idea to re-use stuff across classes
   unless there's significant performance gains to doing so.

*  Classes should protect functions that they do not want overriden, but they
   should avoid protecting the constructor and destructor and related helper
   functions as this prevents proper inheritance.

Typecasting
-------------------------------------------------------------------------------
PHP is a soft-typed language and it falls to us developers to make sure that
we are using the proper inputs.  Where ever possible use explicit type casting.
Where it in't, you're going to have to make sure that you check all your
inputs before you pass them.

All outputs should be cast as an explicit PHP type.

Not properly typecasting is a shooting offence.  Soft types let programmers
get away with a lot of lazy code, but lazy code is buggy code, and frankly, I
don't want it in GNU social if it's going to be buggy.

Consistent exception handling
-------------------------------------------------------------------------------
Consistency is key to good code to begin with, but it is especially important
to be consistent with how we handle errors.  GNU social has a variety of built-
in exception classes.  Use them, wherever it's possible and appropriate, and
they will do the heavy lifting for you.

Additionally, ensure you clean up any and all records and variables that need
cleanup in a function using try { } finally { } even if you do not plan on
catching exceptions (why wouldn't you, though?  That's silly.)

If you do not call an exception handler, you must, at a minimum, record errors
to the log using common_log(level, message)

Ensure all possible control flows of a function have exception handling and
cleanup, where appropriate.  Don't leave endpoints with unhandled exceptions.
Try not to leave something in an error state if it's avoidable.

Return values
-------------------------------------------------------------------------------
All functions must return a value.  Every single one.  This is not optional.

If you are simply making a procedure call, for example as part of a helper
function, then return boolean TRUE on success, and the exception on failure.

When returning the exception, return the whole nine yards, which is to say the
actual PHP exception object, not just an error message.

All return values not the above should be type cast, and you should sanitize
anything returned to ensure it fits into the cast.  You might technically make
an integer a string, for instance, but you should be making sure that integer
SHOULD be a string, if you're returning it, and that it is a valid return
value.

A vast majority of programming errors come down to not checking your inputs
and outputs properly, so please try to do so as best and thoroughly as you can.

Layout and Location of files
-------------------------------------------------------------------------------
`/actions/` contains files that determine what happens when something "happens":
for instance, when someone favourites or repeats a notice.  Code that is
related to a "happening" should go here.

`/classes/` contains abstract definitions of certain "things" in the codebase
such as a user or notice.  If you're making a new "thing", it goes here.

`/lib/` is basically the back-end.  Actions will call something in here to get
stuff done usually, which in turn will probably manipulate information stored
in one or more records represented by a class.

`/extlib/` is where external libraries are located.  If you include a new
external library, it goes here.

`/plugins/` This is a great way to modularize your own new features.  If you want
to create new core features for GNU social, it is probably best to create a
module unless you absolutely must override or modify the core behaviours.


code:
Submission Checklist
================================================================================
This document serves as a handy checklist for submitted merges and patches to
the postActiv project.  Following it isn't a gaurantee a patch will be accepted,
but it will help you avoid common problems.

1. Ensure all code control paths in all functions return a value.

2. Ensure all exceptions are trapped in an exception class, or minimally,
   written to the log with common_log

3. Ensure the coding format standards are adhered to (see coding_standards.md)

4. Ensure that any new class that deals in public data has a corresponding new
   API endpoint.

5. Ensure that all new API endpoints sanitize inputs and outputs properly.

6. Ensure that your version of the code works with PHP 7 on a standard
   LAMP and LEMP stack (Linux+Apache+MariaDB+PHP and Linux+nginx+MariaDB+PHP)

7. If implementing new database functions, ensure they work with MariaDB
   and postgreSQL.

8. Ensure all data that federates does so properly and has mechanisms to
   catch and accomodate for federation transmission failure.

9. Ensure that nothing is left in an error state when it is avoidable.

10. Ensure that all code submitted is properly documented.

11. Ensure that there are no PHP Strict Standards or Parse errors in the code.