vendor/src
doc/generate-api/*
3rdparty/
-src/CMain/bin/*
-src/CMain/lib/*
+
# Test binary, build with `go test -c`
*.test
# Target parameters
PKG_NAME := edge-orchestration
-BIN_DIR := $(BASE_DIR)/bin
-BINARY_FILE := $(PKG_NAME)
-EXEC_SRC_DIR := GoMain
+EXEC_SRC_DIR := GoMain/src
OBJ_SRC_DIR := interface
PKG_DIRS := devicemgr discoverymgr interface restapi/v1 servicemgr scoringmgr orchestrationapi configuremgr
SERVICE_FILE := $(PKG_NAME).service
MANIFEST_FILE := $(PKG_NAME).manifest
+DBUS_SERVICE_FILE := org.tizen.orchestration.service
+DBUS_CONF_FILE := org.tizen.orchestration.conf
+
+# GoMain target
+GOMAIN_DIR := $(BASE_DIR)/src/GoMain
+GOMAIN_BIN_DIR := $(GOMAIN_DIR)/bin
+GOMAIN_BIN_FILE := $(PKG_NAME)
# CMain target
ORG_HEADER_FILE_C := liborchestration.h
-HEADER_FILE_C := orchestration.h
-OBJECT_FILE_C := liborchestration.a
-CMAIN_DIR := $(BASE_DIR)/src/CMain
-CMAIN_INC_DIR := $(CMAIN_DIR)/inc
-CMAIN_BIN_DIR := $(CMAIN_DIR)/bin
-CMAIN_LIB_DIR := $(CMAIN_DIR)/lib
+HEADER_FILE_C := orchestration.h
+OBJECT_FILE_C := liborchestration.a
+CMAIN_BIN_FILE := $(PKG_NAME)
+CMAIN_DIR := $(BASE_DIR)/src/CMain
+CMAIN_INC_DIR := $(CMAIN_DIR)/inc
+CMAIN_BIN_DIR := $(CMAIN_DIR)/bin
+CMAIN_LIB_DIR := $(CMAIN_DIR)/lib
# Library package
LIBPKG_LIB_FILE := liborchestration-client.so
LIBPKG_HEADER_FILE := orchestration_client.h
-LIBPKG_DIR := $(BASE_DIR)/src/libedge-orchestration
-LIBPKG_LIB_DIR := $(LIBPKG_DIR)/lib
-LIBPKG_INC_DIR := $(LIBPKG_DIR)/inc
+LIBPKG_DIR := $(BASE_DIR)/src/libedge-orchestration
+LIBPKG_LIB_DIR := $(LIBPKG_DIR)/lib
+LIBPKG_INC_DIR := $(LIBPKG_DIR)/inc
+LIBPKG_SAMPLE_DIR := $(LIBPKG_DIR)/sample
## edge-orchestration binary build
build-binary:
- $(GOBUILD) -a $(GO_LDFLAGS) -o $(BIN_DIR)/$(BINARY_FILE) $(EXEC_SRC_DIR) || exit 1
- ls -al $(BIN_DIR)
+ $(GOBUILD) -a $(GO_LDFLAGS) -o $(GOMAIN_BIN_DIR)/$(GOMAIN_BIN_FILE) $(EXEC_SRC_DIR) || exit 1
+ ls -al $(GOMAIN_BIN_DIR)
-## edge-orchestration shared object build
+## edge-orchestration static archive build
build-object:
CGO_ENABLED=1 $(GOBUILD) -o $(CMAIN_LIB_DIR)/$(OBJECT_FILE_C) -buildmode=c-archive $(OBJ_SRC_DIR) || exit 1
mv $(CMAIN_LIB_DIR)/$(ORG_HEADER_FILE_C) $(CMAIN_INC_DIR)/$(HEADER_FILE_C)
ls -al $(CMAIN_LIB_DIR)
+## edge-orchestration with d-bus server module
+build-dbus-server:
+ mkdir -p $(CMAIN_BIN_DIR)
$(MAKE) -C $(CMAIN_DIR)
- $(MAKE) -C $(LIBPKG_DIR)
+ ls -al $(CMAIN_BIN_DIR)
+## libedge-orchestration with d-bus client module
+build-dbus-client:
+ $(MAKE) -C $(LIBPKG_DIR)
+ ls -al $(LIBPKG_LIB_DIR)
+ ls -al $(LIBPKG_INC_DIR)
## install output files for packaing
install:
install -d $(DESTDIR)/usr/bin
install -d $(DESTDIR)/etc/$(PKG_NAME)
install -d $(DESTDIR)/usr/lib/systemd/system/multi-user.target.wants
- install -m 755 $(CMAIN_BIN_DIR)/$(BINARY_FILE) $(DESTDIR)/usr/bin/$(BINARY_FILE)
+ install -d $(DESTDIR)/usr/share/dbus-1/system-services
+ install -d $(DESTDIR)/etc/dbus-1/system.d
+ install -m 755 $(CMAIN_BIN_DIR)/$(CMAIN_BIN_FILE) $(DESTDIR)/usr/bin/$(CMAIN_BIN_FILE)
+ install -m 755 $(LIBPKG_SAMPLE_DIR)/orchestration_sample $(DESTDIR)/usr/bin/orchestration_sample
install -m 644 $(BASE_DIR)/$(SERVICE_FILE) $(DESTDIR)/usr/lib/systemd/system/$(SERVICE_FILE)
ln -s ../$(SERVICE_FILE) $(DESTDIR)/usr/lib/systemd/system/multi-user.target.wants/
+
+ install -m 644 $(BASE_DIR)/$(DBUS_SERVICE_FILE) $(DESTDIR)/usr/share/dbus-1/system-services/$(DBUS_SERVICE_FILE)
+ install -m 644 $(BASE_DIR)/$(DBUS_CONF_FILE) $(DESTDIR)/etc/dbus-1/system.d/$(DBUS_CONF_FILE)
install -d $(DESTDIR)/usr/lib
install -d $(DESTDIR)/usr/include/$(PKG_NAME)
$(GOCLEAN)
-rm -f $(CMAIN_INC_DIR)/$(HEADER_FILE_C)
-rm -f $(CMAIN_LIB_DIR)/$(OBJECT_FILE_C)
- -rm -f $(BIN_DIR)/*
+ -rm -f $(GOMAIN_BIN_DIR)/$(GOMAIN_BIN_FILE)
+ -rm -f $(CMAIN_BIN_DIR)/$(CMAIN_BIN_FILE)
## check go style and static analysis
lint:
BINARY_FILE="edge-orchestration"
SERVICE_DIR="/etc/systemd/system"
+BINARY_DIR=$BASE_DIR/"src/CMain/bin"
SERVICE_FILE="edge-orchestration.service"
+DBUS_CONF_FILE="org.tizen.orchestration.conf"
+DBUS_CONF_FILE_x86="org.tizen.orchestration.conf.amd64"
+DBUS_SERVICE_FILE="org.tizen.orchestration.service"
+DBUS_CONF_DIR="/etc/dbus-1/system.d"
+DBUS_SERVICE_DIR="/usr/share/dbus-1/system-services"
BUILD_ALL=false
function install_prerequisite() {
+ echo ""
+ echo "-----------------------------------"
+ echo " Install prerequisite packages"
+ echo "-----------------------------------"
go get github.com/axw/gocov/gocov
go get github.com/matm/gocov-html
go get github.com/Songmu/make2help/cmd/make2help
}
function build_clean() {
+ echo ""
+ echo "-----------------------------------"
+ echo " Build clean"
+ echo "-----------------------------------"
make clean
}
function build_binary() {
+ echo ""
+ echo "----------------------------------------"
+ echo " Create Executable binary from GoMain"
+ echo "----------------------------------------"
make build-binary
}
function build_object() {
+ echo ""
+ echo "----------------------------------------"
+ echo " Create Static object of Orchestration"
+ echo "----------------------------------------"
make build-object
}
+function build_dbus() {
+ echo ""
+ echo "----------------------------------------"
+ echo " Create Executable binary from CMain"
+ echo "----------------------------------------"
+ make build-dbus-server
+
+ echo ""
+ echo "--------------------------------------------------"
+ echo " Create Shared Object from libedge-orchestration"
+ echo "--------------------------------------------------"
+ make build-dbus-client
+}
+
function build_test() {
+ echo ""
+ echo "-------------------------------------"
+ echo " Build test to calculate Coverage"
+ echo "-------------------------------------"
+ mkdir -p /tmp/foo
make test
}
function lint_src_code() {
+ echo ""
+ echo "---------------------------------------"
+ echo " Analysis source code golint & go vet"
+ echo "---------------------------------------"
make lint
}
echo "-----------------------------------"
sudo systemctl stop $SERVICE_FILE
- sudo cp $BASE_DIR/bin/$BINARY_FILE /usr/bin
+ sudo cp $BINARY_DIR/$BINARY_FILE /usr/bin
sudo cp $BASE_DIR/packaging/$SERVICE_FILE $SERVICE_DIR
+ sudo cp $BASE_DIR/packaging/$DBUS_CONF_FILE_x86 $DBUS_CONF_DIR/$DBUS_CONF_FILE
+ sudo cp $BASE_DIR/packaging/$DBUS_SERVICE_FILE $DBUS_SERVICE_DIR
ls -al $SERVICE_DIR | grep $SERVICE_FILE
echo ""
install_prerequisite
build_test
build_object
+ build_dbus
build_binary
lint_src_code
register_service
+++ /dev/null
-## Generating api server code with Swagger tool from JSON
-### 1. Prerequisite
-Make sure Java version 1.8 or later is installed on your computer.
-To check java version, please open terminal and type
-```
-$ java -version
-java version "1.8.0_201"
-Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
-Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
-```
-### 2. Code generate
-```
-$ java -jar swagger-codegen-cli.jar generate -i edge-orchestration-api.json -l go-server
-```
+++ /dev/null
-{
- "swagger": "2.0",
- "info": {
- "title": "Edge Orchestration",
- "description": "Edge Orchestration support to deliver distributed service process environment.",
- "version": "v1-20190318"
- },
- "schemes": [
- "http"
- ],
- "tags": [
- {
- "name": "Discovery Manager",
- "description": "Edge Discovery"
- },
- {
- "name": "Device Resource",
- "description": "Device resource"
- },
- {
- "name": "Service Manager",
- "description": "Managing services"
- }
- ],
- "paths": {
- "/api/v1/discoverymgr/devices": {
- "get": {
- "tags": [
- "Discovery Manager"
- ],
- "description": "Get result of Edge discovery",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/DiscoveryEdgeList"
- }
- }
- }
- }
- },
- "/api/v1/discoverymgr/devices/{deviceid}": {
- "get": {
- "tags": [
- "Discovery Manager"
- ],
- "description": "Get result of Edge discovery",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "deviceid",
- "in": "path",
- "description": "ID of Edge device",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/DiscoveryEdge"
- }
- }
- }
- }
- },
- "/api/v1/device/resource/usage/cpu": {
- "get": {
- "tags": [
- "Device Resource"
- ],
- "description": "Get device cpu usage",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/CPU"
- }
- }
- }
- }
- },
- "/api/v1/device/resource/usage/memory": {
- "get": {
- "tags": [
- "Device Resource"
- ],
- "description": "Get device memory usage",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/Memory"
- }
- }
- }
- }
- },
- "/api/v1/device/resource/usage/network": {
- "get": {
- "tags": [
- "Device Resource"
- ],
- "description": "Get device network usage",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/Network"
- }
- }
- }
- }
- },
- "/api/v1/device/resource/usage/disk": {
- "get": {
- "tags": [
- "Device Resource"
- ],
- "description": "Get device disk usage",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/Disk"
- }
- }
- }
- }
- },
- "/api/v1/servicemgr/services": {
- "post": {
- "tags": [
- "Service Manager"
- ],
- "description": "Create user app object",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "in": "body",
- "name": "body",
- "description": "Parameters for request user service creation",
- "schema": {
- "$ref": "#/definitions/ServiceRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/ServiceObjectList"
- }
- }
- }
- },
- "delete": {
- "tags": [
- "Service Manager"
- ],
- "description": "Destroy user app object",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "appname",
- "in": "path",
- "description": "Name of user app to be destroied",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/APIResponse"
- }
- }
- }
- },
- "get": {
- "tags": [
- "Service Manager"
- ],
- "description": "Get information of user service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/ServiceList"
- }
- }
- }
- }
- },
- "/api/v1/servicemgr/services/{serviceid}": {
- "post": {
- "tags": [
- "Service Manager"
- ],
- "description": "Execute requested service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "serviceid",
- "in": "path",
- "description": "ID of micro-service",
- "required": true,
- "type": "string"
- },
- {
- "in": "body",
- "name": "serviceParam",
- "description": "Parameters for request micro-service creation",
- "schema": {
- "$ref": "#/definitions/MicroServiceRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/APIResponse"
- }
- }
- }
- },
- "delete": {
- "tags": [
- "Service Manager"
- ],
- "description": "Destroy requested micro-service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "serviceid",
- "in": "path",
- "description": "ID of micro-service",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/APIResponse"
- }
- }
- }
- },
- "get": {
- "tags": [
- "Service Manager"
- ],
- "description": "Get information of micro-service",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "serviceid",
- "in": "path",
- "description": "ID of micro-service",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/MicroServiceInfo"
- }
- }
- }
- }
- },
- "/api/v1/servicemgr/services/{appname}": {
- "get": {
- "tags": [
- "Service Manager"
- ],
- "description": "Get information of app",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "appname",
- "in": "path",
- "description": "Name of executed app",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "Successful operation",
- "schema": {
- "$ref": "#/definitions/ServiceInfo"
- }
- }
- }
- }
- }
- },
- "definitions": {
- "EdgeResource": {
- "type": "string",
- "description": "H/W resource of Edge device can support"
- },
- "DiscoveryEdge": {
- "type": "object",
- "properties": {
- "deviceID": {
- "type": "string",
- "description": "Unique ID of Edge device",
- "example": "edge-0001"
- },
- "deviceIPAddr": {
- "type": "string",
- "description": "IP address of Edge device",
- "example": "10.113.175.249"
- },
- "deviceProperties": {
- "type": "array",
- "description": "Edge resources",
- "items": {
- "$ref": "#/definitions/EdgeResource"
- },
- "example": [
- "display",
- "speaker"
- ]
- },
- "status": {
- "type": "string",
- "description": "Status of Edge device",
- "example": "Up"
- }
- }
- },
- "DiscoveryEdgeList": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/DiscoveryEdge"
- }
- },
- "CPU": {
- "type": "object",
- "properties": {
- "cpu": {
- "type": "string",
- "description": "Usage of CPU",
- "example": "0.187383"
- }
- }
- },
- "Memory": {
- "type": "object",
- "properties": {
- "memory": {
- "type": "string",
- "description": "Usage of Memory",
- "example": "11.871336"
- }
- }
- },
- "Network": {
- "type": "object",
- "properties": {
- "network": {
- "type": "string",
- "description": "Usage of Network",
- "example": "0.003023"
- }
- }
- },
- "Disk": {
- "type": "object",
- "properties": {
- "network": {
- "type": "string",
- "description": "Usage of Disk",
- "example": ""
- }
- }
- },
- "SystemParam": {
- "type": "object",
- "properties": {
- "ipAddr": {
- "type": "string",
- "description": "IP Addr of device requesting service",
- "example": "127.0.0.1"
- },
- "port": {
- "type": "string",
- "description": "Port number of device requesting service",
- "example": "5432"
- }
- }
- },
- "UserParam": {
- "type": "object",
- "properties": {
- "arguments": {
- "type": "string",
- "description": "User parameter of requesting service",
- "example": "5"
- }
- }
- },
- "MicroServiceRequest": {
- "type": "object",
- "properties": {
- "systemParam": {
- "$ref": "#/definitions/SystemParam"
- },
- "userParam": {
- "$ref": "#/definitions/UserParam"
- }
- }
- },
- "ServiceObject" : {
- "type": "object",
- "properties": {
- "id": {
- "type": "integer",
- "description": "Created ID of micro-service"
- },
- "time": {
- "type": "string",
- "description": "The time when micro-service created"
- }
- }
- },
- "ServiceObjectList": {
- "type": "object",
- "properties": {
- "serviceList": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/ServiceObject"
- },
- "example": [
- {
- "id": 2,
- "time": "2019-03-14T13:43:38+09:00"
- },
- {
- "id": 3,
- "time": "2019-03-14T13:43:38+09:00"
- }
- ]
- }
- }
- },
- "ServiceRequest": {
- "type": "object",
- "properties": {
- "appName": {
- "type": "string",
- "description": "Name of requested user service",
- "example": "GreetWorldApp"
- },
- "serviceName": {
- "type": "string",
- "description": "Name of micro-services",
- "example": "HelloWorldService"
- },
- "count": {
- "type": "integer",
- "description": "Count of micro-services",
- "example": 2
- }
- }
- },
- "ServiceInfo": {
- "type": "object",
- "properties": {
- "appName": {
- "type": "string",
- "description": "Name of requested user service",
- "example": "GreetWorldApp"
- },
- "serviceList": {
- "$ref": "#/definitions/MicroServiceList"
- }
- }
- },
- "ServiceList": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/ServiceInfo"
- }
- },
- "MicroServiceInfo": {
- "type": "object",
- "properties": {
- "serviceName": {
- "type": "string",
- "description": "Service name",
- "example": "HelloWorldService#1"
- },
- "serviceID": {
- "type": "integer",
- "description": "Unique ID of service",
- "example": 2
- },
- "status": {
- "type": "string",
- "description": "Status of service",
- "example": "started"
- },
- "deviceID": {
- "type": "string",
- "description": "DeviceID on which the service is operating",
- "example": "Edge_deviceID"
- }
- }
- },
- "MicroServiceList": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/MicroServiceInfo"
- },
- "example": [
- {
- "serviceName": "HelloWorldService#1",
- "serviceID": 2,
- "status": "started",
- "deviceID": "10.113.175.249"
- },
- {
- "serviceName": "HelloWorldService#2",
- "serviceID": 3,
- "status": "progressing",
- "deviceID": "10.113.175.239"
- }
- ]
- },
- "APIResponse": {
- "type": "object",
- "properties": {
- "message": {
- "type": "string",
- "description": "Return the status of the request"
- }
- }
- }
- }
-}
-hash: f3dda71f15975f5d756f660cd512e4e3fd7491866747d0cd321f1f751d84d50a
-updated: 2019-03-15T15:03:21.804492286+09:00
+hash: 01e8a43264f5fb6cd2c54a833592025be7cec8a4a5162f2133afc94c3ebff604
+updated: 2019-04-08T09:15:47.971469451+09:00
imports:
- name: github.com/cenkalti/backoff
version: 1e4cf3da559842a91afcb6ea6141451e6c30c618
+- name: github.com/fsnotify/fsnotify
+ version: 1485a34d5d5723fea214f5710708e19a831720e4
- name: github.com/gorilla/mux
version: 15a353a636720571d19e37b34a14499c3afa9991
- name: github.com/grandcat/zeroconf
version: c2d1b4121200e6bf8490af806aa6f3dc6d37446b
+- name: github.com/leemcloughlin/logfile
+ version: 38a861f7eb6b5abd75eda92a133d0359b6726654
- name: github.com/miekg/dns
version: 1f99ca2fa4cadf29da1c0b4bf629a7424818ed31
- name: golang.org/x/crypto
version: fead79001313d15903fb4605b4a1b781532cd93e
subpackages:
- unix
+- name: gopkg.in/sconf/ini.v0
+ version: 2cb1a94f90adf8bf08bfdf8b95518b46d5d9acfa
+- name: gopkg.in/sconf/internal.v0
+ version: 96fc0fdbccb38d6d1e0155e0220996cc8c2f1101
+ subpackages:
+ - internal-
+ - internal-/gcfg
+ - internal-/gcfg/scanner
+ - internal-/gcfg/token
+ - internal-/gcfg/types
+- name: gopkg.in/sconf/sconf.v0
+ version: 07f58c244aed144400f36fcb544eb46e8786951f
testImports: []
- package: github.com/grandcat/zeroconf
- package: gopkg.in/sconf/ini.v0
- package: gopkg.in/sconf/sconf.v0
+- package: github.com/leemcloughlin/logfile
Requires=dbus.socket
[Service]
+Type=dbus
+BusName=org.tizen.orchestration
SmackProcessLabel=System
-Type=simple
ExecStart=/usr/bin/edge-orchestration
Restart=always
RestartSec=0
Source1: %{name}.manifest
Source2: %{name}.service
Source3: lib%{name}.manifest
+Source4: org.tizen.orchestration.service
+Source5: org.tizen.orchestration.conf
Source11: go1.12.linux-armv7.tar.gz
-###BuildRequires: pkgconfig(dlog)
+BuildRequires: pkgconfig(dlog)
BuildRequires: pkgconfig(glib-2.0)
BuildRequires: pkgconfig(gio-2.0)
BuildRequires: pkgconfig(gio-unix-2.0)
-#BuildRequires: pkgconfig(cynara-client)
-#BuildRequires: pkgconfig(cynara-session)
-#BuildRequires: pkgconfig(cynara-creds-gdbus)
Requires(post): dbus
Requires(post): /sbin/ldconfig, /usr/bin/systemctl
cp %{SOURCE1} ./%{name}.manifest
cp %{SOURCE2} ./%{name}.service
cp %{SOURCE3} ./lib%{name}.manifest
+cp %{SOURCE4} ./
+cp %{SOURCE5} ./
%ifarch armv7l
cp %{SOURCE11} ./
tar -zxf %{SOURCE11}
make clean
make build-object %{?_smp_mflags}
+make build-dbus-server %{?_smp_mflags}
+make build-dbus-client %{?_smp_mflags}
%install
export BASE_DIR=.
%{_bindir}/%{name}
%{_unitdir}/%{name}.service
%{_unitdir}/multi-user.target.wants/%{name}.service
+%{_datadir}/dbus-1/system-services/org.tizen.orchestration.service
+%{_sysconfdir}/dbus-1/system.d/org.tizen.orchestration.conf
%dir %{_sysconfdir}/%{name}
%files -n libedge-orchestration
%license LICENSE.Apache-2.0
%defattr(-,root,root,-)
%{_libdir}/liborchestration-client.so
+%{_bindir}/orchestration_sample
%files -n libedge-orchestration-devel
%manifest lib%{name}.manifest
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+ <policy user="root">
+ <allow own="org.tizen.orchestration"/>
+ </policy>
+ <policy context="default">
+ <allow own="org.tizen.orchestration"/>
+ <allow send_destination="org.tizen.orchestration"/>
+ <allow send_interface="org.tizen.orchestration.agent"/>
+ <check send_destination="org.tizen.orchestration" send_interface="org.tizen.orchestration.agent"
+ send_member="request_service" privilege="http://tizen.org/privilege/appmanager.launch"/>
+ </policy>
+</busconfig>
--- /dev/null
+[D-BUS Service]
+Name=org.tizen.orchestration
+User=root
+Exec=/bin/false
+SystemdService=edge-orchestration.service
+
# Target parameters
BIN_DIR := $(BASE_DIR)/bin
-LIB_DIR := $(BASE_DIR)/lib
-INC_DIR := $(BASE_DIR)/inc
-SRC_DIR := $(BASE_DIR)/src
+LIB_DIR := $(BASE_DIR)/lib
+INC_DIR := $(BASE_DIR)/inc
+SRC_DIR := $(BASE_DIR)/src
LIBRARY_FILE := orchestration
-BINARY_FILE := edge-orchestration
+BINARY_FILE := edge-orchestration
SRC_FILES := \
$(SRC_DIR)/main.c \
$(SRC_DIR)/orchestration_server.c
+OBJ_FILES := $(BASE_DIR)/*.o
# Build parameter
CC := gcc
all: clean build
build:
- mkdir -p $(BIN_DIR)
- $(CC) $(CFLAGS) -o $(BIN_DIR)/$(BINARY_FILE) -I $(INC_DIR) `pkg-config --libs --cflags gio-2.0 gio-unix-2.0 glib-2.0` $(SRC_FILES) -L$(LIB_DIR) -l$(LIBRARY_FILE)
+ $(CC) $(CFLAGS) $(SRC_FILES) -o $(BIN_DIR)/$(BINARY_FILE) -I $(INC_DIR) `pkg-config --libs --cflags gio-2.0 gio-unix-2.0 glib-2.0` -L$(LIB_DIR) -l$(LIBRARY_FILE) -ldl
+ -rm -f $(OBJ_FILES)
clean:
- -rm -f $(SRC_DIR)/*.o
+ -rm -f $(OBJ_FILES)
-rm -f $(BIN_DIR)/$(BINARY_FILE)
.PHONY: build clean
\ No newline at end of file
extern int OrchestrationInit();
-extern int OrchestrationRequestService();
+extern int OrchestrationRequestService(char* p0, char* p1);
+
+extern int PrintLog(char* p0);
#ifdef __cplusplus
}
OrchestrationInit();
printf("orchestration_server_initialize call");
+ PrintLog("orchestration_server_initialize call");
result = orchestration_server_initialize(request_cb);
if(result != ORCH_ERROR_NONE){
printf("orchestration_server_initialize failed\n");
orchestration_server_finish();
return 0;
-}
\ No newline at end of file
+}
+++ /dev/null
-/*
- * Edge Orchestration
- *
- * Edge Orchestration support to deliver distributed service process environment.
- *
- * API version: v1-20190307
- * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
- */
-
-package main
-
-import (
- _ "devicemgr"
- _ "discoverymgr"
- "log"
- _ "log"
- _ "net/http"
-
- // restapi "restapi/v1"
- _ "servicemgr"
-)
-
-func main() {
- log.Printf("[%s] Server started", logPrefix)
-
- // devicemgr.InitDeviceMgr()
- // servicemgr.InitServiceMap()
- // discoverymgr.InitDiscovery()
- // router := restapi.NewRouter()
-
- // log.Fatal(http.ListenAndServe(":9090", router))
-
-}
--- /dev/null
+/*
+ * Edge Orchestration
+ *
+ * Edge Orchestration support to deliver distributed service process environment.
+ *
+ * API version: v1-20190307
+ * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
+ */
+
+package main
+
+import (
+ "log"
+ "logmgr"
+ "net/http"
+ restapi "restapi/v1"
+
+ "devicemgr"
+ "discoverymgr"
+ "securemgr"
+ "servicemgr"
+)
+
+// @FIXME: Need to find how to manage key
+var keyFilePath string = "key.txt"
+
+func main() {
+ logmgr.Init()
+ log.Printf("[%s] Server started", logPrefix)
+
+ devicemgr.InitDeviceMgr()
+ servicemgr.Init()
+ discoverymgr.InitDiscovery()
+ securemgr.Init(keyFilePath)
+ router := restapi.NewRouter()
+
+ log.Fatal(http.ListenAndServe(":9090", router))
+
+}
--- /dev/null
+package common
+
+import (
+ "log"
+ "net"
+)
+
+func GetOutboundIP() (addr string, err error) {
+ conn, err := net.Dial("udp", "8.8.8.8:80")
+ if err != nil {
+ return
+ }
+ defer conn.Close()
+
+ localAddr := conn.LocalAddr().(*net.UDPAddr)
+ addr = localAddr.IP.String()
+ log.Println("[getOutboundIP]", addr)
+ return
+}
package configuremgr_test
import (
- "testing"
- "time"
- "os/exec"
- "fmt"
+ "fmt"
+ "os/exec"
+ "testing"
+ "time"
- configuremgr "configuremgr"
- mockconfiguremgr "configuremgr/mock"
+ configuremgr "configuremgr"
+ mockconfiguremgr "configuremgr/mock"
)
+func TestBasicMockConfigureMgr(t *testing.T) {
-func TestBasicMockConfigureMgr(t *testing.T){
+ //copy event environment
+ watchDir := "/tmp/foo"
+ src := "./mock/mysum"
+ dst := watchDir
- //copy event environment
- watchDir := "/tmp/foo"
- src := "./mock/mysum"
- dst := watchDir
+ //linking interface
+ //mock function
+ //test object
+ configuremgrObj := configuremgr.Init()
+ configuremgrObj.IDiscoveryMgr.PushConfPath = mockconfiguremgr.PushConfPathDiscoveryDeviceMock
+ configuremgrObj.IScoringMgr.PushLibPath = mockconfiguremgr.PushLibPathScoringAppMock
- //linking interface
- //mock function
+ go configuremgrObj.Watch(watchDir)
- //test object
- configuremgrObj := configuremgr.Init()
- configuremgrObj.IDiscoveryMgr.PushConfPath = mockconfiguremgr.PushConfPathDiscoveryDeviceMock
- configuremgrObj.IScoringMgr.PushLibPath = mockconfiguremgr.PushLibPathScoringAppMock
-
+ //TODO : push /tmp/foo/simple directory using Cmd package
+ time.Sleep(time.Duration(1 * time.Second))
- go configuremgrObj.Watch(watchDir)
+ //init scenario
+ execCommand("rm -rf /tmp/foo/mysum")
+ time.Sleep(time.Duration(1) * time.Second)
- //TODO : push /tmp/foo/simple directory using Cmd package
- time.Sleep(time.Duration(1 * time.Second))
-
-
- //init scenario
- execCommand("rm -rf /tmp/foo/mysum")
- time.Sleep(time.Duration(1) * time.Second)
+ //user scenario
+ execCommand(fmt.Sprintf("cp -ar %s %s", src, dst))
+ time.Sleep(time.Duration(5) * time.Second)
- //user scenario
- execCommand(fmt.Sprintf("cp -ar %s %s", src, dst))
- time.Sleep(time.Duration(5) * time.Second)
-
- configuremgrObj.Done <- true
+ configuremgrObj.Done <- true
}
-
func execCommand(command string) {
- configuremgr.DLog.Println(command)
- cmd := exec.Command("sh", "-c", command)
- stdoutStderr, err := cmd.CombinedOutput()
- configuremgr.DLog.Printf("%s", stdoutStderr)
- if err != nil {
- configuremgr.ELog.Fatal(err)
- }
-}
\ No newline at end of file
+ configuremgr.DLog.Println(command)
+ cmd := exec.Command("sh", "-c", command)
+ stdoutStderr, err := cmd.CombinedOutput()
+ configuremgr.DLog.Printf("%s", stdoutStderr)
+ if err != nil {
+ configuremgr.ELog.Fatal(err)
+ }
+}
package confdescription
type Doc struct {
- Version struct {
- ConfVersion string
- }
- ServiceInfo struct {
- ServiceName string
- }
- ScoringMethod struct {
- LibFile string
- FunctionName string
- }
- ResourceType struct {
- IntervalTimeMs int
- MaxCount int
- }
-
-}
\ No newline at end of file
+ Version struct {
+ ConfVersion string
+ }
+ ServiceInfo struct {
+ ServiceName string
+ }
+ ScoringMethod struct {
+ LibFile string
+ FunctionName string
+ }
+ ResourceType struct {
+ IntervalTimeMs int
+ MaxCount int
+ }
+}
package configuremgr
import (
- "log"
- "os"
+ "log"
+ "os"
)
var ILog *log.Logger = log.New(os.Stdout, "[configuremgr] INFO : ", log.LstdFlags)
var ELog *log.Logger = log.New(os.Stdout, "[configuremgr] ERROR : ", log.LstdFlags)
-var DLog *log.Logger = log.New(os.Stdout, "[configuremgr] DEBUG : ", log.LstdFlags)
\ No newline at end of file
+var DLog *log.Logger = log.New(os.Stdout, "[configuremgr] DEBUG : ", log.LstdFlags)
)
func main() {
- getdirname("/tmp/foo/mysum")
- getdirname("/tmp/foo/mysum/")
+ getdirname("/tmp/foo/mysum")
+ getdirname("/tmp/foo/mysum/")
}
func getdirname(path string) {
- idx := strings.LastIndex(path, "/")
- if idx == len(path) - 1 {
- path = path[:len(path)-1]
- }
-
- dirname := path[strings.LastIndex(path, "/") + 1:]
+ idx := strings.LastIndex(path, "/")
+ if idx == len(path)-1 {
+ path = path[:len(path)-1]
+ }
- libPath := path + "/" + "lib"+ dirname + ".so"
- confPath := path + "/" + dirname + ".conf"
+ dirname := path[strings.LastIndex(path, "/")+1:]
- fmt.Println("libPath : " + libPath)
- fmt.Println("confPath : " + confPath)
+ libPath := path + "/" + "lib" + dirname + ".so"
+ confPath := path + "/" + dirname + ".conf"
-}
\ No newline at end of file
+ fmt.Println("libPath : " + libPath)
+ fmt.Println("confPath : " + confPath)
+
+}
)
func main() {
- Example()
+ Example()
}
func Example() {
sconf.Must(&cfg).Read(ini.File("./src/configuremgr/incubator/example.ini"))
fmt.Println(cfg.Main.Url)
// Output: http://localhost/
-}
\ No newline at end of file
+}
-package main\r
-\r
-import (\r
- "fmt"\r
- \r
- "gopkg.in/sconf/ini.v0"\r
- "gopkg.in/sconf/sconf.v0"\r
-)\r
-\r
-func main() {\r
- Example()\r
-}\r
-\r
-type doc struct {\r
- Version struct {\r
- ConfVersion string\r
- }\r
- ServiceInfo struct {\r
- ServiceName string\r
- ExecFilePath string\r
- }\r
- ScoringMethod struct {\r
- LibFile string\r
- FuncName string\r
- }\r
- ResourceType struct {\r
- IntervalTimeMs int\r
- MaxCount int\r
- }\r
-\r
-}\r
-\r
-func Example() {\r
- var cfg = new(doc)\r
- sconf.Must(cfg).Read(ini.File("./src/configuremgr/incubator/simple.ini"))\r
- // fmt.Println(cfg.Version.ConfVersion)\r
- fmt.Println(cfg)\r
-\r
- // Output: http://localhost/\r
-}
\ No newline at end of file
+package main
+
+import (
+ "fmt"
+
+ "gopkg.in/sconf/ini.v0"
+ "gopkg.in/sconf/sconf.v0"
+)
+
+func main() {
+ Example()
+}
+
+type doc struct {
+ Version struct {
+ ConfVersion string
+ }
+ ServiceInfo struct {
+ ServiceName string
+ ExecFilePath string
+ }
+ ScoringMethod struct {
+ LibFile string
+ FuncName string
+ }
+ ResourceType struct {
+ IntervalTimeMs int
+ MaxCount int
+ }
+}
+
+func Example() {
+ var cfg = new(doc)
+ sconf.Must(cfg).Read(ini.File("./src/configuremgr/incubator/simple.ini"))
+ // fmt.Println(cfg.Version.ConfVersion)
+ fmt.Println(cfg)
+
+ // Output: http://localhost/
+}
-package main\r
-\r
-import (\r
- "log"\r
- "github.com/fsnotify/fsnotify"\r
-)\r
-\r
-func main() {\r
-\r
- watcher, err := fsnotify.NewWatcher()\r
- if err != nil {\r
- log.Fatal(err)\r
- }\r
- defer watcher.Close()\r
-\r
- done := make(chan bool)\r
- go func() {\r
- for {\r
- select {\r
- case event, ok := <-watcher.Events:\r
- log.Println("log event:", event)\r
- \r
- if !ok {\r
- return\r
- }\r
- if event.Op & fsnotify.Create == fsnotify.Create {\r
- log.Println("Created " + event.Name)\r
- }\r
- case err, ok := <-watcher.Errors:\r
- if !ok {\r
- return\r
- }\r
- log.Println("error:", err)\r
- // default:\r
- // fmt.Println("default")\r
- }\r
- }\r
- }()\r
-\r
- err = watcher.Add("/tmp/foo")\r
- if err != nil {\r
- log.Fatal(err)\r
- }\r
- <-done\r
-}
\ No newline at end of file
+package main
+
+import (
+ "github.com/fsnotify/fsnotify"
+ "log"
+)
+
+func main() {
+
+ watcher, err := fsnotify.NewWatcher()
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer watcher.Close()
+
+ done := make(chan bool)
+ go func() {
+ for {
+ select {
+ case event, ok := <-watcher.Events:
+ log.Println("log event:", event)
+
+ if !ok {
+ return
+ }
+ if event.Op&fsnotify.Create == fsnotify.Create {
+ log.Println("Created " + event.Name)
+ }
+ case err, ok := <-watcher.Errors:
+ if !ok {
+ return
+ }
+ log.Println("error:", err)
+ // default:
+ // fmt.Println("default")
+ }
+ }
+ }()
+
+ err = watcher.Add("/tmp/foo")
+ if err != nil {
+ log.Fatal(err)
+ }
+ <-done
+}
package mockconfiguremgr
import (
- configuremgr "configuremgr"
- confdescription "configuremgr/description"
+ configuremgr "configuremgr"
+ confdescription "configuremgr/description"
)
-func PushConfPathDiscoveryDeviceMock (doc *confdescription.Doc) (err error) {
- configuremgr.ILog.Println("pushConfPathDiscoveryDeviceMock")
- configuremgr.ILog.Println(*doc)
- return
+func PushConfPathDiscoveryDeviceMock(doc *confdescription.Doc) (err error) {
+ configuremgr.ILog.Println("pushConfPathDiscoveryDeviceMock")
+ configuremgr.ILog.Println(*doc)
+ return
}
func PushConfPathAppExecuteMock(doc *confdescription.Doc) (err error) {
- configuremgr.ILog.Println("pushConfPathAppExecuteMock")
- configuremgr.ILog.Println(*doc)
- return
+ configuremgr.ILog.Println("pushConfPathAppExecuteMock")
+ configuremgr.ILog.Println(*doc)
+ return
}
-func PushLibPathScoringAppMock(libPath string, doc *confdescription.Doc, handlersCh chan<- interface{}) (err error) {
- configuremgr.ILog.Println("pushLibPathScoringAppMock")
- configuremgr.ILog.Println(libPath)
- configuremgr.ILog.Println(doc)
- return
+func PushLibPathScoringAppMock(libPath string, doc *confdescription.Doc, handlersCh chan<- interface{}) (err error) {
+ configuremgr.ILog.Println("pushLibPathScoringAppMock")
+ configuremgr.ILog.Println(libPath)
+ configuremgr.ILog.Println(doc)
+ return
}
-
--- /dev/null
+package configuremgr
+
+var logPrefix = "configuremgr"
package configuremgr
import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
- "strings"
- "os"
-
- confdescription "configuremgr/description"
+ // "time"
+
+ confdescription "configuremgr/description"
"github.com/fsnotify/fsnotify"
- "gopkg.in/sconf/ini.v0"
+ "gopkg.in/sconf/ini.v0"
"gopkg.in/sconf/sconf.v0"
)
type ConfigureMgr struct {
+ IDiscoveryMgr struct {
+ PushConfPath func(*confdescription.Doc) error
+ }
- IDiscoveryMgr struct {
- PushConfPath func(*confdescription.Doc) (error)
- }
-
- IAppExecuteMgr struct {
- // PushConfPath func(*confdescription.Doc) (error)
- // ExecuteApp func(target string, name string, args []string, notiChan chan string) (serviceID uint64, err error)
- }
+ IAppExecuteMgr struct {
+ // PushConfPath func(*confdescription.Doc) (error)
+ // ExecuteApp func(target string, name string, args []string, notiChan chan string) (serviceID uint64, err error)
+ }
- IScoringMgr struct {
- PushLibPath func(libPath string, doc *confdescription.Doc, handlersCh chan<- interface{}) (error)
- Ch chan interface{}
- }
+ IScoringMgr struct {
+ PushLibPath func(libPath string, doc *confdescription.Doc, handlersCh chan<- interface{}) error
+ Ch chan interface{}
+ }
- Done chan bool
+ Done chan bool
}
func Init() (configuremgrObj *ConfigureMgr) {
- configuremgrObj = new(ConfigureMgr)
- configuremgrObj.Done = make(chan bool)
+ configuremgrObj = new(ConfigureMgr)
+ configuremgrObj.Done = make(chan bool)
- return
+ return
}
func (cfgMgr *ConfigureMgr) installConfigure(path string) {
-//1. libPath, conf := diretoryname get
-//2. push libPath to scoringMgr
-//3. conf context send discoverydevicemgr
-//4. conf context send executeappmgr
+ //1. libPath, conf := diretoryname get
+ //2. push libPath to scoringMgr
+ //3. conf context send discoverydevicemgr
+ //4. conf context send executeappmgr
+
+ cfg := new(confdescription.Doc)
- cfg := new(confdescription.Doc)
+ libPath, confPath := cfgMgr.getdirname(path)
- libPath, confPath := cfgMgr.getdirname(path)
+ //NOTE : copy but really copy, it can be not existed.
+ for {
+ if _, err := os.Stat(confPath); err == nil {
+ break
+ }
+ }
- //NOTE : copy but really copy, it can be not existed.
- for {
- if _, err := os.Stat(confPath); os.IsNotExist(err) {
+ for {
+ if _, err := os.Stat(libPath); err == nil {
+ break
+ }
+ }
- }else{
- break
- }
- }
- sconf.Must(cfg).Read(ini.File(confPath))
- cfgMgr.IScoringMgr.PushLibPath(libPath, cfg, cfgMgr.IScoringMgr.Ch)
- cfgMgr.IDiscoveryMgr.PushConfPath(cfg)
- // cfgMgr.IAppExecuteMgr.PushConfPath(cfg)
+ sconf.Must(cfg).Read(ini.File(confPath))
+ cfgMgr.IScoringMgr.PushLibPath(libPath, cfg, cfgMgr.IScoringMgr.Ch)
+ cfgMgr.IDiscoveryMgr.PushConfPath(cfg)
+ // cfgMgr.IAppExecuteMgr.PushConfPath(cfg)
}
func (cfgMgr *ConfigureMgr) getdirname(path string) (libPath, confPath string) {
- idx := strings.LastIndex(path, "/")
- if idx == len(path) - 1 {
- path = path[:len(path)-1]
- }
-
- dirname := path[strings.LastIndex(path, "/") + 1:]
+ idx := strings.LastIndex(path, "/")
+ if idx == len(path)-1 {
+ path = path[:len(path)-1]
+ }
- libPath = path + "/" + "lib"+ dirname + ".so"
- confPath = path + "/" + dirname + ".conf"
+ dirname := path[strings.LastIndex(path, "/")+1:]
- DLog.Println("libPath : " + libPath)
- DLog.Println("confPath : " + confPath)
+ libPath = path + "/" + "lib" + dirname + ".so"
+ confPath = path + "/" + dirname + ".conf"
- return
+ DLog.Println("libPath : " + libPath)
+ DLog.Println("confPath : " + confPath)
+
+ return
}
func (cfgMgr *ConfigureMgr) Watch(path string) {
+ // logic for already installed configuration
+ files, err := ioutil.ReadDir(path)
+ if err != nil {
+ ELog.Fatal(err)
+ }
+
+ for _, f := range files {
+ cfgMgr.installConfigure(filepath.Join(path, f.Name()))
+ }
+
+ watcher, err := fsnotify.NewWatcher()
+ if err != nil {
+ ELog.Fatal(err)
+ }
+ defer watcher.Close()
+
+ //TODO : goroutine leak resolve
+ go func() {
+ for {
+ select {
+ case event, ok := <-watcher.Events:
+ if !ok {
+ return
+ }
+
+ ILog.Println("log event:", event)
+ switch event.Op {
+ case fsnotify.Create:
+ // case fsnotify.Chmod:
+ cfgMgr.installConfigure(event.Name)
+
+ }
+
+ case err, ok := <-watcher.Errors:
+ if !ok {
+ return
+ }
+ ELog.Println("error:", err)
+
+ } //selecte end
+ } //for end
+ }()
+
+ err = watcher.Add(path)
+ if err != nil {
+ ELog.Fatal(err)
+ }
+
+ <-cfgMgr.Done
+ ILog.Println("configuremgr watch end")
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- ELog.Fatal(err)
- }
- defer watcher.Close()
-
- //TODO : goroutine leak resolve
- go func() {
- for {
- select {
- case event, ok := <-watcher.Events:
- if !ok {
- return
- }
-
- ILog.Println("log event:", event)
- switch(event.Op){
- case fsnotify.Create:
- // case fsnotify.Chmod:
- cfgMgr.installConfigure(event.Name)
-
- }
-
- case err, ok := <-watcher.Errors:
- if !ok {
- return
- }
- ELog.Println("error:", err)
-
- } //selecte end
- } //for end
- }()
-
- err = watcher.Add(path)
- if err != nil {
- ELog.Fatal(err)
- }
-
- <- cfgMgr.Done
- ILog.Println("configuremgr watch end")
-
-}
\ No newline at end of file
+}
package discoverymgr
import (
+ "common"
+ "errors"
"fmt"
"log"
+ "net"
+ "strings"
"github.com/grandcat/zeroconf"
)
var gServer *zeroconf.Server
-func registerDevice(ServiceNames []string, ret chan error) {
- server, err := zeroconf.Register(serviceName, serviceType, domain, servicePort, ServiceNames, nil)
+//InitDiscovery deploy Orchestration service
+func InitDiscovery() (err error) {
+ registerCh := make(chan error)
+ go registerDevice(nil, registerCh)
+ err = <-registerCh
+ if err != nil {
+ log.Println("[Fail] " + err.Error())
+ return
+ }
+
+ ResetServiceName()
+ return
+}
+
+func registerDevice(AppNames []string, ret chan error) {
+ serviceName, err := getMacAddr()
+
+ if err != nil {
+ ret <- err
+ return
+ }
+
+ server, err := zeroconf.Register(serviceName, serviceType, domain, servicePort, AppNames, nil)
if err != nil {
ret <- err
return
}
- fmt.Println("[Discoverymgr][RegisterDevice] Orchestraition Service Registered")
+ log.Printf("[%s] [RegisterDevice] Orchestraition Service Registered", logPrefix)
gServer = server
defer server.Shutdown()
- ExitChan = make(chan int, 1)
ret <- nil
+ ExitChan = make(chan int, 1)
select {
- case <-ExitChan:
- fmt.Println("Orchestration Agent has been terminated")
+ case exit := <-ExitChan:
+ if exit == 1 {
+ fmt.Println("Orchestration Agent has been terminated")
+ }
}
}
-//InitDiscovery deploy Orchestration service
-func InitDiscovery() error {
+func getMacAddr() (macAddr string, err error) {
+ ifas, err := net.Interfaces()
+ if err != nil {
+ return
+ }
- registerCh := make(chan error)
- go registerDevice(nil, registerCh)
- err := <-registerCh
+ outboundIP, err := common.GetOutboundIP()
if err != nil {
- log.Println("[Fail] " + err.Error())
+ return "", errors.New("Network is Unreachable")
}
- ResetServiceName()
+ for _, ifa := range ifas {
+ addrs, _ := ifa.Addrs()
- return err
+ if len(addrs) != 0 {
+ for _, addr := range addrs {
+ if strings.Contains(addr.String(), outboundIP) == true {
+ macAddr = ifa.HardwareAddr.String()
+ if strings.Compare(macAddr, "") != 0 {
+ log.Println(macAddr)
+ return
+ }
+ }
+ }
+ }
+ }
+ return "", errors.New("Can't get Unique ServiceName")
}
package discoverymgr
import (
- confdescription "configuremgr/description"
"errors"
+
+ confdescription "configuremgr/description"
)
var serverTXT []string
func SetServiceNames(newService string) error {
if newService == "" {
- gServer.SetText(serverTXT)
+ if gServer != nil {
+ gServer.SetText(serverTXT)
+ }
return nil
}
package discoverymgr
import (
- confdescription "configuremgr/description"
"net"
"os"
"testing"
+ "time"
+
+ confdescription "configuremgr/description"
)
//@Todo os dependency check
}
return ipv4
}
+
+func closeDiscovery() {
+ time.Sleep(time.Millisecond * 1)
+ ExitChan <- 1
+}
func TestGetDeviceListWithService(t *testing.T) {
err := InitDiscovery()
if err != nil {
- t.Fail()
+ t.Fatal()
}
doc := new(confdescription.Doc)
doc.ServiceInfo.ServiceName = "test1"
err = AddNewServiceName(doc)
if err != nil {
- t.Fail()
+ closeDiscovery()
+ t.Fatal()
}
ret, err := GetDeviceListWithService(doc.ServiceInfo.ServiceName)
if err != nil {
- t.Fail()
+ closeDiscovery()
+ t.Fatal()
}
ipv4 := getIP()
+ isSuccess := false
for _, v := range ret {
if v == ipv4 {
- return
+ isSuccess = true
}
}
- t.Fail()
+
+ if isSuccess != true {
+ t.Error()
+ }
+
+ closeDiscovery()
}
func TestGetDeviceList(t *testing.T) {
err := InitDiscovery()
if err != nil {
- t.Fail()
+ t.Fatal()
}
ret, err := GetDeviceList()
if err != nil {
- t.Fail()
+ closeDiscovery()
+ t.Fatal()
}
ipv4 := getIP()
- isExist := -1
+ isExist := false
for i, v := range ret {
if v.DeviceIP == ipv4 {
- isExist = i
+ t.Log(i)
+ isExist = true
}
}
- if isExist == -1 {
- t.Error("no device")
- t.Fail()
+ if isExist == false {
+ t.Error()
}
+
+ closeDiscovery()
}
//Package discoverymgr wraps main functions of IoTivity/ocstack with golang to use Discovery functions in go project.
package discoverymgr
+var logPrefix = "discoverymgr"
+
// SimpleResponse structure
type SimpleResponse struct {
Return string `json:"Return"`
import (
"log"
+ "logmgr"
+ "strings"
+ "sync"
+
+ configuremgr "configuremgr"
+ discoverymgr "discoverymgr"
+ orchestrationapi "orchestrationapi"
+ scoringmgr "scoringmgr"
+ servicemgr "servicemgr"
)
//export OrchestrationInit
func OrchestrationInit() (errCode C.int) {
+ logmgr.Init()
log.Printf("[%s] OrchestrationInit", logPrefix)
+
+ orcheEngine := orchestrationapi.Init("")
+
+ orcheEngine.IScoringmgr = scoringmgr.Init()
+ orcheEngine.IConfiguremgr = configuremgr.Init()
+ discoverymgr.InitDiscovery()
+ servicemgr.Init()
+
+ orcheEngine.IScoringmgr.IRunningScore = scoringmgr.LoadScoringGeneralInterface
+ orcheEngine.IScoringmgr.IGetScore = scoringmgr.GetScore
+ orcheEngine.IScoringmgr.Ch = make(chan interface{}, 1024)
+
+ orcheEngine.IConfiguremgr.IDiscoveryMgr.PushConfPath = discoverymgr.AddNewServiceName
+ orcheEngine.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
+ orcheEngine.IConfiguremgr.IScoringMgr.Ch = orcheEngine.IScoringmgr.Ch
+
+ orcheEngine.IDiscoverymgr.GetEndpointDevices = discoverymgr.GetDeviceListWithService
+ orcheEngine.IServicemgr.ExecuteApp = servicemgr.ExecuteApp
+
+ orcheEngine.IScoringmgr.Listening()
+ go orcheEngine.IConfiguremgr.Watch("/etc/edge-orchestration")
+
errCode = 0
return
}
//export OrchestrationRequestService
-func OrchestrationRequestService() (errCode C.int) {
+func OrchestrationRequestService(cAppName *C.char, cArgs *C.char) (handle C.int) {
log.Printf("[%s] OrchestrationRequestService", logPrefix)
- errCode = 0
+ appName := C.GoString(cAppName)
+ args := C.GoString(cArgs)
+
+ argsArr := strings.Split(args, " ")
+ log.Println("appName:", appName, "args:", argsArr)
+ orchestrationapi.RequestService(appName, argsArr)
+
+ errCode := 0
+
+ return C.int(errCode)
+}
+
+var count int
+var mtx sync.Mutex
+
+//export PrintLog
+func PrintLog(cMsg *C.char) (count C.int) {
+ mtx.Lock()
+ msg := C.GoString(cMsg)
+ defer mtx.Unlock()
+ log.Printf(msg)
+ count++
return
}
package main
-var logPrefix = "liborchestration"
+var logPrefix = "interface"
INC_DIR := $(BASE_DIR)/inc
LIB_DIR := $(BASE_DIR)/lib
SRC_DIR := $(BASE_DIR)/src
+SAMPLE_DIR := $(BASE_DIR)/sample
LIBRARY_FILE := liborchestration-client.so
SRC_FILES := \
$(SRC_DIR)/orchestration_client.c \
$(SRC_DIR)/dbus_consumer.c
OBJ_FILES := $(BASE_DIR)/*.o
-# Build parameter
+# Build parameters
CC := gcc
-CFLAGS := -fPIC -Wall
-all: clean build
+default: all
+
+all: build build-sample
+
+build-sample: build
+ $(CC) -Wall $(SAMPLE_DIR)/main.c -o $(SAMPLE_DIR)/orchestration_sample -I$(INC_DIR) -L$(LIB_DIR) -lorchestration-client `pkg-config --libs --cflags gio-2.0 gio-unix-2.0 glib-2.0`
+ -rm -f ./sample/*.o
build:
mkdir -p $(LIB_DIR)
- $(CC) $(CFLAGS) -c -I $(INC_DIR) `pkg-config --libs --cflags gio-2.0 gio-unix-2.0 glib-2.0` $(SRC_FILES)
- $(CC) -shared -g -Wall -Werror -o $(LIB_DIR)/$(LIBRARY_FILE) -I $(INC_DIR) $(OBJ_FILES)
+ $(CC) -fPIC -Wall -c -I $(INC_DIR) `pkg-config --libs --cflags gio-2.0 gio-unix-2.0 glib-2.0` $(SRC_FILES)
+ $(CC) -shared -g -Wall -Werror $(OBJ_FILES) -o $(LIB_DIR)/$(LIBRARY_FILE) -I $(INC_DIR)
+ -rm -f $(OBJ_FILES)
clean:
-rm -f $(OBJ_FILES)
--- /dev/null
+package logmgr\r
+\r
+import (\r
+ "log"\r
+\r
+ "github.com/leemcloughlin/logfile"\r
+)\r
+\r
+const logFilePath = "/var/log"\r
+const logFileName = "orchestration.log"\r
+\r
+// Init for initializing logmgr\r
+func Init() {\r
+ logFile, err := logfile.New(\r
+ &logfile.LogFile{\r
+ FileName: logFilePath + "/" + logFileName,\r
+ FileMode: 0644,\r
+ MaxSize: 500 * 1024, // 500K\r
+ OldVersions: 3,\r
+ Flags: logfile.OverWriteOnStart | logfile.RotateOnStart})\r
+ if err != nil {\r
+ log.Fatalf("Failed to create logFile %s: %s\n", logFileName, err)\r
+ }\r
+\r
+ log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)\r
+ log.SetOutput(logFile)\r
+}\r
--- /dev/null
+
+/*
+$ gcc -fPIC -c myscoring.c && gcc -shared -o libmyscoring.so.1.0.1 myscoring.o
+$ ln -rs libmyscoring.so.1.0.1 libmyscoring.so
+*/
+
+#include <math.h>
+#include <assert.h>
+#include <stdio.h>
+
+#define CNT 6
+
+/*
+features :
+-. there is using moving average, but it is not written code at service_provider.cpp
+-. bandwidth is not meaning Mbps
+*/
+
+//network score mmDiscovery/service_provider.cpp
+static double networkScore(double n) {
+ return 1 / (8770 * pow(n, -0.9));
+}
+
+//cpu score mmDiscovery/service_provider.cpp
+static double cpuScore(double freq, double usage, double count){
+ return ((1 / (5.66 * pow(freq, -0.66))) +
+ (1 / (3.22 * pow(usage, -0.241))) +
+ (1 / (4 * pow(count, -0.3)))) / 3;
+}
+
+//render score mmDiscovery/service_provider.cpp
+//https://github.com/Samsung/Castanets/blob/castanets_63/service_discovery_manager/Component/mmDiscovery/monitor_client.cpp
+static double renderingScore(double r) {
+ return (r < 0) ? 0 : 0.77 * pow(r, -0.43);
+}
+
+//============== INTERFACE API ==============
+double myscoring(double (*getResource)(const char *))
+{
+
+ printf("myscoring\n");
+ double score;
+
+
+ score = 0.0;
+ score += networkScore(getResource("network/bandwidth"));
+ score += cpuScore(getResource("cpu/freq"), getResource("cpu/usage"), getResource("cpu/count"));
+ score /= 2;
+ score += renderingScore(getResource("network/rtt"));
+
+ return score;
+}
+
+#define CNT 6
+
+double myscoring2(double (*getResource)(const char *))
+{
+
+ printf("myscoring\n");
+
+ double score;
+ const char *resourceNames[CNT] = {"cpu/usage", "cpu/count", "memory/free", "memory/available", "network/mbps", "network/bandwidth"};
+ double W[CNT] = {1.48271, 4.125421, 5.3381723, 9.194717234, 2.323, 1.123};
+ double resourceValues[CNT];
+
+ // double someResource;
+ // someResource = getResource("some/usage");
+ // assert(isnan(someResource));
+
+ for (int i = 0; i < CNT; i++)
+ {
+ resourceValues[i] = getResource(resourceNames[i]);
+ printf("resourceNames : %s %f\n", resourceNames[i], resourceValues[i]);
+ }
+
+ score = 0.0;
+ for (int i = 0; i < CNT; i++)
+ {
+ score += resourceValues[i] * W[i];
+ }
+
+ return score;
+}
--- /dev/null
+# Description of service that will be requested
+[Version]
+ConfVersion=v0.0 ; Version of Configuration file
+
+[ServiceInfo]
+ServiceName=ls ; Name of distributed service
+
+[ScoringMethod]
+LibFile=./libmyscoring.so ; Library file name
+FunctionName=myscoring
+
+[ResourceType]
+IntervalTimeMs=1000 ; Interval time of get resource
+MaxCount=10 ; Number of times
--- /dev/null
+/*
+$ gcc -fPIC -c mysum.c && gcc -shared -o libmysum.so.1.0.1 mysum.o
+$ ln -rs libmysum.so.1.0.1 libmysum.so
+*/
+
+
+int add(int a , int b){
+ return a + b;
+}
\ No newline at end of file
ConfVersion=v0.0 ; Version of Configuration file
[ServiceInfo]
-ServiceName=HelloWorldService ; Name of distributed service
+ServiceName=ls ; Name of distributed service
[ScoringMethod]
LibFile=./libmysum.so ; Library file name
package orchestrationapi
+//$
+
import (
+ "discoverymgr"
"fmt"
"os/exec"
"testing"
func TestConfigureMgrScoringMgr(t *testing.T) {
//make orche
- orcheMock := new(Orche)
+ orcheMock := Init("")
+ //create modules
orcheMock.IScoringmgr = scoringmgr.Init()
orcheMock.IConfiguremgr = configuremgr.Init()
- orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringAddInterface
+ //scoringmgr interface
+ orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ orcheMock.IScoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ orcheMock.IScoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
orcheMock.IScoringmgr.IGetScore = mockscoringmgr.GetScoreRandom100Mock
orcheMock.IScoringmgr.Ch = make(chan interface{}, 1024)
-
- orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = pushConfPathDiscoveryDeviceMock
+ //configuremgr interface
+ orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = PushConfPathDiscoveryDeviceMock
orcheMock.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
orcheMock.IConfiguremgr.IScoringMgr.Ch = orcheMock.IScoringmgr.Ch
- orcheMock.IDiscoverymgr.GetEndpointDevices = getEndpointDevicesMock
- orcheMock.IServicemgr.ExecuteApp = executeAppMock
+ //discoverymgr interface
+ orcheMock.IDiscoverymgr.GetEndpointDevices = GetEndpointDevicesMock
+
+ //servicemgr interface
+ orcheMock.IServicemgr.ExecuteApp = ExecuteAppMock
- //start module function
+ //start scoringmgr
orcheMock.IScoringmgr.Listening()
+ orcheMock.IScoringmgr.IStartResourceService()
+
+ //start configuremgr
go orcheMock.IConfiguremgr.Watch("/tmp/foo")
//init scenario
time.Sleep(time.Duration(1) * time.Second)
//user scenario
- execCommand(fmt.Sprintf("cp -ar %s %s", "./mock/mysum/", "/tmp/foo"))
+ execCommand(fmt.Sprintf("cp -arR %s %s", "./mock/mysum/", "/tmp/foo"))
time.Sleep(time.Duration(3) * time.Second)
- //resource release
- orcheMock.IScoringmgr.RemoveLib("mysum")
+ //release scoringmgr
+ orcheMock.IScoringmgr.IStopResourceService()
+ orcheMock.IScoringmgr.RemoveLib("ls")
+
+ //release configuremgr
orcheMock.IConfiguremgr.Done <- true
}
+func TestConfigureMgrScoringMgrMyScoringLib(t *testing.T) {
+
+ //make orche
+ orcheMock := new(Orche)
+
+ //create modules
+ orcheMock.IScoringmgr = scoringmgr.Init()
+ orcheMock.IConfiguremgr = configuremgr.Init()
+
+ //scoringmgr interface
+ orcheMock.IScoringmgr.IRunningScore = scoringmgr.LoadScoringGeneralInterface
+ orcheMock.IScoringmgr.IStartResourceService = scoringmgr.StartResourceService
+ orcheMock.IScoringmgr.IStopResourceService = scoringmgr.StopResourceService
+ orcheMock.IScoringmgr.IGetScore = mockscoringmgr.GetScoreRandom100Mock
+ orcheMock.IScoringmgr.Ch = make(chan interface{}, 1024)
+
+ //configuremgr interface
+ orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = PushConfPathDiscoveryDeviceMock
+ orcheMock.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
+ orcheMock.IConfiguremgr.IScoringMgr.Ch = orcheMock.IScoringmgr.Ch
+
+ //discoverymgr interface
+ orcheMock.IDiscoverymgr.GetEndpointDevices = GetEndpointDevicesMock
+
+ //servicemgr interface
+ orcheMock.IServicemgr.ExecuteApp = ExecuteAppMock
+
+ //start scoringmgr
+ orcheMock.IScoringmgr.Listening()
+ orcheMock.IScoringmgr.IStartResourceService()
+
+ //start configuremgr
+ go orcheMock.IConfiguremgr.Watch("/tmp/foo")
+
+ //init scenario
+ execCommand("rm -rf /tmp/foo/myscoring")
+ time.Sleep(time.Duration(1) * time.Second)
+
+ //user scenario
+ execCommand(fmt.Sprintf("cp -arR %s %s", "./mock/myscoring/", "/tmp/foo"))
+ time.Sleep(time.Duration(3) * time.Second)
+
+ //release scoringmgr
+ orcheMock.IScoringmgr.IStopResourceService()
+ orcheMock.IScoringmgr.RemoveLib("ls")
+
+ //release configuremgr
+ orcheMock.IConfiguremgr.Done <- true
+}
+
//jaehoon.hyun, damon92-lee
func TestConfigureMgrDiscoveryMgr(t *testing.T) {
+ orcheMock := Init("")
+
+ orcheMock.IScoringmgr = scoringmgr.Init()
+ orcheMock.IConfiguremgr = configuremgr.Init()
+
+ orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ orcheMock.IScoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ orcheMock.IScoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
+ orcheMock.IScoringmgr.IGetScore = mockscoringmgr.GetScoreRandom100Mock
+ orcheMock.IScoringmgr.Ch = make(chan interface{}, 1024)
+
+ orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = discoverymgr.AddNewServiceName
+ orcheMock.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
+ orcheMock.IConfiguremgr.IScoringMgr.Ch = orcheMock.IScoringmgr.Ch
+
+ orcheMock.IDiscoverymgr.GetEndpointDevices = discoverymgr.GetDeviceListWithService
+ orcheMock.IServicemgr.ExecuteApp = ExecuteAppMock
+
+ discoverymgr.InitDiscovery()
+
+ //scoringmgr init
+ orcheMock.IScoringmgr.Listening()
+
+ //configuremgr init
+ go orcheMock.IConfiguremgr.Watch("/tmp/foo")
+
+ //init scenario
+ execCommand("rm -rf /tmp/foo/mysum")
+ time.Sleep(time.Duration(1) * time.Second)
+
+ //user scenario
+ execCommand(fmt.Sprintf("cp -ar %s %s", "./mock/mysum/", "/tmp/foo"))
+ time.Sleep(time.Duration(3) * time.Second)
+ RequestService("ls", []string{"-al"})
+ //resource release
+ time.Sleep(time.Duration(1) * time.Second)
+ orcheMock.IScoringmgr.RemoveLib("ls")
+ orcheMock.IConfiguremgr.Done <- true
+ discoverymgr.ExitChan <- 1
}
//jaehoon.hyun, chacha
orcheMock.IScoringmgr = scoringmgr.Init()
orcheMock.IConfiguremgr = configuremgr.Init()
- orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringAddInterface
+ orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ orcheMock.IScoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ orcheMock.IScoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
orcheMock.IScoringmgr.IGetScore = mockscoringmgr.GetScoreRandom100Mock
orcheMock.IScoringmgr.Ch = make(chan interface{}, 1024)
- orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = pushConfPathDiscoveryDeviceMock
+ orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = PushConfPathDiscoveryDeviceMock
orcheMock.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
orcheMock.IConfiguremgr.IScoringMgr.Ch = orcheMock.IScoringmgr.Ch
- orcheMock.IDiscoverymgr.GetEndpointDevices = getEndpointDevicesMock
+ orcheMock.IDiscoverymgr.GetEndpointDevices = GetEndpointDevicesMock
orcheMock.IServicemgr.ExecuteApp = servicemgr.ExecuteApp
//scoringmgr init
//user scenario
execCommand(fmt.Sprintf("cp -ar %s %s", "./mock/mysum/", "/tmp/foo"))
time.Sleep(time.Duration(3) * time.Second)
- RequestService("mysum", "ls", []string{"-al"})
+ RequestService("ls", []string{"-al"})
//resource release
time.Sleep(time.Duration(1) * time.Second)
- orcheMock.IScoringmgr.RemoveLib("mysum")
+ orcheMock.IScoringmgr.RemoveLib("ls")
orcheMock.IConfiguremgr.Done <- true
- // for {
- // }
}
//daemon92-lee, chacha
-func TestDiscoveryMgrServiceMgr(t *testing.T) {
-
+func testDiscoveryMgrServiceMgr(t *testing.T) {
+ // @TODO
}
//jaehoon.hyun, daemon92-lee, jaehoon.hyun
func TestConfigureMgrDiscoveryMgrScoringMgr(t *testing.T) {
+ orcheMock := Init("")
+ orcheMock.IScoringmgr = scoringmgr.Init()
+ orcheMock.IConfiguremgr = configuremgr.Init()
+
+ orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ orcheMock.IScoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ orcheMock.IScoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
+ orcheMock.IScoringmgr.IGetScore = scoringmgr.GetScore
+ orcheMock.IScoringmgr.Ch = make(chan interface{}, 1024)
+
+ orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = discoverymgr.AddNewServiceName
+ orcheMock.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
+ orcheMock.IConfiguremgr.IScoringMgr.Ch = orcheMock.IScoringmgr.Ch
+
+ orcheMock.IDiscoverymgr.GetEndpointDevices = discoverymgr.GetDeviceListWithService
+ orcheMock.IServicemgr.ExecuteApp = ExecuteAppMock
+
+ //scoringmgr init
+ orcheMock.IScoringmgr.Listening()
+
+ //configuremgr init
+ go orcheMock.IConfiguremgr.Watch("/tmp/foo")
+
+ //init scenario
+ execCommand("rm -rf /tmp/foo/mysum")
+ time.Sleep(time.Duration(1) * time.Second)
+
+ discoverymgr.InitDiscovery()
+
+ //user scenario
+ execCommand(fmt.Sprintf("cp -ar %s %s", "../scoringmgr/mock/mysum/", "/tmp/foo"))
+ time.Sleep(time.Duration(3) * time.Second)
+ RequestService("ls", []string{"-al"})
+
+ //resource release
+ time.Sleep(time.Duration(1) * time.Second)
+ orcheMock.IScoringmgr.RemoveLib("ls")
+ orcheMock.IConfiguremgr.Done <- true
+ discoverymgr.ExitChan <- 1
}
//jaehoon.hyun, daemon92-lee, chacha
func TestConfigureMgrDiscoveryMgrServiceMgr(t *testing.T) {
+ orcheMock := Init("")
+
+ orcheMock.IScoringmgr = scoringmgr.Init()
+ orcheMock.IConfiguremgr = configuremgr.Init()
+
+ orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ orcheMock.IScoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ orcheMock.IScoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
+ orcheMock.IScoringmgr.IGetScore = mockscoringmgr.GetScoreRandom100Mock
+ orcheMock.IScoringmgr.Ch = make(chan interface{}, 1024)
+
+ orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = discoverymgr.AddNewServiceName
+ orcheMock.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
+ orcheMock.IConfiguremgr.IScoringMgr.Ch = orcheMock.IScoringmgr.Ch
+
+ orcheMock.IDiscoverymgr.GetEndpointDevices = discoverymgr.GetDeviceListWithService
+ orcheMock.IServicemgr.ExecuteApp = servicemgr.ExecuteApp
+
+ discoverymgr.InitDiscovery()
+ servicemgr.Init()
+
+ //scoringmgr init
+ orcheMock.IScoringmgr.Listening()
+
+ //init scenario
+ execCommand("rm -rf /tmp/foo/mysum")
+ time.Sleep(time.Duration(1) * time.Second)
+
+ //configuremgr init
+ go orcheMock.IConfiguremgr.Watch("/tmp/foo")
+
+ //user scenario
+ execCommand(fmt.Sprintf("cp -ar %s %s", "./mock/mysum/", "/tmp/foo"))
+ time.Sleep(time.Duration(3) * time.Second)
+ RequestService("ls", []string{"-al"})
+
+ //resource release
+ time.Sleep(time.Duration(1) * time.Second)
+ orcheMock.IScoringmgr.RemoveLib("ls")
+ orcheMock.IConfiguremgr.Done <- true
+ discoverymgr.ExitChan <- 1
}
//jaehoon.hyun, chacha, jaehoon.hyun
func TestConfigureMgrServiceMgrScoringMgr(t *testing.T) {
+ orcheMock := Init("")
+
+ orcheMock.IScoringmgr = scoringmgr.Init()
+ orcheMock.IConfiguremgr = configuremgr.Init()
+
+ orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ orcheMock.IScoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ orcheMock.IScoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
+ orcheMock.IScoringmgr.IGetScore = scoringmgr.GetScore
+ orcheMock.IScoringmgr.Ch = make(chan interface{}, 1024)
+
+ orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = PushConfPathDiscoveryDeviceMock
+ orcheMock.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
+ orcheMock.IConfiguremgr.IScoringMgr.Ch = orcheMock.IScoringmgr.Ch
+
+ orcheMock.IDiscoverymgr.GetEndpointDevices = GetEndpointDevicesMock
+ orcheMock.IServicemgr.ExecuteApp = servicemgr.ExecuteApp
+
+ //scoringmgr init
+ orcheMock.IScoringmgr.Listening()
+ //configuremgr init
+ go orcheMock.IConfiguremgr.Watch("/tmp/foo")
+
+ //init scenario
+ execCommand("rm -rf /tmp/foo/mysum")
+ time.Sleep(time.Duration(1) * time.Second)
+
+ servicemgr.Init()
+
+ //user scenario
+ execCommand(fmt.Sprintf("cp -ar %s %s", "./mock/mysum/", "/tmp/foo"))
+ time.Sleep(time.Duration(3) * time.Second)
+ RequestService("ls", []string{"-al"})
+
+ //resource release
+ time.Sleep(time.Duration(1) * time.Second)
+ orcheMock.IScoringmgr.RemoveLib("ls")
+ orcheMock.IConfiguremgr.Done <- true
}
//jaehoon.hyun, daemon92-lee, chacha, jaehoon.hyun
func TestConfigureMgrDiscoveryMgrScoringMgrServiceMgr(t *testing.T) {
+ orcheMock := Init("")
+
+ orcheMock.IScoringmgr = scoringmgr.Init()
+ orcheMock.IConfiguremgr = configuremgr.Init()
+
+ orcheMock.IScoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ orcheMock.IScoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ orcheMock.IScoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
+ orcheMock.IScoringmgr.IGetScore = scoringmgr.GetScore
+ orcheMock.IScoringmgr.Ch = make(chan interface{}, 1024)
+
+ orcheMock.IConfiguremgr.IDiscoveryMgr.PushConfPath = discoverymgr.AddNewServiceName
+ orcheMock.IConfiguremgr.IScoringMgr.PushLibPath = scoringmgr.PushLibPath
+ orcheMock.IConfiguremgr.IScoringMgr.Ch = orcheMock.IScoringmgr.Ch
+
+ orcheMock.IDiscoverymgr.GetEndpointDevices = discoverymgr.GetDeviceListWithService
+ orcheMock.IServicemgr.ExecuteApp = servicemgr.ExecuteApp
+
+ //scoringmgr init
+ orcheMock.IScoringmgr.Listening()
+
+ //configuremgr init
+ go orcheMock.IConfiguremgr.Watch("/tmp/foo")
+
+ //init scenario
+ execCommand("rm -rf /tmp/foo/mysum")
+ time.Sleep(time.Duration(1) * time.Second)
+
+ discoverymgr.InitDiscovery()
+ servicemgr.Init()
+
+ //user scenario
+ execCommand(fmt.Sprintf("cp -ar %s %s", "./mock/mysum/", "/tmp/foo"))
+ time.Sleep(time.Duration(3) * time.Second)
+ RequestService("ls", []string{"-al"})
+
+ //resource release
+ time.Sleep(time.Duration(1) * time.Second)
+ orcheMock.IScoringmgr.RemoveLib("ls")
+ orcheMock.IConfiguremgr.Done <- true
+ discoverymgr.ExitChan <- 1
}
-func pushConfPathDiscoveryDeviceMock(doc *confdescription.Doc) (err error) {
+func PushConfPathDiscoveryDeviceMock(doc *confdescription.Doc) (err error) {
ILog.Println("pushConfPathDiscoveryDeviceMock")
ILog.Println(*doc)
return
}
-func pushLibPathScoringAppMock(libPath string, doc *confdescription.Doc, handlersCh chan<- interface{}) (err error) {
+func PushLibPathScoringAppMock(libPath string, doc *confdescription.Doc, handlersCh chan<- interface{}) (err error) {
ILog.Println("pushLibPathScoringAppMock")
ILog.Println(libPath)
return
}
-func getEndpointDevicesMock(serviceName string) []string {
- DLog.Printf("getEndpointDevicesMock serviceName: %s\n", serviceName)
- return []string{"localhost", "localhost"}
+func GetEndpointDevicesMock(serviceName string) ([]string, error) {
+ DLog.Printf("GetEndpointDevicesMock serviceName: %s\n", serviceName)
+ return []string{"localhost"}, nil
}
-func executeAppMock(target string, name string, args []string, notiChan chan string) (serviceID uint64, err error) {
+func ExecuteAppMock(target string, name string, args []string, notiChan chan string) (serviceID uint64, err error) {
ILog.Println("ExecuteApp")
ILog.Println(target)
ILog.Println(name)
IConfiguremgr *configuremgr.ConfigureMgr
IDiscoverymgr struct {
- GetEndpointDevices func(serviceName string) []string
+ GetEndpointDevices func(serviceName string) ([]string, error)
}
IServicemgr struct {
}
type orcheClient struct {
- libName string
- serviceName string
- args []string
- notiChan chan string
+ appName string
+ args []string
+ notiChan chan string
+ endSignal chan bool
}
var (
}
-func RequestService(libName string, serviceName string, args []string) (handle int) {
+func RequestService(appName string, args []string) (handle int) {
clientID := atomic.LoadInt32(&orchClientID)
atomic.AddInt32(&orchClientID, 1)
- serviceClient := addServiceClient(clientID, libName, serviceName, args)
+ serviceClient := addServiceClient(clientID, appName, args)
go serviceClient.listenNotify()
- endpoints := getEndpointDevices(serviceName)
- deviceScores := sortByScore(gatheringDevicesScore(endpoints, libName))
- executeApp(deviceScores[0].endpoint, serviceName, args, serviceClient.notiChan)
+ endpoints := getEndpointDevices(appName)
+ deviceScores := sortByScore(gatheringDevicesScore(endpoints, appName))
+
+ if len(deviceScores) > 0 {
+ executeApp(deviceScores[0].endpoint, appName, args, serviceClient.notiChan)
+ ILog.Println(deviceScores)
+ }
- ILog.Println(deviceScores)
return
}
func (client *orcheClient) listenNotify() {
select {
case str := <-client.notiChan:
- ILog.Printf("service status changed [path:%s][serviceName:%s][status:%s]\n", client.libName, client.serviceName, str)
+ ILog.Printf("service status changed [appNames:%s][status:%s]\n", client.appName, str)
}
}
-func addServiceClient(clientID int32, libName string, serviceName string, args []string) (client *orcheClient) {
- orcheClients[clientID].libName = libName
+func addServiceClient(clientID int32, appName string, args []string) (client *orcheClient) {
orcheClients[clientID].args = args
- orcheClients[clientID].serviceName = serviceName
+ orcheClients[clientID].appName = appName
client = &orcheClients[clientID]
return
}
-func getEndpointDevices(serviceName string) []string {
- return orcheEngine.IDiscoverymgr.GetEndpointDevices(serviceName)
+func getEndpointDevices(appName string) (deviceList []string) {
+ deviceList, _ = orcheEngine.IDiscoverymgr.GetEndpointDevices(appName)
+ return
}
-func gatheringDevicesScore(endpoints []string, libName string) (deviceScores []deviceScore) {
+func gatheringDevicesScore(endpoints []string, appName string) (deviceScores []deviceScore) {
for _, endpoint := range endpoints {
-
//(chacha)TODO : err occured , notify devicemgr to delete
- score, _ := orcheEngine.IScoringmgr.IGetScore(endpoint, libName)
+ score, _ := orcheEngine.IScoringmgr.IGetScore(endpoint, appName)
deviceScores = append(deviceScores, deviceScore{endpoint, score})
}
import (
"bytes"
"io/ioutil"
+ "log"
"net"
"net/http"
"time"
+
+ "securemgr"
)
// DoGet is for get request
-func DoGet(targetURL string) (respBytes []byte, err error) {
+func DoGet(targetURL string) (respBytes []byte, statusCode int, err error) {
req, err := http.NewRequest("GET", targetURL, nil)
if err != nil {
return
if err != nil {
return
}
+
defer resp.Body.Close()
- respBytes, err = ioutil.ReadAll(resp.Body)
+ statusCode = resp.StatusCode
+ encryptedRespByte, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ log.Println("read resp.Body failed !!", err)
+ return
+ }
+ if len(encryptedRespByte) != 0 {
+ respBytes, err = securemgr.DecryptByte(encryptedRespByte)
+ if err != nil {
+ log.Println("data dncryption failed !!", err)
+ return
+ }
+ }
+
return
}
// DoPost is for post request
-func DoPost(targetURL string, pbytes []byte) (respBytes []byte, err error) {
- buff := bytes.NewBuffer(pbytes)
+func DoPost(targetURL string, pbytes []byte) (respBytes []byte, statusCode int, err error) {
+ if len(pbytes) == 0 {
+ log.Printf("DoPost body length is zero(0) !!")
+ }
+
+ encryptedReqByte, err := securemgr.EncryptByte(pbytes)
+ if err != nil {
+ log.Println("data encryption failed !!")
+ respBytes = nil
+ return
+ }
+
+ buff := bytes.NewBuffer(encryptedReqByte)
req, err := http.NewRequest("POST", targetURL, buff)
if err != nil {
if err != nil {
return
}
+
defer resp.Body.Close()
- respBytes, err = ioutil.ReadAll(resp.Body)
+ statusCode = resp.StatusCode
+ encryptedRespByte, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ log.Println("read resp.Body failed !!", err)
+ return
+ }
+ if len(encryptedRespByte) != 0 {
+ respBytes, err = securemgr.DecryptByte(encryptedRespByte)
+ if err != nil {
+ log.Println("data dncryption failed !!", err)
+ return
+ }
+ }
+
return
}
// DoDelete is for delete request
-func DoDelete(targetURL string) (respBytes []byte, err error) {
+func DoDelete(targetURL string) (respBytes []byte, statusCode int, err error) {
req, err := http.NewRequest("DELETE", targetURL, nil)
if err != nil {
return
if err != nil {
return
}
+
defer resp.Body.Close()
- respBytes, err = ioutil.ReadAll(resp.Body)
+ statusCode = resp.StatusCode
+ encryptedRespByte, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ log.Println("read resp.Body failed !!", err)
+ return
+ }
+ if len(encryptedRespByte) != 0 {
+ respBytes, err = securemgr.DecryptByte(encryptedRespByte)
+ if err != nil {
+ log.Println("data dncryption failed !!", err)
+ return
+ }
+ }
+
return
}
import (
"encoding/json"
"log"
- "testing"
-
"net/http"
"net/http/httptest"
+ "testing"
+
+ "securemgr"
)
var (
mockRemoteAddr string
)
+var keyFilePath string = "./../../securemgr/test/key.txt"
+
+func writeJSONResponse(w http.ResponseWriter, data []byte, status int) {
+ log.Printf("[%s] writeJSONResponse: %s", "test", data)
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ w.WriteHeader(status)
+
+ encryptedData, err := securemgr.EncryptByte(data)
+ if err != nil {
+ log.Println("data encryption failed !!")
+ return
+ }
+ w.Write(encryptedData)
+}
+
func init() {
+ securemgr.Init(keyFilePath)
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Test Handler")
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(r.Method))
+ respInfo := make(map[string]interface{})
+ respInfo["reqMethod"] = r.Method
+ respBytes, err := json.Marshal(respInfo)
+
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ } else {
+ writeJSONResponse(w, respBytes, http.StatusOK)
+ }
}))
mockRemoteAddr = server.URL
+
+ log.Println("mockRemoteAddr:", mockRemoteAddr)
}
func TestDoGet(t *testing.T) {
- resp, err := DoGet(mockRemoteAddr)
+ resp, statusCode, err := DoGet(mockRemoteAddr)
+
+ var responseMsg map[string]interface{}
+ err = json.Unmarshal(resp, &responseMsg)
retError(t, err)
- if string(resp) != "GET" {
+ if statusCode != http.StatusOK {
+ t.Error("statusCode:", statusCode)
+ }
+
+ if string(responseMsg["reqMethod"].(string)) != "GET" {
t.Error()
}
}
msgBytes, err := json.Marshal(msg)
retError(t, err)
- resp, err := DoPost(mockRemoteAddr, msgBytes)
+ resp, statusCode, err := DoPost(mockRemoteAddr, msgBytes)
+
+ var responseMsg map[string]interface{}
+ err = json.Unmarshal(resp, &responseMsg)
retError(t, err)
- if string(resp) != "POST" {
+ if statusCode != http.StatusOK {
+ t.Error("statusCode:", statusCode)
+ }
+
+ if string(responseMsg["reqMethod"].(string)) != "POST" {
t.Error()
}
}
func TestDoDelete(t *testing.T) {
- resp, err := DoDelete(mockRemoteAddr)
+ resp, statusCode, err := DoDelete(mockRemoteAddr)
+
+ var responseMsg map[string]interface{}
+ err = json.Unmarshal(resp, &responseMsg)
retError(t, err)
- if string(resp) != "DELETE" {
+ if statusCode != http.StatusOK {
+ t.Error("statusCode:", statusCode)
+ }
+
+ if string(responseMsg["reqMethod"].(string)) != "DELETE" {
t.Error()
}
package v1
import (
- "devicemgr"
- "discoverymgr"
"encoding/json"
"errors"
+ "io/ioutil"
"log"
"net"
"net/http"
"os/exec"
"restapi/httpclient"
- "scoringmgr"
- "servicemgr"
"strconv"
"github.com/gorilla/mux"
+
+ "devicemgr"
+ "discoverymgr"
+ "scoringmgr"
+ "securemgr"
+ "servicemgr"
)
// APIV1DeviceResourceUsageCPUGet function
func APIV1DiscoverymgrDevicesPost(w http.ResponseWriter, r *http.Request) {
log.Printf("[%s] APIV1DiscoverymgrDevicesPost", logPrefix)
- var distService map[string]string
- decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&distService)
+ encryptedReqByte, _ := ioutil.ReadAll(r.Body)
+ distService, err := securemgr.DecryptByteToJson(encryptedReqByte)
if err != nil {
return
}
+
+ // var distService map[string]string
+ // decoder := json.NewDecoder(r.Body)
+ // err := decoder.Decode(&distService)
+ // if err != nil {
+ // return
+ // }
target := distService["ServiceName"]
- ret, err := discoverymgr.GetDeviceListWithService(target)
+ ret, err := discoverymgr.GetDeviceListWithService(target.(string))
if err != nil {
writeJSONResponse(w, nil, http.StatusBadRequest)
return
func APIV1DiscoverymgrDevicesTXTPost(w http.ResponseWriter, r *http.Request) {
log.Printf("[%s] APIV1DiscoverymgrDevicesTXTPost", logPrefix)
- var distService map[string]string
- decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&distService)
+ encryptedReqByte, _ := ioutil.ReadAll(r.Body)
+ distService, err := securemgr.DecryptByteToJson(encryptedReqByte)
if err != nil {
return
}
+
+ // var distService map[string]string
+ // decoder := json.NewDecoder(r.Body)
+ // err := decoder.Decode(&distService)
+ // if err != nil {
+ // return
+ // }
target := distService["ServiceName"]
- err = discoverymgr.SetServiceNames(target)
+ err = discoverymgr.SetServiceNames(target.(string))
if err != nil {
writeJSONResponse(w, nil, http.StatusBadRequest)
} else {
distService, err := executeServiceHandler(w, r)
if err != nil {
- log.Println(err.Error())
-
smbytes, _ := json.Marshal(servicemgr.ServiceExecutionResponse{Status: servicemgr.ConstServiceStatusFailed})
writeJSONResponse(w, smbytes, http.StatusBadRequest)
} else {
go servicemgr.Run(distService)
-
smbytes, _ := json.Marshal(servicemgr.ServiceExecutionResponse{Status: servicemgr.ConstServiceStatusStarted})
writeJSONResponse(w, smbytes, http.StatusOK)
}
statusNotification, err := notificationHandler(w, r)
if err != nil {
+ log.Println(err)
w.WriteHeader(http.StatusBadRequest)
} else {
go servicemgr.HandleNoti(statusNotification)
log.Printf("[%s] writeJSONResponse: %s", logPrefix, data)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(status)
- w.Write(data)
+
+ encryptedData, err := securemgr.EncryptByte(data)
+ if err != nil {
+ log.Println("data encryption failed !!")
+ return
+ }
+ w.Write(encryptedData)
}
func executeServiceHandler(w http.ResponseWriter, req *http.Request) (distService map[string]interface{}, err error) {
- decoder := json.NewDecoder(req.Body)
- err = decoder.Decode(&distService)
+
+ encryptedReqByte, _ := ioutil.ReadAll(req.Body)
+ distService, err = securemgr.DecryptByteToJson(encryptedReqByte)
+
if err != nil {
return
}
+ // decoder := json.NewDecoder(req.Body)
+ // err = decoder.Decode(&distService)
+ // if err != nil {
+ // return
+ // }
+
_, err = exec.LookPath(distService["ServiceName"].(string))
if err != nil {
err = errors.New("It is Invalid Service")
return
}
- distService["NotificationTargetURL"] = remoteAddr + strconv.Itoa(56002)
+ distService["NotificationTargetURL"] = remoteAddr + ":" + strconv.Itoa(56001)
return
}
func notificationHandler(w http.ResponseWriter, req *http.Request) (statusNoti map[string]interface{}, err error) {
- decoder := json.NewDecoder(req.Body)
- err = decoder.Decode(&statusNoti)
+ // decoder := json.NewDecoder(req.Body)
+ // err = decoder.Decode(&statusNoti)
+
+ encryptedReqByte, _ := ioutil.ReadAll(req.Body)
+ statusNoti, err = securemgr.DecryptByteToJson(encryptedReqByte)
+ if err != nil {
+ log.Println(err)
+ return
+ }
return
}
package v1
import (
- "bytes"
- "devicemgr"
- "discoverymgr"
"encoding/json"
- "io/ioutil"
"log"
"net/http"
"net/http/httptest"
- "scoringmgr"
- "servicemgr"
+ "securemgr"
"strings"
"testing"
+
+ "devicemgr"
+ "discoverymgr"
+ "restapi/httpclient"
+ "scoringmgr"
+ "servicemgr"
)
var (
}
}
-func DoGet(url string) (res *Response, err error) {
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return
- }
-
- client := &http.Client{}
- resp, err := client.Do(req)
- if err != nil {
- return
- }
- defer resp.Body.Close()
-
- contents, err := ioutil.ReadAll(resp.Body)
- if err != nil {
-
- return
- }
-
- res = &Response{Content: string(contents), Code: resp.StatusCode}
- return
-}
-
-func DoPost(url string, body []byte) (res *Response, err error) {
- buff := bytes.NewBuffer(body)
-
- log.Println(url)
- req, err := http.NewRequest("POST", url, buff)
- if err != nil {
- return
- }
-
- req.Header.Add("Content-Type", "application/json")
-
- client := &http.Client{}
- resp, err := client.Do(req)
- if err != nil {
- return
- }
- defer resp.Body.Close()
-
- contents, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return
- }
- res = &Response{Content: string(contents), Code: resp.StatusCode}
-
- return
-}
-
-func DoDelete(url string) (res *Response, err error) {
- req, err := http.NewRequest("DELETE", url, nil)
- if err != nil {
- return
- }
-
- client := &http.Client{}
- resp, err := client.Do(req)
- if err != nil {
- return
- }
- defer resp.Body.Close()
-
- contents, err := ioutil.ReadAll(resp.Body)
- if err != nil {
-
- return
- }
-
- log.Println("[contents]", contents)
- res = &Response{Content: string(contents), Code: resp.StatusCode}
- return
-}
-
func testGet(t *testing.T, targetURI string, statusCode int) {
t.Helper()
- res, err := DoGet(testURL + targetURI)
+ resByte, resStatusCode, err := httpclient.DoGet(testURL + targetURI)
if err != nil {
t.Log(err.Error())
t.Error()
}
- t.Log(res.Content)
- if len(res.Content) == 0 {
+ t.Log(resByte)
+ if len(resByte) == 0 {
t.Error()
}
- AssertEqualInt(t, res.Code, statusCode)
+ AssertEqualInt(t, resStatusCode, statusCode)
+ return
}
func testPost(t *testing.T, targetURI string, bodyContents []byte, statusCode int) (res *Response) {
t.Helper()
- res, err := DoPost(testURL+targetURI, bodyContents)
+ resByte, resStatusCode, err := httpclient.DoPost(testURL+targetURI, bodyContents)
if err != nil {
t.Log(err.Error())
t.Error()
}
- t.Log(res.Content)
- if len(res.Content) == 0 {
+ t.Log(resByte)
+ if len(resByte) == 0 {
t.Error()
}
- AssertEqualInt(t, res.Code, statusCode)
+ AssertEqualInt(t, resStatusCode, statusCode)
+
+ res = &Response{Content: string(resByte), Code: resStatusCode}
+
return
}
testPost(t, targetURI, bdbytes, http.StatusOK)
}
+var keyFilePath string = "./../../securemgr/test/key.txt"
+
func init() {
devicemgr.InitDeviceMgr()
servicemgr.Init()
discoverymgr.InitDiscovery()
scoringmgr.Init()
+ securemgr.Init(keyFilePath)
router := NewRouter()
server := httptest.NewServer(router)
targetURI := ConstServicemgrServicesNoti
- res, err := DoPost(testURL+targetURI+"1", snbytes)
+ _, resStatusCode, err := httpclient.DoPost(testURL+targetURI+"1", snbytes)
if err != nil {
t.Log(err.Error())
t.Error()
}
- AssertEqualInt(t, res.Code, http.StatusOK)
+ AssertEqualInt(t, resStatusCode, http.StatusOK)
}
-!mock/mysum/libmysum.so
\ No newline at end of file
+!mock/mysum/libmysum.so\r
+*.o
\ No newline at end of file
import "unsafe"
import (
- "strings"
"time"
confdescription "configuremgr/description"
Ch chan interface{}
- IRunningScore func(uintptr) float64
- IGetScore func(string, string) (float64, error)
+ IRunningScore func(uintptr) float64
+ IGetScore func(string, string) (float64, error)
+ IStartResourceService func()
+ IStopResourceService func()
}
var (
}
-// RemoveLib is for clean-up channel / handler
+// RemoveLib is for clean-up channel / handler
func (handlers *Handlers) RemoveLib(libName string) {
handler := handlers.table[libName]
func (handlers *Handlers) makeHandler(pairObj pair) (handlerObj *Handler) {
handlerObj = new(Handler)
- handlerObj.handlerName = getLibName(pairObj.libPath)
+ handlerObj.handlerName = pairObj.doc.ServiceInfo.ServiceName
handlerObj.libPath = pairObj.libPath
handlerObj.intervalMs = pairObj.doc.ResourceType.IntervalTimeMs
handlerObj.functionName = pairObj.doc.ScoringMethod.FunctionName
func (handler *Handler) running() {
handler.scoreValue = handler.parents.IRunningScore(handler.symbol)
}
-
-func getLibName(libPath string) string {
- name := strings.Split(libPath, "/")
- lastIdx := len(name)
-
- libName := strings.Split(name[lastIdx-1], ".")
- return strings.TrimPrefix(libName[0], "lib")
-}
--- /dev/null
+package main\r
+\r
+import (\r
+ "fmt"\r
+)\r
+\r
+func getPrime(rangeNum uint64){\r
+\r
+ var targetNum uint64\r
+ var isPrime bool\r
+ var n uint64\r
+ var count uint32\r
+\r
+ targetNum = 2\r
+ isPrime = true\r
+ count = 0\r
+\r
+ for targetNum <= rangeNum {\r
+ \r
+ isPrime = true\r
+\r
+ for n = 2 ; n < targetNum ; n++ {\r
+\r
+ if targetNum % n == 0 {\r
+ isPrime = false\r
+ break;\r
+ }\r
+ }\r
+\r
+ if isPrime {\r
+ fmt.Print(targetNum, " ")\r
+ count++\r
+ }\r
+ \r
+ targetNum += 1\r
+ }\r
+\r
+ fmt.Println("count : ", count)\r
+\r
+}\r
+\r
+func main(){\r
+\r
+ getPrime(100000000000)\r
+}
\ No newline at end of file
package main
-
-
/*
#include <stdlib.h>
#include <dlfcn.h>
func main() {
- // LoadScoringAdd("src/scoringmgr/mock/mysum/libmysum.so", 1000)
- go func(){
+ // LoadScoringAdd("src/scoringmgr/mock/mysum/libmysum.so", 1000)
+ go func() {
symbol := Init()
- LoadScoringAddInterface(symbol)
+ LoadScoringInterfaceAdd(symbol)
}()
- for {}
+ for {
+ }
}
func LoadScoringAdd(libPath string, intervalMs int) {
-
- go func(){
+ go func() {
- sym := C.CString("add")
- defer C.free(unsafe.Pointer(sym))
+ sym := C.CString("add")
+ defer C.free(unsafe.Pointer(sym))
- lib := C.CString(libPath)
-
- defer C.free(unsafe.Pointer(lib))
+ lib := C.CString(libPath)
- handle, err := C.dlopen(lib , C.RTLD_LAZY)
- defer C.dlclose(handle)
+ defer C.free(unsafe.Pointer(lib))
- if err != nil {
- fmt.Println("dlopen error occured")
- }
+ handle, err := C.dlopen(lib, C.RTLD_LAZY)
+ defer C.dlclose(handle)
- symbolPtr, symbolErr := C.dlsym(handle, sym)
- if symbolErr != nil {
- fmt.Println("dlsym error occured")
- }
+ if err != nil {
+ fmt.Println("dlopen error occured")
+ }
- fmt.Printf("addSymbol is at %p\n", symbolPtr)
- fmt.Println(C.wrap_add(symbolPtr, 2,3))
-
- for{
- fmt.Println(C.wrap_add(symbolPtr, 2,3))
- time.Sleep(time.Duration(intervalMs) * time.Millisecond)
- }
- }()
+ symbolPtr, symbolErr := C.dlsym(handle, sym)
+ if symbolErr != nil {
+ fmt.Println("dlsym error occured")
+ }
- return
-}
+ fmt.Printf("addSymbol is at %p\n", symbolPtr)
+ fmt.Println(C.wrap_add(symbolPtr, 2, 3))
+ for {
+ fmt.Println(C.wrap_add(symbolPtr, 2, 3))
+ time.Sleep(time.Duration(intervalMs) * time.Millisecond)
+ }
+ }()
+ return
+}
func Init() unsafe.Pointer {
+ lib := C.CString("src/scoringmgr/mock/mysum/libmysum.so")
+ defer C.free(unsafe.Pointer(lib))
- lib := C.CString("src/scoringmgr/mock/mysum/libmysum.so")
- defer C.free(unsafe.Pointer(lib))
+ dl, err := C.dlopen(lib, C.RTLD_LAZY)
+ //TODO : release dlclose
+ // defer C.dlclose(dl)
- dl, err := C.dlopen(lib , C.RTLD_LAZY)
- //TODO : release dlclose
- // defer C.dlclose(dl)
+ if err != nil {
+ ELog.Fatal("dlopen error occured")
+ }
- if err != nil {
- ELog.Fatal("dlopen error occured")
- }
-
+ sym := C.CString("add")
+ defer C.free(unsafe.Pointer(sym))
- sym := C.CString("add")
- defer C.free(unsafe.Pointer(sym))
-
- symbol, symbolErr := C.dlsym(dl, sym)
- if symbolErr != nil {
- ELog.Fatal("symbol error occured")
- }
+ symbol, symbolErr := C.dlsym(dl, sym)
+ if symbolErr != nil {
+ ELog.Fatal("symbol error occured")
+ }
- return symbol
+ return symbol
}
-func LoadScoringAddInterface(symbol unsafe.Pointer) {
+func LoadScoringInterfaceAdd(symbol unsafe.Pointer) {
- ILog.Println(C.wrap_add(symbol,2,3))
+ ILog.Println(C.wrap_add(symbol, 2, 3))
- return
+ return
}
--- /dev/null
+package main
+
+/*
+#include <stdlib.h>
+#include <dlfcn.h>
+#include "../resourceservice/resourceservice.c"
+#cgo LDFLAGS: -ldl -lpthread
+*/
+import "C"
+import "unsafe"
+import "log"
+import "os"
+import "time"
+
+var ILog *log.Logger = log.New(os.Stdout, "[scoringmgr] INFO : ", log.LstdFlags)
+var ELog *log.Logger = log.New(os.Stdout, "[scoringmgr] ERROR : ", log.LstdFlags)
+var DLog *log.Logger = log.New(os.Stdout, "[scoringmgr] DEBUG : ", log.LstdFlags)
+
+func main() {
+ C.startResourceService()
+
+ symbol := Init()
+ for i := 0 ; i < 3 ; i++ {
+ LoadScoringGeneralInterface(symbol)
+ time.Sleep(time.Duration(1) * time.Second)
+ }
+
+ C.stopResourceService()
+ time.Sleep(time.Duration(1) * time.Second)
+}
+
+func Init() unsafe.Pointer {
+
+
+ lib := C.CString("src/scoringmgr/mock/myscoring/libmyscoring.so")
+ defer C.free(unsafe.Pointer(lib))
+
+ dl, err := C.dlopen(lib , C.RTLD_LAZY)
+ //TODO : release dlclose
+ // defer C.dlclose(dl)
+
+ if err != nil {
+ ELog.Fatal("dlopen error occured")
+ }
+
+
+ sym := C.CString("myscoring")
+ defer C.free(unsafe.Pointer(sym))
+
+ symbol, symbolErr := C.dlsym(dl, sym)
+ if symbolErr != nil {
+ ELog.Fatal("symbol error occured")
+ }
+
+ return symbol
+}
+
+func LoadScoringGeneralInterface(symbol unsafe.Pointer) {
+ ILog.Printf("scorevalue : %f\n", C.wrap_myscoring(symbol))
+
+ return
+}
package main
import (
- "log"
+ "log"
)
type handler struct {
+ handlerName string
+ handlerPath string
- handlerName string
- handlerPath string
-
- devicesScoreValue map[string]float64
- handle int
- interval int
- resourceCount int
-
- scoreValue int
+ devicesScoreValue map[string]float64
+ handle int
+ interval int
+ resourceCount int
+ scoreValue int
}
-
type pair struct {
- a string
- b string
+ a string
+ b string
}
-
type Handlers struct {
-
- Table map[string]*handler
- ch chan pair
+ Table map[string]*handler
+ ch chan pair
}
-var handlers = Handlers {
- Table : make(map[string]*handler),
- ch : make(chan pair, 1024),
-
+var handlers = Handlers{
+ Table: make(map[string]*handler),
+ ch: make(chan pair, 1024),
}
+func main() {
-func main(){
-
- handlerObj := new(handler)
- handlerObj.handlerName = "HelloWorld"
- handlerObj.handlerPath = "/usr/local/lib/libhelloworld"
+ handlerObj := new(handler)
+ handlerObj.handlerName = "HelloWorld"
+ handlerObj.handlerPath = "/usr/local/lib/libhelloworld"
- handlers.Table["A"] = handlerObj
+ handlers.Table["A"] = handlerObj
- log.Println(handlerObj)
+ log.Println(handlerObj)
- done := make(chan bool)
- go func(){
- handlers.ch <- pair{"A", "B"}
- }()
+ done := make(chan bool)
+ go func() {
+ handlers.ch <- pair{"A", "B"}
+ }()
- go func(){
- pairObj := <- handlers.ch
- log.Println(pairObj)
- done <- true
- }()
+ go func() {
+ pairObj := <-handlers.ch
+ log.Println(pairObj)
+ done <- true
+ }()
- <- done
-}
\ No newline at end of file
+ <-done
+}
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+//network
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <linux/sockios.h>
+#include <linux/ethtool.h>
+#include <net/if.h>
+
+//cpu
+#include <sys/sysinfo.h>
+
+#define SSTRNCMP(stream, name) strncmp(stream, name, strlen(name))
+#define NIC_NAME_LEN 64
+#define NIC_MAX_COUNT 32
+
+unsigned long networkPackets(char *ifNamePath)
+{
+ FILE *fd;
+ char buf[64];
+ fd = fopen(ifNamePath, "r");
+ fgets(buf, 64, fd);
+ fclose(fd);
+
+ unsigned long bytes;
+ sscanf(buf, "%ld", &bytes);
+
+ return bytes;
+}
+
+unsigned long networkTotalPackets(char **ifNamesPath, int count)
+{
+ unsigned long totalBytes = 0;
+
+ for (int i = 0; i < count; i++)
+ {
+
+ totalBytes += networkPackets(ifNamesPath[i]);
+ }
+
+ return totalBytes;
+}
+
+void makeIfNamesPath(char (*ifNames)[NIC_NAME_LEN], int count, char **ifNamesPath)
+{
+ for (int i = 0; i < count; i++)
+ {
+ sprintf(ifNamesPath[i * 2], "/sys/class/net/%s/statistics/rx_bytes", ifNames[i]);
+ sprintf(ifNamesPath[i * 2 + 1], "/sys/class/net/%s/statistics/tx_bytes", ifNames[i]);
+
+ // printf("%s\n", ifNamesPath[i * 2]);
+ // printf("%s\n", ifNamesPath[i * 2 + 1]);
+ }
+}
+
+void getInterfaceAdapter(char ifnames[][NIC_NAME_LEN])
+{
+ struct if_nameindex *if_nidxs, *intf;
+
+ if_nidxs = if_nameindex();
+ if (if_nidxs != NULL)
+ {
+ int i = 0;
+ for (intf = if_nidxs; intf->if_index != 0 || intf->if_name != NULL; intf++, i++)
+ {
+ // printf("%s\n", intf->if_name);
+ strcpy(ifnames[i], intf->if_name);
+ }
+
+ ifnames[i][0] = '\0';
+ }
+}
+
+int filteringIfNames(char allIfNames[][NIC_NAME_LEN], char targetIfNames[][NIC_NAME_LEN])
+{
+ int allIfNamesIter = 0;
+ int targetIfNamesIter = 0;
+
+ //iterator
+ while (allIfNames[allIfNamesIter][0] != '\0')
+ {
+ if ((SSTRNCMP(allIfNames[allIfNamesIter], "eth") == 0) || (SSTRNCMP(allIfNames[allIfNamesIter], "enp") == 0) || (SSTRNCMP(allIfNames[allIfNamesIter], "wl") == 0))
+ {
+ printf("%s\n", allIfNames[allIfNamesIter]);
+ strcpy(targetIfNames[targetIfNamesIter++], allIfNames[allIfNamesIter]);
+ }
+
+ allIfNamesIter++;
+ }
+
+ return targetIfNamesIter;
+}
+
+void sampingGetCpuUsage(double *cpuTotal, double *cpuIdle)
+{
+
+ unsigned long cpuAttributes[10];
+
+#ifdef __linux__
+ char stream[1024];
+
+ FILE *file = fopen("/proc/stat", "r");
+ fscanf(file, "cpu ");
+ for (int i = 0; i < 10; i++)
+ {
+ fscanf(file, " %lu", &cpuAttributes[i]);
+ // printf("aa : %lu\n", cpuAttributes[i]);
+ }
+ fclose(file);
+
+ *cpuTotal = 0.0;
+ for (int i = 0; i < 10; i++)
+ {
+ *cpuTotal += cpuAttributes[i];
+ }
+
+ *cpuIdle = cpuAttributes[3];
+
+#else
+#error "Not Support this architecture"
+#endif
+}
+
+//======================= PUBLIC API ===============================
+double getCpuUsage()
+{
+ double cpuTotalPrev, cpuTotalNext;
+ double cpuIdlePrev, cpuIdleNext;
+
+ sampingGetCpuUsage(&cpuTotalPrev, &cpuIdlePrev);
+ usleep(1000 * 1000);
+ sampingGetCpuUsage(&cpuTotalNext, &cpuIdleNext);
+
+ double totalTick;
+ double idleTick;
+
+ double cpuUsage;
+
+ printf("%f %f\n", cpuTotalNext, cpuTotalPrev);
+ printf("%f %f\n", cpuIdleNext, cpuIdlePrev);
+
+ totalTick = (cpuTotalNext - cpuTotalPrev);
+ idleTick = (cpuIdleNext - cpuIdlePrev);
+
+ cpuUsage = 100 * (totalTick - idleTick) / (totalTick);
+
+ return cpuUsage;
+}
+
+double getCpuCount()
+{
+ return get_nprocs();
+}
+
+//it support over linux kernel 3.14
+double getMemoryAvailable()
+{
+
+ long int memAvailable;
+
+#ifdef __linux__
+ char stream[1024];
+
+ FILE *file = fopen("/proc/meminfo", "r");
+
+ //just reading one string , so check return to 1
+ while (fscanf(file, " %1023s", stream) == 1)
+ {
+ if (!SSTRNCMP(stream, "MemAvailable:"))
+ {
+ fscanf(file, " %ld", &memAvailable);
+ }
+ }
+ fclose(file);
+
+#else
+#error "Not Support this architecture"
+#endif
+
+ return (double)memAvailable;
+}
+
+double getMemoryFree()
+{
+
+ long int memFree;
+
+#ifdef __linux__
+ char stream[1024];
+
+ FILE *file = fopen("/proc/meminfo", "r");
+
+ //just reading one string , so check return to 1
+ while (fscanf(file, " %1023s", stream) == 1)
+ {
+ if (!SSTRNCMP(stream, "MemFree:"))
+ {
+ fscanf(file, " %ld", &memFree);
+ }
+ }
+ fclose(file);
+
+#else
+#error "Not Support this architecture"
+#endif
+
+ return (double)memFree;
+}
+
+double networkMbps(char (*ifNames)[NIC_NAME_LEN], int count)
+{
+ unsigned long prevTotalByte, nextTotalByte;
+
+ char **ifNamesPath = malloc(sizeof(char *) * count * 2);
+ for (int i = 0; i < count * 2; i++)
+ {
+ ifNamesPath[i] = malloc(sizeof(char) * 512);
+ }
+
+ makeIfNamesPath(ifNames, count, ifNamesPath);
+ prevTotalByte = networkTotalPackets(ifNamesPath, count * 2);
+ usleep(1000 * 1000);
+ nextTotalByte = networkTotalPackets(ifNamesPath, count * 2);
+
+ double mbps = (nextTotalByte - prevTotalByte) / 1024.0 / 1024.0;
+
+ for (int i = 0; i < count * 2; i++)
+ {
+ free(ifNamesPath[i]);
+ }
+ free(ifNamesPath);
+
+ return mbps;
+}
+
+double networkBandwidth()
+{
+ int sock;
+ struct ifreq ifr;
+ struct ethtool_cmd edata;
+ int rc;
+
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0)
+ {
+ perror("socket");
+ exit(1);
+ }
+
+ strncpy(ifr.ifr_name, "enp4s0", sizeof(ifr.ifr_name));
+ ifr.ifr_data = &edata;
+
+ edata.cmd = ETHTOOL_GSET;
+
+ rc = ioctl(sock, SIOCETHTOOL, &ifr);
+ if (rc < 0)
+ {
+ perror("ioctl");
+ exit(1);
+ }
+ switch (ethtool_cmd_speed(&edata))
+ {
+ case SPEED_10:
+ printf("10Mbps\n");
+ break;
+ case SPEED_100:
+ printf("100Mbps\n");
+ break;
+ case SPEED_1000:
+ printf("1Gbps\n");
+ break;
+ case SPEED_2500:
+ printf("2.5Gbps\n");
+ break;
+ case SPEED_10000:
+ printf("10Gbps\n");
+ break;
+ default:
+ printf("Speed returned is %d\n", edata.speed);
+ }
+
+ return (0);
+}
+//======================= PUBLIC API ===============================
+
+int main(int argc, char **argv)
+{
+
+ char allIfNames[NIC_MAX_COUNT][NIC_NAME_LEN];
+ char targetIfNames[NIC_MAX_COUNT][NIC_NAME_LEN];
+ int targetIfNamesCount;
+
+ getInterfaceAdapter(allIfNames);
+ targetIfNamesCount = filteringIfNames(allIfNames, targetIfNames);
+ printf("count : %d\n", targetIfNamesCount);
+
+ while (1)
+ {
+ printf("=======================\n");
+ printf("networkmbps : %f\n", networkMbps(targetIfNames, targetIfNamesCount));
+ printf("networkbandwidth : %f\n", networkBandwidth());
+ printf("memory available : %f\n", getMemoryAvailable());
+ printf("memory free : %f\n", getMemoryFree());
+ printf("cpu count : %f\n", getCpuCount());
+ printf("cpu usage : %f\n", getCpuUsage());
+ printf("=======================\n");
+ }
+}
\ No newline at end of file
--- /dev/null
+package scoringmgr
+
+/*
+#include "resourceservice/resourceservice.c"
+*/
+import "C"
+import "unsafe"
+
+// LoadScoringGeneralInterface function
+func LoadScoringGeneralInterface(symbol uintptr) float64 {
+
+ ret := C.wrap_myscoring(unsafe.Pointer(symbol))
+ ILog.Println(ret)
+
+ return float64(ret)
+
+}
+
+func StartResourceService() {
+ //NOTE : is it waiting just one second?
+ C.startResourceService()
+}
+
+func StopResourceService() {
+ C.stopResourceService()
+}
+
+//$ touch resourceservice/resourceservice.c
\ No newline at end of file
scoringmgr "scoringmgr"
)
-// LoadScoringAddInterface function
-func LoadScoringAddInterface(symbol uintptr) float64 {
+// LoadScoringInterfaceAdd function
+func LoadScoringInterfaceAdd(symbol uintptr) float64 {
ret := C.wrap_add(unsafe.Pointer(symbol), 2, 3)
scoringmgr.ILog.Println(ret)
return float64(ret)
}
+
+
+func StartResourceServiceAdd() {
+ scoringmgr.ILog.Println("startResourceService of add library not meaning")
+}
+
+func StopResourceServiceAdd() {
+ scoringmgr.ILog.Println("stopResourceService of add library not meaning")
+}
--- /dev/null
+libmyscoring.so.1.0.1
\ No newline at end of file
--- /dev/null
+
+/*
+$ gcc -fPIC -c myscoring.c && gcc -shared -o libmyscoring.so.1.0.1 myscoring.o
+$ ln -rs libmyscoring.so.1.0.1 libmyscoring.so
+*/
+
+#include <math.h>
+#include <assert.h>
+#include <stdio.h>
+
+#define CNT 6
+
+/*
+features :
+-. there is using moving average, but it is not written code at service_provider.cpp
+-. bandwidth is not meaning Mbps
+*/
+
+//network score mmDiscovery/service_provider.cpp
+static double networkScore(double n) {
+ return 1 / (8770 * pow(n, -0.9));
+}
+
+//cpu score mmDiscovery/service_provider.cpp
+static double cpuScore(double freq, double usage, double count){
+ return ((1 / (5.66 * pow(freq, -0.66))) +
+ (1 / (3.22 * pow(usage, -0.241))) +
+ (1 / (4 * pow(count, -0.3)))) / 3;
+}
+
+//render score mmDiscovery/service_provider.cpp
+//https://github.com/Samsung/Castanets/blob/castanets_63/service_discovery_manager/Component/mmDiscovery/monitor_client.cpp
+static double renderingScore(double r) {
+ return (r < 0) ? 0 : 0.77 * pow(r, -0.43);
+}
+
+//============== INTERFACE API ==============
+double myscoring(double (*getResource)(const char *))
+{
+
+ printf("myscoring\n");
+ double score;
+
+
+ score = 0.0;
+ score += networkScore(getResource("network/bandwidth"));
+ score += cpuScore(getResource("cpu/freq"), getResource("cpu/usage"), getResource("cpu/count"));
+ score /= 2;
+ score += renderingScore(getResource("network/rtt"));
+
+ return score;
+}
+
+#define CNT 6
+
+double myscoring2(double (*getResource)(const char *))
+{
+
+ printf("myscoring\n");
+
+ double score;
+ const char *resourceNames[CNT] = {"cpu/usage", "cpu/count", "memory/free", "memory/available", "network/mbps", "network/bandwidth"};
+ double W[CNT] = {1.48271, 4.125421, 5.3381723, 9.194717234, 2.323, 1.123};
+ double resourceValues[CNT];
+
+ // double someResource;
+ // someResource = getResource("some/usage");
+ // assert(isnan(someResource));
+
+ for (int i = 0; i < CNT; i++)
+ {
+ resourceValues[i] = getResource(resourceNames[i]);
+ printf("resourceNames : %s %f\n", resourceNames[i], resourceValues[i]);
+ }
+
+ score = 0.0;
+ for (int i = 0; i < CNT; i++)
+ {
+ score += resourceValues[i] * W[i];
+ }
+
+ return score;
+}
--- /dev/null
+# Description of service that will be requested
+[Version]
+ConfVersion=v0.0 ; Version of Configuration file
+
+[ServiceInfo]
+ServiceName=ls ; Name of distributed service
+
+[ScoringMethod]
+LibFile=./libmyscoring.so ; Library file name
+FunctionName=myscoring
+
+[ResourceType]
+IntervalTimeMs=1000 ; Interval time of get resource
+MaxCount=10 ; Number of times
--- /dev/null
+/*
+$ gcc -fPIC -c mysum.c && gcc -shared -o libmysum.so.1.0.1 mysum.o
+$ ln -rs libmysum.so.1.0.1 libmysum.so
+*/
+
+
+int add(int a , int b){
+ return a + b;
+}
\ No newline at end of file
-# Description of service that will be requested\r
-[Version]\r
-ConfVersion=v0.0 ; Version of Configuration file\r
-\r
-[ServiceInfo]\r
-ServiceName=HelloWorldService ; Name of distributed service\r
-\r
-[ResourceType]\r
-IntervalTimeMs=1000 ; Interval time of get resource\r
-MaxCount=10 ; Number of times\r
-\r
-[ScoringMethod]\r
-LibFile=./libmysum.so ; Library file name\r
-FunctionName=add ; Library function name
\ No newline at end of file
+# Description of service that will be requested
+[Version]
+ConfVersion=v0.0 ; Version of Configuration file
+
+[ServiceInfo]
+ServiceName=ls ; Name of distributed service
+
+[ScoringMethod]
+LibFile=./libmysum.so ; Library file name
+FunctionName=add
+
+[ResourceType]
+IntervalTimeMs=1000 ; Interval time of get resource
+MaxCount=10 ; Number of times
package scoringmgr
import (
- confdescription "configuremgr/description"
+ confdescription "configuremgr/description"
)
type pair struct {
- libPath string
- doc *confdescription.Doc
-}
\ No newline at end of file
+ libPath string
+ doc *confdescription.Doc
+}
--- /dev/null
+#include <stdio.h>\r
+#include <string.h>\r
+#include <stdlib.h>\r
+#include <unistd.h>\r
+#include <pthread.h>\r
+#include <math.h>\r
+\r
+//network\r
+#include <sys/socket.h>\r
+#include <sys/ioctl.h>\r
+#include <netinet/in.h>\r
+#include <linux/sockios.h>\r
+#include <linux/ethtool.h>\r
+#include <net/if.h>\r
+\r
+//cpu\r
+#include <sys/sysinfo.h>\r
+\r
+#define SSTRNCMP(stream, name) strncmp(stream, name, strlen(name))\r
+\r
+typedef double (*tGetResource)(const char *);\r
+\r
+typedef struct\r
+{\r
+ pthread_t t;\r
+ int done;\r
+} tThread;\r
+\r
+tThread cpuUsageThread;\r
+tThread networkUsageThread;\r
+tThread diskUsageThread;\r
+\r
+//============== MEMORY API ================\r
+//it support over linux kernel 3.14\r
+double getMemoryAvailable()\r
+{\r
+\r
+ long int memAvailable;\r
+\r
+#ifdef __linux__\r
+ char stream[1024];\r
+ int ret;\r
+\r
+ FILE *file = fopen("/proc/meminfo", "r");\r
+\r
+ //just reading one string , so check return to 1\r
+ while (fscanf(file, " %1023s", stream) == 1)\r
+ {\r
+ if (!SSTRNCMP(stream, "MemAvailable:"))\r
+ {\r
+ ret = fscanf(file, " %ld", &memAvailable);\r
+ }\r
+ }\r
+ fclose(file);\r
+\r
+#else\r
+#error "Not Support this architecture"\r
+#endif\r
+\r
+ return (double)memAvailable;\r
+}\r
+\r
+double getMemoryFree()\r
+{\r
+\r
+ long int memFree;\r
+\r
+#ifdef __linux__\r
+ char stream[1024];\r
+ int ret;\r
+\r
+ FILE *file = fopen("/proc/meminfo", "r");\r
+\r
+ //just reading one string , so check return to 1\r
+ while (fscanf(file, " %1023s", stream) == 1)\r
+ {\r
+ if (!SSTRNCMP(stream, "MemFree:"))\r
+ {\r
+ ret = fscanf(file, " %ld", &memFree);\r
+ }\r
+ }\r
+ fclose(file);\r
+\r
+#else\r
+#error "Not Support this architecture"\r
+#endif\r
+\r
+ return (double)memFree;\r
+}\r
+//============== MEMORY API ================\r
+\r
+//============== CPU API ===================\r
+\r
+double gCpuUsage;\r
+\r
+static double getCpuUsage()\r
+{\r
+ return gCpuUsage;\r
+}\r
+\r
+static double getCpuCount()\r
+{\r
+ return get_nprocs();\r
+}\r
+\r
+static void sampingGetCpuUsage(double *cpuTotal, double *cpuIdle)\r
+{\r
+\r
+ unsigned long cpuAttributes[10];\r
+\r
+#ifdef __linux__\r
+ char stream[1024];\r
+ int ret;\r
+ FILE *file = fopen("/proc/stat", "r");\r
+ ret = fscanf(file, "cpu ");\r
+ for (int i = 0; i < 10; i++)\r
+ {\r
+ ret = fscanf(file, " %lu", &cpuAttributes[i]);\r
+ // printf("aa : %lu\n", cpuAttributes[i]);\r
+ }\r
+ fclose(file);\r
+\r
+ *cpuTotal = 0.0;\r
+ for (int i = 0; i < 10; i++)\r
+ {\r
+ *cpuTotal += cpuAttributes[i];\r
+ }\r
+\r
+ *cpuIdle = cpuAttributes[3];\r
+\r
+#else\r
+#error "Not Support this architecture"\r
+#endif\r
+}\r
+\r
+double goCpuUsage()\r
+{\r
+ double cpuTotalPrev, cpuTotalNext;\r
+ double cpuIdlePrev, cpuIdleNext;\r
+\r
+ sampingGetCpuUsage(&cpuTotalPrev, &cpuIdlePrev);\r
+ usleep(1000 * 1000);\r
+ sampingGetCpuUsage(&cpuTotalNext, &cpuIdleNext);\r
+\r
+ double totalTick;\r
+ double idleTick;\r
+\r
+ double cpuUsage;\r
+\r
+ totalTick = (cpuTotalNext - cpuTotalPrev);\r
+ idleTick = (cpuIdleNext - cpuIdlePrev);\r
+\r
+ cpuUsage = 100 * (totalTick - idleTick) / (totalTick);\r
+\r
+ return cpuUsage;\r
+}\r
+\r
+static double getCpuFreq()\r
+{\r
+ double freq;\r
+ FILE *file;\r
+ int cnt;\r
+\r
+ file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r");\r
+ cnt = fscanf(file, "%lf", &freq);\r
+ fclose(file);\r
+\r
+ freq = (float)(freq / 1000000);\r
+ return freq;\r
+}\r
+\r
+static void *daemonCpuUsage(void *obj)\r
+{\r
+ printf("daemonCpuUsage\n");\r
+ while (!cpuUsageThread.done)\r
+ {\r
+ gCpuUsage = goCpuUsage();\r
+ }\r
+ printf("daemonNetworkUsage done\n");\r
+\r
+ return NULL;\r
+}\r
+//============== CPU API ===================\r
+\r
+//============== NETWORK API ===================\r
+#define NIC_NAME_LEN 64\r
+#define NIC_MAX_COUNT 32\r
+\r
+double gNetworkMbps;\r
+\r
+static double getNetworkMbps()\r
+{\r
+ return gNetworkMbps;\r
+}\r
+\r
+static double getNetworkBandwidth()\r
+{\r
+ int sock;\r
+ struct ifreq ifr;\r
+ struct ethtool_cmd edata;\r
+ int rc;\r
+\r
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);\r
+ if (sock < 0)\r
+ {\r
+ perror("socket");\r
+ exit(1);\r
+ }\r
+\r
+ strncpy(ifr.ifr_name, "enp4s0", sizeof(ifr.ifr_name));\r
+ ifr.ifr_data = (caddr_t)&edata;\r
+\r
+ edata.cmd = ETHTOOL_GSET;\r
+\r
+ rc = ioctl(sock, SIOCETHTOOL, &ifr);\r
+ if (rc < 0)\r
+ {\r
+ perror("ioctl");\r
+ exit(1);\r
+ }\r
+\r
+ // switch (ethtool_cmd_speed(&edata))\r
+ // {\r
+ // case SPEED_10:\r
+ // printf("10Mbps\n");\r
+ // break;\r
+ // case SPEED_100:\r
+ // printf("100Mbps\n");\r
+ // break;\r
+ // case SPEED_1000:\r
+ // printf("1Gbps\n");\r
+ // break;\r
+ // case SPEED_2500:\r
+ // printf("2.5Gbps\n");\r
+ // break;\r
+ // case SPEED_10000:\r
+ // printf("10Gbps\n");\r
+ // break;\r
+ // default:\r
+ // printf("Speed returned is %d\n", edata.speed);\r
+ // }\r
+\r
+ return (double)ethtool_cmd_speed(&edata);\r
+}\r
+\r
+unsigned long networkPackets(char *ifNamePath)\r
+{\r
+ FILE *fd;\r
+ char buf[64];\r
+\r
+ fd = fopen(ifNamePath, "r");\r
+ char *ret = fgets(buf, 64, fd);\r
+ fclose(fd);\r
+\r
+ unsigned long bytes;\r
+ sscanf(buf, "%ld", &bytes);\r
+\r
+ return bytes;\r
+}\r
+\r
+unsigned long networkTotalPackets(char **ifNamesPath, int count)\r
+{\r
+ unsigned long totalBytes = 0;\r
+\r
+ for (int i = 0; i < count; i++)\r
+ {\r
+\r
+ totalBytes += networkPackets(ifNamesPath[i]);\r
+ }\r
+\r
+ return totalBytes;\r
+}\r
+\r
+void makeIfNamesPath(char (*ifNames)[NIC_NAME_LEN], int count, char **ifNamesPath)\r
+{\r
+ for (int i = 0; i < count; i++)\r
+ {\r
+ sprintf(ifNamesPath[i * 2], "/sys/class/net/%s/statistics/rx_bytes", ifNames[i]);\r
+ sprintf(ifNamesPath[i * 2 + 1], "/sys/class/net/%s/statistics/tx_bytes", ifNames[i]);\r
+\r
+ // printf("%s\n", ifNamesPath[i * 2]);\r
+ // printf("%s\n", ifNamesPath[i * 2 + 1]);\r
+ }\r
+}\r
+\r
+void getInterfaceAdapter(char ifnames[][NIC_NAME_LEN])\r
+{\r
+ struct if_nameindex *if_nidxs, *intf;\r
+\r
+ if_nidxs = if_nameindex();\r
+ if (if_nidxs != NULL)\r
+ {\r
+ int i = 0;\r
+ for (intf = if_nidxs; intf->if_index != 0 || intf->if_name != NULL; intf++, i++)\r
+ {\r
+ // printf("%s\n", intf->if_name);\r
+ strcpy(ifnames[i], intf->if_name);\r
+ }\r
+\r
+ ifnames[i][0] = '\0';\r
+ }\r
+}\r
+\r
+int filteringIfNames(char allIfNames[][NIC_NAME_LEN], char targetIfNames[][NIC_NAME_LEN])\r
+{\r
+ int allIfNamesIter = 0;\r
+ int targetIfNamesIter = 0;\r
+\r
+ //iterator\r
+ while (allIfNames[allIfNamesIter][0] != '\0')\r
+ {\r
+ if ((SSTRNCMP(allIfNames[allIfNamesIter], "eth") == 0) || (SSTRNCMP(allIfNames[allIfNamesIter], "enp") == 0) || (SSTRNCMP(allIfNames[allIfNamesIter], "wl") == 0))\r
+ {\r
+ printf("%s\n", allIfNames[allIfNamesIter]);\r
+ strcpy(targetIfNames[targetIfNamesIter++], allIfNames[allIfNamesIter]);\r
+ }\r
+\r
+ allIfNamesIter++;\r
+ }\r
+\r
+ return targetIfNamesIter;\r
+}\r
+\r
+double networkMbps(char (*ifNames)[NIC_NAME_LEN], int count)\r
+{\r
+ unsigned long prevTotalByte, nextTotalByte;\r
+\r
+ char **ifNamesPath = malloc(sizeof(char *) * count * 2);\r
+ for (int i = 0; i < count * 2; i++)\r
+ {\r
+ ifNamesPath[i] = malloc(sizeof(char) * 512);\r
+ }\r
+\r
+ makeIfNamesPath(ifNames, count, ifNamesPath);\r
+ prevTotalByte = networkTotalPackets(ifNamesPath, count * 2);\r
+ usleep(1000 * 1000);\r
+ nextTotalByte = networkTotalPackets(ifNamesPath, count * 2);\r
+\r
+ double mbps = (nextTotalByte - prevTotalByte) / 1024.0 / 1024.0;\r
+\r
+ for (int i = 0; i < count * 2; i++)\r
+ {\r
+ free(ifNamesPath[i]);\r
+ }\r
+ free(ifNamesPath);\r
+\r
+ return mbps;\r
+}\r
+\r
+static double getNetworkRTT()\r
+{\r
+ //TODO : how to get round trip time at endpoint\r
+ return 1.0;\r
+}\r
+\r
+static void *daemonNetworkUsage(void *obj)\r
+{\r
+ printf("daemonNetworkUsage\n");\r
+\r
+ char allIfNames[NIC_MAX_COUNT][NIC_NAME_LEN];\r
+ char targetIfNames[NIC_MAX_COUNT][NIC_NAME_LEN];\r
+ int targetIfNamesCount;\r
+\r
+ getInterfaceAdapter(allIfNames);\r
+ targetIfNamesCount = filteringIfNames(allIfNames, targetIfNames);\r
+ printf("nic count : %d\n", targetIfNamesCount);\r
+\r
+ while (!networkUsageThread.done)\r
+ {\r
+ gNetworkMbps = networkMbps(targetIfNames, targetIfNamesCount);\r
+ }\r
+ printf("daemonNetworkUsage done\n");\r
+\r
+ return NULL;\r
+}\r
+\r
+//============== NETWORK API ===================\r
+\r
+//============== DISK API ======================\r
+\r
+static void *daemonDiskUsage(void *obj)\r
+{\r
+ printf("daemonDiskUsage\n");\r
+ while (!diskUsageThread.done)\r
+ {\r
+ }\r
+ printf("daemonDiskUsage done\n");\r
+\r
+ return NULL;\r
+}\r
+\r
+//============== DISK API ======================\r
+\r
+//=================== CLIENT API ===============\r
+//cpu\r
+\r
+//cpu\r
+#define CPU_USAGE "cpu/usage"\r
+#define CPU_COUNT "cpu/count"\r
+#define CPU_FREQ "cpu/freq"\r
+\r
+//memory\r
+#define MEMORY_FREE "memory/free"\r
+#define MEMORY_AVAILABLE "memory/available"\r
+\r
+//storage\r
+// #define STORAGE_MBPS "storage/mbps"\r
+// #define STORAGE_FREE "stroage/free"\r
+\r
+//network\r
+#define NETWORK_MBPS "network/mbps"\r
+#define NETWORK_BANDWIDTH "network/bandwidth"\r
+#define NETWORK_RTT "network/rtt"\r
+\r
+double getResource(const char *resourceName)\r
+{\r
+\r
+ if (!SSTRNCMP(resourceName, CPU_USAGE))\r
+ {\r
+ return getCpuUsage();\r
+ }\r
+ else if (!SSTRNCMP(resourceName, CPU_COUNT))\r
+ {\r
+ return getCpuCount();\r
+ }\r
+ else if (!SSTRNCMP(resourceName, CPU_FREQ))\r
+ {\r
+ return getCpuFreq();\r
+ }\r
+ else if (!SSTRNCMP(resourceName, MEMORY_FREE))\r
+ {\r
+ return getMemoryFree();\r
+ }\r
+ else if (!SSTRNCMP(resourceName, MEMORY_AVAILABLE))\r
+ {\r
+ return getMemoryAvailable();\r
+ }\r
+ else if (!SSTRNCMP(resourceName, NETWORK_MBPS))\r
+ {\r
+ return getNetworkMbps();\r
+ }\r
+ else if (!SSTRNCMP(resourceName, NETWORK_BANDWIDTH))\r
+ {\r
+ return getNetworkBandwidth();\r
+ }\r
+ else if (!SSTRNCMP(resourceName, NETWORK_RTT))\r
+ {\r
+ return getNetworkRTT();\r
+ }\r
+ else\r
+ {\r
+ return NAN;\r
+ }\r
+}\r
+\r
+//===================== PUBLIC API =============\r
+void startResourceService()\r
+{\r
+ printf("startResourceService\n");\r
+ pthread_create(&cpuUsageThread.t, NULL, daemonCpuUsage, NULL);\r
+ pthread_create(&networkUsageThread.t, NULL, daemonNetworkUsage, NULL);\r
+ pthread_create(&diskUsageThread.t, NULL, daemonDiskUsage, NULL);\r
+}\r
+\r
+void stopResourceService()\r
+{\r
+ printf("stopResourceService\n");\r
+ cpuUsageThread.done = 1;\r
+ networkUsageThread.done = 1;\r
+ diskUsageThread.done = 1;\r
+}\r
+\r
+double\r
+wrap_myscoring(void *f)\r
+{\r
+ return ((double (*)(tGetResource))f)(getResource);\r
+}
\ No newline at end of file
package scoringmgr
import (
+ "common"
"encoding/json"
"errors"
- "net"
+ "log"
+ "net/http"
"restapi/httpclient"
"strconv"
"strings"
// GetScore is getting score of device
func GetScore(target string, name string) (scoreValue float64, err error) {
- if strings.Compare(target, getOutboundIP()) == 0 || strings.Compare(target, httpclient.ConstLocalTarget) == 0 {
+ outboundIP, outboundIPErr := common.GetOutboundIP()
+ if outboundIPErr != nil {
+ outboundIP = ""
+ }
+
+ if strings.Compare(target, outboundIP) == 0 || strings.Compare(target, httpclient.ConstLocalTarget) == 0 {
scoreValue, err = getScoreLocalEnv(name)
} else {
scoreValue, err = getScoreRemoteEnv(target, name)
func getScoreRemoteEnv(target string, name string) (scoreValue float64, err error) {
targetURL := httpclient.ConstPrefixHTTP + target + ":" + strconv.Itoa(httpclient.ConstWellknownPort) + "/api/v1/scoringmgr/score/" + name
- respBytes, err := httpclient.DoGet(targetURL)
+ respBytes, statusCode, err := httpclient.DoGet(targetURL)
if checkError(err) == true {
return scoreValue, err
}
var responseMsg map[string]interface{}
err = json.Unmarshal(respBytes, &responseMsg)
-
if err == nil {
scoreValue = responseMsg["ScoreValue"].(float64)
}
-
- return
-}
-
-func getOutboundIP() string {
- conn, err := net.Dial("udp", "8.8.8.8:80")
- if err != nil {
- ELog.Fatal(err)
+ if statusCode != http.StatusOK {
+ log.Println(statusCode)
}
- defer conn.Close()
- localAddr := conn.LocalAddr().(*net.UDPAddr)
-
- return localAddr.IP.String()
+ return
}
func checkError(err error) bool {
package scoringmgr_test
+//$ touch resourceservice/resourceservice.c
+
import (
+ "context"
+ "fmt"
+ "net/http"
"testing"
"time"
+ "gopkg.in/sconf/ini.v0"
+ "gopkg.in/sconf/sconf.v0"
+
confdescription "configuremgr/description"
+ restapi "restapi/v1"
scoringmgr "scoringmgr"
mockscoringmgr "scoringmgr/mock"
-
- "gopkg.in/sconf/ini.v0"
- "gopkg.in/sconf/sconf.v0"
)
-func TestBasicMockScoringMgr(t *testing.T) {
+func TestBasicMockScoringMgrMySumLib(t *testing.T) {
+
+ //interface
+ iscoringmgr := scoringmgr.Init()
+ iscoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ iscoringmgr.IGetScore = mockscoringmgr.GetScoreRandom100Mock
+ iscoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ iscoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
+ iscoringmgr.Ch = make(chan interface{}, 1024)
+
+ //start
+ iscoringmgr.Listening()
+ iscoringmgr.IStartResourceService()
+
+ //setting
+ time.Sleep(time.Duration(1) * time.Second)
+ appNmae := "ls"
+ libName := "mysum"
+ libPath := fmt.Sprintf("mock/%s/lib%s.so", libName, libName)
+ confPath := fmt.Sprintf("mock/%s/%s.conf", libName, libName)
+ cfg := new(confdescription.Doc)
+ sconf.Must(cfg).Read(ini.File(confPath))
+ scoringmgr.ILog.Println(cfg)
+ scoringmgr.PushLibPath(libPath, cfg, iscoringmgr.Ch)
+
+ //working on
+ time.Sleep(time.Duration(3) * time.Second)
+ scoreValue, _ := iscoringmgr.IGetScore("mock", "mock")
+ scoringmgr.DLog.Printf("scorevalue : %f\n", scoreValue)
+
+ //release
+ iscoringmgr.IStopResourceService()
+ iscoringmgr.RemoveLib(appNmae)
+ time.Sleep(time.Duration(1) * time.Second)
+}
+
+func TestBasicMockScoringMgrMySumLibGetScoreLocal(t *testing.T) {
+
+ //interface
+ iscoringmgr := scoringmgr.Init()
+ iscoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ iscoringmgr.IGetScore = scoringmgr.GetScore
+ iscoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ iscoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
+ iscoringmgr.Ch = make(chan interface{}, 1024)
+
+ //start
+ iscoringmgr.Listening()
+ iscoringmgr.IStartResourceService()
+
+ //setting
+ time.Sleep(time.Duration(1) * time.Second)
+ appName := "ls"
+ libName := "mysum"
+ libPath := fmt.Sprintf("mock/%s/lib%s.so", libName, libName)
+ confPath := fmt.Sprintf("mock/%s/%s.conf", libName, libName)
+ cfg := new(confdescription.Doc)
+ sconf.Must(cfg).Read(ini.File(confPath))
+ scoringmgr.ILog.Println(cfg)
+ scoringmgr.PushLibPath(libPath, cfg, iscoringmgr.Ch)
+
+ //working on
+ time.Sleep(time.Duration(3) * time.Second)
+ scoreValue, _ := iscoringmgr.IGetScore("localhost", "mysum")
+ scoringmgr.DLog.Printf("scorevalue : %f\n", scoreValue)
+
+ //release
+ iscoringmgr.IStopResourceService()
+ iscoringmgr.RemoveLib(appName)
+ time.Sleep(time.Duration(1) * time.Second)
+}
+
+func TestBasicMockScoringMgrMySumLibGetScoreRemote(t *testing.T) {
+
+ //interface
+ iscoringmgr := scoringmgr.Init()
+ iscoringmgr.IRunningScore = mockscoringmgr.LoadScoringInterfaceAdd
+ iscoringmgr.IGetScore = scoringmgr.GetScore
+ iscoringmgr.IStartResourceService = mockscoringmgr.StartResourceServiceAdd
+ iscoringmgr.IStopResourceService = mockscoringmgr.StopResourceServiceAdd
+ iscoringmgr.Ch = make(chan interface{}, 1024)
+
+ //start
+ iscoringmgr.Listening()
+ iscoringmgr.IStartResourceService()
+ router := restapi.NewRouter()
+ server := &http.Server{Addr: fmt.Sprintf(":%d", 56001), Handler: router}
+ go server.ListenAndServe()
+ time.Sleep(time.Duration(1) * time.Second)
+
+ //setting
+ appName := "ls"
+ libName := "mysum"
+ libPath := fmt.Sprintf("mock/%s/lib%s.so", libName, libName)
+ confPath := fmt.Sprintf("mock/%s/%s.conf", libName, libName)
+ cfg := new(confdescription.Doc)
+ sconf.Must(cfg).Read(ini.File(confPath))
+ scoringmgr.ILog.Println(cfg)
+ scoringmgr.PushLibPath(libPath, cfg, iscoringmgr.Ch)
+ time.Sleep(time.Duration(3) * time.Second)
- scoringHandlers := scoringmgr.Init()
- scoringHandlers.IRunningScore = mockscoringmgr.LoadScoringAddInterface
- scoringHandlers.IGetScore = mockscoringmgr.GetScoreRandom100Mock
- scoringHandlers.Ch = make(chan interface{}, 1024)
+ //working on
+ scoreValue, _ := iscoringmgr.IGetScore("127.0.0.1", "mysum")
+ scoringmgr.DLog.Printf("scorevalue : %f\n", scoreValue)
+ time.Sleep(time.Duration(1) * time.Second)
- scoringHandlers.Listening()
+ //release
+ iscoringmgr.IStopResourceService()
+ iscoringmgr.RemoveLib(appName)
+ if err := server.Shutdown(context.TODO()); err != nil {
+ panic(err)
+ }
+ time.Sleep(time.Duration(1) * time.Second)
+}
+func TestScoringMgrMyScoringLib(t *testing.T) {
+
+ //interface
+ iscoringmgr := scoringmgr.Init()
+ iscoringmgr.IRunningScore = scoringmgr.LoadScoringGeneralInterface
+ iscoringmgr.IGetScore = mockscoringmgr.GetScoreRandom100Mock
+ iscoringmgr.IStartResourceService = scoringmgr.StartResourceService
+ iscoringmgr.IStopResourceService = scoringmgr.StopResourceService
+ iscoringmgr.Ch = make(chan interface{}, 1024)
+
+ //start
+ iscoringmgr.Listening()
+ iscoringmgr.IStartResourceService()
time.Sleep(time.Duration(1) * time.Second)
- libPath := "mock/mysum/libmysum.so"
- confPath := "mock/mysum/mysum.conf"
+ //setting
+ appName := "ls"
+ libName := "myscoring"
+ libPath := fmt.Sprintf("mock/%s/lib%s.so", libName, libName)
+ confPath := fmt.Sprintf("mock/%s/%s.conf", libName, libName)
+ cfg := new(confdescription.Doc)
+ sconf.Must(cfg).Read(ini.File(confPath))
+ scoringmgr.ILog.Println(cfg)
+ scoringmgr.PushLibPath(libPath, cfg, iscoringmgr.Ch)
+
+ //working on
+ time.Sleep(time.Duration(3) * time.Second)
+ scoreValue, _ := iscoringmgr.IGetScore("mock", "mock")
+ scoringmgr.DLog.Printf("scorevalue : %f\n", scoreValue)
+
+ //release
+ iscoringmgr.IStopResourceService()
+ iscoringmgr.RemoveLib(appName)
+ time.Sleep(time.Duration(1) * time.Second)
+
+}
+func TestScoringMgrMyScoringLibGetScoreLocal(t *testing.T) {
+
+ //interface
+ iscoringmgr := scoringmgr.Init()
+ iscoringmgr.IRunningScore = scoringmgr.LoadScoringGeneralInterface
+ iscoringmgr.IGetScore = scoringmgr.GetScore
+ iscoringmgr.IStartResourceService = scoringmgr.StartResourceService
+ iscoringmgr.IStopResourceService = scoringmgr.StopResourceService
+ iscoringmgr.Ch = make(chan interface{}, 1024)
+
+ //start
+ iscoringmgr.Listening()
+ iscoringmgr.IStartResourceService()
+ time.Sleep(time.Duration(1) * time.Second)
+ //setting
+ appName := "ls"
+ libName := "myscoring"
+ libPath := fmt.Sprintf("mock/%s/lib%s.so", libName, libName)
+ confPath := fmt.Sprintf("mock/%s/%s.conf", libName, libName)
cfg := new(confdescription.Doc)
sconf.Must(cfg).Read(ini.File(confPath))
- scoringmgr.PushLibPath(libPath, cfg, scoringHandlers.Ch)
+ scoringmgr.ILog.Println(cfg)
+ scoringmgr.PushLibPath(libPath, cfg, iscoringmgr.Ch)
- time.Sleep(time.Duration(5) * time.Second)
+ //working on
+ time.Sleep(time.Duration(3) * time.Second)
+ scoreValue, _ := iscoringmgr.IGetScore("localhost", "myscoring")
+ scoringmgr.DLog.Printf("scorevalue : %f\n", scoreValue)
+
+ //release
+ iscoringmgr.IStopResourceService()
+ iscoringmgr.RemoveLib(appName)
+
+ time.Sleep(time.Duration(1) * time.Second)
+
+}
+
+func TestScoringMgrMyScoringLibGetScoreRemote(t *testing.T) {
+
+ //interface
+ iscoringmgr := scoringmgr.Init()
+ iscoringmgr.IRunningScore = scoringmgr.LoadScoringGeneralInterface
+ iscoringmgr.IGetScore = scoringmgr.GetScore
+ iscoringmgr.IStartResourceService = scoringmgr.StartResourceService
+ iscoringmgr.IStopResourceService = scoringmgr.StopResourceService
+ iscoringmgr.Ch = make(chan interface{}, 1024)
+
+ //start
+ iscoringmgr.Listening()
+ iscoringmgr.IStartResourceService()
+ router := restapi.NewRouter()
+ server := &http.Server{Addr: fmt.Sprintf(":%d", 56001), Handler: router}
+ go server.ListenAndServe()
+ time.Sleep(time.Duration(1) * time.Second)
+
+ //setting
+ appName := "ls"
+ libName := "myscoring"
+ libPath := fmt.Sprintf("mock/%s/lib%s.so", libName, libName)
+ confPath := fmt.Sprintf("mock/%s/%s.conf", libName, libName)
+ cfg := new(confdescription.Doc)
+ sconf.Must(cfg).Read(ini.File(confPath))
+ scoringmgr.ILog.Println(cfg)
+ scoringmgr.PushLibPath(libPath, cfg, iscoringmgr.Ch)
+
+ //working on
+ time.Sleep(time.Duration(3) * time.Second)
+ scoreValue, _ := iscoringmgr.IGetScore("127.0.0.1", "myscoring")
+ scoringmgr.DLog.Printf("scorevalue : %f\n", scoreValue)
+
+ //release
+ iscoringmgr.IStopResourceService()
+ iscoringmgr.RemoveLib(appName)
+ if err := server.Shutdown(context.TODO()); err != nil {
+ panic(err)
+ }
+
+ time.Sleep(time.Duration(1) * time.Second)
}
--- /dev/null
+package scoringmgr
+
+var logPrefix = "scoringmgr"
--- /dev/null
+package securemgr
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+)
+
+var passphrase []byte
+
+// Init securemanager and read key file
+func Init(filePath string) (err error) {
+ passphrase, err = ioutil.ReadFile(filePath)
+ if err != nil {
+ log.Println("Can't read passphrase key from keyFilePath !!")
+ log.Println(err)
+ return err
+ } else {
+ return nil
+ }
+}
+
+// Encrpyt from []byte to []byte
+func EncryptByte(byteData []byte) (encryptedByte []byte, err error) {
+ if len(byteData) == 0 {
+ return nil, fmt.Errorf("input of EncryptByte is empty !")
+ }
+
+ block, err := aes.NewCipher(createHashByte(passphrase)) // AES 대칭키 암호화 블록 생성
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ nonce := make([]byte, gcm.NonceSize())
+ if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
+ log.Println(err)
+ return
+ }
+
+ encryptedByte = gcm.Seal(nonce, nonce, byteData, nil)
+ return
+}
+
+// Encrpyt from map[string]interface{} to []byte
+func EncrptJsonToByte(jsonMap map[string]interface{}) (encryptedByte []byte, err error) {
+ jsonByte, err := json.Marshal(jsonMap)
+ if err != nil {
+ log.Println("json.Mmarshal failed !!", err)
+ return
+ }
+ encryptedByte, err = EncryptByte(jsonByte)
+ if err != nil {
+ log.Println("data encryption failed !!")
+ return
+ }
+ return
+}
+
+// Decrypt from []byte to []byte
+func DecryptByte(byteData []byte) (decryptedByte []byte, err error) {
+ if len(byteData) == 0 {
+ return nil, fmt.Errorf("input of DecryptByte is empty !")
+ }
+
+ block, err := aes.NewCipher(createHashByte(passphrase))
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return
+ }
+
+ nonceSize := gcm.NonceSize()
+ nonce, ciphertext := byteData[:nonceSize], byteData[nonceSize:]
+ decryptedByte, err = gcm.Open(nil, nonce, ciphertext, nil)
+ if err != nil {
+ return
+ }
+ return
+}
+
+// Decrpyt from []byte to map[string]interface{}
+func DecryptByteToJson(data []byte) (jsonMap map[string]interface{}, err error) {
+ decrpytedByte, err := DecryptByte(data)
+ if err != nil {
+ log.Println("data dncryption failed !!", err)
+ return
+ }
+
+ err = json.Unmarshal(decrpytedByte, &jsonMap)
+ if err != nil {
+ log.Println("json.Unmarshal failed !!", err)
+ return
+ }
+ return
+}
+
+func createHashByte(key []byte) []byte {
+ hash := sha256.Sum256(key)
+ return hash[:]
+}
+
+func createHash(key string) []byte {
+ return createHashByte([]byte(key))
+}
--- /dev/null
+asdfasdfasdfasdfadsfa
\ No newline at end of file
--- /dev/null
+package securemgr
+
+var logPrefix = "securemgr"
-package servicemgr
+package servicemgr_test
import (
"strings"
import (
"encoding/json"
"log"
+ "net/http"
"os"
"os/exec"
"restapi/httpclient"
if strings.Compare(p.notificationTargetURL, httpclient.ConstLocalTarget) == 0 {
HandleNoti(statusNotificationRequest)
} else {
- targetURL := p.notificationTargetURL + ConstServiceStatusNotiURI + strconv.FormatUint(p.serviceID, 10)
- _, err = httpclient.DoPost(targetURL, reqbytes)
+ targetURL := httpclient.ConstPrefixHTTP + p.notificationTargetURL + ConstServiceStatusNotiURI + strconv.FormatUint(p.serviceID, 10)
+ _, statusCode, err := httpclient.DoPost(targetURL, reqbytes)
if err != nil {
log.Println(err.Error())
}
+ if statusCode != http.StatusOK {
+ log.Println(statusCode)
+ }
}
return
}
)
// initServiceMap is for initializing service map
-func initServiceMap() {
+func InitServiceMap() {
ServiceMap = ConcurrentMap{items: make(map[uint64]interface{})}
}
package servicemgr
import (
+ "common"
"encoding/json"
"errors"
"fmt"
"log"
- "net"
+ "net/http"
"os/exec"
- "restapi/httpclient"
"strconv"
"strings"
+
+ "restapi/httpclient"
)
// Init is for initializing serviemgr
func Init() {
- initServiceMap()
+ InitServiceMap()
}
// ExecuteApp function
}
appInfo[ConstKeyServiceName] = name
- if strings.Compare(target, getOutboundIP()) == 0 || strings.Compare(target, httpclient.ConstLocalTarget) == 0 {
+ outboundIP, outboundIPErr := common.GetOutboundIP()
+ if outboundIPErr != nil {
+ outboundIP = ""
+ }
+
+ if strings.Compare(target, outboundIP) == 0 || strings.Compare(target, httpclient.ConstLocalTarget) == 0 {
appInfo[ConstKeyNotiTargetURL] = httpclient.ConstLocalTarget
err = executeLocalEnv(appInfo)
} else {
return false
}
-func getOutboundIP() string {
- conn, err := net.Dial("udp", "8.8.8.8:80")
- if err != nil {
- log.Fatal(err)
- }
- defer conn.Close()
-
- localAddr := conn.LocalAddr().(*net.UDPAddr)
-
- return localAddr.IP.String()
-}
-
func executeLocalEnv(appInfo map[string]interface{}) (err error) {
// check execution validation
_, err = exec.LookPath(appInfo[ConstKeyServiceName].(string))
}
func executeRemoteEnv(appInfo map[string]interface{}, target string) (err error) {
- reqBytes, _ := json.Marshal(appInfo)
- executeTarget := target + ":" + strconv.Itoa(httpclient.ConstWellknownPort) + ConstServiceExecuteURI
- respBytes, err := httpclient.DoPost(executeTarget, reqBytes)
+ reqBytes, _ := json.Marshal(appInfo)
+ executeTarget := httpclient.ConstPrefixHTTP + target + ":" + strconv.Itoa(httpclient.ConstWellknownPort) + ConstServiceExecuteURI
+ respBytes, statusCode, err := httpclient.DoPost(executeTarget, reqBytes)
var responseMsg map[string]interface{}
-
err = json.Unmarshal(respBytes, &responseMsg)
if err != nil {
return
}
+ if statusCode != http.StatusOK {
+ log.Println(statusCode)
+ }
log.Println("[JSON] : ", responseMsg)
-package servicemgr
+package servicemgr_test
import (
+ "common"
+ "context"
+ "fmt"
+ "net/http"
+ "securemgr"
"strings"
"testing"
"time"
+
+ restapi "restapi/v1"
+ servicemgr "servicemgr"
)
var (
- // targetRemoteDeviceAddr = "10.113.175.144"
- targetLocalAddr = getOutboundIP()
- serviceID uint64
+ targetRemoteDeviceAddr = "127.0.0.1"
+ targetLocalAddr, _ = common.GetOutboundIP()
+ serviceID uint64
)
+var keyFilePath string = "./../securemgr/test/key.txt"
+
func init() {
- Init()
+ servicemgr.Init()
+ securemgr.Init(keyFilePath)
}
func TestExecuteApp(t *testing.T) {
notiChan := make(chan string)
- _, err := ExecuteApp(targetLocalAddr, serviceName, nil, notiChan)
+ _, err := servicemgr.ExecuteApp(targetLocalAddr, serviceName, nil, notiChan)
retError(t, err)
time.Sleep(time.Millisecond * 10)
notiChan := make(chan string)
args := []string{"-ail"}
- _, err := ExecuteApp(targetLocalAddr, serviceName, args, notiChan)
+ _, err := servicemgr.ExecuteApp(targetLocalAddr, serviceName, args, notiChan)
retError(t, err)
time.Sleep(time.Millisecond * 10)
func TestHandleNoti(t *testing.T) {
notiChan := make(chan string, 1)
- id, err := ExecuteApp(targetLocalAddr, serviceName, nil, notiChan)
+ id, err := servicemgr.ExecuteApp(targetLocalAddr, serviceName, nil, notiChan)
retError(t, err)
statusNotificationRequest := make(map[string]interface{})
- statusNotificationRequest[ConstKeyServiceID] = id
- statusNotificationRequest[ConstKeyStatus] = ConstServiceStatusFinished
+ statusNotificationRequest[servicemgr.ConstKeyServiceID] = id
+ statusNotificationRequest[servicemgr.ConstKeyStatus] = servicemgr.ConstServiceStatusFinished
- HandleNoti(statusNotificationRequest)
+ servicemgr.HandleNoti(statusNotificationRequest)
select {
case str := <-notiChan:
t.Log(str)
- if strings.Compare(ConstServiceStatusFinished, str) != 0 {
+ if strings.Compare(servicemgr.ConstServiceStatusFinished, str) != 0 {
t.Error()
}
}
}
-// @ System Test
-// func systemtestExecuteRemoteApp(t *testing.T) {
-// _, err := ExecuteApp(ConstPrefixHTTP+targetRemoteDeviceAddr, serviceName, nil, nil)
-// retError(t, err)
+func TestSystemtestExecuteRemoteApp(t *testing.T) {
+ //init
+ router := restapi.NewRouter()
+ server := &http.Server{Addr: fmt.Sprintf(":%d", 56001), Handler: router}
+ go server.ListenAndServe()
+ time.Sleep(time.Duration(1) * time.Second)
+
+ //setting
+ _, err := servicemgr.ExecuteApp(targetRemoteDeviceAddr, serviceName, nil, nil)
+ retError(t, err)
+
+ //user scenario
+ time.Sleep(time.Duration(3) * time.Second)
+
+ //release
+ if err := server.Shutdown(context.TODO()); err != nil {
+ panic(err)
+ }
+
+ time.Sleep(time.Millisecond * 10)
+}
-// time.Sleep(time.Millisecond * 10)
-// }
+func TestSystemtestExecuteRemoteAppFailed(t *testing.T) {
+ //init
+ router := restapi.NewRouter()
+ server := &http.Server{Addr: fmt.Sprintf(":%d", 56001), Handler: router}
+ go server.ListenAndServe()
+ time.Sleep(time.Duration(1) * time.Second)
-// func systemtestExecuteRemoteAppFailed(t *testing.T) {
-// _, err := ExecuteApp(ConstPrefixHTTP+targetRemoteDeviceAddr, "main", nil, nil)
+ //setting
+ _, err := servicemgr.ExecuteApp(targetRemoteDeviceAddr, "main", nil, nil)
+ time.Sleep(time.Duration(3) * time.Second)
-// if err.Error() != "Failed" {
-// t.Error()
-// }
+ //user scenario
+ if err.Error() != "Failed" {
+ t.Error(err.Error())
+ }
+ time.Sleep(time.Duration(1) * time.Second)
-// time.Sleep(time.Millisecond * 10)
-// }
+ //release
+ if err := server.Shutdown(context.TODO()); err != nil {
+ panic(err)
+ }
+ time.Sleep(time.Millisecond * 10)
+}
-package servicemgr
+package servicemgr_test
import (
"strings"
"testing"
+ servicemgr "servicemgr"
)
func TestMapSetGet(t *testing.T) {
- initServiceMap()
- ServiceMap.Set(uint64(1), serviceName)
+ servicemgr.InitServiceMap()
+ servicemgr.ServiceMap.Set(uint64(1), serviceName)
- str, _ := ServiceMap.Get(uint64(1))
+ str, _ := servicemgr.ServiceMap.Get(uint64(1))
assertEqualStr(t, str.(string), serviceName)
}
func TestMapRemove(t *testing.T) {
- initServiceMap()
- ServiceMap.Set(uint64(1), serviceName)
+ servicemgr.InitServiceMap()
+ servicemgr.ServiceMap.Set(uint64(1), serviceName)
- ServiceMap.Remove(uint64(1))
+ servicemgr.ServiceMap.Remove(uint64(1))
- exist, _ := ServiceMap.Get(uint64(1))
+ exist, _ := servicemgr.ServiceMap.Get(uint64(1))
if exist != nil {
t.Error("ConcurrentMap Remove API is failed")
}
}
func TestMapModify(t *testing.T) {
- initServiceMap()
- ServiceMap.Set(uint64(1), serviceName)
- ServiceMap.Set(uint64(1), serviceName2)
+ servicemgr.InitServiceMap()
+ servicemgr.ServiceMap.Set(uint64(1), serviceName)
+ servicemgr.ServiceMap.Set(uint64(1), serviceName2)
- str, _ := ServiceMap.Get(uint64(1))
+ str, _ := servicemgr.ServiceMap.Get(uint64(1))
assertEqualStr(t, str.(string), serviceName2)
}
func TestMapIter(t *testing.T) {
- initServiceMap()
- ServiceMap.Set(uint64(1), serviceName)
- ServiceMap.Set(uint64(2), serviceName2)
+ servicemgr.InitServiceMap()
+ servicemgr.ServiceMap.Set(uint64(1), serviceName)
+ servicemgr.ServiceMap.Set(uint64(2), serviceName2)
- mapItem := ServiceMap.Iter()
+ mapItem := servicemgr.ServiceMap.Iter()
compareStr := make([]string, 10)
idx := 0
--- /dev/null
+
+# logfile
+ import "github.com/leemcloughlin/logfile"
+
+LogFile is an output handler for the standard Go (golang) log library to allow logging
+to a file.
+
+LogFile supports the following features:
+
+
+ Writes to the log file are buffered but are automatically flushed.
+
+ Safe to use with multiple goroutines.
+
+ Log files can have a maxium size and on reaching it the file can either be
+ truncated or "rotated" (moved aside: log -> log.1, log.1 -> log.2...).
+
+ A log file can be moved aside outside of the program (perhaps by something
+ like Linux's logrotate) and LogFile will detect this and close/reopen the file
+
+ A default rotate function is provided but users can provide their own (see
+ RotateFile). The default uses the OldVersions value to decide how many
+ versions to keep. Note that writing to the log is blocked while rotating so
+ keep RotateFile quick.
+
+ By default messages are still sent to standard error as well as the file
+
+ There are command line flags to override default behavior (requires
+ flag.Parse to be called)
+
+ Actually buffering can result in a lot less writes which is useful on devices
+ (like flash memory) that have limited write cycles. The downside is that
+ messages may be lost on panic or unplanned exit.
+
+Note that LogFile creates a goroutine on New. To ensure its deleted call Close
+
+Command line arguments:
+
+
+ -logcheckseconds int
+ Default seconds to check log file still exists (default 60)
+ -logfile string
+ Use as the filename for the first LogFile created without a filename
+ -logflushseconds int
+ Default seconds to wait before flushing pending writes to the log file (default -1)
+ If <= 0 then the log is writen before returning.
+ -logmax int
+ Default maximum file size, 0 = no limit
+ -lognostderr
+ Default to no logging to stderr
+ -logversions int
+ Default old versions of file to keep (otherwise deleted)
+
+Example:
+
+
+ // was -logfile passed?
+ if logfile.Defaults.FileName != "" {
+ logFileName = logfile.Defaults.FileName
+ }
+
+ logFile, err := logfile.New(
+ &logfile.LogFile{
+ FileName: logFileName,
+ MaxSize: 500 * 1024, // 500K duh!
+ Flags: logfile.FileOnly | logfile.OverWriteOnStart})
+ if err != nil {
+ log.Fatalf("Failed to create logFile %s: %s\n", logFileName, err)
+ }
+
+ log.SetOutput(logFile)
+
+ log.Print("hello")
+ logFile.Close()
+
+
+
+
+## Constants
+``` go
+const (
+ // Flags
+ FileOnly = 1 << iota // Log only to file, not to stderr
+ OverWriteOnStart // Note the default is to append
+ RotateOnStart
+ NoErrors // Disables printing internal errors to stderr
+
+)
+```
+
+## Variables
+``` go
+var (
+ // These are the defaults for a LogFile. Most can be overridden on the command line
+ Defaults = LogFile{
+ Flags: 0,
+ FileName: "",
+ FileMode: 0644,
+ MaxSize: 0,
+ OldVersions: 0,
+ CheckSeconds: 60,
+ FlushSeconds: -1,
+ }
+ NoStderr = false
+)
+```
+
+## func FileNameVersion
+``` go
+func FileNameVersion(fileName string, v int) string
+```
+FileNameVersion returns a versioned log file name for rotating.
+If v is zero it returns the file name unmodified.
+Otherwise it add .v to the end
+
+
+
+## type LogFile
+``` go
+type LogFile struct {
+ // Flags override default behaviour (see also command line flag -lognostderr)
+ Flags int
+
+ // FileName to write to.
+ // See also the -logfile command line flag
+ FileName string
+
+ // FileMode for any newly created log files
+ FileMode os.FileMode
+
+ // If MaxSize is non zero and if log file is about to become bigger than
+ // MaxSize it will be closed, passed to RotateFile, then a new, empty
+ // log file will be created and opened.
+ // See also the -logmax command line flag
+ MaxSize int64
+
+ // CheckSeconds is how often LogFile will test to see if the log file
+ // still exists as it may have been moved aside by something like Linux's
+ // logrotate.
+ // Note that a checking file existance is little expensive on a most
+ // Linux systems so limiting checking is a good option.
+ // On calling New if this is zero the default value (60) will be used
+ // See also the -logcheckseconds command line flag
+ CheckSeconds int
+
+ // RotateFileFunc is called whenever the log file needs "rotating"
+ // (moved aside: log -> log.1, log.1 -> log.2...)
+ // Rotating could be because the file is about to exceed MaxSize or
+ // because logging is just starting and the RotateOnStart flag is set.
+ // If nil a default is provided that rotates up to a OldVerions and deletes
+ // any older.
+ // Never call this directly. If you need to rotate logs call lp.RotateFile()
+ RotateFileFunc func()
+
+ // When the default RotateFile is called this is the number of old versions
+ // to keep.
+ // See also the -logversions command line flag
+ OldVersions int
+
+ // FlushSeconds is how often the log file is writen out. Note that the log
+ // file will be writen to immdiately if the buffer gets full or on the log
+ // file being closed.
+ // If FlushSeconds is zero the default value is used. If less than zero
+ // the log file will be flushed after every write
+ // CAUTION: If not the default (-1) then writes are buffered and may not be
+ // writen out if the program exits/panics
+ FlushSeconds int
+ // contains filtered or unexported fields
+}
+```
+LogFile implements an io.Writer so can used by the standard log library
+
+
+
+
+
+
+
+
+
+### func New
+``` go
+func New(lp *LogFile) (*LogFile, error)
+```
+New creates, if necessary, and opens a log file.
+If a LogFile is passed any empty fields are filled with suitable defaults.
+If nil is passed an empty LogFile is created and then filled in.
+Once finished with the LogFile call Close()
+
+
+
+
+### func (\*LogFile) Close
+``` go
+func (lp *LogFile) Close()
+```
+Close flushs any pending data out and then closes a log file opened by calling New()
+
+
+
+### func (\*LogFile) Flush
+``` go
+func (lp *LogFile) Flush()
+```
+Flush writes any pending log entries out
+
+
+
+### func (\*LogFile) PrintError
+``` go
+func (lp *LogFile) PrintError(format string, args ...interface{})
+```
+PrintError prints out internal errors to standard error (if not turned off by the NoErrors flag)
+
+
+
+### func (\*LogFile) RotateFile
+``` go
+func (lp *LogFile) RotateFile()
+```
+RotateFile requests an immediate file rotation.
+
+
+
+### func (\*LogFile) RotateFileFuncDefault
+``` go
+func (lp *LogFile) RotateFileFuncDefault()
+```
+RotateFileFuncDefault only rotates if OldVersions non zero.
+It deletes the oldest version and renames the others log -> log.1, log.1 -> log.2...
+
+
+
+### func (\*LogFile) Write
+``` go
+func (lp *LogFile) Write(p []byte) (n int, err error)
+```
+Write is called by Log to write log entries.
+
+
+
+
+
+
+
+
+
+- - -
+Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
\ No newline at end of file
--- /dev/null
+/*
+File summary: logfile interface
+Package: logfile
+Author: Lee McLoughlin
+
+Copyright (C) 2015 LMMR Tech Ltd
+*/
+
+/*
+LogFile is an output handler for the standard Go (golang) log library to allow logging
+to a file.
+
+LogFile supports the following features:
+ Writes to the log file are buffered but are automatically flushed.
+
+ Safe to use with multiple goroutines.
+
+ Log files can have a maxium size and on reaching it the file can either be
+ truncated or "rotated" (moved aside: log -> log.1, log.1 -> log.2...).
+
+ A log file can be moved aside outside of the program (perhaps by something
+ like Linux's logrotate) and LogFile will detect this and close/reopen the file
+
+ A default rotate function is provided but users can provide their own (see
+ RotateFile). The default uses the OldVersions value to decide how many
+ versions to keep. Note that writing to the log is blocked while rotating so
+ keep RotateFile quick.
+
+ By default messages are still sent to standard error as well as the file
+
+ There are command line flags to override default behavior (requires
+ flag.Parse to be called)
+
+ Actually buffering can result in a lot less writes which is useful on devices
+ (like flash memory) that have limited write cycles. The downside is that
+ messages may be lost on panic or unplanned exit.
+
+Note that LogFile creates a goroutine on New. To ensure its deleted call Close
+
+Command line arguments:
+ -logcheckseconds int
+ Default seconds to check log file still exists (default 60)
+ -logfile string
+ Use as the filename for the first LogFile created without a filename
+ -logflushseconds int
+ Default seconds to wait before flushing pending writes to the log file (default -1)
+ If <= 0 then the log is writen before returning.
+ -logmax int
+ Default maximum file size, 0 = no limit
+ -lognostderr
+ Default to no logging to stderr
+ -logversions int
+ Default old versions of file to keep (otherwise deleted)
+
+Example:
+ // was -logfile passed?
+ if logfile.Defaults.FileName != "" {
+ logFileName = logfile.Defaults.FileName
+ }
+
+ logFile, err := logfile.New(
+ &logfile.LogFile{
+ FileName: logFileName,
+ MaxSize: 500 * 1024, // 500K duh!
+ Flags: logfile.FileOnly | logfile.OverWriteOnStart})
+ if err != nil {
+ log.Fatalf("Failed to create logFile %s: %s\n", logFileName, err)
+ }
+
+ log.SetOutput(logFile)
+
+ log.Print("hello")
+ logFile.Close()
+*/
+package logfile
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "os"
+ "time"
+)
+
+var (
+ // These are the defaults for a LogFile. Most can be overridden on the command line
+ Defaults = LogFile{
+ Flags: 0,
+ FileName: "",
+ FileMode: 0644,
+ MaxSize: 0,
+ OldVersions: 0,
+ CheckSeconds: 60,
+ FlushSeconds: -1, // Immediate write is the default
+ }
+ NoStderr = false
+
+ errorSeconds = 60
+ defaultFileNameUsed = false
+)
+
+const (
+ // Flags
+ FileOnly = 1 << iota // Log only to file, not to stderr
+ OverWriteOnStart // Note the default is to append
+ RotateOnStart
+ NoErrors // Disables printing internal errors to stderr
+
+ truncateLog = true
+ noTruncateLog = false
+)
+
+func init() {
+ flag.StringVar(&Defaults.FileName, "logfile", Defaults.FileName, "Use as the filename for the first LogFile created without a filename")
+ flag.Int64Var(&Defaults.MaxSize, "logmax", Defaults.MaxSize, "Default maximum file size, 0 = no limit")
+ flag.IntVar(&Defaults.OldVersions, "logversions", Defaults.OldVersions, "Default old versions of file to keep (otherwise deleted)")
+ flag.BoolVar(&NoStderr, "lognostderr", NoStderr, "Default to no logging to stderr")
+ flag.IntVar(&Defaults.CheckSeconds, "logcheckseconds", Defaults.CheckSeconds, "Default seconds to check log file still exists")
+ flag.IntVar(&Defaults.FlushSeconds, "logflushseconds", Defaults.FlushSeconds, "Default seconds to wait before flushing pending writes to the log file")
+
+ if NoStderr {
+ Defaults.Flags = FileOnly
+ }
+}
+
+// LogFile implements an io.Writer so can used by the standard log library
+type LogFile struct {
+ // Flags override default behaviour (see also command line flag -lognostderr)
+ Flags int
+
+ // FileName to write to.
+ // See also the -logfile command line flag
+ FileName string
+
+ // FileMode for any newly created log files
+ FileMode os.FileMode
+
+ // If MaxSize is non zero and if log file is about to become bigger than
+ // MaxSize it will be closed, passed to RotateFile, then a new, empty
+ // log file will be created and opened.
+ // See also the -logmax command line flag
+ MaxSize int64
+
+ // CheckSeconds is how often LogFile will test to see if the log file
+ // still exists as it may have been moved aside by something like Linux's
+ // logrotate.
+ // Note that a checking file existance is little expensive on a most
+ // Linux systems so limiting checking is a good option.
+ // On calling New if this is zero the default value (60) will be used
+ // See also the -logcheckseconds command line flag
+ CheckSeconds int
+
+ // RotateFileFunc is called whenever the log file needs "rotating"
+ // (moved aside: log -> log.1, log.1 -> log.2...)
+ // Rotating could be because the file is about to exceed MaxSize or
+ // because logging is just starting and the RotateOnStart flag is set.
+ // If nil a default is provided that rotates up to a OldVerions and deletes
+ // any older.
+ // Never call this directly. If you need to rotate logs call lp.RotateFile()
+ RotateFileFunc func()
+
+ // When the default RotateFile is called this is the number of old versions
+ // to keep.
+ // See also the -logversions command line flag
+ OldVersions int
+
+ // FlushSeconds is how often the log file is writen out. Note that the log
+ // file will be writen to immdiately if the buffer gets full or on the log
+ // file being closed.
+ // If FlushSeconds is zero the default value is used. If less than zero
+ // the log file will be flushed after every write
+ // CAUTION: If not the default (-1) then writes are buffered and may not be
+ // writen out if the program exits/panics
+ FlushSeconds int
+
+ file *os.File
+ lastChecked time.Time
+ size int64
+ messages chan logMessage
+ buf *bufio.Writer
+}
+
+// New creates, if necessary, and opens a log file.
+// If a LogFile is passed any empty fields are filled with suitable defaults.
+// If nil is passed an empty LogFile is created and then filled in.
+// Once finished with the LogFile call Close()
+func New(lp *LogFile) (*LogFile, error) {
+ if lp == nil {
+ lp = new(LogFile)
+ if lp == nil {
+ return nil, fmt.Errorf("failed to create LogFile (out of memory?)")
+ }
+ }
+ if lp.FileName == "" {
+ if !defaultFileNameUsed {
+ lp.FileName = Defaults.FileName
+ // the logfile passed via the command line is only used once
+ defaultFileNameUsed = true
+ }
+ }
+ if lp.FileName == "" {
+ return lp, fmt.Errorf("LogFile no file name")
+ }
+ if lp.FileMode == 0 {
+ lp.FileMode = Defaults.FileMode
+ }
+ if lp.MaxSize == 0 {
+ lp.MaxSize = Defaults.MaxSize
+ }
+ if lp.RotateFileFunc == nil {
+ lp.RotateFileFunc = lp.RotateFileFuncDefault
+ }
+ if lp.CheckSeconds == 0 {
+ lp.CheckSeconds = Defaults.CheckSeconds
+ }
+ if lp.FlushSeconds == 0 {
+ lp.FlushSeconds = Defaults.FlushSeconds
+ }
+ if lp.Flags == 0 {
+ if NoStderr {
+ lp.Flags = FileOnly
+ }
+ }
+ lp.messages = make(chan logMessage, logMessages)
+ if lp.messages == nil {
+ return nil, fmt.Errorf("LogFile failed to create channel (out of memory?)")
+ }
+ lp.messages <- logMessage{action: openLog}
+ ready := make(chan (bool))
+ go logger(lp, ready)
+ if !<-ready {
+ return lp, fmt.Errorf("LogFile failed to create file %s", lp.FileName)
+ }
+
+ return lp, nil
+}
+
+// Messages sent to the log handling goroutine: logger
+type logMessage struct {
+ action logAction
+ data []byte
+ complete chan<- bool
+}
+
+type logAction int
+
+const (
+ openLog logAction = iota
+ writeLog
+ rotateLog
+ flushLog
+ closeLog
+
+ logMessages = 100
+)
+
+// logger loops until closeLog or an error happens handling log related actions.
+// Once the log file is opened true is sent to the ready channel. If there
+// if a problem opening the log file false is sent.
+func logger(lp *LogFile, ready chan (bool)) {
+ // flushChan will be nil unless FlushSeconds > 0
+ // Note that a negative FlushSeconds is handled in writeLog
+ var flushChan <-chan time.Time
+ if lp.FlushSeconds > 0 {
+ flushTicker := time.NewTicker(time.Second * time.Duration(lp.FlushSeconds))
+ defer flushTicker.Stop()
+ flushChan = flushTicker.C
+ }
+
+ // vanishChan will be nil unless CheckSeconds > 0
+ var vanishChan <-chan time.Time
+ if lp.CheckSeconds > 0 {
+ vanishTicker := time.NewTicker(time.Second * time.Duration(lp.CheckSeconds))
+ defer vanishTicker.Stop()
+ vanishChan = vanishTicker.C
+ }
+
+ // Just in case... regularly check that this goroutine is still needed
+ errorTicker := time.NewTicker(time.Second * time.Duration(errorSeconds))
+ defer errorTicker.Stop()
+
+ for {
+ select {
+ case message := <-lp.messages:
+ switch message.action {
+ case openLog:
+ ready <- lp.startLog()
+ case writeLog:
+ lp.writeLog(message.data)
+ case flushLog:
+ lp.flushLog()
+ message.complete <- true
+ case rotateLog:
+ lp.rotateLog()
+ case closeLog:
+ lp.closeLog()
+ message.complete <- true
+ return
+ }
+ case <-flushChan:
+ lp.flushLog()
+ case <-vanishChan:
+ lp.vanishedLog()
+ case <-errorTicker.C:
+ if lp.file == nil {
+ return
+ }
+ }
+ }
+}
+
+// startLog creates or opens the log file. Depending on Flags the logfile may
+// be rotated first. If all goes well startLog returns true.
+// On a problem an error is printed to stderr (subject to the NoErrors flag)
+// and false returned.
+func (lp *LogFile) startLog() bool {
+ if (lp.Flags&RotateOnStart) == RotateOnStart && lp.RotateFileFunc != nil {
+ lp.RotateFileFunc()
+ }
+
+ truncated := lp.Flags&OverWriteOnStart == OverWriteOnStart
+
+ return lp.openLogFile(truncated)
+}
+
+// openLogFile returns true if the file successfully opened and buffered.
+// The truncated option will cause the file to be truncated on opening.
+func (lp *LogFile) openLogFile(truncated bool) bool {
+ lp.closeLog()
+
+ var err error
+
+ flags := os.O_RDWR | os.O_CREATE
+ if truncated {
+ flags = flags | os.O_TRUNC
+ } else {
+ flags = flags | os.O_APPEND
+ }
+
+ lp.file, err = os.OpenFile(lp.FileName, flags, lp.FileMode)
+ if err != nil {
+ lp.PrintError("LogFile failed to create %s: %s\n", lp.FileName, err)
+ lp.file = nil
+ return false
+ }
+
+ // Find the file size
+ if truncated {
+ lp.size = 0
+ } else {
+ fi, err := os.Stat(lp.FileName)
+ if err == nil {
+ lp.size = fi.Size()
+ } else {
+ lp.PrintError("LogFile unable to find initial filesize for %s: %s\n", lp.FileName, err)
+ lp.size = 0
+ // Hmmm... should I stop logging... no better to try and continue
+ err = nil
+ }
+ }
+
+ lp.buf = bufio.NewWriter(lp.file)
+ if lp.buf == nil {
+ lp.PrintError("LogFile error cannot create buffer for %s (out of memory?)\n", lp.FileName)
+ lp.file.Close()
+ lp.file = nil
+ return false
+ }
+
+ return true
+}
+
+// writeLog writes p to stderr if required then writes it to the file.
+// If writing to the file would cause the file to go over its size limit the file
+// is closed, rotated (which may do nothing) and the opened with truncation.
+func (lp *LogFile) writeLog(p []byte) {
+ fileOnly := lp.Flags&FileOnly == FileOnly
+
+ if !fileOnly {
+ _, err := os.Stderr.Write(p)
+ if err != nil {
+ // Well I can't write to stderr to report it... so just return
+ return
+ }
+ }
+
+ if lp.file == nil {
+ return
+ }
+
+ // Am I about to go over my file size limit?
+ if lp.MaxSize > 0 && (lp.size+int64(len(p))) >= lp.MaxSize {
+ lp.closeLog()
+
+ if lp.RotateFileFunc != nil {
+ lp.RotateFileFunc()
+ }
+
+ // Recreate the logfile truncating it (in case it wasn't rotated)
+ if !lp.openLogFile(truncateLog) {
+ return
+ }
+ }
+
+ n, err := lp.buf.Write(p)
+ if err != nil {
+ lp.PrintError("Logfile error writing to %s: %s\n", lp.FileName, err)
+ }
+ if lp.FlushSeconds <= 0 {
+ lp.flushLog()
+ }
+
+ lp.size += int64(n)
+
+ return
+}
+
+// rotateLog closes the log file, calls the (possibly user) RotateFileFunc and
+// reopens the log file
+func (lp *LogFile) rotateLog() {
+ if lp.RotateFileFunc == nil {
+ return
+ }
+ lp.closeLog()
+ lp.RotateFileFunc()
+ lp.openLogFile(noTruncateLog)
+}
+
+// flushLog flushes out any pending writes to the log file
+func (lp *LogFile) flushLog() {
+ if lp.file == nil {
+ return
+ }
+
+ err := lp.buf.Flush()
+ if err != nil {
+ lp.PrintError("LogFile error flushing %s: %s\n", lp.FileName, err)
+ }
+}
+
+// vanishLog checks that the log file hasn't vanished.
+// Perhaps it has been moved aside by something like Linux logrotate.
+// If it has vanished then the log file is closed and reopened
+func (lp *LogFile) vanishedLog() {
+ _, err := os.Stat(lp.FileName)
+ if err == nil {
+ return
+ }
+ // Close and reopen the file
+ lp.closeLog()
+ lp.openLogFile(noTruncateLog)
+}
+
+// closeLog flushes and closes a log file
+func (lp *LogFile) closeLog() {
+ if lp.file == nil {
+ return
+ }
+
+ lp.flushLog()
+
+ err := lp.file.Close()
+ if err != nil {
+ lp.PrintError("LogFile error closing %s: %s\n", lp.FileName, err)
+ }
+
+ lp.file = nil
+}
+
+// PrintError prints out internal errors to standard error (if not turned off by the NoErrors flag)
+func (lp *LogFile) PrintError(format string, args ...interface{}) {
+ if lp.Flags&NoErrors == NoErrors {
+ return
+ }
+ fmt.Fprintf(os.Stderr, format, args...)
+}
+
+// FileNameVersion returns a versioned log file name for rotating.
+// If v is zero it returns the file name unmodified.
+// Otherwise it add .v to the end
+func FileNameVersion(fileName string, v int) string {
+ if v == 0 {
+ return fileName
+ }
+ return fmt.Sprintf("%s.%d", fileName, v)
+}
+
+// RotateFileFuncDefault only rotates if OldVersions non zero.
+// It deletes the oldest version and renames the others log -> log.1, log.1 -> log.2...
+func (lp *LogFile) RotateFileFuncDefault() {
+ if lp.OldVersions <= 0 {
+ return
+ }
+
+ // Delete the oldest
+ oldFileName := FileNameVersion(lp.FileName, lp.OldVersions)
+ _, err := os.Stat(oldFileName)
+ if err == nil {
+ err := os.Remove(oldFileName)
+ if err != nil {
+ lp.PrintError("LogFile error removing old file %s: %s\n", oldFileName, err)
+ }
+ }
+
+ // Rename the others log -> log.1, log.1 -> log.2...
+ for v := lp.OldVersions - 1; v >= 0; v-- {
+ oldFilename := FileNameVersion(lp.FileName, v)
+ olderFileName := FileNameVersion(lp.FileName, v+1)
+ _, err = os.Stat(oldFilename)
+ if err != nil {
+ // Old file does not exist
+ continue
+ }
+ err := os.Rename(oldFilename, olderFileName)
+ if err != nil {
+ lp.PrintError("LogFile error renaming old file %s to %s: %s\n", oldFilename, olderFileName, err)
+ }
+ }
+}
+
+// RotateFile requests an immediate file rotation.
+func (lp *LogFile) RotateFile() {
+ lp.messages <- logMessage{action: rotateLog}
+}
+
+// Flush writes any pending log entries out
+func (lp *LogFile) Flush() {
+ complete := make(chan bool)
+ lp.messages <- logMessage{action: flushLog, complete: complete}
+ <-complete
+}
+
+// Write is called by Log to write log entries.
+func (lp *LogFile) Write(p []byte) (n int, err error) {
+ // LogFile cannot guarantee that it will have finished with p before this
+ // function returns. To prevent corruption use a copy of p.
+ pLen := len(p)
+ buf := make([]byte, pLen)
+ copy(buf, p)
+
+ lp.messages <- logMessage{action: writeLog, data: buf}
+ if lp.FlushSeconds <= 0 {
+ lp.Flush()
+ }
+ return pLen, nil
+}
+
+// Close flushs any pending data out and then closes a log file opened by calling New()
+func (lp *LogFile) Close() {
+ complete := make(chan bool)
+ lp.messages <- logMessage{action: closeLog, complete: complete}
+ // wait for the logfile to close
+ <-complete
+}
--- /dev/null
+/*
+File summary: logfile testing
+Package: logfile
+Author: Lee McLoughlin
+
+Copyright (C) 2015 LMMR Tech Ltd
+*/
+
+package logfile
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "strings"
+ "testing"
+ "time"
+)
+
+const (
+ tmpDir = "/tmp"
+ tmpPrefix = "lftest"
+ showDebug = true
+)
+
+// Return a unique filename or an error
+func tempFileName() (string, error) {
+ f, err := ioutil.TempFile(tmpDir, tmpPrefix)
+ if err != nil {
+ return "", err
+ }
+ f.Close()
+ debug("tempFileName " + f.Name())
+ return f.Name(), nil
+}
+
+func debug(msg string) {
+ if !showDebug {
+ return
+ }
+ fmt.Fprintln(os.Stderr, msg)
+}
+
+func Test_FilenameVersions(t *testing.T) {
+ debug("Test_Filenames start")
+ defer debug("Test_Filenames end")
+
+ logFileName := "example.log"
+ v0 := FileNameVersion(logFileName, 0)
+ if logFileName != v0 {
+ t.Errorf("FileNameVersion wrong expected %s got %s", logFileName, v0)
+ }
+ v1 := FileNameVersion(logFileName, 1)
+ lfv1 := logFileName + ".1"
+ if lfv1 != v1 {
+ t.Errorf("FileNameVersion wrong expected %s got %s", lfv1, v1)
+ }
+}
+
+func Test_DefaultCreate(t *testing.T) {
+ debug("Test_DefaultCreate start")
+ defer debug("Test_DefaultCreate end")
+
+ logFileName, err := tempFileName()
+ if err != nil {
+ t.Errorf("Failed to create temporary file: %s\n", err)
+ return
+ }
+
+ // Pretend the -logfile flag was used
+ Defaults.FileName = logFileName
+
+ logFile, err := New(nil)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ log.SetFlags(0)
+ log.SetOutput(logFile)
+
+ msg := "hello\n"
+ log.Print(msg)
+ logFile.Flush()
+
+ fi, err := os.Stat(logFileName)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ logFile.Close()
+
+ contents, err := ioutil.ReadFile(logFileName)
+ if err != nil {
+ t.Errorf("Failed to read log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ size := int64(len(msg))
+ if fi.Size() != size {
+ t.Errorf("Wrong logfile size for %s expected %d got %d\n", logFileName, size, fi.Size())
+ } else if string(contents) != msg {
+ t.Errorf("Wrong logfile contents for %s expected %s got %d\n", logFileName, msg, fi.Size())
+ } else {
+ t.Log("Log file created and has correct size and contents")
+ }
+
+ os.Remove(logFileName)
+}
+
+func Test_BigMessages(t *testing.T) {
+ debug("Test_BigMessages start")
+ defer debug("Test_BigMessages end")
+
+ logFileName, err := tempFileName()
+ if err != nil {
+ t.Errorf("Failed to create temporary file: %s\n", err)
+ return
+ }
+
+ // Flush and check every second
+ // Naughty: set the internal error check timer
+ errorSeconds = 1
+ logFile, err := New(&LogFile{
+ FileName: logFileName,
+ FlushSeconds: 1,
+ CheckSeconds: 1,
+ Flags: FileOnly})
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ log.SetFlags(0)
+ log.SetOutput(logFile)
+
+ msg := ""
+ for i := 0; i < 5; i++ {
+ line := strings.Repeat(string('0'+i), 70) + "\n"
+ log.Print(line)
+ msg = msg + line
+ }
+ size := int64(len(msg))
+
+ // Check for log being writen
+ for i := 0; i < 10; i++ {
+ fi, err := os.Stat(logFileName)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+ if fi.Size() == size {
+ break
+ }
+ t.Logf("Log size after %d seconds %d", i, fi.Size())
+ time.Sleep(time.Second)
+ }
+
+ // Wait a bit so flush and check are run
+ time.Sleep(time.Second * 3)
+ logFile.Close()
+
+ fi, err := os.Stat(logFileName)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ contents, err := ioutil.ReadFile(logFileName)
+ if err != nil {
+ t.Errorf("Failed to read log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ if fi.Size() != size {
+ t.Errorf("Wrong logfile size for %s expected %d got %d\n", logFileName, size, fi.Size())
+ } else if string(contents) != msg {
+ t.Errorf("Wrong logfile contents for %s expected %s got %d\n", logFileName, msg, fi.Size())
+ } else {
+ t.Log("Log file created and has correct size and contents")
+ }
+
+ os.Remove(logFileName)
+}
+
+func Test_Rotation(t *testing.T) {
+ debug("Test_Rotation start")
+ defer debug("Test_Rotation end")
+
+ logFileName, err := tempFileName()
+ if err != nil {
+ t.Errorf("Failed to create temporary file: %s\n", err)
+ return
+ }
+
+ logFile, err := New(&LogFile{
+ FileName: logFileName,
+ MaxSize: 71, // Same as size of test lines below
+ OldVersions: 2,
+ Flags: FileOnly})
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ log.SetFlags(0)
+ log.SetOutput(logFile)
+
+ msg := ""
+ for i := 0; i < 5; i++ {
+ line := strings.Repeat(string('0'+i), 70) + "\n"
+ log.Print(line)
+ msg = msg + line
+ }
+ logFile.Close()
+
+ oldest := 4
+ for i := 0; i < 3; i++ {
+ lf := FileNameVersion(logFileName, i)
+ fi, err := os.Stat(lf)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", lf, err)
+ return
+ }
+
+ contents, err := ioutil.ReadFile(lf)
+ if err != nil {
+ t.Errorf("Failed to read log file %s: %s\n", lf, err)
+ return
+ }
+
+ line := strings.Repeat(string('0'+oldest), 70) + "\n"
+ oldest--
+
+ size := int64(len(line))
+ if fi.Size() != size {
+ t.Errorf("Wrong logfile size for %s expected %d got %d\n", lf, size, fi.Size())
+ } else if string(contents) != line {
+ t.Errorf("Wrong logfile contents for %s expected %s got %s\n", lf, line, contents)
+ } else {
+ t.Logf("Log file %s created and has correct size and contents", lf)
+ os.Remove(lf)
+ }
+ }
+}
+
+func Test_ExplicitRotation(t *testing.T) {
+ debug("Test_ExplicitRotation start")
+ defer debug("Test_ExplicitRotation end")
+
+ logFileName, err := tempFileName()
+ if err != nil {
+ t.Errorf("Failed to create temporary file: %s\n", err)
+ return
+ }
+
+ // Flush and check every second
+ logFile, err := New(&LogFile{
+ FileName: logFileName,
+ OldVersions: 2,
+ Flags: FileOnly})
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ log.SetFlags(0)
+ log.SetOutput(logFile)
+
+ line := strings.Repeat(string('0'), 70) + "\n"
+ log.Print(line)
+
+ t.Logf("Forcing rotation")
+ logFile.RotateFile()
+
+ line = strings.Repeat(string('1'), 70) + "\n"
+ log.Print(line)
+
+ logFile.Close()
+
+ oldest := 1
+ for i := 0; i < 2; i++ {
+ lf := FileNameVersion(logFileName, i)
+ fi, err := os.Stat(lf)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", lf, err)
+ return
+ }
+
+ contents, err := ioutil.ReadFile(lf)
+ if err != nil {
+ t.Errorf("Failed to read log file %s: %s\n", lf, err)
+ return
+ }
+
+ line := strings.Repeat(string('0'+oldest), 70) + "\n"
+ oldest--
+
+ size := int64(len(line))
+ if fi.Size() != size {
+ t.Errorf("Wrong logfile size for %s expected %d got %d\n", lf, size, fi.Size())
+ } else if string(contents) != line {
+ t.Errorf("Wrong logfile contents for %s expected %s got %s\n", lf, line, contents)
+ } else {
+ t.Logf("Log file %s created and has correct size and contents", lf)
+ os.Remove(lf)
+ }
+ }
+}
+
+func Test_OverWriteOnStart(t *testing.T) {
+ debug("Test_OverWriteOnStart start")
+ defer debug("Test_OverWriteOnStart end")
+
+ logFileName, err := tempFileName()
+ if err != nil {
+ t.Errorf("Failed to create temporary file: %s\n", err)
+ return
+ }
+
+ f, err := os.Create(logFileName)
+ if err != nil {
+ t.Errorf("Failed to create file %s: %s\n", logFileName, err)
+ return
+ }
+ fmt.Fprintf(f, "I AM GOING TO BE OVERWRITEN\n")
+ f.Close()
+
+ logFile, err := New(&LogFile{FileName: logFileName, Flags: OverWriteOnStart})
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ log.SetOutput(logFile)
+ logFile.Close()
+
+ fi, err := os.Stat(logFileName)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ if fi.Size() != 0 {
+ t.Errorf("Wrong logfile size for %s expected 0 got %d\n", logFileName, fi.Size())
+ } else {
+ t.Log("Log file created and has correct size")
+ }
+
+ os.Remove(logFileName)
+}
+
+func Test_LogVanish(t *testing.T) {
+ debug("Test_LogVanish start")
+ defer debug("Test_LogVanish end")
+
+ logFileName, err := tempFileName()
+ if err != nil {
+ t.Errorf("Failed to create temporary file: %s\n", err)
+ return
+ }
+
+ logFile, err := New(&LogFile{
+ FileName: logFileName,
+ CheckSeconds: 1,
+ Flags: OverWriteOnStart})
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ log.SetOutput(logFile)
+ log.Print("testing")
+
+ // Remove the logfile and wait long enough for LogFile to notice (CheckSeconds is 1)
+ os.Remove(logFileName)
+ time.Sleep(time.Second * 2)
+
+ // This is all that should appear in the logfile
+ msg := "testing again\n"
+ log.Print(msg)
+
+ logFile.Close()
+
+ fi, err := os.Stat(logFileName)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ if fi.Size() != int64(len(msg)) {
+ t.Errorf("Wrong logfile size for %s expected %d got %d\n", logFileName, len(msg), fi.Size())
+ } else {
+ t.Log("Log file created and has correct size")
+ }
+
+ os.Remove(logFileName)
+}
+
+func Test_AppendOnStart(t *testing.T) {
+ debug("Test_AppendOnStart start")
+ defer debug("Test_AppendOnStart end")
+
+ logFileName, err := tempFileName()
+ if err != nil {
+ t.Errorf("Failed to create temporary file: %s\n", err)
+ return
+ }
+
+ contents := "I AM GOING TO BE APPENDED TO\n"
+
+ f, err := os.Create(logFileName)
+ if err != nil {
+ t.Errorf("Failed to create file %s: %s\n", logFileName, err)
+ return
+ }
+ fmt.Fprint(f, contents)
+ f.Close()
+
+ logFile, err := New(&LogFile{FileName: logFileName})
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ log.SetFlags(0)
+ log.SetOutput(logFile)
+
+ fi, err := os.Stat(logFileName)
+ if err != nil {
+ t.Errorf("Failed to create log file %s: %s\n", logFileName, err)
+ return
+ }
+
+ size := int64(len(contents))
+ if fi.Size() != size {
+ t.Errorf("Wrong logfile size for %s expected %d got %d\n", logFileName, size, fi.Size())
+ } else {
+ t.Log("Log file created and has correct size")
+ }
+
+ os.Remove(logFileName)
+}
+
+func ExampleLogFile() {
+ debug("ExampleLogFile start")
+ defer debug("ExampleLogFile end")
+
+ logFileName, err := tempFileName()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to create temporary file: %s\n", err)
+ return
+ }
+
+ logFile, err := New(
+ &LogFile{
+ FileName: logFileName,
+ MaxSize: 500 * 1024,
+ Flags: OverWriteOnStart})
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to create log file %s: %s\n", logFileName, err)
+ os.Exit(1)
+ }
+
+ log.SetOutput(logFile)
+ log.Print("hello")
+ logFile.Close()
+
+ writen, err := ioutil.ReadFile(logFileName)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to read log file %s: %s\n", logFileName, err)
+ os.Exit(1)
+ }
+ fmt.Print(string(writen))
+ // Output: hello
+
+ os.Remove(logFileName)
+}
+++ /dev/null
-### Please give general description of the problem
-
-### Please provide code snippets to reproduce the problem described above
-
-### Do you have any suggestion to fix the problem?
\ No newline at end of file
+++ /dev/null
-### What problem should be fixed?
-
-### Have you added test cases to catch the problem?
+++ /dev/null
-testdata/conf_out.ini
-ini.sublime-project
-ini.sublime-workspace
-testdata/conf_reflect.ini
-.idea
-/.vscode
+++ /dev/null
-sudo: false
-language: go
-go:
- - 1.6.x
- - 1.7.x
- - 1.8.x
- - 1.9.x
- - 1.10.x
- - 1.11.x
-
-script:
- - go get golang.org/x/tools/cmd/cover
- - go get github.com/smartystreets/goconvey
- - mkdir -p $HOME/gopath/src/gopkg.in
- - ln -s $HOME/gopath/src/github.com/go-ini/ini $HOME/gopath/src/gopkg.in/ini.v1
- - cd $HOME/gopath/src/gopkg.in/ini.v1
- - go test -v -cover -race
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright 2014 Unknwon
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+++ /dev/null
-.PHONY: build test bench vet coverage
-
-build: vet bench
-
-test:
- go test -v -cover -race
-
-bench:
- go test -v -cover -race -test.bench=. -test.benchmem
-
-vet:
- go vet
-
-coverage:
- go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
+++ /dev/null
-INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg)](https://sourcegraph.com/github.com/go-ini/ini)
-===
-
-![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
-
-Package ini provides INI file read and write functionality in Go.
-
-## Features
-
-- Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
-- Read with recursion values.
-- Read with parent-child sections.
-- Read with auto-increment key names.
-- Read with multiple-line values.
-- Read with tons of helper methods.
-- Read and convert values to Go types.
-- Read and **WRITE** comments of sections and keys.
-- Manipulate sections, keys and comments with ease.
-- Keep sections and keys in order as you parse and save.
-
-## Installation
-
-The minimum requirement of Go is **1.6**.
-
-To use a tagged revision:
-
-```sh
-$ go get gopkg.in/ini.v1
-```
-
-To use with latest changes:
-
-```sh
-$ go get github.com/go-ini/ini
-```
-
-Please add `-u` flag to update in the future.
-
-## Getting Help
-
-- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
-- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
-
-## License
-
-This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
+++ /dev/null
-// Copyright 2017 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini_test
-
-import (
- "testing"
-
- "gopkg.in/ini.v1"
-)
-
-func newTestFile(block bool) *ini.File {
- c, _ := ini.Load([]byte(_CONF_DATA))
- c.BlockMode = block
- return c
-}
-
-func Benchmark_Key_Value(b *testing.B) {
- c := newTestFile(true)
- for i := 0; i < b.N; i++ {
- c.Section("").Key("NAME").Value()
- }
-}
-
-func Benchmark_Key_Value_NonBlock(b *testing.B) {
- c := newTestFile(false)
- for i := 0; i < b.N; i++ {
- c.Section("").Key("NAME").Value()
- }
-}
-
-func Benchmark_Key_Value_ViaSection(b *testing.B) {
- c := newTestFile(true)
- sec := c.Section("")
- for i := 0; i < b.N; i++ {
- sec.Key("NAME").Value()
- }
-}
-
-func Benchmark_Key_Value_ViaSection_NonBlock(b *testing.B) {
- c := newTestFile(false)
- sec := c.Section("")
- for i := 0; i < b.N; i++ {
- sec.Key("NAME").Value()
- }
-}
-
-func Benchmark_Key_Value_Direct(b *testing.B) {
- c := newTestFile(true)
- key := c.Section("").Key("NAME")
- for i := 0; i < b.N; i++ {
- key.Value()
- }
-}
-
-func Benchmark_Key_Value_Direct_NonBlock(b *testing.B) {
- c := newTestFile(false)
- key := c.Section("").Key("NAME")
- for i := 0; i < b.N; i++ {
- key.Value()
- }
-}
-
-func Benchmark_Key_String(b *testing.B) {
- c := newTestFile(true)
- for i := 0; i < b.N; i++ {
- _ = c.Section("").Key("NAME").String()
- }
-}
-
-func Benchmark_Key_String_NonBlock(b *testing.B) {
- c := newTestFile(false)
- for i := 0; i < b.N; i++ {
- _ = c.Section("").Key("NAME").String()
- }
-}
-
-func Benchmark_Key_String_ViaSection(b *testing.B) {
- c := newTestFile(true)
- sec := c.Section("")
- for i := 0; i < b.N; i++ {
- _ = sec.Key("NAME").String()
- }
-}
-
-func Benchmark_Key_String_ViaSection_NonBlock(b *testing.B) {
- c := newTestFile(false)
- sec := c.Section("")
- for i := 0; i < b.N; i++ {
- _ = sec.Key("NAME").String()
- }
-}
-
-func Benchmark_Key_SetValue(b *testing.B) {
- c := newTestFile(true)
- for i := 0; i < b.N; i++ {
- c.Section("").Key("NAME").SetValue("10")
- }
-}
-
-func Benchmark_Key_SetValue_VisSection(b *testing.B) {
- c := newTestFile(true)
- sec := c.Section("")
- for i := 0; i < b.N; i++ {
- sec.Key("NAME").SetValue("10")
- }
-}
+++ /dev/null
-// Copyright 2016 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "fmt"
-)
-
-type ErrDelimiterNotFound struct {
- Line string
-}
-
-func IsErrDelimiterNotFound(err error) bool {
- _, ok := err.(ErrDelimiterNotFound)
- return ok
-}
-
-func (err ErrDelimiterNotFound) Error() string {
- return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
-}
+++ /dev/null
-// Copyright 2017 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "strings"
- "sync"
-)
-
-// File represents a combination of a or more INI file(s) in memory.
-type File struct {
- options LoadOptions
- dataSources []dataSource
-
- // Should make things safe, but sometimes doesn't matter.
- BlockMode bool
- lock sync.RWMutex
-
- // To keep data in order.
- sectionList []string
- // Actual data is stored here.
- sections map[string]*Section
-
- NameMapper
- ValueMapper
-}
-
-// newFile initializes File object with given data sources.
-func newFile(dataSources []dataSource, opts LoadOptions) *File {
- if len(opts.KeyValueDelimiters) == 0 {
- opts.KeyValueDelimiters = "=:"
- }
- return &File{
- BlockMode: true,
- dataSources: dataSources,
- sections: make(map[string]*Section),
- sectionList: make([]string, 0, 10),
- options: opts,
- }
-}
-
-// Empty returns an empty file object.
-func Empty() *File {
- // Ignore error here, we sure our data is good.
- f, _ := Load([]byte(""))
- return f
-}
-
-// NewSection creates a new section.
-func (f *File) NewSection(name string) (*Section, error) {
- if len(name) == 0 {
- return nil, errors.New("error creating new section: empty section name")
- } else if f.options.Insensitive && name != DEFAULT_SECTION {
- name = strings.ToLower(name)
- }
-
- if f.BlockMode {
- f.lock.Lock()
- defer f.lock.Unlock()
- }
-
- if inSlice(name, f.sectionList) {
- return f.sections[name], nil
- }
-
- f.sectionList = append(f.sectionList, name)
- f.sections[name] = newSection(f, name)
- return f.sections[name], nil
-}
-
-// NewRawSection creates a new section with an unparseable body.
-func (f *File) NewRawSection(name, body string) (*Section, error) {
- section, err := f.NewSection(name)
- if err != nil {
- return nil, err
- }
-
- section.isRawSection = true
- section.rawBody = body
- return section, nil
-}
-
-// NewSections creates a list of sections.
-func (f *File) NewSections(names ...string) (err error) {
- for _, name := range names {
- if _, err = f.NewSection(name); err != nil {
- return err
- }
- }
- return nil
-}
-
-// GetSection returns section by given name.
-func (f *File) GetSection(name string) (*Section, error) {
- if len(name) == 0 {
- name = DEFAULT_SECTION
- }
- if f.options.Insensitive {
- name = strings.ToLower(name)
- }
-
- if f.BlockMode {
- f.lock.RLock()
- defer f.lock.RUnlock()
- }
-
- sec := f.sections[name]
- if sec == nil {
- return nil, fmt.Errorf("section '%s' does not exist", name)
- }
- return sec, nil
-}
-
-// Section assumes named section exists and returns a zero-value when not.
-func (f *File) Section(name string) *Section {
- sec, err := f.GetSection(name)
- if err != nil {
- // Note: It's OK here because the only possible error is empty section name,
- // but if it's empty, this piece of code won't be executed.
- sec, _ = f.NewSection(name)
- return sec
- }
- return sec
-}
-
-// Section returns list of Section.
-func (f *File) Sections() []*Section {
- if f.BlockMode {
- f.lock.RLock()
- defer f.lock.RUnlock()
- }
-
- sections := make([]*Section, len(f.sectionList))
- for i, name := range f.sectionList {
- sections[i] = f.sections[name]
- }
- return sections
-}
-
-// ChildSections returns a list of child sections of given section name.
-func (f *File) ChildSections(name string) []*Section {
- return f.Section(name).ChildSections()
-}
-
-// SectionStrings returns list of section names.
-func (f *File) SectionStrings() []string {
- list := make([]string, len(f.sectionList))
- copy(list, f.sectionList)
- return list
-}
-
-// DeleteSection deletes a section.
-func (f *File) DeleteSection(name string) {
- if f.BlockMode {
- f.lock.Lock()
- defer f.lock.Unlock()
- }
-
- if len(name) == 0 {
- name = DEFAULT_SECTION
- }
-
- for i, s := range f.sectionList {
- if s == name {
- f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
- delete(f.sections, name)
- return
- }
- }
-}
-
-func (f *File) reload(s dataSource) error {
- r, err := s.ReadCloser()
- if err != nil {
- return err
- }
- defer r.Close()
-
- return f.parse(r)
-}
-
-// Reload reloads and parses all data sources.
-func (f *File) Reload() (err error) {
- for _, s := range f.dataSources {
- if err = f.reload(s); err != nil {
- // In loose mode, we create an empty default section for nonexistent files.
- if os.IsNotExist(err) && f.options.Loose {
- f.parse(bytes.NewBuffer(nil))
- continue
- }
- return err
- }
- }
- return nil
-}
-
-// Append appends one or more data sources and reloads automatically.
-func (f *File) Append(source interface{}, others ...interface{}) error {
- ds, err := parseDataSource(source)
- if err != nil {
- return err
- }
- f.dataSources = append(f.dataSources, ds)
- for _, s := range others {
- ds, err = parseDataSource(s)
- if err != nil {
- return err
- }
- f.dataSources = append(f.dataSources, ds)
- }
- return f.Reload()
-}
-
-func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
- equalSign := DefaultFormatLeft + "=" + DefaultFormatRight
-
- if PrettyFormat || PrettyEqual {
- equalSign = " = "
- }
-
- // Use buffer to make sure target is safe until finish encoding.
- buf := bytes.NewBuffer(nil)
- for i, sname := range f.sectionList {
- sec := f.Section(sname)
- if len(sec.Comment) > 0 {
- // Support multiline comments
- lines := strings.Split(sec.Comment, LineBreak)
- for i := range lines {
- if lines[i][0] != '#' && lines[i][0] != ';' {
- lines[i] = "; " + lines[i]
- } else {
- lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
- }
-
- if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
- return nil, err
- }
- }
- }
-
- if i > 0 || DefaultHeader {
- if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
- return nil, err
- }
- } else {
- // Write nothing if default section is empty
- if len(sec.keyList) == 0 {
- continue
- }
- }
-
- if sec.isRawSection {
- if _, err := buf.WriteString(sec.rawBody); err != nil {
- return nil, err
- }
-
- if PrettySection {
- // Put a line between sections
- if _, err := buf.WriteString(LineBreak); err != nil {
- return nil, err
- }
- }
- continue
- }
-
- // Count and generate alignment length and buffer spaces using the
- // longest key. Keys may be modifed if they contain certain characters so
- // we need to take that into account in our calculation.
- alignLength := 0
- if PrettyFormat {
- for _, kname := range sec.keyList {
- keyLength := len(kname)
- // First case will surround key by ` and second by """
- if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
- keyLength += 2
- } else if strings.Contains(kname, "`") {
- keyLength += 6
- }
-
- if keyLength > alignLength {
- alignLength = keyLength
- }
- }
- }
- alignSpaces := bytes.Repeat([]byte(" "), alignLength)
-
- KEY_LIST:
- for _, kname := range sec.keyList {
- key := sec.Key(kname)
- if len(key.Comment) > 0 {
- if len(indent) > 0 && sname != DEFAULT_SECTION {
- buf.WriteString(indent)
- }
-
- // Support multiline comments
- lines := strings.Split(key.Comment, LineBreak)
- for i := range lines {
- if lines[i][0] != '#' && lines[i][0] != ';' {
- lines[i] = "; " + strings.TrimSpace(lines[i])
- } else {
- lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
- }
-
- if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
- return nil, err
- }
- }
- }
-
- if len(indent) > 0 && sname != DEFAULT_SECTION {
- buf.WriteString(indent)
- }
-
- switch {
- case key.isAutoIncrement:
- kname = "-"
- case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
- kname = "`" + kname + "`"
- case strings.Contains(kname, "`"):
- kname = `"""` + kname + `"""`
- }
-
- for _, val := range key.ValueWithShadows() {
- if _, err := buf.WriteString(kname); err != nil {
- return nil, err
- }
-
- if key.isBooleanType {
- if kname != sec.keyList[len(sec.keyList)-1] {
- buf.WriteString(LineBreak)
- }
- continue KEY_LIST
- }
-
- // Write out alignment spaces before "=" sign
- if PrettyFormat {
- buf.Write(alignSpaces[:alignLength-len(kname)])
- }
-
- // In case key value contains "\n", "`", "\"", "#" or ";"
- if strings.ContainsAny(val, "\n`") {
- val = `"""` + val + `"""`
- } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
- val = "`" + val + "`"
- }
- if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
- return nil, err
- }
- }
-
- for _, val := range key.nestedValues {
- if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil {
- return nil, err
- }
- }
- }
-
- if PrettySection {
- // Put a line between sections
- if _, err := buf.WriteString(LineBreak); err != nil {
- return nil, err
- }
- }
- }
-
- return buf, nil
-}
-
-// WriteToIndent writes content into io.Writer with given indention.
-// If PrettyFormat has been set to be true,
-// it will align "=" sign with spaces under each section.
-func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
- buf, err := f.writeToBuffer(indent)
- if err != nil {
- return 0, err
- }
- return buf.WriteTo(w)
-}
-
-// WriteTo writes file content into io.Writer.
-func (f *File) WriteTo(w io.Writer) (int64, error) {
- return f.WriteToIndent(w, "")
-}
-
-// SaveToIndent writes content to file system with given value indention.
-func (f *File) SaveToIndent(filename, indent string) error {
- // Note: Because we are truncating with os.Create,
- // so it's safer to save to a temporary file location and rename afte done.
- buf, err := f.writeToBuffer(indent)
- if err != nil {
- return err
- }
-
- return ioutil.WriteFile(filename, buf.Bytes(), 0666)
-}
-
-// SaveTo writes content to file system.
-func (f *File) SaveTo(filename string) error {
- return f.SaveToIndent(filename, "")
-}
+++ /dev/null
-// Copyright 2017 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini_test
-
-import (
- "bytes"
- "io/ioutil"
- "testing"
-
- . "github.com/smartystreets/goconvey/convey"
- "gopkg.in/ini.v1"
-)
-
-func TestEmpty(t *testing.T) {
- Convey("Create an empty object", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- // Should only have the default section
- So(len(f.Sections()), ShouldEqual, 1)
-
- // Default section should not contain any key
- So(len(f.Section("").Keys()), ShouldBeZeroValue)
- })
-}
-
-func TestFile_NewSection(t *testing.T) {
- Convey("Create a new section", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- sec, err := f.NewSection("author")
- So(err, ShouldBeNil)
- So(sec, ShouldNotBeNil)
- So(sec.Name(), ShouldEqual, "author")
-
- So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author"})
-
- Convey("With duplicated name", func() {
- sec, err := f.NewSection("author")
- So(err, ShouldBeNil)
- So(sec, ShouldNotBeNil)
-
- // Does nothing if section already exists
- So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author"})
- })
-
- Convey("With empty string", func() {
- _, err := f.NewSection("")
- So(err, ShouldNotBeNil)
- })
- })
-}
-
-func TestFile_NewRawSection(t *testing.T) {
- Convey("Create a new raw section", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000`)
- So(err, ShouldBeNil)
- So(sec, ShouldNotBeNil)
- So(sec.Name(), ShouldEqual, "comments")
-
- So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "comments"})
- So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000`)
-
- Convey("With duplicated name", func() {
- sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000`)
- So(err, ShouldBeNil)
- So(sec, ShouldNotBeNil)
- So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "comments"})
-
- // Overwrite previous existed section
- So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000`)
- })
-
- Convey("With empty string", func() {
- _, err := f.NewRawSection("", "")
- So(err, ShouldNotBeNil)
- })
- })
-}
-
-func TestFile_NewSections(t *testing.T) {
- Convey("Create new sections", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- So(f.NewSections("package", "author"), ShouldBeNil)
- So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "package", "author"})
-
- Convey("With duplicated name", func() {
- So(f.NewSections("author", "features"), ShouldBeNil)
-
- // Ignore section already exists
- So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "package", "author", "features"})
- })
-
- Convey("With empty string", func() {
- So(f.NewSections("", ""), ShouldNotBeNil)
- })
- })
-}
-
-func TestFile_GetSection(t *testing.T) {
- Convey("Get a section", t, func() {
- f, err := ini.Load(_FULL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- sec, err := f.GetSection("author")
- So(err, ShouldBeNil)
- So(sec, ShouldNotBeNil)
- So(sec.Name(), ShouldEqual, "author")
-
- Convey("Section not exists", func() {
- _, err := f.GetSection("404")
- So(err, ShouldNotBeNil)
- })
- })
-}
-
-func TestFile_Section(t *testing.T) {
- Convey("Get a section", t, func() {
- f, err := ini.Load(_FULL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- sec := f.Section("author")
- So(sec, ShouldNotBeNil)
- So(sec.Name(), ShouldEqual, "author")
-
- Convey("Section not exists", func() {
- sec := f.Section("404")
- So(sec, ShouldNotBeNil)
- So(sec.Name(), ShouldEqual, "404")
- })
- })
-
- Convey("Get default section in lower case with insensitive load", t, func() {
- f, err := ini.InsensitiveLoad([]byte(`
-[default]
-NAME = ini
-VERSION = v1`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("name").String(), ShouldEqual, "ini")
- So(f.Section("").Key("version").String(), ShouldEqual, "v1")
- })
-}
-
-func TestFile_Sections(t *testing.T) {
- Convey("Get all sections", t, func() {
- f, err := ini.Load(_FULL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- secs := f.Sections()
- names := []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}
- So(len(secs), ShouldEqual, len(names))
- for i, name := range names {
- So(secs[i].Name(), ShouldEqual, name)
- }
- })
-}
-
-func TestFile_ChildSections(t *testing.T) {
- Convey("Get child sections by parent name", t, func() {
- f, err := ini.Load([]byte(`
-[node]
-[node.biz1]
-[node.biz2]
-[node.biz3]
-[node.bizN]
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- children := f.ChildSections("node")
- names := []string{"node.biz1", "node.biz2", "node.biz3", "node.bizN"}
- So(len(children), ShouldEqual, len(names))
- for i, name := range names {
- So(children[i].Name(), ShouldEqual, name)
- }
- })
-}
-
-func TestFile_SectionStrings(t *testing.T) {
- Convey("Get all section names", t, func() {
- f, err := ini.Load(_FULL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"})
- })
-}
-
-func TestFile_DeleteSection(t *testing.T) {
- Convey("Delete a section", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- f.NewSections("author", "package", "features")
- f.DeleteSection("features")
- f.DeleteSection("")
- So(f.SectionStrings(), ShouldResemble, []string{"author", "package"})
- })
-}
-
-func TestFile_Append(t *testing.T) {
- Convey("Append a data source", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- So(f.Append(_MINIMAL_CONF, []byte(`
-[author]
-NAME = Unknwon`)), ShouldBeNil)
-
- Convey("With bad input", func() {
- So(f.Append(123), ShouldNotBeNil)
- So(f.Append(_MINIMAL_CONF, 123), ShouldNotBeNil)
- })
- })
-}
-
-func TestFile_WriteTo(t *testing.T) {
- Convey("Write content to somewhere", t, func() {
- f, err := ini.Load(_FULL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- f.Section("author").Comment = `Information about package author
-# Bio can be written in multiple lines.`
- f.Section("author").Key("NAME").Comment = "This is author name"
- f.Section("note").NewBooleanKey("boolean_key")
- f.Section("note").NewKey("more", "notes")
-
- var buf bytes.Buffer
- _, err = f.WriteTo(&buf)
- So(err, ShouldBeNil)
-
- golden := "testdata/TestFile_WriteTo.golden"
- if *update {
- ioutil.WriteFile(golden, buf.Bytes(), 0644)
- }
-
- expected, err := ioutil.ReadFile(golden)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, string(expected))
- })
-
- Convey("Support multiline comments", t, func() {
- f, err := ini.Load([]byte(`
-#
-# general.domain
-#
-# Domain name of XX system.
-domain = mydomain.com
-`))
- So(err, ShouldBeNil)
-
- f.Section("").Key("test").Comment = "Multiline\nComment"
-
- var buf bytes.Buffer
- _, err = f.WriteTo(&buf)
- So(err, ShouldBeNil)
-
- So(buf.String(), ShouldEqual, `#
-# general.domain
-#
-# Domain name of XX system.
-domain = mydomain.com
-; Multiline
-; Comment
-test =
-
-`)
-
- })
-}
-
-func TestFile_SaveTo(t *testing.T) {
- Convey("Write content to somewhere", t, func() {
- f, err := ini.Load(_FULL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
- So(f.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
- })
-}
+++ /dev/null
-// +build go1.6
-
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package ini provides INI file read and write functionality in Go.
-package ini
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "regexp"
- "runtime"
-)
-
-const (
- // Name for default section. You can use this constant or the string literal.
- // In most of cases, an empty string is all you need to access the section.
- DEFAULT_SECTION = "DEFAULT"
-
- // Maximum allowed depth when recursively substituing variable names.
- _DEPTH_VALUES = 99
- _VERSION = "1.42.0"
-)
-
-// Version returns current package version literal.
-func Version() string {
- return _VERSION
-}
-
-var (
- // Delimiter to determine or compose a new line.
- // This variable will be changed to "\r\n" automatically on Windows
- // at package init time.
- LineBreak = "\n"
-
- // Place custom spaces when PrettyFormat and PrettyEqual are both disabled
- DefaultFormatLeft = ""
- DefaultFormatRight = ""
-
- // Variable regexp pattern: %(variable)s
- varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
-
- // Indicate whether to align "=" sign with spaces to produce pretty output
- // or reduce all possible spaces for compact format.
- PrettyFormat = true
-
- // Place spaces around "=" sign even when PrettyFormat is false
- PrettyEqual = false
-
- // Explicitly write DEFAULT section header
- DefaultHeader = false
-
- // Indicate whether to put a line between sections
- PrettySection = true
-)
-
-func init() {
- if runtime.GOOS == "windows" {
- LineBreak = "\r\n"
- }
-}
-
-func inSlice(str string, s []string) bool {
- for _, v := range s {
- if str == v {
- return true
- }
- }
- return false
-}
-
-// dataSource is an interface that returns object which can be read and closed.
-type dataSource interface {
- ReadCloser() (io.ReadCloser, error)
-}
-
-// sourceFile represents an object that contains content on the local file system.
-type sourceFile struct {
- name string
-}
-
-func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
- return os.Open(s.name)
-}
-
-// sourceData represents an object that contains content in memory.
-type sourceData struct {
- data []byte
-}
-
-func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
- return ioutil.NopCloser(bytes.NewReader(s.data)), nil
-}
-
-// sourceReadCloser represents an input stream with Close method.
-type sourceReadCloser struct {
- reader io.ReadCloser
-}
-
-func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
- return s.reader, nil
-}
-
-func parseDataSource(source interface{}) (dataSource, error) {
- switch s := source.(type) {
- case string:
- return sourceFile{s}, nil
- case []byte:
- return &sourceData{s}, nil
- case io.ReadCloser:
- return &sourceReadCloser{s}, nil
- default:
- return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
- }
-}
-
-type LoadOptions struct {
- // Loose indicates whether the parser should ignore nonexistent files or return error.
- Loose bool
- // Insensitive indicates whether the parser forces all section and key names to lowercase.
- Insensitive bool
- // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
- IgnoreContinuation bool
- // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
- IgnoreInlineComment bool
- // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
- SkipUnrecognizableLines bool
- // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
- // This type of keys are mostly used in my.cnf.
- AllowBooleanKeys bool
- // AllowShadows indicates whether to keep track of keys with same name under same section.
- AllowShadows bool
- // AllowNestedValues indicates whether to allow AWS-like nested values.
- // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
- AllowNestedValues bool
- // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
- // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
- // Relevant quote: Values can also span multiple lines, as long as they are indented deeper
- // than the first line of the value.
- AllowPythonMultilineValues bool
- // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
- // Docs: https://docs.python.org/2/library/configparser.html
- // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
- // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
- SpaceBeforeInlineComment bool
- // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
- // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
- UnescapeValueDoubleQuotes bool
- // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
- // when value is NOT surrounded by any quotes.
- // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
- UnescapeValueCommentSymbols bool
- // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
- // conform to key/value pairs. Specify the names of those blocks here.
- UnparseableSections []string
- // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
- KeyValueDelimiters string
- // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
- PreserveSurroundedQuote bool
-}
-
-func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
- sources := make([]dataSource, len(others)+1)
- sources[0], err = parseDataSource(source)
- if err != nil {
- return nil, err
- }
- for i := range others {
- sources[i+1], err = parseDataSource(others[i])
- if err != nil {
- return nil, err
- }
- }
- f := newFile(sources, opts)
- if err = f.Reload(); err != nil {
- return nil, err
- }
- return f, nil
-}
-
-// Load loads and parses from INI data sources.
-// Arguments can be mixed of file name with string type, or raw data in []byte.
-// It will return error if list contains nonexistent files.
-func Load(source interface{}, others ...interface{}) (*File, error) {
- return LoadSources(LoadOptions{}, source, others...)
-}
-
-// LooseLoad has exactly same functionality as Load function
-// except it ignores nonexistent files instead of returning error.
-func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
- return LoadSources(LoadOptions{Loose: true}, source, others...)
-}
-
-// InsensitiveLoad has exactly same functionality as Load function
-// except it forces all section and key names to be lowercased.
-func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
- return LoadSources(LoadOptions{Insensitive: true}, source, others...)
-}
-
-// ShadowLoad has exactly same functionality as Load function
-// except it allows have shadow keys.
-func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
- return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
-}
+++ /dev/null
-// Copyright 2017 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "testing"
-
- . "github.com/smartystreets/goconvey/convey"
-)
-
-func Test_Version(t *testing.T) {
- Convey("Get version", t, func() {
- So(Version(), ShouldEqual, _VERSION)
- })
-}
-
-func Test_isSlice(t *testing.T) {
- Convey("Check if a string is in the slice", t, func() {
- ss := []string{"a", "b", "c"}
- So(inSlice("a", ss), ShouldBeTrue)
- So(inSlice("d", ss), ShouldBeFalse)
- })
-}
+++ /dev/null
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini_test
-
-import (
- "bytes"
- "flag"
- "io/ioutil"
- "testing"
-
- . "github.com/smartystreets/goconvey/convey"
- "gopkg.in/ini.v1"
-)
-
-const (
- _CONF_DATA = `
- ; Package name
- NAME = ini
- ; Package version
- VERSION = v1
- ; Package import path
- IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
-
- # Information about package author
- # Bio can be written in multiple lines.
- [author]
- NAME = Unknwon ; Succeeding comment
- E-MAIL = fake@localhost
- GITHUB = https://github.com/%(NAME)s
- BIO = """Gopher.
- Coding addict.
- Good man.
- """ # Succeeding comment`
- _MINIMAL_CONF = "testdata/minimal.ini"
- _FULL_CONF = "testdata/full.ini"
- _NOT_FOUND_CONF = "testdata/404.ini"
-)
-
-var update = flag.Bool("update", false, "Update .golden files")
-
-func TestLoad(t *testing.T) {
- Convey("Load from good data sources", t, func() {
- f, err := ini.Load([]byte(`
-NAME = ini
-VERSION = v1
-IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s`),
- "testdata/minimal.ini",
- ioutil.NopCloser(bytes.NewReader([]byte(`
-[author]
-NAME = Unknwon
-`))),
- )
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- // Validate values make sure all sources are loaded correctly
- sec := f.Section("")
- So(sec.Key("NAME").String(), ShouldEqual, "ini")
- So(sec.Key("VERSION").String(), ShouldEqual, "v1")
- So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
-
- sec = f.Section("author")
- So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
- So(sec.Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
- })
-
- Convey("Load from bad data sources", t, func() {
- Convey("Invalid input", func() {
- _, err := ini.Load(_NOT_FOUND_CONF)
- So(err, ShouldNotBeNil)
- })
-
- Convey("Unsupported type", func() {
- _, err := ini.Load(123)
- So(err, ShouldNotBeNil)
- })
- })
-
- Convey("Can't properly parse INI files containing `#` or `;` in value", t, func() {
- f, err := ini.Load([]byte(`
- [author]
- NAME = U#n#k#n#w#o#n
- GITHUB = U;n;k;n;w;o;n
- `))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- sec := f.Section("author")
- nameValue := sec.Key("NAME").String()
- githubValue := sec.Key("GITHUB").String()
- So(nameValue, ShouldEqual, "U")
- So(githubValue, ShouldEqual, "U")
- })
-
- Convey("Can't parse small python-compatible INI files", t, func() {
- f, err := ini.Load([]byte(`
-[long]
-long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
- foo
- bar
- foobar
- barfoo
- -----END RSA PRIVATE KEY-----
-`))
- So(err, ShouldNotBeNil)
- So(f, ShouldBeNil)
- So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
- })
-
- Convey("Can't parse big python-compatible INI files", t, func() {
- f, err := ini.Load([]byte(`
-[long]
-long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
- 1foo
- 2bar
- 3foobar
- 4barfoo
- 5foo
- 6bar
- 7foobar
- 8barfoo
- 9foo
- 10bar
- 11foobar
- 12barfoo
- 13foo
- 14bar
- 15foobar
- 16barfoo
- 17foo
- 18bar
- 19foobar
- 20barfoo
- 21foo
- 22bar
- 23foobar
- 24barfoo
- 25foo
- 26bar
- 27foobar
- 28barfoo
- 29foo
- 30bar
- 31foobar
- 32barfoo
- 33foo
- 34bar
- 35foobar
- 36barfoo
- 37foo
- 38bar
- 39foobar
- 40barfoo
- 41foo
- 42bar
- 43foobar
- 44barfoo
- 45foo
- 46bar
- 47foobar
- 48barfoo
- 49foo
- 50bar
- 51foobar
- 52barfoo
- 53foo
- 54bar
- 55foobar
- 56barfoo
- 57foo
- 58bar
- 59foobar
- 60barfoo
- 61foo
- 62bar
- 63foobar
- 64barfoo
- 65foo
- 66bar
- 67foobar
- 68barfoo
- 69foo
- 70bar
- 71foobar
- 72barfoo
- 73foo
- 74bar
- 75foobar
- 76barfoo
- 77foo
- 78bar
- 79foobar
- 80barfoo
- 81foo
- 82bar
- 83foobar
- 84barfoo
- 85foo
- 86bar
- 87foobar
- 88barfoo
- 89foo
- 90bar
- 91foobar
- 92barfoo
- 93foo
- 94bar
- 95foobar
- 96barfoo
- -----END RSA PRIVATE KEY-----
-`))
- So(err, ShouldNotBeNil)
- So(f, ShouldBeNil)
- So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
- })
-}
-
-func TestLooseLoad(t *testing.T) {
- Convey("Load from data sources with option `Loose` true", t, func() {
- f, err := ini.LoadSources(ini.LoadOptions{Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- Convey("Inverse case", func() {
- _, err = ini.Load(_NOT_FOUND_CONF)
- So(err, ShouldNotBeNil)
- })
- })
-}
-
-func TestInsensitiveLoad(t *testing.T) {
- Convey("Insensitive to section and key names", t, func() {
- f, err := ini.InsensitiveLoad(_MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `[author]
-e-mail = u@gogs.io
-
-`)
- })
-
- Convey("Inverse case", func() {
- f, err := ini.Load(_MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
- })
- })
-}
-
-func TestLoadSources(t *testing.T) {
- Convey("Load from data sources with options", t, func() {
- Convey("with true `AllowPythonMultilineValues`", func() {
- Convey("Ignore nonexistent files", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- Convey("Inverse case", func() {
- _, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, _NOT_FOUND_CONF)
- So(err, ShouldNotBeNil)
- })
- })
-
- Convey("Insensitive to section and key names", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, _MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `[author]
-e-mail = u@gogs.io
-
-`)
- })
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, _MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
- })
- })
-
- Convey("Ignore continuation lines", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: true,
- IgnoreContinuation: true,
- }, []byte(`
-key1=a\b\
-key2=c\d\
-key3=value`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
- So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
- So(f.Section("").Key("key3").String(), ShouldEqual, "value")
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
-key1=a\b\
-key2=c\d\`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
- })
- })
-
- Convey("Ignore inline comments", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: true,
- IgnoreInlineComment: true,
- }, []byte(`
-key1=value ;comment
-key2=value2 #comment2
-key3=val#ue #comment3`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
- So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
- So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`)
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
-key1=value ;comment
-key2=value2 #comment2`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
- So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
- So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
- So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
- })
- })
-
- Convey("Skip unrecognizable lines", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- SkipUnrecognizableLines: true,
- }, []byte(`
-GenerationDepth: 13
-
-BiomeRarityScale: 100
-
-################
-# Biome Groups #
-################
-
-BiomeGroup(NormalBiomes, 3, 99, RoofedForestEnchanted, ForestSakura, FloatingJungle
-BiomeGroup(IceBiomes, 4, 85, Ice Plains)
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("GenerationDepth").String(), ShouldEqual, "13")
- So(f.Section("").Key("BiomeRarityScale").String(), ShouldEqual, "100")
- So(f.Section("").HasKey("BiomeGroup"), ShouldBeFalse)
- })
-
- Convey("Allow boolean type keys", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: true,
- AllowBooleanKeys: true,
- }, []byte(`
-key1=hello
-#key2
-key3`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
- So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `key1 = hello
-# key2
-key3
-`)
- })
-
- Convey("Inverse case", func() {
- _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
-key1=hello
-#key2
-key3`))
- So(err, ShouldNotBeNil)
- })
- })
-
- Convey("Allow shadow keys", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(`
-[remote "origin"]
-url = https://github.com/Antergone/test1.git
-url = https://github.com/Antergone/test2.git
-fetch = +refs/heads/*:refs/remotes/origin/*`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
- So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
- "https://github.com/Antergone/test1.git",
- "https://github.com/Antergone/test2.git",
- })
- So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `[remote "origin"]
-url = https://github.com/Antergone/test1.git
-url = https://github.com/Antergone/test2.git
-fetch = +refs/heads/*:refs/remotes/origin/*
-
-`)
- })
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
-[remote "origin"]
-url = https://github.com/Antergone/test1.git
-url = https://github.com/Antergone/test2.git`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
- })
- })
-
- Convey("Unescape double quotes inside value", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: true,
- UnescapeValueDoubleQuotes: true,
- }, []byte(`
-create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
-create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
- })
- })
-
- Convey("Unescape comment symbols inside value", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: true,
- IgnoreInlineComment: true,
- UnescapeValueCommentSymbols: true,
- }, []byte(`
-key = test value <span style="color: %s\; background: %s">more text</span>
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
- })
-
- Convey("Can parse small python-compatible INI files", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: true,
- Insensitive: true,
- UnparseableSections: []string{"core_lesson", "comments"},
- }, []byte(`
-[long]
-long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
- foo
- bar
- foobar
- barfoo
- -----END RSA PRIVATE KEY-----
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, "-----BEGIN RSA PRIVATE KEY-----\nfoo\nbar\nfoobar\nbarfoo\n-----END RSA PRIVATE KEY-----")
- })
-
- Convey("Can parse big python-compatible INI files", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: true,
- Insensitive: true,
- UnparseableSections: []string{"core_lesson", "comments"},
- }, []byte(`
-[long]
-long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
- 1foo
- 2bar
- 3foobar
- 4barfoo
- 5foo
- 6bar
- 7foobar
- 8barfoo
- 9foo
- 10bar
- 11foobar
- 12barfoo
- 13foo
- 14bar
- 15foobar
- 16barfoo
- 17foo
- 18bar
- 19foobar
- 20barfoo
- 21foo
- 22bar
- 23foobar
- 24barfoo
- 25foo
- 26bar
- 27foobar
- 28barfoo
- 29foo
- 30bar
- 31foobar
- 32barfoo
- 33foo
- 34bar
- 35foobar
- 36barfoo
- 37foo
- 38bar
- 39foobar
- 40barfoo
- 41foo
- 42bar
- 43foobar
- 44barfoo
- 45foo
- 46bar
- 47foobar
- 48barfoo
- 49foo
- 50bar
- 51foobar
- 52barfoo
- 53foo
- 54bar
- 55foobar
- 56barfoo
- 57foo
- 58bar
- 59foobar
- 60barfoo
- 61foo
- 62bar
- 63foobar
- 64barfoo
- 65foo
- 66bar
- 67foobar
- 68barfoo
- 69foo
- 70bar
- 71foobar
- 72barfoo
- 73foo
- 74bar
- 75foobar
- 76barfoo
- 77foo
- 78bar
- 79foobar
- 80barfoo
- 81foo
- 82bar
- 83foobar
- 84barfoo
- 85foo
- 86bar
- 87foobar
- 88barfoo
- 89foo
- 90bar
- 91foobar
- 92barfoo
- 93foo
- 94bar
- 95foobar
- 96barfoo
- -----END RSA PRIVATE KEY-----
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, `-----BEGIN RSA PRIVATE KEY-----
-1foo
-2bar
-3foobar
-4barfoo
-5foo
-6bar
-7foobar
-8barfoo
-9foo
-10bar
-11foobar
-12barfoo
-13foo
-14bar
-15foobar
-16barfoo
-17foo
-18bar
-19foobar
-20barfoo
-21foo
-22bar
-23foobar
-24barfoo
-25foo
-26bar
-27foobar
-28barfoo
-29foo
-30bar
-31foobar
-32barfoo
-33foo
-34bar
-35foobar
-36barfoo
-37foo
-38bar
-39foobar
-40barfoo
-41foo
-42bar
-43foobar
-44barfoo
-45foo
-46bar
-47foobar
-48barfoo
-49foo
-50bar
-51foobar
-52barfoo
-53foo
-54bar
-55foobar
-56barfoo
-57foo
-58bar
-59foobar
-60barfoo
-61foo
-62bar
-63foobar
-64barfoo
-65foo
-66bar
-67foobar
-68barfoo
-69foo
-70bar
-71foobar
-72barfoo
-73foo
-74bar
-75foobar
-76barfoo
-77foo
-78bar
-79foobar
-80barfoo
-81foo
-82bar
-83foobar
-84barfoo
-85foo
-86bar
-87foobar
-88barfoo
-89foo
-90bar
-91foobar
-92barfoo
-93foo
-94bar
-95foobar
-96barfoo
------END RSA PRIVATE KEY-----`)
- })
-
- Convey("Allow unparsable sections", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: true,
- Insensitive: true,
- UnparseableSections: []string{"core_lesson", "comments"},
- }, []byte(`
-Lesson_Location = 87
-Lesson_Status = C
-Score = 3
-Time = 00:02:30
-
-[CORE_LESSON]
-my lesson state data – 1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000 – end my lesson state data
-
-[COMMENTS]
-<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("score").String(), ShouldEqual, "3")
- So(f.Section("").Body(), ShouldBeEmpty)
- So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000 – end my lesson state data`)
- So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `lesson_location = 87
-lesson_status = C
-score = 3
-time = 00:02:30
-
-[core_lesson]
-my lesson state data – 1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000 – end my lesson state data
-
-[comments]
-<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
-`)
- })
-
- Convey("Inverse case", func() {
- _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
-[CORE_LESSON]
-my lesson state data – 1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000 – end my lesson state data`))
- So(err, ShouldNotBeNil)
- })
- })
-
- Convey("And false `SpaceBeforeInlineComment`", func() {
- Convey("Can't parse INI files containing `#` or `;` in value", func() {
- f, err := ini.LoadSources(
- ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: false},
- []byte(`
-[author]
-NAME = U#n#k#n#w#o#n
-GITHUB = U;n;k;n;w;o;n
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
- sec := f.Section("author")
- nameValue := sec.Key("NAME").String()
- githubValue := sec.Key("GITHUB").String()
- So(nameValue, ShouldEqual, "U")
- So(githubValue, ShouldEqual, "U")
- })
- })
-
- Convey("And true `SpaceBeforeInlineComment`", func() {
- Convey("Can parse INI files containing `#` or `;` in value", func() {
- f, err := ini.LoadSources(
- ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: true},
- []byte(`
-[author]
-NAME = U#n#k#n#w#o#n
-GITHUB = U;n;k;n;w;o;n
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
- sec := f.Section("author")
- nameValue := sec.Key("NAME").String()
- githubValue := sec.Key("GITHUB").String()
- So(nameValue, ShouldEqual, "U#n#k#n#w#o#n")
- So(githubValue, ShouldEqual, "U;n;k;n;w;o;n")
- })
- })
- })
-
- Convey("with false `AllowPythonMultilineValues`", func() {
- Convey("Ignore nonexistent files", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- Convey("Inverse case", func() {
- _, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, _NOT_FOUND_CONF)
- So(err, ShouldNotBeNil)
- })
- })
-
- Convey("Insensitive to section and key names", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, Insensitive: true}, _MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `[author]
-e-mail = u@gogs.io
-
-`)
- })
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, _MINIMAL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
- })
- })
-
- Convey("Ignore continuation lines", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: false,
- IgnoreContinuation: true,
- }, []byte(`
-key1=a\b\
-key2=c\d\
-key3=value`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
- So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
- So(f.Section("").Key("key3").String(), ShouldEqual, "value")
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
-key1=a\b\
-key2=c\d\`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
- })
- })
-
- Convey("Ignore inline comments", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: false,
- IgnoreInlineComment: true,
- }, []byte(`
-key1=value ;comment
-key2=value2 #comment2
-key3=val#ue #comment3`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
- So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
- So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`)
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
-key1=value ;comment
-key2=value2 #comment2`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
- So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
- So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
- So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
- })
- })
-
- Convey("Allow boolean type keys", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: false,
- AllowBooleanKeys: true,
- }, []byte(`
-key1=hello
-#key2
-key3`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
- So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `key1 = hello
-# key2
-key3
-`)
- })
-
- Convey("Inverse case", func() {
- _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
-key1=hello
-#key2
-key3`))
- So(err, ShouldNotBeNil)
- })
- })
-
- Convey("Allow shadow keys", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(`
-[remote "origin"]
-url = https://github.com/Antergone/test1.git
-url = https://github.com/Antergone/test2.git
-fetch = +refs/heads/*:refs/remotes/origin/*`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
- So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
- "https://github.com/Antergone/test1.git",
- "https://github.com/Antergone/test2.git",
- })
- So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `[remote "origin"]
-url = https://github.com/Antergone/test1.git
-url = https://github.com/Antergone/test2.git
-fetch = +refs/heads/*:refs/remotes/origin/*
-
-`)
- })
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
-[remote "origin"]
-url = https://github.com/Antergone/test1.git
-url = https://github.com/Antergone/test2.git`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
- })
- })
-
- Convey("Unescape double quotes inside value", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: false,
- UnescapeValueDoubleQuotes: true,
- }, []byte(`
-create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
-
- Convey("Inverse case", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
-create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
- })
- })
-
- Convey("Unescape comment symbols inside value", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: false,
- IgnoreInlineComment: true,
- UnescapeValueCommentSymbols: true,
- }, []byte(`
-key = test value <span style="color: %s\; background: %s">more text</span>
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
- })
-
- Convey("Can't parse small python-compatible INI files", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
-[long]
-long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
- foo
- bar
- foobar
- barfoo
- -----END RSA PRIVATE KEY-----
-`))
- So(err, ShouldNotBeNil)
- So(f, ShouldBeNil)
- So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
- })
-
- Convey("Can't parse big python-compatible INI files", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
-[long]
-long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
- 1foo
- 2bar
- 3foobar
- 4barfoo
- 5foo
- 6bar
- 7foobar
- 8barfoo
- 9foo
- 10bar
- 11foobar
- 12barfoo
- 13foo
- 14bar
- 15foobar
- 16barfoo
- 17foo
- 18bar
- 19foobar
- 20barfoo
- 21foo
- 22bar
- 23foobar
- 24barfoo
- 25foo
- 26bar
- 27foobar
- 28barfoo
- 29foo
- 30bar
- 31foobar
- 32barfoo
- 33foo
- 34bar
- 35foobar
- 36barfoo
- 37foo
- 38bar
- 39foobar
- 40barfoo
- 41foo
- 42bar
- 43foobar
- 44barfoo
- 45foo
- 46bar
- 47foobar
- 48barfoo
- 49foo
- 50bar
- 51foobar
- 52barfoo
- 53foo
- 54bar
- 55foobar
- 56barfoo
- 57foo
- 58bar
- 59foobar
- 60barfoo
- 61foo
- 62bar
- 63foobar
- 64barfoo
- 65foo
- 66bar
- 67foobar
- 68barfoo
- 69foo
- 70bar
- 71foobar
- 72barfoo
- 73foo
- 74bar
- 75foobar
- 76barfoo
- 77foo
- 78bar
- 79foobar
- 80barfoo
- 81foo
- 82bar
- 83foobar
- 84barfoo
- 85foo
- 86bar
- 87foobar
- 88barfoo
- 89foo
- 90bar
- 91foobar
- 92barfoo
- 93foo
- 94bar
- 95foobar
- 96barfoo
- -----END RSA PRIVATE KEY-----
-`))
- So(err, ShouldNotBeNil)
- So(f, ShouldBeNil)
- So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
- })
-
- Convey("Allow unparsable sections", func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowPythonMultilineValues: false,
- Insensitive: true,
- UnparseableSections: []string{"core_lesson", "comments"},
- }, []byte(`
-Lesson_Location = 87
-Lesson_Status = C
-Score = 3
-Time = 00:02:30
-
-[CORE_LESSON]
-my lesson state data – 1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000 – end my lesson state data
-
-[COMMENTS]
-<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("score").String(), ShouldEqual, "3")
- So(f.Section("").Body(), ShouldBeEmpty)
- So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000 – end my lesson state data`)
- So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
-
- Convey("Write out", func() {
- var buf bytes.Buffer
- _, err := f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `lesson_location = 87
-lesson_status = C
-score = 3
-time = 00:02:30
-
-[core_lesson]
-my lesson state data – 1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000 – end my lesson state data
-
-[comments]
-<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
-`)
- })
-
- Convey("Inverse case", func() {
- _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
-[CORE_LESSON]
-my lesson state data – 1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000 – end my lesson state data`))
- So(err, ShouldNotBeNil)
- })
- })
-
- Convey("And false `SpaceBeforeInlineComment`", func() {
- Convey("Can't parse INI files containing `#` or `;` in value", func() {
- f, err := ini.LoadSources(
- ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: false},
- []byte(`
-[author]
-NAME = U#n#k#n#w#o#n
-GITHUB = U;n;k;n;w;o;n
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
- sec := f.Section("author")
- nameValue := sec.Key("NAME").String()
- githubValue := sec.Key("GITHUB").String()
- So(nameValue, ShouldEqual, "U")
- So(githubValue, ShouldEqual, "U")
- })
- })
-
- Convey("And true `SpaceBeforeInlineComment`", func() {
- Convey("Can parse INI files containing `#` or `;` in value", func() {
- f, err := ini.LoadSources(
- ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: true},
- []byte(`
-[author]
-NAME = U#n#k#n#w#o#n
-GITHUB = U;n;k;n;w;o;n
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
- sec := f.Section("author")
- nameValue := sec.Key("NAME").String()
- githubValue := sec.Key("GITHUB").String()
- So(nameValue, ShouldEqual, "U#n#k#n#w#o#n")
- So(githubValue, ShouldEqual, "U;n;k;n;w;o;n")
- })
- })
- })
- })
-}
-
-func Test_KeyValueDelimiters(t *testing.T) {
- Convey("Custom key-value delimiters", t, func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- KeyValueDelimiters: "?!",
- }, []byte(`
-[section]
-key1?value1
-key2!value2
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("section").Key("key1").String(), ShouldEqual, "value1")
- So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
- })
-}
-
-func Test_PreserveSurroundedQuote(t *testing.T) {
- Convey("Preserve surrounded quote test", t, func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- PreserveSurroundedQuote: true,
- }, []byte(`
-[section]
-key1 = "value1"
-key2 = value2
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("section").Key("key1").String(), ShouldEqual, "\"value1\"")
- So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
- })
-
- Convey("Preserve surrounded quote test inverse test", t, func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- PreserveSurroundedQuote: false,
- }, []byte(`
-[section]
-key1 = "value1"
-key2 = value2
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("section").Key("key1").String(), ShouldEqual, "value1")
- So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
- })
-}
+++ /dev/null
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "bytes"
- "errors"
- "fmt"
- "strconv"
- "strings"
- "time"
-)
-
-// Key represents a key under a section.
-type Key struct {
- s *Section
- Comment string
- name string
- value string
- isAutoIncrement bool
- isBooleanType bool
-
- isShadow bool
- shadows []*Key
-
- nestedValues []string
-}
-
-// newKey simply return a key object with given values.
-func newKey(s *Section, name, val string) *Key {
- return &Key{
- s: s,
- name: name,
- value: val,
- }
-}
-
-func (k *Key) addShadow(val string) error {
- if k.isShadow {
- return errors.New("cannot add shadow to another shadow key")
- } else if k.isAutoIncrement || k.isBooleanType {
- return errors.New("cannot add shadow to auto-increment or boolean key")
- }
-
- shadow := newKey(k.s, k.name, val)
- shadow.isShadow = true
- k.shadows = append(k.shadows, shadow)
- return nil
-}
-
-// AddShadow adds a new shadow key to itself.
-func (k *Key) AddShadow(val string) error {
- if !k.s.f.options.AllowShadows {
- return errors.New("shadow key is not allowed")
- }
- return k.addShadow(val)
-}
-
-func (k *Key) addNestedValue(val string) error {
- if k.isAutoIncrement || k.isBooleanType {
- return errors.New("cannot add nested value to auto-increment or boolean key")
- }
-
- k.nestedValues = append(k.nestedValues, val)
- return nil
-}
-
-func (k *Key) AddNestedValue(val string) error {
- if !k.s.f.options.AllowNestedValues {
- return errors.New("nested value is not allowed")
- }
- return k.addNestedValue(val)
-}
-
-// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
-type ValueMapper func(string) string
-
-// Name returns name of key.
-func (k *Key) Name() string {
- return k.name
-}
-
-// Value returns raw value of key for performance purpose.
-func (k *Key) Value() string {
- return k.value
-}
-
-// ValueWithShadows returns raw values of key and its shadows if any.
-func (k *Key) ValueWithShadows() []string {
- if len(k.shadows) == 0 {
- return []string{k.value}
- }
- vals := make([]string, len(k.shadows)+1)
- vals[0] = k.value
- for i := range k.shadows {
- vals[i+1] = k.shadows[i].value
- }
- return vals
-}
-
-// NestedValues returns nested values stored in the key.
-// It is possible returned value is nil if no nested values stored in the key.
-func (k *Key) NestedValues() []string {
- return k.nestedValues
-}
-
-// transformValue takes a raw value and transforms to its final string.
-func (k *Key) transformValue(val string) string {
- if k.s.f.ValueMapper != nil {
- val = k.s.f.ValueMapper(val)
- }
-
- // Fail-fast if no indicate char found for recursive value
- if !strings.Contains(val, "%") {
- return val
- }
- for i := 0; i < _DEPTH_VALUES; i++ {
- vr := varPattern.FindString(val)
- if len(vr) == 0 {
- break
- }
-
- // Take off leading '%(' and trailing ')s'.
- noption := vr[2 : len(vr)-2]
-
- // Search in the same section.
- nk, err := k.s.GetKey(noption)
- if err != nil || k == nk {
- // Search again in default section.
- nk, _ = k.s.f.Section("").GetKey(noption)
- }
-
- // Substitute by new value and take off leading '%(' and trailing ')s'.
- val = strings.Replace(val, vr, nk.value, -1)
- }
- return val
-}
-
-// String returns string representation of value.
-func (k *Key) String() string {
- return k.transformValue(k.value)
-}
-
-// Validate accepts a validate function which can
-// return modifed result as key value.
-func (k *Key) Validate(fn func(string) string) string {
- return fn(k.String())
-}
-
-// parseBool returns the boolean value represented by the string.
-//
-// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
-// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
-// Any other value returns an error.
-func parseBool(str string) (value bool, err error) {
- switch str {
- case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
- return true, nil
- case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
- return false, nil
- }
- return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
-}
-
-// Bool returns bool type value.
-func (k *Key) Bool() (bool, error) {
- return parseBool(k.String())
-}
-
-// Float64 returns float64 type value.
-func (k *Key) Float64() (float64, error) {
- return strconv.ParseFloat(k.String(), 64)
-}
-
-// Int returns int type value.
-func (k *Key) Int() (int, error) {
- v, err := strconv.ParseInt(k.String(), 0, 64)
- return int(v), err
-}
-
-// Int64 returns int64 type value.
-func (k *Key) Int64() (int64, error) {
- return strconv.ParseInt(k.String(), 0, 64)
-}
-
-// Uint returns uint type valued.
-func (k *Key) Uint() (uint, error) {
- u, e := strconv.ParseUint(k.String(), 0, 64)
- return uint(u), e
-}
-
-// Uint64 returns uint64 type value.
-func (k *Key) Uint64() (uint64, error) {
- return strconv.ParseUint(k.String(), 0, 64)
-}
-
-// Duration returns time.Duration type value.
-func (k *Key) Duration() (time.Duration, error) {
- return time.ParseDuration(k.String())
-}
-
-// TimeFormat parses with given format and returns time.Time type value.
-func (k *Key) TimeFormat(format string) (time.Time, error) {
- return time.Parse(format, k.String())
-}
-
-// Time parses with RFC3339 format and returns time.Time type value.
-func (k *Key) Time() (time.Time, error) {
- return k.TimeFormat(time.RFC3339)
-}
-
-// MustString returns default value if key value is empty.
-func (k *Key) MustString(defaultVal string) string {
- val := k.String()
- if len(val) == 0 {
- k.value = defaultVal
- return defaultVal
- }
- return val
-}
-
-// MustBool always returns value without error,
-// it returns false if error occurs.
-func (k *Key) MustBool(defaultVal ...bool) bool {
- val, err := k.Bool()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatBool(defaultVal[0])
- return defaultVal[0]
- }
- return val
-}
-
-// MustFloat64 always returns value without error,
-// it returns 0.0 if error occurs.
-func (k *Key) MustFloat64(defaultVal ...float64) float64 {
- val, err := k.Float64()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
- return defaultVal[0]
- }
- return val
-}
-
-// MustInt always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustInt(defaultVal ...int) int {
- val, err := k.Int()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
- return defaultVal[0]
- }
- return val
-}
-
-// MustInt64 always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustInt64(defaultVal ...int64) int64 {
- val, err := k.Int64()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatInt(defaultVal[0], 10)
- return defaultVal[0]
- }
- return val
-}
-
-// MustUint always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustUint(defaultVal ...uint) uint {
- val, err := k.Uint()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
- return defaultVal[0]
- }
- return val
-}
-
-// MustUint64 always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
- val, err := k.Uint64()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatUint(defaultVal[0], 10)
- return defaultVal[0]
- }
- return val
-}
-
-// MustDuration always returns value without error,
-// it returns zero value if error occurs.
-func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
- val, err := k.Duration()
- if len(defaultVal) > 0 && err != nil {
- k.value = defaultVal[0].String()
- return defaultVal[0]
- }
- return val
-}
-
-// MustTimeFormat always parses with given format and returns value without error,
-// it returns zero value if error occurs.
-func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
- val, err := k.TimeFormat(format)
- if len(defaultVal) > 0 && err != nil {
- k.value = defaultVal[0].Format(format)
- return defaultVal[0]
- }
- return val
-}
-
-// MustTime always parses with RFC3339 format and returns value without error,
-// it returns zero value if error occurs.
-func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
- return k.MustTimeFormat(time.RFC3339, defaultVal...)
-}
-
-// In always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) In(defaultVal string, candidates []string) string {
- val := k.String()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InFloat64 always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
- val := k.MustFloat64()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InInt always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InInt(defaultVal int, candidates []int) int {
- val := k.MustInt()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InInt64 always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
- val := k.MustInt64()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InUint always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
- val := k.MustUint()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InUint64 always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
- val := k.MustUint64()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InTimeFormat always parses with given format and returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
- val := k.MustTimeFormat(format)
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InTime always parses with RFC3339 format and returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
- return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
-}
-
-// RangeFloat64 checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
- val := k.MustFloat64()
- if val < min || val > max {
- return defaultVal
- }
- return val
-}
-
-// RangeInt checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeInt(defaultVal, min, max int) int {
- val := k.MustInt()
- if val < min || val > max {
- return defaultVal
- }
- return val
-}
-
-// RangeInt64 checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
- val := k.MustInt64()
- if val < min || val > max {
- return defaultVal
- }
- return val
-}
-
-// RangeTimeFormat checks if value with given format is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
- val := k.MustTimeFormat(format)
- if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
- return defaultVal
- }
- return val
-}
-
-// RangeTime checks if value with RFC3339 format is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
- return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
-}
-
-// Strings returns list of string divided by given delimiter.
-func (k *Key) Strings(delim string) []string {
- str := k.String()
- if len(str) == 0 {
- return []string{}
- }
-
- runes := []rune(str)
- vals := make([]string, 0, 2)
- var buf bytes.Buffer
- escape := false
- idx := 0
- for {
- if escape {
- escape = false
- if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
- buf.WriteRune('\\')
- }
- buf.WriteRune(runes[idx])
- } else {
- if runes[idx] == '\\' {
- escape = true
- } else if strings.HasPrefix(string(runes[idx:]), delim) {
- idx += len(delim) - 1
- vals = append(vals, strings.TrimSpace(buf.String()))
- buf.Reset()
- } else {
- buf.WriteRune(runes[idx])
- }
- }
- idx += 1
- if idx == len(runes) {
- break
- }
- }
-
- if buf.Len() > 0 {
- vals = append(vals, strings.TrimSpace(buf.String()))
- }
-
- return vals
-}
-
-// StringsWithShadows returns list of string divided by given delimiter.
-// Shadows will also be appended if any.
-func (k *Key) StringsWithShadows(delim string) []string {
- vals := k.ValueWithShadows()
- results := make([]string, 0, len(vals)*2)
- for i := range vals {
- if len(vals) == 0 {
- continue
- }
-
- results = append(results, strings.Split(vals[i], delim)...)
- }
-
- for i := range results {
- results[i] = k.transformValue(strings.TrimSpace(results[i]))
- }
- return results
-}
-
-// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Float64s(delim string) []float64 {
- vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
- return vals
-}
-
-// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Ints(delim string) []int {
- vals, _ := k.parseInts(k.Strings(delim), true, false)
- return vals
-}
-
-// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Int64s(delim string) []int64 {
- vals, _ := k.parseInt64s(k.Strings(delim), true, false)
- return vals
-}
-
-// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Uints(delim string) []uint {
- vals, _ := k.parseUints(k.Strings(delim), true, false)
- return vals
-}
-
-// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Uint64s(delim string) []uint64 {
- vals, _ := k.parseUint64s(k.Strings(delim), true, false)
- return vals
-}
-
-// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
-// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
-func (k *Key) TimesFormat(format, delim string) []time.Time {
- vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
- return vals
-}
-
-// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
-// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
-func (k *Key) Times(delim string) []time.Time {
- return k.TimesFormat(time.RFC3339, delim)
-}
-
-// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
-// it will not be included to result list.
-func (k *Key) ValidFloat64s(delim string) []float64 {
- vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
-// not be included to result list.
-func (k *Key) ValidInts(delim string) []int {
- vals, _ := k.parseInts(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
-// then it will not be included to result list.
-func (k *Key) ValidInt64s(delim string) []int64 {
- vals, _ := k.parseInt64s(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
-// then it will not be included to result list.
-func (k *Key) ValidUints(delim string) []uint {
- vals, _ := k.parseUints(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
-// integer, then it will not be included to result list.
-func (k *Key) ValidUint64s(delim string) []uint64 {
- vals, _ := k.parseUint64s(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
-func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
- vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
- return vals
-}
-
-// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
-func (k *Key) ValidTimes(delim string) []time.Time {
- return k.ValidTimesFormat(time.RFC3339, delim)
-}
-
-// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
-func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
- return k.parseFloat64s(k.Strings(delim), false, true)
-}
-
-// StrictInts returns list of int divided by given delimiter or error on first invalid input.
-func (k *Key) StrictInts(delim string) ([]int, error) {
- return k.parseInts(k.Strings(delim), false, true)
-}
-
-// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
-func (k *Key) StrictInt64s(delim string) ([]int64, error) {
- return k.parseInt64s(k.Strings(delim), false, true)
-}
-
-// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
-func (k *Key) StrictUints(delim string) ([]uint, error) {
- return k.parseUints(k.Strings(delim), false, true)
-}
-
-// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
-func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
- return k.parseUint64s(k.Strings(delim), false, true)
-}
-
-// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
-// or error on first invalid input.
-func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
- return k.parseTimesFormat(format, k.Strings(delim), false, true)
-}
-
-// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
-// or error on first invalid input.
-func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
- return k.StrictTimesFormat(time.RFC3339, delim)
-}
-
-// parseFloat64s transforms strings to float64s.
-func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
- vals := make([]float64, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.ParseFloat(str, 64)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// parseInts transforms strings to ints.
-func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
- vals := make([]int, 0, len(strs))
- for _, str := range strs {
- valInt64, err := strconv.ParseInt(str, 0, 64)
- val := int(valInt64)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// parseInt64s transforms strings to int64s.
-func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
- vals := make([]int64, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.ParseInt(str, 0, 64)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// parseUints transforms strings to uints.
-func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
- vals := make([]uint, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.ParseUint(str, 0, 0)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, uint(val))
- }
- }
- return vals, nil
-}
-
-// parseUint64s transforms strings to uint64s.
-func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
- vals := make([]uint64, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.ParseUint(str, 0, 64)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// parseTimesFormat transforms strings to times in given format.
-func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
- vals := make([]time.Time, 0, len(strs))
- for _, str := range strs {
- val, err := time.Parse(format, str)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// SetValue changes key value.
-func (k *Key) SetValue(v string) {
- if k.s.f.BlockMode {
- k.s.f.lock.Lock()
- defer k.s.f.lock.Unlock()
- }
-
- k.value = v
- k.s.keysHash[k.name] = v
-}
+++ /dev/null
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini_test
-
-import (
- "bytes"
- "fmt"
- "strings"
- "testing"
- "time"
-
- . "github.com/smartystreets/goconvey/convey"
- "gopkg.in/ini.v1"
-)
-
-func TestKey_AddShadow(t *testing.T) {
- Convey("Add shadow to a key", t, func() {
- f, err := ini.ShadowLoad([]byte(`
-[notes]
--: note1`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- So(k.AddShadow("ini.v1"), ShouldBeNil)
- So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"})
-
- Convey("Add shadow to boolean key", func() {
- k, err := f.Section("").NewBooleanKey("published")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- So(k.AddShadow("beta"), ShouldNotBeNil)
- })
-
- Convey("Add shadow to auto-increment key", func() {
- So(f.Section("notes").Key("#1").AddShadow("beta"), ShouldNotBeNil)
- })
- })
-
- Convey("Shadow is not allowed", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- So(k.AddShadow("ini.v1"), ShouldNotBeNil)
- })
-}
-
-// Helpers for slice tests.
-func float64sEqual(values []float64, expected ...float64) {
- So(values, ShouldHaveLength, len(expected))
- for i, v := range expected {
- So(values[i], ShouldEqual, v)
- }
-}
-
-func intsEqual(values []int, expected ...int) {
- So(values, ShouldHaveLength, len(expected))
- for i, v := range expected {
- So(values[i], ShouldEqual, v)
- }
-}
-
-func int64sEqual(values []int64, expected ...int64) {
- So(values, ShouldHaveLength, len(expected))
- for i, v := range expected {
- So(values[i], ShouldEqual, v)
- }
-}
-
-func uintsEqual(values []uint, expected ...uint) {
- So(values, ShouldHaveLength, len(expected))
- for i, v := range expected {
- So(values[i], ShouldEqual, v)
- }
-}
-
-func uint64sEqual(values []uint64, expected ...uint64) {
- So(values, ShouldHaveLength, len(expected))
- for i, v := range expected {
- So(values[i], ShouldEqual, v)
- }
-}
-
-func timesEqual(values []time.Time, expected ...time.Time) {
- So(values, ShouldHaveLength, len(expected))
- for i, v := range expected {
- So(values[i].String(), ShouldEqual, v.String())
- }
-}
-
-func TestKey_Helpers(t *testing.T) {
- Convey("Getting and setting values", t, func() {
- f, err := ini.Load(_FULL_CONF)
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- Convey("Get string representation", func() {
- sec := f.Section("")
- So(sec, ShouldNotBeNil)
- So(sec.Key("NAME").Value(), ShouldEqual, "ini")
- So(sec.Key("NAME").String(), ShouldEqual, "ini")
- So(sec.Key("NAME").Validate(func(in string) string {
- return in
- }), ShouldEqual, "ini")
- So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
- So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
-
- Convey("With ValueMapper", func() {
- f.ValueMapper = func(in string) string {
- if in == "gopkg.in/%(NAME)s.%(VERSION)s" {
- return "github.com/go-ini/ini"
- }
- return in
- }
- So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "github.com/go-ini/ini")
- })
- })
-
- Convey("Get values in non-default section", func() {
- sec := f.Section("author")
- So(sec, ShouldNotBeNil)
- So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
- So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
-
- sec = f.Section("package")
- So(sec, ShouldNotBeNil)
- So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
- })
-
- Convey("Get auto-increment key names", func() {
- keys := f.Section("features").Keys()
- for i, k := range keys {
- So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
- }
- })
-
- Convey("Get parent-keys that are available to the child section", func() {
- parentKeys := f.Section("package.sub").ParentKeys()
- for _, k := range parentKeys {
- So(k.Name(), ShouldEqual, "CLONE_URL")
- }
- })
-
- Convey("Get overwrite value", func() {
- So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
- })
-
- Convey("Get sections", func() {
- sections := f.Sections()
- for i, name := range []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} {
- So(sections[i].Name(), ShouldEqual, name)
- }
- })
-
- Convey("Get parent section value", func() {
- So(f.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
- So(f.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
- })
-
- Convey("Get multiple line value", func() {
- So(f.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
- })
-
- Convey("Get values with type", func() {
- sec := f.Section("types")
- v1, err := sec.Key("BOOL").Bool()
- So(err, ShouldBeNil)
- So(v1, ShouldBeTrue)
-
- v1, err = sec.Key("BOOL_FALSE").Bool()
- So(err, ShouldBeNil)
- So(v1, ShouldBeFalse)
-
- v2, err := sec.Key("FLOAT64").Float64()
- So(err, ShouldBeNil)
- So(v2, ShouldEqual, 1.25)
-
- v3, err := sec.Key("INT").Int()
- So(err, ShouldBeNil)
- So(v3, ShouldEqual, 10)
-
- v4, err := sec.Key("INT").Int64()
- So(err, ShouldBeNil)
- So(v4, ShouldEqual, 10)
-
- v5, err := sec.Key("UINT").Uint()
- So(err, ShouldBeNil)
- So(v5, ShouldEqual, 3)
-
- v6, err := sec.Key("UINT").Uint64()
- So(err, ShouldBeNil)
- So(v6, ShouldEqual, 3)
-
- t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
- So(err, ShouldBeNil)
- v7, err := sec.Key("TIME").Time()
- So(err, ShouldBeNil)
- So(v7.String(), ShouldEqual, t.String())
-
- v8, err := sec.Key("HEX_NUMBER").Int()
- So(err, ShouldBeNil)
- So(v8, ShouldEqual, 0x3000)
-
- Convey("Must get values with type", func() {
- So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
- So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
- So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
- So(sec.Key("INT").MustInt(), ShouldEqual, 10)
- So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
- So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
- So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
- So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
- So(sec.Key("HEX_NUMBER").MustInt(), ShouldEqual, 0x3000)
-
- dur, err := time.ParseDuration("2h45m")
- So(err, ShouldBeNil)
- So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
-
- Convey("Must get values with default value", func() {
- So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
- So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
- So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
- So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
- So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15)
- So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
- So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6)
- So(sec.Key("HEX_NUMBER_404").MustInt(0x3001), ShouldEqual, 0x3001)
-
- t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
- So(err, ShouldBeNil)
- So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
-
- So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
-
- Convey("Must should set default as key value", func() {
- So(sec.Key("STRING_404").String(), ShouldEqual, "404")
- So(sec.Key("BOOL_404").String(), ShouldEqual, "true")
- So(sec.Key("FLOAT64_404").String(), ShouldEqual, "2.5")
- So(sec.Key("INT_404").String(), ShouldEqual, "15")
- So(sec.Key("INT64_404").String(), ShouldEqual, "15")
- So(sec.Key("UINT_404").String(), ShouldEqual, "6")
- So(sec.Key("UINT64_404").String(), ShouldEqual, "6")
- So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z")
- So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s")
- So(sec.Key("HEX_NUMBER_404").String(), ShouldEqual, "12289")
- })
- })
- })
- })
-
- Convey("Get value with candidates", func() {
- sec := f.Section("types")
- So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
- So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
- So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
- So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
- So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
- So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
-
- zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
- So(err, ShouldBeNil)
- t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
- So(err, ShouldBeNil)
- So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
-
- Convey("Get value with candidates and default value", func() {
- So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
- So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
- So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
- So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
- So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
- So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
- So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
- })
- })
-
- Convey("Get values in range", func() {
- sec := f.Section("types")
- So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
- So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
- So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
-
- minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
- So(err, ShouldBeNil)
- midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
- So(err, ShouldBeNil)
- maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
- So(err, ShouldBeNil)
- t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
- So(err, ShouldBeNil)
- So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
-
- Convey("Get value in range with default value", func() {
- So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
- So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
- So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
- So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
- })
- })
-
- Convey("Get values into slice", func() {
- sec := f.Section("array")
- So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
- So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
-
- vals1 := sec.Key("FLOAT64S").Float64s(",")
- float64sEqual(vals1, 1.1, 2.2, 3.3)
-
- vals2 := sec.Key("INTS").Ints(",")
- intsEqual(vals2, 1, 2, 3)
-
- vals3 := sec.Key("INTS").Int64s(",")
- int64sEqual(vals3, 1, 2, 3)
-
- vals4 := sec.Key("UINTS").Uints(",")
- uintsEqual(vals4, 1, 2, 3)
-
- vals5 := sec.Key("UINTS").Uint64s(",")
- uint64sEqual(vals5, 1, 2, 3)
-
- t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
- So(err, ShouldBeNil)
- vals6 := sec.Key("TIMES").Times(",")
- timesEqual(vals6, t, t, t)
- })
-
- Convey("Test string slice escapes", func() {
- sec := f.Section("string escapes")
- So(sec.Key("key1").Strings(","), ShouldResemble, []string{"value1", "value2", "value3"})
- So(sec.Key("key2").Strings(","), ShouldResemble, []string{"value1, value2"})
- So(sec.Key("key3").Strings(","), ShouldResemble, []string{`val\ue1`, "value2"})
- So(sec.Key("key4").Strings(","), ShouldResemble, []string{`value1\`, `value\\2`})
- So(sec.Key("key5").Strings(",,"), ShouldResemble, []string{"value1,, value2"})
- So(sec.Key("key6").Strings(" "), ShouldResemble, []string{"aaa", "bbb and space", "ccc"})
- })
-
- Convey("Get valid values into slice", func() {
- sec := f.Section("array")
- vals1 := sec.Key("FLOAT64S").ValidFloat64s(",")
- float64sEqual(vals1, 1.1, 2.2, 3.3)
-
- vals2 := sec.Key("INTS").ValidInts(",")
- intsEqual(vals2, 1, 2, 3)
-
- vals3 := sec.Key("INTS").ValidInt64s(",")
- int64sEqual(vals3, 1, 2, 3)
-
- vals4 := sec.Key("UINTS").ValidUints(",")
- uintsEqual(vals4, 1, 2, 3)
-
- vals5 := sec.Key("UINTS").ValidUint64s(",")
- uint64sEqual(vals5, 1, 2, 3)
-
- t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
- So(err, ShouldBeNil)
- vals6 := sec.Key("TIMES").ValidTimes(",")
- timesEqual(vals6, t, t, t)
- })
-
- Convey("Get values one type into slice of another type", func() {
- sec := f.Section("array")
- vals1 := sec.Key("STRINGS").ValidFloat64s(",")
- So(vals1, ShouldBeEmpty)
-
- vals2 := sec.Key("STRINGS").ValidInts(",")
- So(vals2, ShouldBeEmpty)
-
- vals3 := sec.Key("STRINGS").ValidInt64s(",")
- So(vals3, ShouldBeEmpty)
-
- vals4 := sec.Key("STRINGS").ValidUints(",")
- So(vals4, ShouldBeEmpty)
-
- vals5 := sec.Key("STRINGS").ValidUint64s(",")
- So(vals5, ShouldBeEmpty)
-
- vals6 := sec.Key("STRINGS").ValidTimes(",")
- So(vals6, ShouldBeEmpty)
- })
-
- Convey("Get valid values into slice without errors", func() {
- sec := f.Section("array")
- vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",")
- So(err, ShouldBeNil)
- float64sEqual(vals1, 1.1, 2.2, 3.3)
-
- vals2, err := sec.Key("INTS").StrictInts(",")
- So(err, ShouldBeNil)
- intsEqual(vals2, 1, 2, 3)
-
- vals3, err := sec.Key("INTS").StrictInt64s(",")
- So(err, ShouldBeNil)
- int64sEqual(vals3, 1, 2, 3)
-
- vals4, err := sec.Key("UINTS").StrictUints(",")
- So(err, ShouldBeNil)
- uintsEqual(vals4, 1, 2, 3)
-
- vals5, err := sec.Key("UINTS").StrictUint64s(",")
- So(err, ShouldBeNil)
- uint64sEqual(vals5, 1, 2, 3)
-
- t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
- So(err, ShouldBeNil)
- vals6, err := sec.Key("TIMES").StrictTimes(",")
- So(err, ShouldBeNil)
- timesEqual(vals6, t, t, t)
- })
-
- Convey("Get invalid values into slice", func() {
- sec := f.Section("array")
- vals1, err := sec.Key("STRINGS").StrictFloat64s(",")
- So(vals1, ShouldBeEmpty)
- So(err, ShouldNotBeNil)
-
- vals2, err := sec.Key("STRINGS").StrictInts(",")
- So(vals2, ShouldBeEmpty)
- So(err, ShouldNotBeNil)
-
- vals3, err := sec.Key("STRINGS").StrictInt64s(",")
- So(vals3, ShouldBeEmpty)
- So(err, ShouldNotBeNil)
-
- vals4, err := sec.Key("STRINGS").StrictUints(",")
- So(vals4, ShouldBeEmpty)
- So(err, ShouldNotBeNil)
-
- vals5, err := sec.Key("STRINGS").StrictUint64s(",")
- So(vals5, ShouldBeEmpty)
- So(err, ShouldNotBeNil)
-
- vals6, err := sec.Key("STRINGS").StrictTimes(",")
- So(vals6, ShouldBeEmpty)
- So(err, ShouldNotBeNil)
- })
- })
-}
-
-func TestKey_StringsWithShadows(t *testing.T) {
- Convey("Get strings of shadows of a key", t, func() {
- f, err := ini.ShadowLoad([]byte(""))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NUMS", "1,2")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- k, err = f.Section("").NewKey("NUMS", "4,5,6")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- So(k.StringsWithShadows(","), ShouldResemble, []string{"1", "2", "4", "5", "6"})
- })
-}
-
-func TestKey_SetValue(t *testing.T) {
- Convey("Set value of key", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- So(k.Value(), ShouldEqual, "ini")
-
- k.SetValue("ini.v1")
- So(k.Value(), ShouldEqual, "ini.v1")
- })
-}
-
-func TestKey_NestedValues(t *testing.T) {
- Convey("Read and write nested values", t, func() {
- f, err := ini.LoadSources(ini.LoadOptions{
- AllowNestedValues: true,
- }, []byte(`
-aws_access_key_id = foo
-aws_secret_access_key = bar
-region = us-west-2
-s3 =
- max_concurrent_requests=10
- max_queue_size=1000`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("s3").NestedValues(), ShouldResemble, []string{"max_concurrent_requests=10", "max_queue_size=1000"})
-
- var buf bytes.Buffer
- _, err = f.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `aws_access_key_id = foo
-aws_secret_access_key = bar
-region = us-west-2
-s3 =
- max_concurrent_requests=10
- max_queue_size=1000
-
-`)
- })
-}
-
-func TestRecursiveValues(t *testing.T) {
- Convey("Recursive values should not reflect on same key", t, func() {
- f, err := ini.Load([]byte(`
-NAME = ini
-expires = yes
-[package]
-NAME = %(NAME)s
-expires = %(expires)s`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
- So(f.Section("package").Key("NAME").String(), ShouldEqual, "ini")
- So(f.Section("package").Key("expires").String(), ShouldEqual, "yes")
- })
-}
+++ /dev/null
-// Copyright 2015 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "regexp"
- "strconv"
- "strings"
- "unicode"
-)
-
-var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)")
-
-type tokenType int
-
-const (
- _TOKEN_INVALID tokenType = iota
- _TOKEN_COMMENT
- _TOKEN_SECTION
- _TOKEN_KEY
-)
-
-type parser struct {
- buf *bufio.Reader
- isEOF bool
- count int
- comment *bytes.Buffer
-}
-
-func newParser(r io.Reader) *parser {
- return &parser{
- buf: bufio.NewReader(r),
- count: 1,
- comment: &bytes.Buffer{},
- }
-}
-
-// BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
-// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
-func (p *parser) BOM() error {
- mask, err := p.buf.Peek(2)
- if err != nil && err != io.EOF {
- return err
- } else if len(mask) < 2 {
- return nil
- }
-
- switch {
- case mask[0] == 254 && mask[1] == 255:
- fallthrough
- case mask[0] == 255 && mask[1] == 254:
- p.buf.Read(mask)
- case mask[0] == 239 && mask[1] == 187:
- mask, err := p.buf.Peek(3)
- if err != nil && err != io.EOF {
- return err
- } else if len(mask) < 3 {
- return nil
- }
- if mask[2] == 191 {
- p.buf.Read(mask)
- }
- }
- return nil
-}
-
-func (p *parser) readUntil(delim byte) ([]byte, error) {
- data, err := p.buf.ReadBytes(delim)
- if err != nil {
- if err == io.EOF {
- p.isEOF = true
- } else {
- return nil, err
- }
- }
- return data, nil
-}
-
-func cleanComment(in []byte) ([]byte, bool) {
- i := bytes.IndexAny(in, "#;")
- if i == -1 {
- return nil, false
- }
- return in[i:], true
-}
-
-func readKeyName(delimiters string, in []byte) (string, int, error) {
- line := string(in)
-
- // Check if key name surrounded by quotes.
- var keyQuote string
- if line[0] == '"' {
- if len(line) > 6 && string(line[0:3]) == `"""` {
- keyQuote = `"""`
- } else {
- keyQuote = `"`
- }
- } else if line[0] == '`' {
- keyQuote = "`"
- }
-
- // Get out key name
- endIdx := -1
- if len(keyQuote) > 0 {
- startIdx := len(keyQuote)
- // FIXME: fail case -> """"""name"""=value
- pos := strings.Index(line[startIdx:], keyQuote)
- if pos == -1 {
- return "", -1, fmt.Errorf("missing closing key quote: %s", line)
- }
- pos += startIdx
-
- // Find key-value delimiter
- i := strings.IndexAny(line[pos+startIdx:], delimiters)
- if i < 0 {
- return "", -1, ErrDelimiterNotFound{line}
- }
- endIdx = pos + i
- return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
- }
-
- endIdx = strings.IndexAny(line, delimiters)
- if endIdx < 0 {
- return "", -1, ErrDelimiterNotFound{line}
- }
- return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
-}
-
-func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
- for {
- data, err := p.readUntil('\n')
- if err != nil {
- return "", err
- }
- next := string(data)
-
- pos := strings.LastIndex(next, valQuote)
- if pos > -1 {
- val += next[:pos]
-
- comment, has := cleanComment([]byte(next[pos:]))
- if has {
- p.comment.Write(bytes.TrimSpace(comment))
- }
- break
- }
- val += next
- if p.isEOF {
- return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
- }
- }
- return val, nil
-}
-
-func (p *parser) readContinuationLines(val string) (string, error) {
- for {
- data, err := p.readUntil('\n')
- if err != nil {
- return "", err
- }
- next := strings.TrimSpace(string(data))
-
- if len(next) == 0 {
- break
- }
- val += next
- if val[len(val)-1] != '\\' {
- break
- }
- val = val[:len(val)-1]
- }
- return val, nil
-}
-
-// hasSurroundedQuote check if and only if the first and last characters
-// are quotes \" or \'.
-// It returns false if any other parts also contain same kind of quotes.
-func hasSurroundedQuote(in string, quote byte) bool {
- return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
- strings.IndexByte(in[1:], quote) == len(in)-2
-}
-
-func (p *parser) readValue(in []byte,
- parserBufferSize int,
- ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols, allowPythonMultilines, spaceBeforeInlineComment, preserveSurroundedQuote bool) (string, error) {
-
- line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
- if len(line) == 0 {
- return "", nil
- }
-
- var valQuote string
- if len(line) > 3 && string(line[0:3]) == `"""` {
- valQuote = `"""`
- } else if line[0] == '`' {
- valQuote = "`"
- } else if unescapeValueDoubleQuotes && line[0] == '"' {
- valQuote = `"`
- }
-
- if len(valQuote) > 0 {
- startIdx := len(valQuote)
- pos := strings.LastIndex(line[startIdx:], valQuote)
- // Check for multi-line value
- if pos == -1 {
- return p.readMultilines(line, line[startIdx:], valQuote)
- }
-
- if unescapeValueDoubleQuotes && valQuote == `"` {
- return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
- }
- return line[startIdx : pos+startIdx], nil
- }
-
- lastChar := line[len(line)-1]
- // Won't be able to reach here if value only contains whitespace
- line = strings.TrimSpace(line)
- trimmedLastChar := line[len(line)-1]
-
- // Check continuation lines when desired
- if !ignoreContinuation && trimmedLastChar == '\\' {
- return p.readContinuationLines(line[:len(line)-1])
- }
-
- // Check if ignore inline comment
- if !ignoreInlineComment {
- var i int
- if spaceBeforeInlineComment {
- i = strings.Index(line, " #")
- if i == -1 {
- i = strings.Index(line, " ;")
- }
-
- } else {
- i = strings.IndexAny(line, "#;")
- }
-
- if i > -1 {
- p.comment.WriteString(line[i:])
- line = strings.TrimSpace(line[:i])
- }
-
- }
-
- // Trim single and double quotes
- if (hasSurroundedQuote(line, '\'') ||
- hasSurroundedQuote(line, '"')) && !preserveSurroundedQuote {
- line = line[1 : len(line)-1]
- } else if len(valQuote) == 0 && unescapeValueCommentSymbols {
- if strings.Contains(line, `\;`) {
- line = strings.Replace(line, `\;`, ";", -1)
- }
- if strings.Contains(line, `\#`) {
- line = strings.Replace(line, `\#`, "#", -1)
- }
- } else if allowPythonMultilines && lastChar == '\n' {
- parserBufferPeekResult, _ := p.buf.Peek(parserBufferSize)
- peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
-
- val := line
-
- for {
- peekData, peekErr := peekBuffer.ReadBytes('\n')
- if peekErr != nil {
- if peekErr == io.EOF {
- return val, nil
- }
- return "", peekErr
- }
-
- peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
- if len(peekMatches) != 3 {
- return val, nil
- }
-
- // NOTE: Return if not a python-ini multi-line value.
- currentIdentSize := len(peekMatches[1])
- if currentIdentSize <= 0 {
- return val, nil
- }
-
- // NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
- _, err := p.readUntil('\n')
- if err != nil {
- return "", err
- }
-
- val += fmt.Sprintf("\n%s", peekMatches[2])
- }
- }
-
- return line, nil
-}
-
-// parse parses data through an io.Reader.
-func (f *File) parse(reader io.Reader) (err error) {
- p := newParser(reader)
- if err = p.BOM(); err != nil {
- return fmt.Errorf("BOM: %v", err)
- }
-
- // Ignore error because default section name is never empty string.
- name := DEFAULT_SECTION
- if f.options.Insensitive {
- name = strings.ToLower(DEFAULT_SECTION)
- }
- section, _ := f.NewSection(name)
-
- // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
- var isLastValueEmpty bool
- var lastRegularKey *Key
-
- var line []byte
- var inUnparseableSection bool
-
- // NOTE: Iterate and increase `currentPeekSize` until
- // the size of the parser buffer is found.
- // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
- parserBufferSize := 0
- // NOTE: Peek 1kb at a time.
- currentPeekSize := 1024
-
- if f.options.AllowPythonMultilineValues {
- for {
- peekBytes, _ := p.buf.Peek(currentPeekSize)
- peekBytesLength := len(peekBytes)
-
- if parserBufferSize >= peekBytesLength {
- break
- }
-
- currentPeekSize *= 2
- parserBufferSize = peekBytesLength
- }
- }
-
- for !p.isEOF {
- line, err = p.readUntil('\n')
- if err != nil {
- return err
- }
-
- if f.options.AllowNestedValues &&
- isLastValueEmpty && len(line) > 0 {
- if line[0] == ' ' || line[0] == '\t' {
- lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
- continue
- }
- }
-
- line = bytes.TrimLeftFunc(line, unicode.IsSpace)
- if len(line) == 0 {
- continue
- }
-
- // Comments
- if line[0] == '#' || line[0] == ';' {
- // Note: we do not care ending line break,
- // it is needed for adding second line,
- // so just clean it once at the end when set to value.
- p.comment.Write(line)
- continue
- }
-
- // Section
- if line[0] == '[' {
- // Read to the next ']' (TODO: support quoted strings)
- closeIdx := bytes.LastIndexByte(line, ']')
- if closeIdx == -1 {
- return fmt.Errorf("unclosed section: %s", line)
- }
-
- name := string(line[1:closeIdx])
- section, err = f.NewSection(name)
- if err != nil {
- return err
- }
-
- comment, has := cleanComment(line[closeIdx+1:])
- if has {
- p.comment.Write(comment)
- }
-
- section.Comment = strings.TrimSpace(p.comment.String())
-
- // Reset aotu-counter and comments
- p.comment.Reset()
- p.count = 1
-
- inUnparseableSection = false
- for i := range f.options.UnparseableSections {
- if f.options.UnparseableSections[i] == name ||
- (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
- inUnparseableSection = true
- continue
- }
- }
- continue
- }
-
- if inUnparseableSection {
- section.isRawSection = true
- section.rawBody += string(line)
- continue
- }
-
- kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
- if err != nil {
- // Treat as boolean key when desired, and whole line is key name.
- if IsErrDelimiterNotFound(err) {
- switch {
- case f.options.AllowBooleanKeys:
- kname, err := p.readValue(line,
- parserBufferSize,
- f.options.IgnoreContinuation,
- f.options.IgnoreInlineComment,
- f.options.UnescapeValueDoubleQuotes,
- f.options.UnescapeValueCommentSymbols,
- f.options.AllowPythonMultilineValues,
- f.options.SpaceBeforeInlineComment,
- f.options.PreserveSurroundedQuote)
- if err != nil {
- return err
- }
- key, err := section.NewBooleanKey(kname)
- if err != nil {
- return err
- }
- key.Comment = strings.TrimSpace(p.comment.String())
- p.comment.Reset()
- continue
-
- case f.options.SkipUnrecognizableLines:
- continue
- }
- }
- return err
- }
-
- // Auto increment.
- isAutoIncr := false
- if kname == "-" {
- isAutoIncr = true
- kname = "#" + strconv.Itoa(p.count)
- p.count++
- }
-
- value, err := p.readValue(line[offset:],
- parserBufferSize,
- f.options.IgnoreContinuation,
- f.options.IgnoreInlineComment,
- f.options.UnescapeValueDoubleQuotes,
- f.options.UnescapeValueCommentSymbols,
- f.options.AllowPythonMultilineValues,
- f.options.SpaceBeforeInlineComment,
- f.options.PreserveSurroundedQuote)
- if err != nil {
- return err
- }
- isLastValueEmpty = len(value) == 0
-
- key, err := section.NewKey(kname, value)
- if err != nil {
- return err
- }
- key.isAutoIncrement = isAutoIncr
- key.Comment = strings.TrimSpace(p.comment.String())
- p.comment.Reset()
- lastRegularKey = key
- }
- return nil
-}
+++ /dev/null
-// Copyright 2016 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini_test
-
-import (
- "testing"
-
- . "github.com/smartystreets/goconvey/convey"
- "gopkg.in/ini.v1"
-)
-
-func TestBOM(t *testing.T) {
- Convey("Test handling BOM", t, func() {
- Convey("UTF-8-BOM", func() {
- f, err := ini.Load("testdata/UTF-8-BOM.ini")
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "example@email.com")
- })
-
- Convey("UTF-16-LE-BOM", func() {
- f, err := ini.Load("testdata/UTF-16-LE-BOM.ini")
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
- })
-
- Convey("UTF-16-BE-BOM", func() {
- })
- })
-}
-
-func TestBadLoad(t *testing.T) {
- Convey("Load with bad data", t, func() {
- Convey("Bad section name", func() {
- _, err := ini.Load([]byte("[]"))
- So(err, ShouldNotBeNil)
-
- _, err = ini.Load([]byte("["))
- So(err, ShouldNotBeNil)
- })
-
- Convey("Bad keys", func() {
- _, err := ini.Load([]byte(`"""name`))
- So(err, ShouldNotBeNil)
-
- _, err = ini.Load([]byte(`"""name"""`))
- So(err, ShouldNotBeNil)
-
- _, err = ini.Load([]byte(`""=1`))
- So(err, ShouldNotBeNil)
-
- _, err = ini.Load([]byte(`=`))
- So(err, ShouldNotBeNil)
-
- _, err = ini.Load([]byte(`name`))
- So(err, ShouldNotBeNil)
- })
-
- Convey("Bad values", func() {
- _, err := ini.Load([]byte(`name="""Unknwon`))
- So(err, ShouldNotBeNil)
- })
- })
-}
+++ /dev/null
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "errors"
- "fmt"
- "strings"
-)
-
-// Section represents a config section.
-type Section struct {
- f *File
- Comment string
- name string
- keys map[string]*Key
- keyList []string
- keysHash map[string]string
-
- isRawSection bool
- rawBody string
-}
-
-func newSection(f *File, name string) *Section {
- return &Section{
- f: f,
- name: name,
- keys: make(map[string]*Key),
- keyList: make([]string, 0, 10),
- keysHash: make(map[string]string),
- }
-}
-
-// Name returns name of Section.
-func (s *Section) Name() string {
- return s.name
-}
-
-// Body returns rawBody of Section if the section was marked as unparseable.
-// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
-func (s *Section) Body() string {
- return strings.TrimSpace(s.rawBody)
-}
-
-// SetBody updates body content only if section is raw.
-func (s *Section) SetBody(body string) {
- if !s.isRawSection {
- return
- }
- s.rawBody = body
-}
-
-// NewKey creates a new key to given section.
-func (s *Section) NewKey(name, val string) (*Key, error) {
- if len(name) == 0 {
- return nil, errors.New("error creating new key: empty key name")
- } else if s.f.options.Insensitive {
- name = strings.ToLower(name)
- }
-
- if s.f.BlockMode {
- s.f.lock.Lock()
- defer s.f.lock.Unlock()
- }
-
- if inSlice(name, s.keyList) {
- if s.f.options.AllowShadows {
- if err := s.keys[name].addShadow(val); err != nil {
- return nil, err
- }
- } else {
- s.keys[name].value = val
- s.keysHash[name] = val
- }
- return s.keys[name], nil
- }
-
- s.keyList = append(s.keyList, name)
- s.keys[name] = newKey(s, name, val)
- s.keysHash[name] = val
- return s.keys[name], nil
-}
-
-// NewBooleanKey creates a new boolean type key to given section.
-func (s *Section) NewBooleanKey(name string) (*Key, error) {
- key, err := s.NewKey(name, "true")
- if err != nil {
- return nil, err
- }
-
- key.isBooleanType = true
- return key, nil
-}
-
-// GetKey returns key in section by given name.
-func (s *Section) GetKey(name string) (*Key, error) {
- // FIXME: change to section level lock?
- if s.f.BlockMode {
- s.f.lock.RLock()
- }
- if s.f.options.Insensitive {
- name = strings.ToLower(name)
- }
- key := s.keys[name]
- if s.f.BlockMode {
- s.f.lock.RUnlock()
- }
-
- if key == nil {
- // Check if it is a child-section.
- sname := s.name
- for {
- if i := strings.LastIndex(sname, "."); i > -1 {
- sname = sname[:i]
- sec, err := s.f.GetSection(sname)
- if err != nil {
- continue
- }
- return sec.GetKey(name)
- } else {
- break
- }
- }
- return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
- }
- return key, nil
-}
-
-// HasKey returns true if section contains a key with given name.
-func (s *Section) HasKey(name string) bool {
- key, _ := s.GetKey(name)
- return key != nil
-}
-
-// Haskey is a backwards-compatible name for HasKey.
-// TODO: delete me in v2
-func (s *Section) Haskey(name string) bool {
- return s.HasKey(name)
-}
-
-// HasValue returns true if section contains given raw value.
-func (s *Section) HasValue(value string) bool {
- if s.f.BlockMode {
- s.f.lock.RLock()
- defer s.f.lock.RUnlock()
- }
-
- for _, k := range s.keys {
- if value == k.value {
- return true
- }
- }
- return false
-}
-
-// Key assumes named Key exists in section and returns a zero-value when not.
-func (s *Section) Key(name string) *Key {
- key, err := s.GetKey(name)
- if err != nil {
- // It's OK here because the only possible error is empty key name,
- // but if it's empty, this piece of code won't be executed.
- key, _ = s.NewKey(name, "")
- return key
- }
- return key
-}
-
-// Keys returns list of keys of section.
-func (s *Section) Keys() []*Key {
- keys := make([]*Key, len(s.keyList))
- for i := range s.keyList {
- keys[i] = s.Key(s.keyList[i])
- }
- return keys
-}
-
-// ParentKeys returns list of keys of parent section.
-func (s *Section) ParentKeys() []*Key {
- var parentKeys []*Key
- sname := s.name
- for {
- if i := strings.LastIndex(sname, "."); i > -1 {
- sname = sname[:i]
- sec, err := s.f.GetSection(sname)
- if err != nil {
- continue
- }
- parentKeys = append(parentKeys, sec.Keys()...)
- } else {
- break
- }
-
- }
- return parentKeys
-}
-
-// KeyStrings returns list of key names of section.
-func (s *Section) KeyStrings() []string {
- list := make([]string, len(s.keyList))
- copy(list, s.keyList)
- return list
-}
-
-// KeysHash returns keys hash consisting of names and values.
-func (s *Section) KeysHash() map[string]string {
- if s.f.BlockMode {
- s.f.lock.RLock()
- defer s.f.lock.RUnlock()
- }
-
- hash := map[string]string{}
- for key, value := range s.keysHash {
- hash[key] = value
- }
- return hash
-}
-
-// DeleteKey deletes a key from section.
-func (s *Section) DeleteKey(name string) {
- if s.f.BlockMode {
- s.f.lock.Lock()
- defer s.f.lock.Unlock()
- }
-
- for i, k := range s.keyList {
- if k == name {
- s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
- delete(s.keys, name)
- delete(s.keysHash, name)
- return
- }
- }
-}
-
-// ChildSections returns a list of child sections of current section.
-// For example, "[parent.child1]" and "[parent.child12]" are child sections
-// of section "[parent]".
-func (s *Section) ChildSections() []*Section {
- prefix := s.name + "."
- children := make([]*Section, 0, 3)
- for _, name := range s.f.sectionList {
- if strings.HasPrefix(name, prefix) {
- children = append(children, s.f.sections[name])
- }
- }
- return children
-}
+++ /dev/null
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini_test
-
-import (
- "testing"
-
- . "github.com/smartystreets/goconvey/convey"
- "gopkg.in/ini.v1"
-)
-
-func TestSection_SetBody(t *testing.T) {
- Convey("Set body of raw section", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000`)
- So(err, ShouldBeNil)
- So(sec, ShouldNotBeNil)
- So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000
-111111111111111111100000000000111000000000`)
-
- sec.SetBody("1111111111111111111000000000000000001110000")
- So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000`)
-
- Convey("Set for non-raw section", func() {
- sec, err := f.NewSection("author")
- So(err, ShouldBeNil)
- So(sec, ShouldNotBeNil)
- So(sec.Body(), ShouldBeEmpty)
-
- sec.SetBody("1111111111111111111000000000000000001110000")
- So(sec.Body(), ShouldBeEmpty)
- })
- })
-}
-
-func TestSection_NewKey(t *testing.T) {
- Convey("Create a new key", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- So(k.Name(), ShouldEqual, "NAME")
- So(k.Value(), ShouldEqual, "ini")
-
- Convey("With duplicated name", func() {
- k, err := f.Section("").NewKey("NAME", "ini.v1")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- // Overwrite previous existed key
- So(k.Value(), ShouldEqual, "ini.v1")
- })
-
- Convey("With empty string", func() {
- _, err := f.Section("").NewKey("", "")
- So(err, ShouldNotBeNil)
- })
- })
-
- Convey("Create keys with same name and allow shadow", t, func() {
- f, err := ini.ShadowLoad([]byte(""))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- k, err = f.Section("").NewKey("NAME", "ini.v1")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"})
- })
-}
-
-func TestSection_NewBooleanKey(t *testing.T) {
- Convey("Create a new boolean key", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewBooleanKey("start-ssh-server")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- So(k.Name(), ShouldEqual, "start-ssh-server")
- So(k.Value(), ShouldEqual, "true")
-
- Convey("With empty string", func() {
- _, err := f.Section("").NewBooleanKey("")
- So(err, ShouldNotBeNil)
- })
- })
-}
-
-func TestSection_GetKey(t *testing.T) {
- Convey("Get a key", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- k, err = f.Section("").GetKey("NAME")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- So(k.Name(), ShouldEqual, "NAME")
- So(k.Value(), ShouldEqual, "ini")
-
- Convey("Key not exists", func() {
- _, err := f.Section("").GetKey("404")
- So(err, ShouldNotBeNil)
- })
-
- Convey("Key exists in parent section", func() {
- k, err := f.Section("parent").NewKey("AGE", "18")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- k, err = f.Section("parent.child.son").GetKey("AGE")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- So(k.Value(), ShouldEqual, "18")
- })
- })
-}
-
-func TestSection_HasKey(t *testing.T) {
- Convey("Check if a key exists", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- So(f.Section("").HasKey("NAME"), ShouldBeTrue)
- So(f.Section("").Haskey("NAME"), ShouldBeTrue)
- So(f.Section("").HasKey("404"), ShouldBeFalse)
- So(f.Section("").Haskey("404"), ShouldBeFalse)
- })
-}
-
-func TestSection_HasValue(t *testing.T) {
- Convey("Check if contains a value in any key", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- So(f.Section("").HasValue("ini"), ShouldBeTrue)
- So(f.Section("").HasValue("404"), ShouldBeFalse)
- })
-}
-
-func TestSection_Key(t *testing.T) {
- Convey("Get a key", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- k = f.Section("").Key("NAME")
- So(k, ShouldNotBeNil)
- So(k.Name(), ShouldEqual, "NAME")
- So(k.Value(), ShouldEqual, "ini")
-
- Convey("Key not exists", func() {
- k := f.Section("").Key("404")
- So(k, ShouldNotBeNil)
- So(k.Name(), ShouldEqual, "404")
- })
-
- Convey("Key exists in parent section", func() {
- k, err := f.Section("parent").NewKey("AGE", "18")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- k = f.Section("parent.child.son").Key("AGE")
- So(k, ShouldNotBeNil)
- So(k.Value(), ShouldEqual, "18")
- })
- })
-}
-
-func TestSection_Keys(t *testing.T) {
- Convey("Get all keys in a section", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- k, err = f.Section("").NewKey("VERSION", "v1")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- keys := f.Section("").Keys()
- names := []string{"NAME", "VERSION", "IMPORT_PATH"}
- So(len(keys), ShouldEqual, len(names))
- for i, name := range names {
- So(keys[i].Name(), ShouldEqual, name)
- }
- })
-}
-
-func TestSection_ParentKeys(t *testing.T) {
- Convey("Get all keys of parent sections", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("package").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- k, err = f.Section("package").NewKey("VERSION", "v1")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- keys := f.Section("package.sub.sub2").ParentKeys()
- names := []string{"NAME", "VERSION", "IMPORT_PATH"}
- So(len(keys), ShouldEqual, len(names))
- for i, name := range names {
- So(keys[i].Name(), ShouldEqual, name)
- }
- })
-}
-
-func TestSection_KeyStrings(t *testing.T) {
- Convey("Get all key names in a section", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- k, err = f.Section("").NewKey("VERSION", "v1")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
- k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- So(f.Section("").KeyStrings(), ShouldResemble, []string{"NAME", "VERSION", "IMPORT_PATH"})
- })
-}
-
-func TestSection_KeyHash(t *testing.T) {
- Convey("Get clone of key hash", t, func() {
- f, err := ini.Load([]byte(`
-key = one
-[log]
-name = app
-file = a.log
-`), []byte(`
-key = two
-[log]
-name = app2
-file = b.log
-`))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.Section("").Key("key").String(), ShouldEqual, "two")
-
- hash := f.Section("log").KeysHash()
- relation := map[string]string{
- "name": "app2",
- "file": "b.log",
- }
- for k, v := range hash {
- So(v, ShouldEqual, relation[k])
- }
- })
-}
-
-func TestSection_DeleteKey(t *testing.T) {
- Convey("Delete a key", t, func() {
- f := ini.Empty()
- So(f, ShouldNotBeNil)
-
- k, err := f.Section("").NewKey("NAME", "ini")
- So(err, ShouldBeNil)
- So(k, ShouldNotBeNil)
-
- So(f.Section("").HasKey("NAME"), ShouldBeTrue)
- f.Section("").DeleteKey("NAME")
- So(f.Section("").HasKey("NAME"), ShouldBeFalse)
- })
-}
+++ /dev/null
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "bytes"
- "errors"
- "fmt"
- "reflect"
- "strings"
- "time"
- "unicode"
-)
-
-// NameMapper represents a ini tag name mapper.
-type NameMapper func(string) string
-
-// Built-in name getters.
-var (
- // AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
- AllCapsUnderscore NameMapper = func(raw string) string {
- newstr := make([]rune, 0, len(raw))
- for i, chr := range raw {
- if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
- if i > 0 {
- newstr = append(newstr, '_')
- }
- }
- newstr = append(newstr, unicode.ToUpper(chr))
- }
- return string(newstr)
- }
- // TitleUnderscore converts to format title_underscore.
- TitleUnderscore NameMapper = func(raw string) string {
- newstr := make([]rune, 0, len(raw))
- for i, chr := range raw {
- if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
- if i > 0 {
- newstr = append(newstr, '_')
- }
- chr -= ('A' - 'a')
- }
- newstr = append(newstr, chr)
- }
- return string(newstr)
- }
-)
-
-func (s *Section) parseFieldName(raw, actual string) string {
- if len(actual) > 0 {
- return actual
- }
- if s.f.NameMapper != nil {
- return s.f.NameMapper(raw)
- }
- return raw
-}
-
-func parseDelim(actual string) string {
- if len(actual) > 0 {
- return actual
- }
- return ","
-}
-
-var reflectTime = reflect.TypeOf(time.Now()).Kind()
-
-// setSliceWithProperType sets proper values to slice based on its type.
-func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
- var strs []string
- if allowShadow {
- strs = key.StringsWithShadows(delim)
- } else {
- strs = key.Strings(delim)
- }
-
- numVals := len(strs)
- if numVals == 0 {
- return nil
- }
-
- var vals interface{}
- var err error
-
- sliceOf := field.Type().Elem().Kind()
- switch sliceOf {
- case reflect.String:
- vals = strs
- case reflect.Int:
- vals, err = key.parseInts(strs, true, false)
- case reflect.Int64:
- vals, err = key.parseInt64s(strs, true, false)
- case reflect.Uint:
- vals, err = key.parseUints(strs, true, false)
- case reflect.Uint64:
- vals, err = key.parseUint64s(strs, true, false)
- case reflect.Float64:
- vals, err = key.parseFloat64s(strs, true, false)
- case reflectTime:
- vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
- default:
- return fmt.Errorf("unsupported type '[]%s'", sliceOf)
- }
- if err != nil && isStrict {
- return err
- }
-
- slice := reflect.MakeSlice(field.Type(), numVals, numVals)
- for i := 0; i < numVals; i++ {
- switch sliceOf {
- case reflect.String:
- slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
- case reflect.Int:
- slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
- case reflect.Int64:
- slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
- case reflect.Uint:
- slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
- case reflect.Uint64:
- slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
- case reflect.Float64:
- slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
- case reflectTime:
- slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
- }
- }
- field.Set(slice)
- return nil
-}
-
-func wrapStrictError(err error, isStrict bool) error {
- if isStrict {
- return err
- }
- return nil
-}
-
-// setWithProperType sets proper value to field based on its type,
-// but it does not return error for failing parsing,
-// because we want to use default value that is already assigned to strcut.
-func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
- switch t.Kind() {
- case reflect.String:
- if len(key.String()) == 0 {
- return nil
- }
- field.SetString(key.String())
- case reflect.Bool:
- boolVal, err := key.Bool()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.SetBool(boolVal)
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- durationVal, err := key.Duration()
- // Skip zero value
- if err == nil && int64(durationVal) > 0 {
- field.Set(reflect.ValueOf(durationVal))
- return nil
- }
-
- intVal, err := key.Int64()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.SetInt(intVal)
- // byte is an alias for uint8, so supporting uint8 breaks support for byte
- case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- durationVal, err := key.Duration()
- // Skip zero value
- if err == nil && uint64(durationVal) > 0 {
- field.Set(reflect.ValueOf(durationVal))
- return nil
- }
-
- uintVal, err := key.Uint64()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.SetUint(uintVal)
-
- case reflect.Float32, reflect.Float64:
- floatVal, err := key.Float64()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.SetFloat(floatVal)
- case reflectTime:
- timeVal, err := key.Time()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.Set(reflect.ValueOf(timeVal))
- case reflect.Slice:
- return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
- default:
- return fmt.Errorf("unsupported type '%s'", t)
- }
- return nil
-}
-
-func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
- opts := strings.SplitN(tag, ",", 3)
- rawName = opts[0]
- if len(opts) > 1 {
- omitEmpty = opts[1] == "omitempty"
- }
- if len(opts) > 2 {
- allowShadow = opts[2] == "allowshadow"
- }
- return rawName, omitEmpty, allowShadow
-}
-
-func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
- if val.Kind() == reflect.Ptr {
- val = val.Elem()
- }
- typ := val.Type()
-
- for i := 0; i < typ.NumField(); i++ {
- field := val.Field(i)
- tpField := typ.Field(i)
-
- tag := tpField.Tag.Get("ini")
- if tag == "-" {
- continue
- }
-
- rawName, _, allowShadow := parseTagOptions(tag)
- fieldName := s.parseFieldName(tpField.Name, rawName)
- if len(fieldName) == 0 || !field.CanSet() {
- continue
- }
-
- isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
- isStruct := tpField.Type.Kind() == reflect.Struct
- if isAnonymous {
- field.Set(reflect.New(tpField.Type.Elem()))
- }
-
- if isAnonymous || isStruct {
- if sec, err := s.f.GetSection(fieldName); err == nil {
- if err = sec.mapTo(field, isStrict); err != nil {
- return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
- }
- continue
- }
- }
-
- if key, err := s.GetKey(fieldName); err == nil {
- delim := parseDelim(tpField.Tag.Get("delim"))
- if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
- return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
- }
- }
- }
- return nil
-}
-
-// MapTo maps section to given struct.
-func (s *Section) MapTo(v interface{}) error {
- typ := reflect.TypeOf(v)
- val := reflect.ValueOf(v)
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- } else {
- return errors.New("cannot map to non-pointer struct")
- }
-
- return s.mapTo(val, false)
-}
-
-// MapTo maps section to given struct in strict mode,
-// which returns all possible error including value parsing error.
-func (s *Section) StrictMapTo(v interface{}) error {
- typ := reflect.TypeOf(v)
- val := reflect.ValueOf(v)
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- } else {
- return errors.New("cannot map to non-pointer struct")
- }
-
- return s.mapTo(val, true)
-}
-
-// MapTo maps file to given struct.
-func (f *File) MapTo(v interface{}) error {
- return f.Section("").MapTo(v)
-}
-
-// MapTo maps file to given struct in strict mode,
-// which returns all possible error including value parsing error.
-func (f *File) StrictMapTo(v interface{}) error {
- return f.Section("").StrictMapTo(v)
-}
-
-// MapTo maps data sources to given struct with name mapper.
-func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
- cfg, err := Load(source, others...)
- if err != nil {
- return err
- }
- cfg.NameMapper = mapper
- return cfg.MapTo(v)
-}
-
-// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
-// which returns all possible error including value parsing error.
-func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
- cfg, err := Load(source, others...)
- if err != nil {
- return err
- }
- cfg.NameMapper = mapper
- return cfg.StrictMapTo(v)
-}
-
-// MapTo maps data sources to given struct.
-func MapTo(v, source interface{}, others ...interface{}) error {
- return MapToWithMapper(v, nil, source, others...)
-}
-
-// StrictMapTo maps data sources to given struct in strict mode,
-// which returns all possible error including value parsing error.
-func StrictMapTo(v, source interface{}, others ...interface{}) error {
- return StrictMapToWithMapper(v, nil, source, others...)
-}
-
-// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
-func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
- slice := field.Slice(0, field.Len())
- if field.Len() == 0 {
- return nil
- }
-
- var buf bytes.Buffer
- sliceOf := field.Type().Elem().Kind()
- for i := 0; i < field.Len(); i++ {
- switch sliceOf {
- case reflect.String:
- buf.WriteString(slice.Index(i).String())
- case reflect.Int, reflect.Int64:
- buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
- case reflect.Uint, reflect.Uint64:
- buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
- case reflect.Float64:
- buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
- case reflectTime:
- buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
- default:
- return fmt.Errorf("unsupported type '[]%s'", sliceOf)
- }
- buf.WriteString(delim)
- }
- key.SetValue(buf.String()[:buf.Len()-1])
- return nil
-}
-
-// reflectWithProperType does the opposite thing as setWithProperType.
-func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
- switch t.Kind() {
- case reflect.String:
- key.SetValue(field.String())
- case reflect.Bool:
- key.SetValue(fmt.Sprint(field.Bool()))
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- key.SetValue(fmt.Sprint(field.Int()))
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- key.SetValue(fmt.Sprint(field.Uint()))
- case reflect.Float32, reflect.Float64:
- key.SetValue(fmt.Sprint(field.Float()))
- case reflectTime:
- key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
- case reflect.Slice:
- return reflectSliceWithProperType(key, field, delim)
- default:
- return fmt.Errorf("unsupported type '%s'", t)
- }
- return nil
-}
-
-// CR: copied from encoding/json/encode.go with modifications of time.Time support.
-// TODO: add more test coverage.
-func isEmptyValue(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
- return v.Len() == 0
- case reflect.Bool:
- return !v.Bool()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return v.Uint() == 0
- case reflect.Float32, reflect.Float64:
- return v.Float() == 0
- case reflect.Interface, reflect.Ptr:
- return v.IsNil()
- case reflectTime:
- t, ok := v.Interface().(time.Time)
- return ok && t.IsZero()
- }
- return false
-}
-
-func (s *Section) reflectFrom(val reflect.Value) error {
- if val.Kind() == reflect.Ptr {
- val = val.Elem()
- }
- typ := val.Type()
-
- for i := 0; i < typ.NumField(); i++ {
- field := val.Field(i)
- tpField := typ.Field(i)
-
- tag := tpField.Tag.Get("ini")
- if tag == "-" {
- continue
- }
-
- opts := strings.SplitN(tag, ",", 2)
- if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
- continue
- }
-
- fieldName := s.parseFieldName(tpField.Name, opts[0])
- if len(fieldName) == 0 || !field.CanSet() {
- continue
- }
-
- if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
- (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
- // Note: The only error here is section doesn't exist.
- sec, err := s.f.GetSection(fieldName)
- if err != nil {
- // Note: fieldName can never be empty here, ignore error.
- sec, _ = s.f.NewSection(fieldName)
- }
-
- // Add comment from comment tag
- if len(sec.Comment) == 0 {
- sec.Comment = tpField.Tag.Get("comment")
- }
-
- if err = sec.reflectFrom(field); err != nil {
- return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
- }
- continue
- }
-
- // Note: Same reason as secion.
- key, err := s.GetKey(fieldName)
- if err != nil {
- key, _ = s.NewKey(fieldName, "")
- }
-
- // Add comment from comment tag
- if len(key.Comment) == 0 {
- key.Comment = tpField.Tag.Get("comment")
- }
-
- if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
- return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
- }
-
- }
- return nil
-}
-
-// ReflectFrom reflects secion from given struct.
-func (s *Section) ReflectFrom(v interface{}) error {
- typ := reflect.TypeOf(v)
- val := reflect.ValueOf(v)
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- } else {
- return errors.New("cannot reflect from non-pointer struct")
- }
-
- return s.reflectFrom(val)
-}
-
-// ReflectFrom reflects file from given struct.
-func (f *File) ReflectFrom(v interface{}) error {
- return f.Section("").ReflectFrom(v)
-}
-
-// ReflectFrom reflects data sources from given struct with name mapper.
-func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
- cfg.NameMapper = mapper
- return cfg.ReflectFrom(v)
-}
-
-// ReflectFrom reflects data sources from given struct.
-func ReflectFrom(cfg *File, v interface{}) error {
- return ReflectFromWithMapper(cfg, v, nil)
-}
+++ /dev/null
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini_test
-
-import (
- "bytes"
- "fmt"
- "strings"
- "testing"
- "time"
-
- . "github.com/smartystreets/goconvey/convey"
- "gopkg.in/ini.v1"
-)
-
-type testNested struct {
- Cities []string `delim:"|"`
- Visits []time.Time
- Years []int
- Numbers []int64
- Ages []uint
- Populations []uint64
- Coordinates []float64
- Note string
- Unused int `ini:"-"`
-}
-
-type TestEmbeded struct {
- GPA float64
-}
-
-type testStruct struct {
- Name string `ini:"NAME"`
- Age int
- Male bool
- Money float64
- Born time.Time
- Time time.Duration `ini:"Duration"`
- Others testNested
- *TestEmbeded `ini:"grade"`
- Unused int `ini:"-"`
- Unsigned uint
- Omitted bool `ini:"omitthis,omitempty"`
- Shadows []string `ini:",,allowshadow"`
- ShadowInts []int `ini:"Shadows,,allowshadow"`
-}
-
-const _CONF_DATA_STRUCT = `
-NAME = Unknwon
-Age = 21
-Male = true
-Money = 1.25
-Born = 1993-10-07T20:17:05Z
-Duration = 2h45m
-Unsigned = 3
-omitthis = true
-Shadows = 1, 2
-Shadows = 3, 4
-
-[Others]
-Cities = HangZhou|Boston
-Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
-Years = 1993,1994
-Numbers = 10010,10086
-Ages = 18,19
-Populations = 12345678,98765432
-Coordinates = 192.168,10.11
-Note = Hello world!
-
-[grade]
-GPA = 2.8
-
-[foo.bar]
-Here = there
-When = then
-`
-
-type unsupport struct {
- Byte byte
-}
-
-type unsupport2 struct {
- Others struct {
- Cities byte
- }
-}
-
-type Unsupport3 struct {
- Cities byte
-}
-
-type unsupport4 struct {
- *Unsupport3 `ini:"Others"`
-}
-
-type defaultValue struct {
- Name string
- Age int
- Male bool
- Money float64
- Born time.Time
- Cities []string
-}
-
-type fooBar struct {
- Here, When string
-}
-
-const _INVALID_DATA_CONF_STRUCT = `
-Name =
-Age = age
-Male = 123
-Money = money
-Born = nil
-Cities =
-`
-
-func Test_MapToStruct(t *testing.T) {
- Convey("Map to struct", t, func() {
- Convey("Map file to struct", func() {
- ts := new(testStruct)
- So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
-
- So(ts.Name, ShouldEqual, "Unknwon")
- So(ts.Age, ShouldEqual, 21)
- So(ts.Male, ShouldBeTrue)
- So(ts.Money, ShouldEqual, 1.25)
- So(ts.Unsigned, ShouldEqual, 3)
-
- t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
- So(err, ShouldBeNil)
- So(ts.Born.String(), ShouldEqual, t.String())
-
- dur, err := time.ParseDuration("2h45m")
- So(err, ShouldBeNil)
- So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
-
- So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
- So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
- So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
- So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
- So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
- So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
- So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
- So(ts.Others.Note, ShouldEqual, "Hello world!")
- So(ts.TestEmbeded.GPA, ShouldEqual, 2.8)
- })
-
- Convey("Map section to struct", func() {
- foobar := new(fooBar)
- f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
- So(err, ShouldBeNil)
-
- So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
- So(foobar.Here, ShouldEqual, "there")
- So(foobar.When, ShouldEqual, "then")
- })
-
- Convey("Map to non-pointer struct", func() {
- f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- So(f.MapTo(testStruct{}), ShouldNotBeNil)
- })
-
- Convey("Map to unsupported type", func() {
- f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
- So(err, ShouldBeNil)
- So(f, ShouldNotBeNil)
-
- f.NameMapper = func(raw string) string {
- if raw == "Byte" {
- return "NAME"
- }
- return raw
- }
- So(f.MapTo(&unsupport{}), ShouldNotBeNil)
- So(f.MapTo(&unsupport2{}), ShouldNotBeNil)
- So(f.MapTo(&unsupport4{}), ShouldNotBeNil)
- })
-
- Convey("Map to omitempty field", func() {
- ts := new(testStruct)
- So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
-
- So(ts.Omitted, ShouldEqual, true)
- })
-
- Convey("Map with shadows", func() {
- f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
- So(err, ShouldBeNil)
- ts := new(testStruct)
- So(f.MapTo(ts), ShouldBeNil)
-
- So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
- So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
- })
-
- Convey("Map from invalid data source", func() {
- So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
- })
-
- Convey("Map to wrong types and gain default values", func() {
- f, err := ini.Load([]byte(_INVALID_DATA_CONF_STRUCT))
- So(err, ShouldBeNil)
-
- t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
- So(err, ShouldBeNil)
- dv := &defaultValue{"Joe", 10, true, 1.25, t, []string{"HangZhou", "Boston"}}
- So(f.MapTo(dv), ShouldBeNil)
- So(dv.Name, ShouldEqual, "Joe")
- So(dv.Age, ShouldEqual, 10)
- So(dv.Male, ShouldBeTrue)
- So(dv.Money, ShouldEqual, 1.25)
- So(dv.Born.String(), ShouldEqual, t.String())
- So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
- })
- })
-
- Convey("Map to struct in strict mode", t, func() {
- f, err := ini.Load([]byte(`
-name=bruce
-age=a30`))
- So(err, ShouldBeNil)
-
- type Strict struct {
- Name string `ini:"name"`
- Age int `ini:"age"`
- }
- s := new(Strict)
-
- So(f.Section("").StrictMapTo(s), ShouldNotBeNil)
- })
-
- Convey("Map slice in strict mode", t, func() {
- f, err := ini.Load([]byte(`
-names=alice, bruce`))
- So(err, ShouldBeNil)
-
- type Strict struct {
- Names []string `ini:"names"`
- }
- s := new(Strict)
-
- So(f.Section("").StrictMapTo(s), ShouldBeNil)
- So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]")
- })
-}
-
-func Test_ReflectFromStruct(t *testing.T) {
- Convey("Reflect from struct", t, func() {
- type Embeded struct {
- Dates []time.Time `delim:"|" comment:"Time data"`
- Places []string
- Years []int
- Numbers []int64
- Ages []uint
- Populations []uint64
- Coordinates []float64
- None []int
- }
- type Author struct {
- Name string `ini:"NAME"`
- Male bool
- Age int `comment:"Author's age"`
- Height uint
- GPA float64
- Date time.Time
- NeverMind string `ini:"-"`
- *Embeded `ini:"infos" comment:"Embeded section"`
- }
-
- t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
- So(err, ShouldBeNil)
- a := &Author{"Unknwon", true, 21, 100, 2.8, t, "",
- &Embeded{
- []time.Time{t, t},
- []string{"HangZhou", "Boston"},
- []int{1993, 1994},
- []int64{10010, 10086},
- []uint{18, 19},
- []uint64{12345678, 98765432},
- []float64{192.168, 10.11},
- []int{},
- }}
- cfg := ini.Empty()
- So(ini.ReflectFrom(cfg, a), ShouldBeNil)
-
- var buf bytes.Buffer
- _, err = cfg.WriteTo(&buf)
- So(err, ShouldBeNil)
- So(buf.String(), ShouldEqual, `NAME = Unknwon
-Male = true
-; Author's age
-Age = 21
-Height = 100
-GPA = 2.8
-Date = 1993-10-07T20:17:05Z
-
-; Embeded section
-[infos]
-; Time data
-Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
-Places = HangZhou,Boston
-Years = 1993,1994
-Numbers = 10010,10086
-Ages = 18,19
-Populations = 12345678,98765432
-Coordinates = 192.168,10.11
-None =
-
-`)
-
- Convey("Reflect from non-point struct", func() {
- So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil)
- })
-
- Convey("Reflect from struct with omitempty", func() {
- cfg := ini.Empty()
- type SpecialStruct struct {
- FirstName string `ini:"first_name"`
- LastName string `ini:"last_name"`
- JustOmitMe string `ini:"omitempty"`
- LastLogin time.Time `ini:"last_login,omitempty"`
- LastLogin2 time.Time `ini:",omitempty"`
- NotEmpty int `ini:"omitempty"`
- }
-
- So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
-
- var buf bytes.Buffer
- _, err = cfg.WriteTo(&buf)
- So(buf.String(), ShouldEqual, `first_name = John
-last_name = Doe
-omitempty = 9
-
-`)
- })
- })
-}
-
-type testMapper struct {
- PackageName string
-}
-
-func Test_NameGetter(t *testing.T) {
- Convey("Test name mappers", t, func() {
- So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
-
- cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
- So(err, ShouldBeNil)
- So(cfg, ShouldNotBeNil)
-
- cfg.NameMapper = ini.AllCapsUnderscore
- tg := new(testMapper)
- So(cfg.MapTo(tg), ShouldBeNil)
- So(tg.PackageName, ShouldEqual, "ini")
- })
-}
-
-type testDurationStruct struct {
- Duration time.Duration `ini:"Duration"`
-}
-
-func Test_Duration(t *testing.T) {
- Convey("Duration less than 16m50s", t, func() {
- ds := new(testDurationStruct)
- So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil)
-
- dur, err := time.ParseDuration("16m49s")
- So(err, ShouldBeNil)
- So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds())
- })
-}
+++ /dev/null
-; Package name
-NAME = ini
-; Package version
-VERSION = v1
-; Package import path
-IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
-
-; Information about package author
-# Bio can be written in multiple lines.
-[author]
-; This is author name
-NAME = Unknwon
-E-MAIL = u@gogs.io
-GITHUB = https://github.com/%(NAME)s
-# Succeeding comment
-BIO = """Gopher.
-Coding addict.
-Good man.
-"""
-
-[package]
-CLONE_URL = https://%(IMPORT_PATH)s
-
-[package.sub]
-UNUSED_KEY = should be deleted
-
-[features]
-- = Support read/write comments of keys and sections
-- = Support auto-increment of key names
-- = Support load multiple files to overwrite key values
-
-[types]
-STRING = str
-BOOL = true
-BOOL_FALSE = false
-FLOAT64 = 1.25
-INT = 10
-TIME = 2015-01-01T20:17:05Z
-DURATION = 2h45m
-UINT = 3
-HEX_NUMBER = 0x3000
-
-[array]
-STRINGS = en, zh, de
-FLOAT64S = 1.1, 2.2, 3.3
-INTS = 1, 2, 3
-UINTS = 1, 2, 3
-TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
-
-[note]
-empty_lines = next line is empty
-boolean_key
-more = notes
-
-; Comment before the section
-; This is a comment for the section too
-[comments]
-; Comment before key
-key = value
-; This is a comment for key2
-key2 = value2
-key3 = "one", "two", "three"
-
-[string escapes]
-key1 = value1, value2, value3
-key2 = value1\, value2
-key3 = val\ue1, value2
-key4 = value1\\, value\\\\2
-key5 = value1\,, value2
-key6 = aaa bbb\ and\ space ccc
-
-[advance]
-value with quotes = some value
-value quote2 again = some value
-includes comment sign = `my#password`
-includes comment sign2 = `my;password`
-true = 2+3=5
-`1+1=2` = true
-`6+1=7` = true
-"""`5+5`""" = 10
-`"6+6"` = 12
-`7-2=4` = false
-ADDRESS = """404 road,
-NotFound, State, 50000"""
-two_lines = how about continuation lines?
-lots_of_lines = 1 2 3 4
-
+++ /dev/null
-[author]
-E-MAIL = example@email.com
\ No newline at end of file
+++ /dev/null
-; Package name
-NAME = ini
-; Package version
-VERSION = v1
-; Package import path
-IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
-
-# Information about package author
-# Bio can be written in multiple lines.
-[author]
-NAME = Unknwon
-E-MAIL = u@gogs.io
-GITHUB = https://github.com/%(NAME)s
-BIO = """Gopher.
-Coding addict.
-Good man.
-""" # Succeeding comment
-
-[package]
-CLONE_URL = https://%(IMPORT_PATH)s
-
-[package.sub]
-UNUSED_KEY = should be deleted
-
-[features]
--: Support read/write comments of keys and sections
--: Support auto-increment of key names
--: Support load multiple files to overwrite key values
-
-[types]
-STRING = str
-BOOL = true
-BOOL_FALSE = false
-FLOAT64 = 1.25
-INT = 10
-TIME = 2015-01-01T20:17:05Z
-DURATION = 2h45m
-UINT = 3
-HEX_NUMBER = 0x3000
-
-[array]
-STRINGS = en, zh, de
-FLOAT64S = 1.1, 2.2, 3.3
-INTS = 1, 2, 3
-UINTS = 1, 2, 3
-TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
-
-[note]
-empty_lines = next line is empty\
-
-; Comment before the section
-[comments] ; This is a comment for the section too
-; Comment before key
-key = "value"
-key2 = "value2" ; This is a comment for key2
-key3 = "one", "two", "three"
-
-[string escapes]
-key1 = value1, value2, value3
-key2 = value1\, value2
-key3 = val\ue1, value2
-key4 = value1\\, value\\\\2
-key5 = value1\,, value2
-key6 = aaa bbb\ and\ space ccc
-
-[advance]
-value with quotes = "some value"
-value quote2 again = 'some value'
-includes comment sign = `my#password`
-includes comment sign2 = `my;password`
-true = 2+3=5
-"1+1=2" = true
-"""6+1=7""" = true
-"""`5+5`""" = 10
-`"6+6"` = 12
-`7-2=4` = false
-ADDRESS = `404 road,
-NotFound, State, 50000`
-two_lines = how about \
- continuation lines?
-lots_of_lines = 1 \
- 2 \
- 3 \
- 4 \
+++ /dev/null
-[author]
-E-MAIL = u@gogs.io
\ No newline at end of file