mirror of
https://github.com/MickMake/GoSungrow.git
synced 2025-06-01 10:39:11 +02:00
Committed v2.3.0
This commit is contained in:
parent
0daafb1d6d
commit
d24e69975d
1127
.idea/workspace.xml
generated
1127
.idea/workspace.xml
generated
File diff suppressed because it is too large
Load Diff
341
EXAMPLES.md
341
EXAMPLES.md
@ -0,0 +1,341 @@
|
||||
# Examples of all known working commands
|
||||
|
||||
## Authentication
|
||||
|
||||
GoSungrow api login
|
||||
|
||||
|
||||
## High-level info commands
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow info [command]
|
||||
|
||||
Available Commands:
|
||||
get Info - Get info from iSolarCloud (table)
|
||||
raw Info - Get info from iSolarCloud (raw)
|
||||
json Info - Get info from iSolarCloud (json)
|
||||
csv Info - Get info from iSolarCloud (json)
|
||||
put Info - Set info on iSolarCloud
|
||||
```
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow info get [command]
|
||||
|
||||
Available Commands:
|
||||
point-names Info - Get iSolarCloud point names.
|
||||
mqtt Info - Get iSolarCloud MQTT service login details.
|
||||
search-point-names Info - Get iSolarCloud search point names.
|
||||
devices Info - Get iSolarCloud devices.
|
||||
models Info - Get ALL iSolarCloud models.
|
||||
templates Info - Get all defined templates.
|
||||
template-points Info - List data points used in report template.
|
||||
device-points Info - List all available device data points.
|
||||
```
|
||||
|
||||
|
||||
## Get device details
|
||||
|
||||
GoSungrow info get device
|
||||
GoSungrow info get search-point-names
|
||||
GoSungrow info get models
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
```
|
||||
GoSungrow info get templates
|
||||
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Template Id ┃ Template Name ┃ Update On ┃
|
||||
┣━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┫
|
||||
┃ 8042 │ Critical │ 2022-02-15 13:00:28 ┃
|
||||
┃ 8041 │ extras │ 2022-02-15 09:40:04 ┃
|
||||
┃ 8036 │ C │ 2022-02-15 09:31:35 ┃
|
||||
┃ 8039 │ v │ 2022-02-15 09:31:10 ┃
|
||||
┃ 8040 │ A │ 2022-02-15 09:30:56 ┃
|
||||
┃ 8034 │ Percent │ 2022-02-15 09:30:41 ┃
|
||||
┃ 8038 │ MWh │ 2022-02-15 09:09:22 ┃
|
||||
┃ 8037 │ MW │ 2022-02-15 09:03:22 ┃
|
||||
┃ 8033 │ kW │ 2022-02-15 09:01:19 ┃
|
||||
┃ 8035 │ Hours │ 2022-02-15 08:55:56 ┃
|
||||
┃ 8031 │ kWh │ 2022-02-15 07:57:36 ┃
|
||||
┃ 7981 │ Power │ 2022-02-09 10:03:40 ┃
|
||||
┗━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━┛
|
||||
```
|
||||
|
||||
|
||||
## High-level data commands
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow data [command]
|
||||
|
||||
Available Commands:
|
||||
get Data - Get high-level data from iSolarCloud (table)
|
||||
raw Data - Get high-level data from iSolarCloud (raw)
|
||||
json Data - Get high-level data from iSolarCloud (json)
|
||||
csv Data - Get high-level data from iSolarCloud (json)
|
||||
graph Data - Get high-level data from iSolarCloud (graph)
|
||||
```
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow data get [command]
|
||||
|
||||
Available Commands:
|
||||
stats Data - Get current inverter stats, (last 5 minutes).
|
||||
template Data - Get data from report template.
|
||||
points Data - Get points data for a specific date.
|
||||
real-time Data - Get iSolarCloud real-time data.
|
||||
psdetails Data - Get iSolarCloud ps details.
|
||||
```
|
||||
|
||||
|
||||
## General
|
||||
|
||||
```
|
||||
GoSungrow data get stats
|
||||
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━┓
|
||||
┃ Date ┃ Point Id ┃ Point Name ┃ Value ┃ Unit ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━┫
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Co2 Reduce │ 0 │ kg ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Co2 Reduce Total │ 5819 │ kg ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Curr Power │ 788 │ W ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Daily Irradiation │ -- │ ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Equivalent Hour │ 0.27 │ Hour ┃
|
||||
|
||||
...
|
||||
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Today Income │ 0.397 │ AUD ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Total Capacity │ │ ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Total Energy │ 176.5 │ kWh ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Total Income │ 35.806 │ AUD ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Use Energy │ 6.7 │ kWh ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━┛
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
```
|
||||
GoSungrow info get template-points 7981
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓
|
||||
┃ PointStruct Id ┃ Description ┃ Unit ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━┫
|
||||
┃ 1129147_11_0_0.p83032 │ Meter AC Power │ kW ┃
|
||||
┃ 1129147_11_0_0.p83119 │ Daily Feed-in Energy (PV) │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83549 │ Grid active power │ kW ┃
|
||||
┃ 1129147_11_0_0.p83002 │ Inverter AC Power │ kW ┃
|
||||
┃ 1129147_11_0_0.p83011 │ Meter E-daily Consumption │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83022 │ Daily Yield of Plant │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83006 │ Meter Daily Yield │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83033 │ Plant Power │ kW ┃
|
||||
┃ 1129147_11_0_0.p83072 │ Daily Feed-in Energy │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83097 │ Daily Load Energy Consumption from PV │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83102 │ Daily Purchased Energy │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13028 │ Daily Battery Charging Energy │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13112 │ Daily PV Yield │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13147 │ Daily Purchased Energy │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13173 │ Daily Feed-in Energy (PV) │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13174 │ Daily Battery Charging Energy from PV │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13199 │ Daily Load Energy Consumption │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13116 │ Daily Load Energy Consumption from PV │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13122 │ Daily Feed-in Energy │ kWh ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┛
|
||||
```
|
||||
|
||||
```
|
||||
GoSungrow data get template 8042 20220212
|
||||
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┓
|
||||
┃ Date/Time ┃ Point Id ┃ Point Name ┃ Value ┃ Units ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━┫
|
||||
┃ 2022-02-12 00:00:00 │ 1129147_11_0_0.p83106 │ Load Power │ 396 │ kW ┃
|
||||
┃ 2022-02-12 00:05:00 │ 1129147_11_0_0.p83106 │ Load Power │ 480 │ kW ┃
|
||||
┃ 2022-02-12 00:10:00 │ 1129147_11_0_0.p83106 │ Load Power │ 487 │ kW ┃
|
||||
┃ 2022-02-12 00:15:00 │ 1129147_11_0_0.p83106 │ Load Power │ 497 │ kW ┃
|
||||
┃ 2022-02-12 00:20:00 │ 1129147_11_0_0.p83106 │ Load Power │ 524 │ kW ┃
|
||||
┃ 2022-02-12 00:25:00 │ 1129147_11_0_0.p83106 │ Load Power │ 541 │ kW ┃
|
||||
┃ 2022-02-12 00:30:00 │ 1129147_11_0_0.p83106 │ Load Power │ 554 │ kW ┃
|
||||
|
||||
...
|
||||
|
||||
┃ 2022-02-12 23:55:00 │ 1129147_14_1_1.p13019 │ Internal Air Temperature │ 19.4 │ ℃ ┃
|
||||
┃ 2022-02-13 00:00:00 │ 1129147_14_1_1.p13019 │ Internal Air Temperature │ 22.3 │ ℃ ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━┛
|
||||
```
|
||||
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p13019","min_left_axis":-6000,"max_left_axis":12000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p13019"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p83106","min_left_axis":-6000,"max_left_axis":12000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p83106"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":"1","value_column":"4","search_column":"3","search_string":"p83106","file_name":"foo.png"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p13019","min_left_axis":0,"max_left_axis":0}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p13149","min_left_axis":0,"max_left_axis":0}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","file_name":"foo.png"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","min_left_axis":-6000,"max_left_axis":1000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","min_left_axis":-6000,"max_left_axis":12000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","min_left_axis":-6000,"max_left_axis":42000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","min_left_axis":0,"max_left_axis":0}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":3,"search_string":"p83106","file_name":"foo.png"}'
|
||||
GoSungrow data raw template '' 20220201
|
||||
GoSungrow data save template '' 20220201
|
||||
GoSungrow data save template 8042 20220212
|
||||
|
||||
|
||||
## Hacking
|
||||
|
||||
|
||||
## Base API commands
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow api [command]
|
||||
|
||||
Examples:
|
||||
GoSungrow api login
|
||||
GoSungrow api get <endpoint> <args>
|
||||
GoSungrow api raw <endpoint> <args>
|
||||
GoSungrow api save <endpoint> <args>
|
||||
GoSungrow api put <endpoint> <args>
|
||||
|
||||
Available Commands:
|
||||
ls List iSolarCloud api endpoints/areas
|
||||
login Login to iSolarCloud
|
||||
get Get details from iSolarCloud
|
||||
raw Raw details from iSolarCloud
|
||||
save Save details from iSolarCloud as JSON
|
||||
put Put details onto iSolarCloud
|
||||
```
|
||||
|
||||
## Get device details
|
||||
|
||||
GoSungrow api get getPsList
|
||||
GoSungrow api get getPsListByName
|
||||
GoSungrow api get getPsDataSupplementTaskList
|
||||
GoSungrow api get getPsUser
|
||||
GoSungrow api get queryPsIdList
|
||||
GoSungrow api get getInstallInfoList
|
||||
GoSungrow api get getPsListStaticData
|
||||
GoSungrow api get queryAllPsIdAndName
|
||||
GoSungrow api get getPsDetail '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPsDetailWithPsType '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPsHealthState '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPsWeatherList '{"ps_id":"1171348"}'
|
||||
GoSungrow api get queryDeviceList '{"ps_id":"1171348"}'
|
||||
GoSungrow api get queryDeviceListByUserId '{"ps_id":"1171348"}'
|
||||
GoSungrow api get queryDeviceListForApp '{"ps_id":"1171348"}'
|
||||
GoSungrow api get findPsType '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getDeviceList '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getIncomeSettingInfos '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerChargeSettingInfo '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerStationInfo '{"ps_id":"1171348"}'
|
||||
GoSungrow api get WebAppService.getDeviceUuid '{"ps_key":"1171348"}'
|
||||
GoSungrow api get getPowerStationForHousehold '{"ps_id":"1171348"}'
|
||||
|
||||
|
||||
## Get device data
|
||||
|
||||
GoSungrow api get energyTrend
|
||||
GoSungrow api get getKpiInfo
|
||||
GoSungrow api get queryPsProfit '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerStationData '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get queryPsProfit '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerStationData '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get queryPsProfit '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerStationData '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerTrendDayData '{"BeginTime":"20221004"}'
|
||||
GoSungrow api get getPowerStatistics '{"ps_id":"1171348"}'
|
||||
GoSungrow api get WebAppService.showPSView '{"ps_id":"1171348"}'
|
||||
|
||||
|
||||
## Reports
|
||||
|
||||
GoSungrow api get getPsReport '{"report_type":"1","date_id":"20220201","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get getPsReport '{"report_type":"1","date_id":"20220201","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get getPsReport '{"report_type":"1","date_id":"20220201","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"1"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"2"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"3"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"4"}'
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
GoSungrow api get template 20220202
|
||||
GoSungrow api get template 8042 20220212
|
||||
GoSungrow api get getTemplateList
|
||||
GoSungrow api get WebAppService.queryUserCurveTemplateData '{"template_id":"8042","date_type":"1","start_time":"20220223000000","end_time":"20220223235900"}'
|
||||
|
||||
|
||||
## User/installer/support info
|
||||
|
||||
GoSungrow api get getUserList
|
||||
GoSungrow api get getUserPsOrderList
|
||||
GoSungrow api get getPhotoInfo
|
||||
GoSungrow api get getOrgListByName
|
||||
GoSungrow api get getOrgListByUserId
|
||||
GoSungrow api get getOrgListForUser
|
||||
GoSungrow api get queryUserList
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"dealer_org_code":"AUSCEKK7"}'
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"org_id":"362245"}'
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"org_id":"80384"}'
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"org_id":"80393"}'
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"org_id":"300977"}'
|
||||
|
||||
|
||||
## Meta-data
|
||||
|
||||
GoSungrow api get getDeviceTypeInfoList
|
||||
GoSungrow api get getDeviceTypeList
|
||||
GoSungrow api get getInvertDataList
|
||||
GoSungrow api get getModuleLogTaskList
|
||||
GoSungrow api get powerDevicePointList
|
||||
GoSungrow api get getPowerDevicePointNames '{"device_type":"1"}'
|
||||
GoSungrow api get getPowerDevicePointNames '{"device_type":"2"}'
|
||||
GoSungrow api get getPowerDevicePointNames '{"device_type":"7"}'
|
||||
GoSungrow api get getDeviceModelInfoList
|
||||
GoSungrow api get queryUnitList
|
||||
GoSungrow api get getPowerSettingCharges
|
||||
GoSungrow api get getPowerDevicePointNames '{"device_type":"1"}'
|
||||
GoSungrow api get getPowerDeviceModelTechList '{"device_type":"1"}'
|
||||
GoSungrow api get getPowerDevicePointInfo '{"id":"1"}'
|
||||
|
||||
|
||||
## Task commands
|
||||
|
||||
GoSungrow api get queryBatchCreatePsTaskList
|
||||
GoSungrow api get getPowerDeviceSetTaskDetailList '{"query_type":"2","task_id":"1","uuid":"844763"}'
|
||||
GoSungrow api get getPowerDeviceSetTaskDetailList '{"query_type":"7","task_id":"1","uuid":"844763"}'
|
||||
GoSungrow api get getPowerDeviceSetTaskDetailList '{"size":0,"curPage":0}'
|
||||
GoSungrow api get getPowerDeviceSetTaskList '{"size":0,"curPage":0}'
|
||||
GoSungrow api get getPowerDeviceSetTaskList '{"size":0,"curPage":1}'
|
||||
GoSungrow api get getRemoteUpgradeSubTasksList '{"query_type":"1","task_id":"1577700"}'
|
||||
GoSungrow api get getRemoteUpgradeTaskList '{"ps_id_list":"1171348"}'
|
||||
|
||||
|
||||
## Misc commands
|
||||
|
||||
GoSungrow api get getDevicePoints '{"point_id":"13003"}'
|
||||
GoSungrow api get getPowerPictureList
|
||||
|
||||
|
||||
## Hacking
|
||||
|
||||
GoSungrow api get checkUnitStatus
|
||||
GoSungrow api get getAllPowerDeviceSetName
|
||||
GoSungrow api get getAreaList
|
||||
GoSungrow api get getCloudList
|
||||
GoSungrow api get getConfigList
|
||||
GoSungrow api get getDeviceInfo
|
||||
GoSungrow api get getOSSConfig
|
||||
GoSungrow api get getOssObjectStream
|
||||
GoSungrow api get getEncryptPublicKey
|
||||
GoSungrow api get getFaultCount
|
||||
GoSungrow api get getFaultDetail '{"fault_code":"34"}'
|
||||
GoSungrow api get getFaultMsgByFaultCode '{"fault_code":"703"}'
|
||||
GoSungrow api get getFaultMsgListWithYYYYMM
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"appkey": "93D72E60331ABDCDC7B39ADC2D1F32B3",
|
||||
"config": "/Users/mick/.GoSungrow/config.json",
|
||||
"config": "",
|
||||
"debug": false,
|
||||
"host": "https://augateway.isolarcloud.com",
|
||||
"password": "",
|
||||
@ -8,4 +8,4 @@
|
||||
"timeout": "30s",
|
||||
"token-expiry": "",
|
||||
"user": ""
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,341 @@
|
||||
# Examples of all known working commands
|
||||
|
||||
## Authentication
|
||||
|
||||
GoSungrow api login
|
||||
|
||||
|
||||
## High-level info commands
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow info [command]
|
||||
|
||||
Available Commands:
|
||||
get Info - Get info from iSolarCloud (table)
|
||||
raw Info - Get info from iSolarCloud (raw)
|
||||
json Info - Get info from iSolarCloud (json)
|
||||
csv Info - Get info from iSolarCloud (json)
|
||||
put Info - Set info on iSolarCloud
|
||||
```
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow info get [command]
|
||||
|
||||
Available Commands:
|
||||
point-names Info - Get iSolarCloud point names.
|
||||
mqtt Info - Get iSolarCloud MQTT service login details.
|
||||
search-point-names Info - Get iSolarCloud search point names.
|
||||
devices Info - Get iSolarCloud devices.
|
||||
models Info - Get ALL iSolarCloud models.
|
||||
templates Info - Get all defined templates.
|
||||
template-points Info - List data points used in report template.
|
||||
device-points Info - List all available device data points.
|
||||
```
|
||||
|
||||
|
||||
## Get device details
|
||||
|
||||
GoSungrow info get device
|
||||
GoSungrow info get search-point-names
|
||||
GoSungrow info get models
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
```
|
||||
GoSungrow info get templates
|
||||
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Template Id ┃ Template Name ┃ Update On ┃
|
||||
┣━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┫
|
||||
┃ 8042 │ Critical │ 2022-02-15 13:00:28 ┃
|
||||
┃ 8041 │ extras │ 2022-02-15 09:40:04 ┃
|
||||
┃ 8036 │ C │ 2022-02-15 09:31:35 ┃
|
||||
┃ 8039 │ v │ 2022-02-15 09:31:10 ┃
|
||||
┃ 8040 │ A │ 2022-02-15 09:30:56 ┃
|
||||
┃ 8034 │ Percent │ 2022-02-15 09:30:41 ┃
|
||||
┃ 8038 │ MWh │ 2022-02-15 09:09:22 ┃
|
||||
┃ 8037 │ MW │ 2022-02-15 09:03:22 ┃
|
||||
┃ 8033 │ kW │ 2022-02-15 09:01:19 ┃
|
||||
┃ 8035 │ Hours │ 2022-02-15 08:55:56 ┃
|
||||
┃ 8031 │ kWh │ 2022-02-15 07:57:36 ┃
|
||||
┃ 7981 │ Power │ 2022-02-09 10:03:40 ┃
|
||||
┗━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━┛
|
||||
```
|
||||
|
||||
|
||||
## High-level data commands
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow data [command]
|
||||
|
||||
Available Commands:
|
||||
get Data - Get high-level data from iSolarCloud (table)
|
||||
raw Data - Get high-level data from iSolarCloud (raw)
|
||||
json Data - Get high-level data from iSolarCloud (json)
|
||||
csv Data - Get high-level data from iSolarCloud (json)
|
||||
graph Data - Get high-level data from iSolarCloud (graph)
|
||||
```
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow data get [command]
|
||||
|
||||
Available Commands:
|
||||
stats Data - Get current inverter stats, (last 5 minutes).
|
||||
template Data - Get data from report template.
|
||||
points Data - Get points data for a specific date.
|
||||
real-time Data - Get iSolarCloud real-time data.
|
||||
psdetails Data - Get iSolarCloud ps details.
|
||||
```
|
||||
|
||||
|
||||
## General
|
||||
|
||||
```
|
||||
GoSungrow data get stats
|
||||
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━┓
|
||||
┃ Date ┃ Point Id ┃ Point Name ┃ Value ┃ Unit ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━┫
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Co2 Reduce │ 0 │ kg ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Co2 Reduce Total │ 5819 │ kg ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Curr Power │ 788 │ W ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Daily Irradiation │ -- │ ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1129147.0 │ Equivalent Hour │ 0.27 │ Hour ┃
|
||||
|
||||
...
|
||||
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Today Income │ 0.397 │ AUD ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Total Capacity │ │ ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Total Energy │ 176.5 │ kWh ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Total Income │ 35.806 │ AUD ┃
|
||||
┃ 2022-10-08 09:55:00 │ 1171348.0 │ Use Energy │ 6.7 │ kWh ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━┷━━━━━━┛
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
```
|
||||
GoSungrow info get template-points 7981
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓
|
||||
┃ PointStruct Id ┃ Description ┃ Unit ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━┫
|
||||
┃ 1129147_11_0_0.p83032 │ Meter AC Power │ kW ┃
|
||||
┃ 1129147_11_0_0.p83119 │ Daily Feed-in Energy (PV) │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83549 │ Grid active power │ kW ┃
|
||||
┃ 1129147_11_0_0.p83002 │ Inverter AC Power │ kW ┃
|
||||
┃ 1129147_11_0_0.p83011 │ Meter E-daily Consumption │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83022 │ Daily Yield of Plant │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83006 │ Meter Daily Yield │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83033 │ Plant Power │ kW ┃
|
||||
┃ 1129147_11_0_0.p83072 │ Daily Feed-in Energy │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83097 │ Daily Load Energy Consumption from PV │ kWh ┃
|
||||
┃ 1129147_11_0_0.p83102 │ Daily Purchased Energy │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13028 │ Daily Battery Charging Energy │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13112 │ Daily PV Yield │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13147 │ Daily Purchased Energy │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13173 │ Daily Feed-in Energy (PV) │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13174 │ Daily Battery Charging Energy from PV │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13199 │ Daily Load Energy Consumption │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13116 │ Daily Load Energy Consumption from PV │ kWh ┃
|
||||
┃ 1129147_14_1_1.p13122 │ Daily Feed-in Energy │ kWh ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━┛
|
||||
```
|
||||
|
||||
```
|
||||
GoSungrow data get template 8042 20220212
|
||||
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┓
|
||||
┃ Date/Time ┃ Point Id ┃ Point Name ┃ Value ┃ Units ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━┫
|
||||
┃ 2022-02-12 00:00:00 │ 1129147_11_0_0.p83106 │ Load Power │ 396 │ kW ┃
|
||||
┃ 2022-02-12 00:05:00 │ 1129147_11_0_0.p83106 │ Load Power │ 480 │ kW ┃
|
||||
┃ 2022-02-12 00:10:00 │ 1129147_11_0_0.p83106 │ Load Power │ 487 │ kW ┃
|
||||
┃ 2022-02-12 00:15:00 │ 1129147_11_0_0.p83106 │ Load Power │ 497 │ kW ┃
|
||||
┃ 2022-02-12 00:20:00 │ 1129147_11_0_0.p83106 │ Load Power │ 524 │ kW ┃
|
||||
┃ 2022-02-12 00:25:00 │ 1129147_11_0_0.p83106 │ Load Power │ 541 │ kW ┃
|
||||
┃ 2022-02-12 00:30:00 │ 1129147_11_0_0.p83106 │ Load Power │ 554 │ kW ┃
|
||||
|
||||
...
|
||||
|
||||
┃ 2022-02-12 23:55:00 │ 1129147_14_1_1.p13019 │ Internal Air Temperature │ 19.4 │ ℃ ┃
|
||||
┃ 2022-02-13 00:00:00 │ 1129147_14_1_1.p13019 │ Internal Air Temperature │ 22.3 │ ℃ ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━┛
|
||||
```
|
||||
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p13019","min_left_axis":-6000,"max_left_axis":12000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p13019"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p83106","min_left_axis":-6000,"max_left_axis":12000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p83106"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":"1","value_column":"4","search_column":"3","search_string":"p83106","file_name":"foo.png"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p13019","min_left_axis":0,"max_left_axis":0}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p13149","min_left_axis":0,"max_left_axis":0}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","file_name":"foo.png"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","min_left_axis":-6000,"max_left_axis":1000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","min_left_axis":-6000,"max_left_axis":12000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","min_left_axis":-6000,"max_left_axis":42000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":2,"search_string":"p83106","min_left_axis":0,"max_left_axis":0}'
|
||||
GoSungrow data graph template 8042 20220212 '{"title":"Testing 1. 2. 3.","time_column":1,"value_column":4,"search_column":3,"search_string":"p83106","file_name":"foo.png"}'
|
||||
GoSungrow data raw template '' 20220201
|
||||
GoSungrow data save template '' 20220201
|
||||
GoSungrow data save template 8042 20220212
|
||||
|
||||
|
||||
## Hacking
|
||||
|
||||
|
||||
## Base API commands
|
||||
|
||||
```
|
||||
Usage:
|
||||
GoSungrow api [command]
|
||||
|
||||
Examples:
|
||||
GoSungrow api login
|
||||
GoSungrow api get <endpoint> <args>
|
||||
GoSungrow api raw <endpoint> <args>
|
||||
GoSungrow api save <endpoint> <args>
|
||||
GoSungrow api put <endpoint> <args>
|
||||
|
||||
Available Commands:
|
||||
ls List iSolarCloud api endpoints/areas
|
||||
login Login to iSolarCloud
|
||||
get Get details from iSolarCloud
|
||||
raw Raw details from iSolarCloud
|
||||
save Save details from iSolarCloud as JSON
|
||||
put Put details onto iSolarCloud
|
||||
```
|
||||
|
||||
## Get device details
|
||||
|
||||
GoSungrow api get getPsList
|
||||
GoSungrow api get getPsListByName
|
||||
GoSungrow api get getPsDataSupplementTaskList
|
||||
GoSungrow api get getPsUser
|
||||
GoSungrow api get queryPsIdList
|
||||
GoSungrow api get getInstallInfoList
|
||||
GoSungrow api get getPsListStaticData
|
||||
GoSungrow api get queryAllPsIdAndName
|
||||
GoSungrow api get getPsDetail '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPsDetailWithPsType '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPsHealthState '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPsWeatherList '{"ps_id":"1171348"}'
|
||||
GoSungrow api get queryDeviceList '{"ps_id":"1171348"}'
|
||||
GoSungrow api get queryDeviceListByUserId '{"ps_id":"1171348"}'
|
||||
GoSungrow api get queryDeviceListForApp '{"ps_id":"1171348"}'
|
||||
GoSungrow api get findPsType '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getDeviceList '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getIncomeSettingInfos '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerChargeSettingInfo '{"ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerStationInfo '{"ps_id":"1171348"}'
|
||||
GoSungrow api get WebAppService.getDeviceUuid '{"ps_key":"1171348"}'
|
||||
GoSungrow api get getPowerStationForHousehold '{"ps_id":"1171348"}'
|
||||
|
||||
|
||||
## Get device data
|
||||
|
||||
GoSungrow api get energyTrend
|
||||
GoSungrow api get getKpiInfo
|
||||
GoSungrow api get queryPsProfit '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerStationData '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get queryPsProfit '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerStationData '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get queryPsProfit '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerStationData '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get getPowerTrendDayData '{"BeginTime":"20221004"}'
|
||||
GoSungrow api get getPowerStatistics '{"ps_id":"1171348"}'
|
||||
GoSungrow api get WebAppService.showPSView '{"ps_id":"1171348"}'
|
||||
|
||||
|
||||
## Reports
|
||||
|
||||
GoSungrow api get getPsReport '{"report_type":"1","date_id":"20220201","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get getPsReport '{"report_type":"1","date_id":"20220201","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get getPsReport '{"report_type":"1","date_id":"20220201","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"1"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"2"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"3"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"4"}'
|
||||
|
||||
|
||||
## Templates
|
||||
|
||||
GoSungrow api get template 20220202
|
||||
GoSungrow api get template 8042 20220212
|
||||
GoSungrow api get getTemplateList
|
||||
GoSungrow api get WebAppService.queryUserCurveTemplateData '{"template_id":"8042","date_type":"1","start_time":"20220223000000","end_time":"20220223235900"}'
|
||||
|
||||
|
||||
## User/installer/support info
|
||||
|
||||
GoSungrow api get getUserList
|
||||
GoSungrow api get getUserPsOrderList
|
||||
GoSungrow api get getPhotoInfo
|
||||
GoSungrow api get getOrgListByName
|
||||
GoSungrow api get getOrgListByUserId
|
||||
GoSungrow api get getOrgListForUser
|
||||
GoSungrow api get queryUserList
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"dealer_org_code":"AUSCEKK7"}'
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"org_id":"362245"}'
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"org_id":"80384"}'
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"org_id":"80393"}'
|
||||
GoSungrow api get getInstallerInfoByDealerOrgCodeOrId '{"org_id":"300977"}'
|
||||
|
||||
|
||||
## Meta-data
|
||||
|
||||
GoSungrow api get getDeviceTypeInfoList
|
||||
GoSungrow api get getDeviceTypeList
|
||||
GoSungrow api get getInvertDataList
|
||||
GoSungrow api get getModuleLogTaskList
|
||||
GoSungrow api get powerDevicePointList
|
||||
GoSungrow api get getPowerDevicePointNames '{"device_type":"1"}'
|
||||
GoSungrow api get getPowerDevicePointNames '{"device_type":"2"}'
|
||||
GoSungrow api get getPowerDevicePointNames '{"device_type":"7"}'
|
||||
GoSungrow api get getDeviceModelInfoList
|
||||
GoSungrow api get queryUnitList
|
||||
GoSungrow api get getPowerSettingCharges
|
||||
GoSungrow api get getPowerDevicePointNames '{"device_type":"1"}'
|
||||
GoSungrow api get getPowerDeviceModelTechList '{"device_type":"1"}'
|
||||
GoSungrow api get getPowerDevicePointInfo '{"id":"1"}'
|
||||
|
||||
|
||||
## Task commands
|
||||
|
||||
GoSungrow api get queryBatchCreatePsTaskList
|
||||
GoSungrow api get getPowerDeviceSetTaskDetailList '{"query_type":"2","task_id":"1","uuid":"844763"}'
|
||||
GoSungrow api get getPowerDeviceSetTaskDetailList '{"query_type":"7","task_id":"1","uuid":"844763"}'
|
||||
GoSungrow api get getPowerDeviceSetTaskDetailList '{"size":0,"curPage":0}'
|
||||
GoSungrow api get getPowerDeviceSetTaskList '{"size":0,"curPage":0}'
|
||||
GoSungrow api get getPowerDeviceSetTaskList '{"size":0,"curPage":1}'
|
||||
GoSungrow api get getRemoteUpgradeSubTasksList '{"query_type":"1","task_id":"1577700"}'
|
||||
GoSungrow api get getRemoteUpgradeTaskList '{"ps_id_list":"1171348"}'
|
||||
|
||||
|
||||
## Misc commands
|
||||
|
||||
GoSungrow api get getDevicePoints '{"point_id":"13003"}'
|
||||
GoSungrow api get getPowerPictureList
|
||||
|
||||
|
||||
## Hacking
|
||||
|
||||
GoSungrow api get checkUnitStatus
|
||||
GoSungrow api get getAllPowerDeviceSetName
|
||||
GoSungrow api get getAreaList
|
||||
GoSungrow api get getCloudList
|
||||
GoSungrow api get getConfigList
|
||||
GoSungrow api get getDeviceInfo
|
||||
GoSungrow api get getOSSConfig
|
||||
GoSungrow api get getOssObjectStream
|
||||
GoSungrow api get getEncryptPublicKey
|
||||
GoSungrow api get getFaultCount
|
||||
GoSungrow api get getFaultDetail '{"fault_code":"34"}'
|
||||
GoSungrow api get getFaultMsgByFaultCode '{"fault_code":"703"}'
|
||||
GoSungrow api get getFaultMsgListWithYYYYMM
|
@ -15,7 +15,7 @@ var Examples string
|
||||
const (
|
||||
Description = "GoSungrow - GoLang implementation to access the iSolarCloud API updated by SunGrow inverters"
|
||||
BinaryName = "GoSungrow"
|
||||
BinaryVersion = "2.2.1"
|
||||
BinaryVersion = "2.3.0"
|
||||
SourceRepo = "github.com/MickMake/" + BinaryName
|
||||
BinaryRepo = "github.com/MickMake/" + BinaryName
|
||||
|
||||
|
31
examples.txt
31
examples.txt
@ -1,3 +1,6 @@
|
||||
GoSungrow api login
|
||||
GoSungrow api ls areas
|
||||
|
||||
GoSungrow api get WebAppService.getDeviceUuid '{"ps_key":"1171348"}'
|
||||
GoSungrow api get WebAppService.showPSView '{"ps_id":"1171348"}'
|
||||
GoSungrow api get checkUnitStatus
|
||||
@ -53,8 +56,6 @@ GoSungrow api get reportList '{"ps_id":"1171348","report_type":"1"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"2"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"3"}'
|
||||
GoSungrow api get reportList '{"ps_id":"1171348","report_type":"4"}'
|
||||
GoSungrow api login
|
||||
GoSungrow api ls areas
|
||||
GoSungrow api raw queryDeviceList '{"ps_id":"1171348"}'
|
||||
GoSungrow api save WebAppService.queryUserCurveTemplateData '{"template_id":"8042","date_type":"1","start_time":"20220223000000","end_time":"20220223235900"}'
|
||||
GoSungrow api save getPsDetail '{"ps_id":""}'
|
||||
@ -72,6 +73,7 @@ GoSungrow data get template 8042 20220227
|
||||
GoSungrow data get template-points 8040
|
||||
GoSungrow data get template-points 8041
|
||||
GoSungrow data get template-points 8042
|
||||
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p13019","min_left_axis":-6000,"max_left_axis":12000}'
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p13019"}'
|
||||
GoSungrow data graph template 8042 20220212 '{"search_string":"p83106","min_left_axis":-6000,"max_left_axis":12000}'
|
||||
@ -90,21 +92,17 @@ GoSungrow data save template '' 20220201
|
||||
GoSungrow data save template 8042 20220212
|
||||
GoSungrow get raw template 20220202
|
||||
GoSungrow get template 8042 20220212
|
||||
|
||||
GoSungrow data get stats
|
||||
GoSungrow data get search-point-names
|
||||
GoSungrow data get device
|
||||
GoSungrow data get model
|
||||
|
||||
GoSungrow api raw getPowerStationForHousehold '{"ps_id":"1171348"}'
|
||||
> "org_index_code":"|80384|80393|300977|",
|
||||
|
||||
GoSungrow api raw getInstallerInfoByDealerOrgCodeOrId '{"dealer_org_code":"AUSCEKK7"}'
|
||||
GoSungrow api raw getInstallerInfoByDealerOrgCodeOrId '{"org_id":"362245"}'
|
||||
GoSungrow api raw getInstallerInfoByDealerOrgCodeOrId '{"org_id":"80384"}'
|
||||
GoSungrow api raw getInstallerInfoByDealerOrgCodeOrId '{"org_id":"80393"}'
|
||||
GoSungrow api raw getInstallerInfoByDealerOrgCodeOrId '{"org_id":"300977"}'
|
||||
|
||||
GoSungrow api raw getKpiInfo
|
||||
GoSungrow api raw getInstallInfoList
|
||||
GoSungrow api raw getIncomeSettingInfos '{"ps_id":"1171348"}'
|
||||
@ -118,43 +116,22 @@ GoSungrow api raw getOrgListForUser
|
||||
GoSungrow api raw getPowerDevicePointNames '{"device_type":"1"}'
|
||||
GoSungrow api raw getPowerDeviceModelTechList '{"device_type":"1"}'
|
||||
GoSungrow api raw getPowerDevicePointInfo '{"id":"1"}'
|
||||
GoSungrow api raw getPowerStationForHousehold '{"ps_id":"1171348"}'
|
||||
|
||||
GoSungrow api raw queryPsProfit '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api raw getPowerStationData '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"20221001","date_type":"1","ps_id":"1171348"}'
|
||||
|
||||
GoSungrow api raw queryPsProfit '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api raw getPowerStationData '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"202210","date_type":"2","ps_id":"1171348"}'
|
||||
|
||||
GoSungrow api raw queryPsProfit '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api raw getPowerStationData '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
GoSungrow api get getHouseholdStoragePsReport '{"date_id":"2022","date_type":"3","ps_id":"1171348"}'
|
||||
|
||||
GoSungrow api raw getPowerStationInfo '{"ps_id":"1171348"}'
|
||||
GoSungrow api raw getPowerTrendDayData '{"BeginTime":"20221004"}'
|
||||
GoSungrow api raw getPowerSettingCharges
|
||||
GoSungrow api raw queryUnitList
|
||||
GoSungrow api raw queryPsIdList
|
||||
GoSungrow api get getEncryptPublicKey
|
||||
|
||||
GoSungrow api raw getFaultCount
|
||||
GoSungrow api raw getFaultDetail '{"fault_code":"34"}'
|
||||
GoSungrow api raw getFaultMsgByFaultCode '{"fault_code":"703"}'
|
||||
GoSungrow api get getFaultMsgListWithYYYYMM
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
"energy_flow"
|
||||
1 / 3 - PV2Load && PV2Battery
|
||||
|
||||
|
||||
"1" - PV to Load
|
||||
"3" - Battery to Load
|
||||
"5" - PV to Battery
|
||||
|
||||
https://augateway.isolarcloud.com/v1/commonService/getMqttConfigInfoByAppkey
|
||||
sensor.sungrow_total_load_active_power == sensor.sungrow_total_active_power
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
package google
|
||||
|
||||
const (
|
||||
DefaultGoogleClientId = "424242424242-42424242424242424242424242424242.apps.googleusercontent.com"
|
||||
DefaultGoogleClientSecret = "424242424242424242424242"
|
||||
DefaultGoogleCredentials = `{"installed":{"client_id":"424242424242-42424242424242424242424242424242.apps.googleusercontent.com","project_id":"isolarcloud","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"424242424242424242424242","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}}
|
||||
`
|
||||
DefaultAuthTokenFile = "GoogleSheetsAuthToken.json"
|
||||
)
|
111
google/sheet.go
111
google/sheet.go
@ -1,111 +0,0 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"GoSungrow/Only"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/oauth2"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Retrieve a token, saves the token, then returns the generated client.
|
||||
func (s *Sheet) getClient() (*http.Client, error) {
|
||||
var ret *http.Client
|
||||
var err error
|
||||
|
||||
for range Only.Once {
|
||||
// The file token.json stores the user's access and refresh tokens, and is
|
||||
// created automatically when the authorization flow completes for the first
|
||||
// time.
|
||||
|
||||
var err error
|
||||
err = s.tokenFromFile()
|
||||
if err != nil {
|
||||
err = s.getTokenFromWeb()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = s.saveToken()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ret = s.oAuthConfig.Client(context.Background(), s.token)
|
||||
}
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// RequestCommon a token from the web, then returns the retrieved token.
|
||||
func (s *Sheet) getTokenFromWeb() error {
|
||||
var err error
|
||||
|
||||
for range Only.Once {
|
||||
authURL := s.oAuthConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
|
||||
fmt.Printf("Allow access to this application by going to this URL:\n%v\n", authURL)
|
||||
fmt.Printf("\nThen copy-paste the authorization code here: ")
|
||||
|
||||
var authCode string
|
||||
_, err = fmt.Scan(&authCode)
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to read authorization code: %v", err))
|
||||
break
|
||||
}
|
||||
|
||||
s.token, err = s.oAuthConfig.Exchange(context.TODO(), authCode)
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to retrieve token from web: %v", err))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieves a token from a local file.
|
||||
func (s *Sheet) tokenFromFile() error {
|
||||
var err error
|
||||
|
||||
for range Only.Once {
|
||||
if s.TokenFile == "" {
|
||||
err = errors.New("Empty token filename")
|
||||
break
|
||||
}
|
||||
|
||||
var f *os.File
|
||||
f, err = os.Open(s.TokenFile)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
//ret = &oauth2.token{}
|
||||
err = json.NewDecoder(f).Decode(s.token)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Saves a token to a file path.
|
||||
func (s *Sheet) saveToken() error {
|
||||
var err error
|
||||
|
||||
for range Only.Once {
|
||||
fmt.Printf("Saving token file to: %s\n", s.TokenFile)
|
||||
var f *os.File
|
||||
f, err = os.OpenFile(s.TokenFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to cache oauth token: %v", err))
|
||||
break
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
err = json.NewEncoder(f).Encode(s.token)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
193
google/struct.go
193
google/struct.go
@ -1,193 +0,0 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"GoSungrow/Only"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/sheets/v4"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const DefaultId = "SUPERSECRETKEY"
|
||||
|
||||
type Sheet struct {
|
||||
Id string
|
||||
SheetName string
|
||||
Range string
|
||||
Credentials []byte
|
||||
TokenFile string
|
||||
token *oauth2.Token
|
||||
oAuthConfig *oauth2.Config
|
||||
|
||||
Data SheetData
|
||||
}
|
||||
type SheetData [][]interface{}
|
||||
|
||||
func (s *Sheet) Set(cfg Sheet) {
|
||||
for range Only.Once {
|
||||
s.Id = cfg.Id
|
||||
s.SheetName = cfg.SheetName
|
||||
s.Range = cfg.Range
|
||||
|
||||
s.Credentials = cfg.Credentials
|
||||
s.TokenFile = cfg.TokenFile
|
||||
s.token = cfg.token
|
||||
|
||||
s.Verify()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sheet) Verify() bool {
|
||||
var ok bool
|
||||
for range Only.Once {
|
||||
if s.Id == "" {
|
||||
s.Id = DefaultId
|
||||
}
|
||||
if s.SheetName == "" {
|
||||
s.SheetName = ""
|
||||
}
|
||||
if s.Range == "" {
|
||||
s.Range = "A1"
|
||||
}
|
||||
if len(s.Credentials) == 0 {
|
||||
s.Credentials = []byte(DefaultGoogleCredentials)
|
||||
}
|
||||
|
||||
// token *oauth2.token
|
||||
// oAuthConfig *oauth2.Config
|
||||
|
||||
if s.oAuthConfig == nil {
|
||||
s.oAuthConfig = &oauth2.Config{}
|
||||
}
|
||||
if s.token == nil {
|
||||
s.token = &oauth2.Token{}
|
||||
}
|
||||
|
||||
if len(s.TokenFile) == 0 {
|
||||
var err error
|
||||
s.TokenFile, err = os.UserHomeDir()
|
||||
if err != nil {
|
||||
s.TokenFile = ""
|
||||
break
|
||||
}
|
||||
s.TokenFile = filepath.Join(s.TokenFile, ".GoSungrow", DefaultAuthTokenFile)
|
||||
}
|
||||
|
||||
ok = true
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *Sheet) ReadSheet() (SheetData, error) {
|
||||
var err error
|
||||
var csv SheetData
|
||||
|
||||
for range Only.Once {
|
||||
if !s.Verify() {
|
||||
break
|
||||
}
|
||||
|
||||
// If modifying these scopes, delete your previously saved token.json.
|
||||
s.oAuthConfig, err = google.ConfigFromJSON(s.Credentials, sheets.SpreadsheetsReadonlyScope)
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to parse client secret file to config: %v", err))
|
||||
break
|
||||
}
|
||||
|
||||
var client *http.Client
|
||||
client, err = s.getClient()
|
||||
|
||||
ctx := context.Background()
|
||||
var srv *sheets.Service
|
||||
srv, err = sheets.NewService(ctx, option.WithHTTPClient(client))
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to retrieve Sheets client: %v", err))
|
||||
break
|
||||
}
|
||||
|
||||
readRange := fmt.Sprintf("%s!%s", s.SheetName, s.Range)
|
||||
var resp *sheets.ValueRange
|
||||
resp, err = srv.Spreadsheets.Values.Get(s.Id, readRange).Do()
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to retrieve data from sheet: %v", err))
|
||||
break
|
||||
}
|
||||
|
||||
if len(resp.Values) == 0 {
|
||||
fmt.Println("No data found.")
|
||||
break
|
||||
}
|
||||
|
||||
for _, row := range resp.Values {
|
||||
// Print columns A and E, which correspond to indices 0 and 4.
|
||||
fmt.Printf("%v\n", row)
|
||||
}
|
||||
}
|
||||
|
||||
return csv, err
|
||||
}
|
||||
|
||||
func (s *Sheet) WriteSheet() error {
|
||||
var err error
|
||||
|
||||
for range Only.Once {
|
||||
if !s.Verify() {
|
||||
break
|
||||
}
|
||||
|
||||
// If modifying these scopes, delete your previously saved token.json.
|
||||
s.oAuthConfig, err = google.ConfigFromJSON(s.Credentials, sheets.SpreadsheetsScope)
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to parse client secret file to config: %v", err))
|
||||
break
|
||||
}
|
||||
|
||||
var client *http.Client
|
||||
client, err = s.getClient()
|
||||
//client = getClient(s.oAuthConfig)
|
||||
|
||||
ctx := context.Background()
|
||||
var srv *sheets.Service
|
||||
srv, err = sheets.NewService(ctx, option.WithHTTPClient(client))
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to retrieve Sheets client: %v", err))
|
||||
break
|
||||
}
|
||||
|
||||
//var itt = srv.Spreadsheets.getSheetByName('_EmailList');
|
||||
//if (!itt) {
|
||||
// ss.insertSheet('_EmailList');
|
||||
//}
|
||||
|
||||
vr := sheets.ValueRange{
|
||||
MajorDimension: "",
|
||||
Range: s.SheetName + "!" + s.Range,
|
||||
Values: s.Data,
|
||||
ServerResponse: googleapi.ServerResponse{},
|
||||
ForceSendFields: nil,
|
||||
NullFields: nil,
|
||||
}
|
||||
|
||||
writeRange := fmt.Sprintf("%s!%s", s.SheetName, s.Range)
|
||||
fmt.Printf("Updating Google sheet '%s'.\n\tWorksheet: %s\n\tStarting from: %s\n",
|
||||
s.Id,
|
||||
s.SheetName,
|
||||
s.Range,
|
||||
)
|
||||
_, err = srv.Spreadsheets.Values.Update(s.Id, writeRange, &vr).ValueInputOption("USER_ENTERED").Do() // or "RAW"
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to retrieve data from sheet. %v", err))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -11,7 +11,7 @@ const Url = "/v1/devService/getDeviceList"
|
||||
const Disabled = false
|
||||
|
||||
type RequestData struct {
|
||||
PsId int64 `json:"ps_id" required:"true"`
|
||||
PsId string `json:"ps_id" required:"true"`
|
||||
}
|
||||
|
||||
func (rd RequestData) IsValid() error {
|
||||
@ -26,7 +26,7 @@ func (rd RequestData) Help() string {
|
||||
type ResultData struct {
|
||||
PageList []struct {
|
||||
AttrID int64 `json:"attr_id"`
|
||||
ChnnlID int64 `json:"chnnl_id"`
|
||||
ChannelId int64 `json:"chnnl_id"`
|
||||
CommandStatus int64 `json:"command_status"`
|
||||
ConnectState int64 `json:"connect_state"`
|
||||
DataFlag int64 `json:"data_flag"`
|
||||
@ -132,7 +132,7 @@ func (e *EndPoint) GetDataTable() output.Table {
|
||||
d.PsID,
|
||||
d.DeviceType,
|
||||
d.DeviceCode,
|
||||
d.ChnnlID,
|
||||
d.ChannelId,
|
||||
d.TypeName,
|
||||
d.DeviceProSn,
|
||||
d.DeviceModel,
|
||||
|
@ -41,15 +41,15 @@ type ResultData struct {
|
||||
CuspPowerQuantity interface{} `json:"cusp_power_quantity"`
|
||||
CuspUsePowerQuantity interface{} `json:"cusp_use_power_quantity"`
|
||||
DateID int64 `json:"date_id"`
|
||||
FlatNetPowerQuantity int64 `json:"flat_net_power_quantity"`
|
||||
FlatPowerQuantity int64 `json:"flat_power_quantity"`
|
||||
FlatNetPowerQuantity float64 `json:"flat_net_power_quantity"`
|
||||
FlatPowerQuantity float64 `json:"flat_power_quantity"`
|
||||
FlatUsePowerQuantity float64 `json:"flat_use_power_quantity"`
|
||||
NetPowerProfit float64 `json:"net_power_profit"`
|
||||
NetPowerQuantityTotal int64 `json:"net_power_quantity_total"`
|
||||
NetPowerQuantityTotal float64 `json:"net_power_quantity_total"`
|
||||
PeakNetPowerQuantity interface{} `json:"peak_net_power_quantity"`
|
||||
PeakPowerQuantity interface{} `json:"peak_power_quantity"`
|
||||
PeakUsePowerQuantity interface{} `json:"peak_use_power_quantity"`
|
||||
PowerQuantityTotal int64 `json:"power_quantity_total"`
|
||||
PowerQuantityTotal float64 `json:"power_quantity_total"`
|
||||
SubsidyProfit interface{} `json:"subsidy_profit"`
|
||||
TotalProfit float64 `json:"total_profit"`
|
||||
UpdateTime string `json:"update_time"`
|
||||
|
@ -97,7 +97,7 @@ type ResultData struct {
|
||||
SubsidyProfitOriginalUnit string `json:"subsidy_profit_original_unit"`
|
||||
SubsidyProfitTran string `json:"subsidy_profit_tran"`
|
||||
SubsidyProfitUnit string `json:"subsidy_profit_unit"`
|
||||
TimeStamp string `json:"time_stamp"`
|
||||
TimeStamp interface{} `json:"time_stamp"` // Sad that this alternates between string and int64.
|
||||
TotalProfit string `json:"total_profit"`
|
||||
TotalProfitOriginalUnit string `json:"total_profit_original_unit"`
|
||||
TotalProfitTran string `json:"total_profit_tran"`
|
||||
|
@ -113,8 +113,8 @@ func (sg *SunGrow) GetTemplateData(template string, date string, filter string)
|
||||
break
|
||||
}
|
||||
|
||||
table.SetTitle("Template %s on %s for %s", template, when.String(), psId)
|
||||
table.SetFilePrefix(data.SetFilenamePrefix("%s-%s-%s", when.String(), template, psId))
|
||||
table.SetTitle("Template %s on %s for %d", template, when.String(), psId)
|
||||
table.SetFilePrefix(data.SetFilenamePrefix("%s-%s-%d", when.String(), template, psId))
|
||||
table.SetGraphFilter(filter)
|
||||
table.SetSaveFile(sg.SaveAsFile)
|
||||
table.OutputType = sg.OutputType
|
||||
@ -694,7 +694,7 @@ func (sg *SunGrow) GetDevices(psIds ...int64) error {
|
||||
for _, psId := range psIds {
|
||||
ep := sg.GetByStruct(
|
||||
"AppService.getDeviceList",
|
||||
getDeviceList.RequestData{PsId: psId},
|
||||
getDeviceList.RequestData{PsId: strconv.FormatInt(psId, 10)},
|
||||
DefaultCacheTimeout,
|
||||
)
|
||||
if sg.Error != nil {
|
||||
|
@ -1,72 +0,0 @@
|
||||
package lsgo
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
// declare the struct that holds all the arguments
|
||||
type arguments struct {
|
||||
paths *[]string
|
||||
all *bool
|
||||
bytes *bool
|
||||
mdate *bool
|
||||
owner *bool
|
||||
nogroup *bool
|
||||
perms *bool
|
||||
long *bool
|
||||
dirs *bool
|
||||
files *bool
|
||||
links *bool
|
||||
linkRel *bool
|
||||
sortSize *bool
|
||||
sortTime *bool
|
||||
sortKind *bool
|
||||
backwards *bool
|
||||
stats *bool
|
||||
icons *bool
|
||||
nerdfont *bool
|
||||
recurse *bool
|
||||
find *string
|
||||
}
|
||||
|
||||
var args = arguments{
|
||||
kingpin.Arg("paths", "the files(s) and/or folder(s) to display").Default(".").Strings(),
|
||||
kingpin.Flag("all", "show hidden files").Short('a').Bool(),
|
||||
kingpin.Flag("bytes", "include size").Short('b').Bool(),
|
||||
kingpin.Flag("mdate", "include modification date").Short('m').Bool(),
|
||||
kingpin.Flag("owner", "include owner and group").Short('o').Bool(),
|
||||
kingpin.Flag("nogroup", "hide group").Short('N').Bool(),
|
||||
kingpin.Flag("perms", "include permissions for owner, group, and other").Short('p').Bool(),
|
||||
kingpin.Flag("long", "include size, date, owner, and permissions").Short('l').Bool(),
|
||||
kingpin.Flag("dirs", "only show directories").Short('d').Bool(),
|
||||
kingpin.Flag("files", "only show files").Short('f').Bool(),
|
||||
kingpin.Flag("links", "show paths for symlinks").Short('L').Bool(),
|
||||
kingpin.Flag("link-rel", "show symlinks as relative paths if shorter than absolute path").Short('R').Bool(),
|
||||
kingpin.Flag("size", "sort items by size").Short('s').Bool(),
|
||||
kingpin.Flag("time", "sort items by time").Short('t').Bool(),
|
||||
kingpin.Flag("kind", "sort items by extension").Short('k').Bool(),
|
||||
kingpin.Flag("backwards", "reverse the sort order of --size, --time, or --kind").Short('B').Bool(),
|
||||
kingpin.Flag("stats", "show statistics").Short('S').Bool(),
|
||||
kingpin.Flag("icons", "show folder icon before dirs").Short('i').Bool(),
|
||||
kingpin.Flag("nerd-font", "show nerd font glyphs before file names").Short('n').Bool(),
|
||||
kingpin.Flag("recurse", "traverse all dirs recursively").Short('r').Bool(),
|
||||
kingpin.Flag("find", "filter items with a regexp").Short('F').String(),
|
||||
}
|
||||
|
||||
func argsPostParse() {
|
||||
if *args.long {
|
||||
args.bytes = &True
|
||||
args.mdate = &True
|
||||
args.owner = &True
|
||||
args.perms = &True
|
||||
args.links = &True
|
||||
}
|
||||
if *args.dirs && *args.files {
|
||||
log.Fatal("--dirs and --files cannot both be set")
|
||||
}
|
||||
if *args.nerdfont && *args.icons {
|
||||
log.Fatal("--nerd-font and --icons cannot both be set")
|
||||
}
|
||||
}
|
351
lsgo/colors.go
351
lsgo/colors.go
@ -1,351 +0,0 @@
|
||||
package lsgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// see the color codes
|
||||
// http://i.stack.imgur.com/UQVe5.png
|
||||
|
||||
// Fg wraps an 8-bit foreground color code in the ANSI escape sequence
|
||||
func Fg(code int) string {
|
||||
colored := []string{"\x1b[38;5;", strconv.Itoa(code), "m"}
|
||||
return strings.Join(colored, "")
|
||||
}
|
||||
|
||||
// Bg wraps an 8-bit background color code in the ANSI escape sequence
|
||||
func Bg(code int) string {
|
||||
colored := []string{"\x1b[48;5;", strconv.Itoa(code), "m"}
|
||||
return strings.Join(colored, "")
|
||||
}
|
||||
|
||||
// Rgb2code converts RGB values (up to 5) to an 8-bit color code
|
||||
func Rgb2code(r int, g int, b int) int {
|
||||
code := 36*r + 6*g + b + 16
|
||||
if code < 16 || 231 < code {
|
||||
panic(fmt.Errorf("Invalid RGB values (%d, %d, %d)", r, g, b))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// Gray2code converts a scalar of "grayness" to an 8-bit color code
|
||||
func Gray2code(lightness int) int {
|
||||
code := lightness + 232
|
||||
if code < 232 || 255 < code {
|
||||
panic(fmt.Errorf("Invalid lightness value (%d) for gray", lightness))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// FgRGB converts RGB values (up to 5) to an ANSI-escaped foreground 8-bit color code
|
||||
func FgRGB(r int, g int, b int) string {
|
||||
return Fg(Rgb2code(r, g, b))
|
||||
}
|
||||
|
||||
// BgRGB converts RGB values (up to 5) to an ANSI-escaped background 8-bit color code
|
||||
func BgRGB(r int, g int, b int) string {
|
||||
return Bg(Rgb2code(r, g, b))
|
||||
}
|
||||
|
||||
// FgGray converts a scalar of "grayness" to an ANSI-escaped foreground 8-bit color code
|
||||
func FgGray(lightness int) string {
|
||||
return Fg(Gray2code(lightness))
|
||||
}
|
||||
|
||||
// BgGray converts a scalar of "grayness" to an ANSI-escaped background 8-bit color code
|
||||
func BgGray(lightness int) string {
|
||||
return Bg(Gray2code(lightness))
|
||||
}
|
||||
|
||||
const (
|
||||
// Reset undoes ANSI color codes
|
||||
Reset = "\x1b[0m"
|
||||
// Bold makes text bold
|
||||
Bold = "\x1b[1m"
|
||||
)
|
||||
|
||||
var (
|
||||
// FileColor is a mapping of filetypes to colors
|
||||
FileColor = map[string][2]string{
|
||||
"as": [2]string{Fg(196), Fg(124)},
|
||||
"asm": [2]string{Fg(223), Fg(215)},
|
||||
"bf": [2]string{Fg(223), Fg(215)},
|
||||
"c": [2]string{Fg(39), Fg(27)},
|
||||
"clj": [2]string{Fg(204), Fg(162)},
|
||||
"coffee": [2]string{Fg(136), Fg(94)},
|
||||
"cr": [2]string{Fg(82), Fg(70)},
|
||||
"cson": [2]string{Fg(136), Fg(94)},
|
||||
"css": [2]string{Fg(219), Fg(207)},
|
||||
"dart": [2]string{Fg(43), Fg(31)},
|
||||
"diff": [2]string{Fg(10), Fg(9)},
|
||||
"elm": [2]string{Fg(51), Fg(39)},
|
||||
"erl": [2]string{Fg(161), Fg(89)},
|
||||
"ex": [2]string{Fg(99), Fg(57)},
|
||||
"f": [2]string{Fg(208), Fg(94)},
|
||||
"fs": [2]string{Fg(45), Fg(32)},
|
||||
"gb": [2]string{Fg(43), Fg(29)},
|
||||
"go": [2]string{Fg(121), Fg(109)},
|
||||
"graphql": [2]string{Fg(219), Fg(207)},
|
||||
"groovy": [2]string{Fg(223), Fg(215)},
|
||||
"gv": [2]string{Fg(141), Fg(99)},
|
||||
"hs": [2]string{Fg(99), Fg(57)},
|
||||
"html": [2]string{Fg(87), Fg(73)},
|
||||
"ino": [2]string{Fg(43), Fg(29)},
|
||||
"java": [2]string{Fg(136), Fg(94)},
|
||||
"jl": [2]string{Fg(141), Fg(99)},
|
||||
"js": [2]string{FgRGB(4, 4, 0), FgRGB(2, 2, 0)},
|
||||
"jsx": [2]string{Fg(87), Fg(73)},
|
||||
"lock": [2]string{FgGray(8), FgGray(5)},
|
||||
"log": [2]string{FgGray(8), FgGray(5)},
|
||||
"lua": [2]string{Fg(39), Fg(27)},
|
||||
"m": [2]string{Fg(208), Fg(196)},
|
||||
"md": [2]string{Fg(87), Fg(73)},
|
||||
"ml": [2]string{Fg(208), Fg(94)},
|
||||
"php": [2]string{Fg(30), Fg(22)},
|
||||
"pl": [2]string{Fg(99), Fg(57)},
|
||||
"py": [2]string{Fg(34), Fg(28)},
|
||||
"r": [2]string{Fg(51), Fg(39)},
|
||||
"rb": [2]string{FgRGB(5, 1, 0), FgRGB(3, 1, 1)},
|
||||
"rs": [2]string{Fg(208), Fg(94)},
|
||||
"scala": [2]string{Fg(196), Fg(124)},
|
||||
"sh": [2]string{FgRGB(4, 0, 4), FgRGB(2, 0, 2)},
|
||||
"sol": [2]string{Fg(39), Fg(27)},
|
||||
"sql": [2]string{Fg(193), Fg(148)},
|
||||
"svelte": [2]string{Fg(208), Fg(196)},
|
||||
"swift": [2]string{Fg(223), Fg(215)},
|
||||
"vim": [2]string{Fg(34), Fg(28)},
|
||||
"vue": [2]string{Fg(43), Fg(29)},
|
||||
"xml": [2]string{Fg(87), Fg(73)},
|
||||
|
||||
"compiled": [2]string{FgGray(8), FgGray(5)},
|
||||
"compress": [2]string{FgRGB(5, 0, 0), FgRGB(3, 0, 0)},
|
||||
"document": [2]string{FgRGB(5, 0, 0), FgRGB(3, 0, 0)},
|
||||
"media": [2]string{Fg(141), Fg(99)},
|
||||
"_default": [2]string{FgGray(23), FgGray(12)},
|
||||
}
|
||||
// FileAliases converts alternative extensions to their canonical mapping in FileColor
|
||||
FileAliases = map[string]string{
|
||||
"s": "asm",
|
||||
"b": "bf",
|
||||
"c++": "c",
|
||||
"cc": "c",
|
||||
"cpp": "c",
|
||||
"cs": "c",
|
||||
"cxx": "c",
|
||||
"d": "c",
|
||||
"h": "c",
|
||||
"h++": "c",
|
||||
"hh": "c",
|
||||
"hpp": "c",
|
||||
"hxx": "c",
|
||||
"pxd": "c",
|
||||
"cljc": "clj",
|
||||
"cljs": "clj",
|
||||
"class": "compiled",
|
||||
"elc": "compiled",
|
||||
"hi": "compiled",
|
||||
"o": "compiled",
|
||||
"pyc": "compiled",
|
||||
"7z": "compress",
|
||||
"Z": "compress",
|
||||
"bz2": "compress",
|
||||
"deb": "compress",
|
||||
"dmg": "compress",
|
||||
"dpkg": "compress",
|
||||
"gz": "compress",
|
||||
"iso": "compress",
|
||||
"jar": "compress",
|
||||
"lzma": "compress",
|
||||
"par": "compress",
|
||||
"rar": "compress",
|
||||
"rpm": "compress",
|
||||
"tar": "compress",
|
||||
"tc": "compress",
|
||||
"tgz": "compress",
|
||||
"txz": "compress",
|
||||
"whl": "compress",
|
||||
"xz": "compress",
|
||||
"z": "compress",
|
||||
"zip": "compress",
|
||||
"less": "css",
|
||||
"sass": "css",
|
||||
"scss": "css",
|
||||
"styl": "css",
|
||||
"djvu": "document",
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"dvi": "document",
|
||||
"eml": "document",
|
||||
"fotd": "document",
|
||||
"odp": "document",
|
||||
"odt": "document",
|
||||
"pdf": "document",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"rtf": "document",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
"f03": "f",
|
||||
"f77": "f",
|
||||
"f90": "f",
|
||||
"f95": "f",
|
||||
"for": "f",
|
||||
"fpp": "f",
|
||||
"ftn": "f",
|
||||
"fsi": "fs",
|
||||
"fsscript": "fs",
|
||||
"fsx": "fs",
|
||||
"dna": "gb",
|
||||
"gsh": "groovy",
|
||||
"gvy": "groovy",
|
||||
"gy": "groovy",
|
||||
"htm": "html",
|
||||
"xhtml": "html",
|
||||
"bson": "js",
|
||||
"json": "js",
|
||||
"mjs": "js",
|
||||
"ts": "js",
|
||||
"tsx": "jsx",
|
||||
"cjsx": "jsx",
|
||||
"mat": "m",
|
||||
"markdown": "md",
|
||||
"mkd": "md",
|
||||
"rst": "md",
|
||||
"sml": "ml",
|
||||
"mli": "ml",
|
||||
"aac": "media",
|
||||
"alac": "media",
|
||||
"audio": "media",
|
||||
"avi": "media",
|
||||
"bmp": "media",
|
||||
"cbr": "media",
|
||||
"cbz": "media",
|
||||
"eps": "media",
|
||||
"flac": "media",
|
||||
"flv": "media",
|
||||
"gif": "media",
|
||||
"ico": "media",
|
||||
"image": "media",
|
||||
"jpeg": "media",
|
||||
"jpg": "media",
|
||||
"m2v": "media",
|
||||
"m4a": "media",
|
||||
"mka": "media",
|
||||
"mkv": "media",
|
||||
"mov": "media",
|
||||
"mp3": "media",
|
||||
"mp4": "media",
|
||||
"mpeg": "media",
|
||||
"mpg": "media",
|
||||
"nef": "media",
|
||||
"ogg": "media",
|
||||
"ogm": "media",
|
||||
"ogv": "media",
|
||||
"opus": "media",
|
||||
"orf": "media",
|
||||
"pbm": "media",
|
||||
"pgm": "media",
|
||||
"png": "media",
|
||||
"pnm": "media",
|
||||
"ppm": "media",
|
||||
"pxm": "media",
|
||||
"stl": "media",
|
||||
"svg": "media",
|
||||
"tif": "media",
|
||||
"tiff": "media",
|
||||
"video": "media",
|
||||
"vob": "media",
|
||||
"wav": "media",
|
||||
"webm": "media",
|
||||
"webp": "media",
|
||||
"wma": "media",
|
||||
"wmv": "media",
|
||||
"xpm": "media",
|
||||
"php3": "php",
|
||||
"php4": "php",
|
||||
"php5": "php",
|
||||
"phpt": "php",
|
||||
"phtml": "php",
|
||||
"ipynb": "py",
|
||||
"pickle": "py",
|
||||
"pkl": "py",
|
||||
"pyx": "py",
|
||||
"bash": "sh",
|
||||
"csh": "sh",
|
||||
"fish": "sh",
|
||||
"ksh": "sh",
|
||||
"zsh": "sh",
|
||||
"plpgsql": "sql",
|
||||
"plsql": "sql",
|
||||
"psql": "sql",
|
||||
"tsql": "sql",
|
||||
"vimrc": "vim",
|
||||
}
|
||||
// SizeColor has the color mappings for ranges of file sizes
|
||||
SizeColor = map[string]string{
|
||||
"B": Fg(27),
|
||||
"K": Fg(33),
|
||||
"M": Fg(81),
|
||||
"G": Fg(123),
|
||||
"T": Fg(159),
|
||||
}
|
||||
// ConfigColor holds mappings for other various colors
|
||||
ConfigColor = map[string]map[string]string{
|
||||
"dir": map[string]string{
|
||||
"name": Bold + BgRGB(0, 0, 2) + FgGray(23),
|
||||
"ext": FgRGB(2, 2, 5),
|
||||
},
|
||||
".dir": map[string]string{
|
||||
"name": Bold + BgRGB(0, 0, 1) + FgGray(23),
|
||||
"ext": FgRGB(2, 2, 5),
|
||||
},
|
||||
"folderHeader": map[string]string{
|
||||
"arrow": FgRGB(3, 2, 0),
|
||||
"main": BgGray(2) + FgRGB(3, 2, 0),
|
||||
"slash": FgGray(5),
|
||||
"lastFolder": Bold + FgRGB(5, 5, 0),
|
||||
"error": BgRGB(5, 0, 0) + FgRGB(5, 5, 0),
|
||||
},
|
||||
"link": map[string]string{
|
||||
"name": Bold + FgRGB(0, 5, 0),
|
||||
"nameDir": Bold + BgRGB(0, 0, 2) + FgRGB(0, 5, 5),
|
||||
"arrow": FgRGB(1, 0, 1),
|
||||
"path": FgRGB(4, 0, 4),
|
||||
"broken": FgRGB(5, 0, 0),
|
||||
},
|
||||
"device": map[string]string{
|
||||
"name": Bold + BgGray(3) + Fg(220),
|
||||
},
|
||||
"socket": map[string]string{
|
||||
"name": Bold + Bg(53) + Fg(15),
|
||||
},
|
||||
"pipe": map[string]string{
|
||||
"name": Bold + Bg(94) + Fg(15),
|
||||
},
|
||||
"stats": map[string]string{
|
||||
"text": BgGray(2) + FgGray(15),
|
||||
"number": Fg(24),
|
||||
"ms": Fg(39),
|
||||
},
|
||||
}
|
||||
// PermsColor holds color mappings for users and groups
|
||||
PermsColor = map[string]map[string]string{
|
||||
"user": map[string]string{
|
||||
"root": FgRGB(5, 0, 2),
|
||||
"daemon": FgRGB(4, 2, 1),
|
||||
"_self": FgRGB(0, 4, 0),
|
||||
"_default": FgRGB(0, 3, 3),
|
||||
},
|
||||
"group": map[string]string{
|
||||
"wheel": FgRGB(3, 0, 0),
|
||||
"staff": FgRGB(0, 2, 0),
|
||||
"admin": FgRGB(2, 2, 0),
|
||||
"_default": FgRGB(2, 0, 2),
|
||||
},
|
||||
"other": map[string]string{
|
||||
"_default": BgGray(2) + FgGray(15),
|
||||
},
|
||||
}
|
||||
)
|
@ -1,402 +0,0 @@
|
||||
package lsgo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getIconForFile(name, ext string) string {
|
||||
// default icon for all files. try to find a better one though...
|
||||
icon := icons["file"]
|
||||
|
||||
// resolve aliased extensions
|
||||
extKey := strings.ToLower(ext)
|
||||
alias, hasAlias := aliases[extKey]
|
||||
if hasAlias {
|
||||
extKey = alias
|
||||
}
|
||||
|
||||
// see if we can find a better icon based on extension alone
|
||||
betterIcon, hasBetterIcon := icons[extKey]
|
||||
if hasBetterIcon {
|
||||
icon = betterIcon
|
||||
}
|
||||
|
||||
// now look for icons based on full names
|
||||
fullName := name
|
||||
if ext != "" {
|
||||
fullName += "." + ext
|
||||
}
|
||||
|
||||
fullName = strings.ToLower(fullName)
|
||||
fullAlias, hasFullAlias := aliases[fullName]
|
||||
if hasFullAlias {
|
||||
fullName = fullAlias
|
||||
}
|
||||
bestIcon, hasBestIcon := icons[fullName]
|
||||
if hasBestIcon {
|
||||
icon = bestIcon
|
||||
}
|
||||
return icon
|
||||
}
|
||||
|
||||
func getIconForFolder(name string) string {
|
||||
icon := folders["folder"]
|
||||
betterIcon, hasBetterIcon := folders[name]
|
||||
if hasBetterIcon {
|
||||
icon = betterIcon
|
||||
}
|
||||
return icon
|
||||
}
|
||||
|
||||
var icons = map[string]string{
|
||||
"ai": "\ue7b4",
|
||||
"android": "\ue70e",
|
||||
"apple": "\uf179",
|
||||
"as": "\ue60b",
|
||||
"asm": "\ufb19",
|
||||
"audio": "\uf1c7",
|
||||
"avro": "\ue60b",
|
||||
"bf": "\uf067",
|
||||
"binary": "\uf471",
|
||||
"c": "\ue61e",
|
||||
"cargo.lock": "\uf487",
|
||||
"cargo.toml": "\uf487",
|
||||
"cfg": "\uf423",
|
||||
"clj": "\ue768",
|
||||
"coffee": "\ue751",
|
||||
"conf": "\ue615",
|
||||
"cpp": "\ue61d",
|
||||
"cr": "\ue23e",
|
||||
"cs": "\uf81a",
|
||||
"cson": "\ue601",
|
||||
"css": "\ue749",
|
||||
"d": "\ue7af",
|
||||
"dart": "\ue798",
|
||||
"db": "\uf1c0",
|
||||
"deb": "\uf306",
|
||||
"diff": "\uf440",
|
||||
"doc": "\uf1c2",
|
||||
"dockerfile": "\ue7b0",
|
||||
"dpkg": "\uf17c",
|
||||
"ebook": "\uf02d",
|
||||
"elm": "\ue62c",
|
||||
"env": "\uf462",
|
||||
"erl": "\ue7b1",
|
||||
"ex": "\ue62d",
|
||||
"f": "\uf794",
|
||||
"file": "\uf15b",
|
||||
"font": "\uf031",
|
||||
"fs": "\ue7a7",
|
||||
"gb": "\ue272",
|
||||
"gform": "\uf298",
|
||||
"git": "\ue702",
|
||||
"go": "\ue724",
|
||||
"graphql": "\ue284",
|
||||
"groovy": "\ue775",
|
||||
"gruntfile.js": "\ue74c",
|
||||
"gulpfile.js": "\ue610",
|
||||
"gv": "\ue225",
|
||||
"h": "\uf0fd",
|
||||
"hs": "\ue777",
|
||||
"html": "\uf13b",
|
||||
"ics": "\uf073",
|
||||
"image": "\uf1c5",
|
||||
"iml": "\ue7b5",
|
||||
"ini": "\uf669",
|
||||
"ino": "\ue255",
|
||||
"iso": "\uf7c9",
|
||||
"java": "\ue738",
|
||||
"jenkinsfile": "\ue767",
|
||||
"jl": "\ue624",
|
||||
"js": "\ue781",
|
||||
"json": "\ue60b",
|
||||
"jsx": "\ue7ba",
|
||||
"key": "\uf43d",
|
||||
"less": "\ue758",
|
||||
"lock": "\uf720",
|
||||
"log": "\uf18d",
|
||||
"lua": "\ue620",
|
||||
"m": "\ufb27",
|
||||
"maintainers": "\uf0c0",
|
||||
"makefile": "\ue20f",
|
||||
"md": "\uf48a",
|
||||
"mjs": "\ue718",
|
||||
"ml": "\ufb26",
|
||||
"mustache": "\ue60f",
|
||||
"nc": "\uf7c0",
|
||||
"npmignore": "\ue71e",
|
||||
"passwd": "\uf023",
|
||||
"patch": "\uf440",
|
||||
"pdf": "\uf1c1",
|
||||
"php": "\ue608",
|
||||
"pl": "\ue7a1",
|
||||
"ppt": "\uf1c4",
|
||||
"psd": "\ue7b8",
|
||||
"py": "\ue606",
|
||||
"r": "\ufcd2",
|
||||
"rb": "\ue21e",
|
||||
"rdb": "\ue76d",
|
||||
"rpm": "\uf17c",
|
||||
"rs": "\ue7a8",
|
||||
"rss": "\uf09e",
|
||||
"rst": "\uf66a",
|
||||
"rubydoc": "\ue73b",
|
||||
"sass": "\ue603",
|
||||
"scala": "\ue737",
|
||||
"shell": "\uf489",
|
||||
"shp": "\ufa5f",
|
||||
"sol": "\ufcb9",
|
||||
"sql": "\ue706",
|
||||
"sqlite3": "\ue7c4",
|
||||
"styl": "\ue600",
|
||||
"swift": "\ue755",
|
||||
"tex": "\u222b",
|
||||
"tfrecord": "\ufb27",
|
||||
"toml": "\uf669",
|
||||
"ts": "\ufbe4",
|
||||
"twig": "\ue61c",
|
||||
"txt": "\uf15c",
|
||||
"vagrantfile": "\ue21e",
|
||||
"video": "\uf03d",
|
||||
"vim": "\ue62b",
|
||||
"vue": "\ufd42",
|
||||
"windows": "\uf17a",
|
||||
"xls": "\uf1c3",
|
||||
"xml": "\ue796",
|
||||
"yml": "\ue601",
|
||||
"zip": "\uf410",
|
||||
}
|
||||
|
||||
var aliases = map[string]string{
|
||||
"apk": "android",
|
||||
"gradle": "android",
|
||||
"ds_store": "apple",
|
||||
"localized": "apple",
|
||||
"s": "asm",
|
||||
"aac": "audio",
|
||||
"alac": "audio",
|
||||
"flac": "audio",
|
||||
"m4a": "audio",
|
||||
"mka": "audio",
|
||||
"mp3": "audio",
|
||||
"ogg": "audio",
|
||||
"opus": "audio",
|
||||
"wav": "audio",
|
||||
"wma": "audio",
|
||||
"b": "bf",
|
||||
"bson": "binary",
|
||||
"feather": "binary",
|
||||
"mat": "binary",
|
||||
"o": "binary",
|
||||
"pb": "binary",
|
||||
"pickle": "binary",
|
||||
"pkl": "binary",
|
||||
"conf": "cfg",
|
||||
"config": "cfg",
|
||||
"cljc": "clj",
|
||||
"cljs": "clj",
|
||||
"editorconfig": "conf",
|
||||
"rc": "conf",
|
||||
"c++": "cpp",
|
||||
"cc": "cpp",
|
||||
"cxx": "cpp",
|
||||
"scss": "css",
|
||||
"docx": "doc",
|
||||
"gdoc": "doc",
|
||||
"epub": "ebook",
|
||||
"ipynb": "ebook",
|
||||
"mobi": "ebook",
|
||||
"f03": "f",
|
||||
"f77": "f",
|
||||
"f90": "f",
|
||||
"f95": "f",
|
||||
"for": "f",
|
||||
"fpp": "f",
|
||||
"ftn": "f",
|
||||
"eot": "font",
|
||||
"otf": "font",
|
||||
"ttf": "font",
|
||||
"woff": "font",
|
||||
"woff2": "font",
|
||||
"fsi": "fs",
|
||||
"fsscript": "fs",
|
||||
"fsx": "fs",
|
||||
"dna": "gb",
|
||||
"gitattributes": "git",
|
||||
"gitconfig": "git",
|
||||
"gitignore": "git",
|
||||
"gitignore_global": "git",
|
||||
"gitmirrorall": "git",
|
||||
"gitmodules": "git",
|
||||
"gsh": "groovy",
|
||||
"gvy": "groovy",
|
||||
"gy": "groovy",
|
||||
"h++": "h",
|
||||
"hh": "h",
|
||||
"hpp": "h",
|
||||
"hxx": "h",
|
||||
"lhs": "hs",
|
||||
"htm": "html",
|
||||
"xhtml": "html",
|
||||
"bmp": "image",
|
||||
"cbr": "image",
|
||||
"cbz": "image",
|
||||
"dvi": "image",
|
||||
"eps": "image",
|
||||
"gif": "image",
|
||||
"ico": "image",
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"nef": "image",
|
||||
"orf": "image",
|
||||
"pbm": "image",
|
||||
"pgm": "image",
|
||||
"png": "image",
|
||||
"pnm": "image",
|
||||
"ppm": "image",
|
||||
"pxm": "image",
|
||||
"stl": "image",
|
||||
"svg": "image",
|
||||
"tif": "image",
|
||||
"tiff": "image",
|
||||
"webp": "image",
|
||||
"xpm": "image",
|
||||
"disk": "iso",
|
||||
"dmg": "iso",
|
||||
"smi": "iso",
|
||||
"img": "iso",
|
||||
"vhd": "iso",
|
||||
"vhdx": "iso",
|
||||
"vmdk": "iso",
|
||||
"jar": "java",
|
||||
"properties": "json",
|
||||
"webmanifest": "json",
|
||||
"tsx": "jsx",
|
||||
"cjsx": "jsx",
|
||||
"cer": "key",
|
||||
"crt": "key",
|
||||
"der": "key",
|
||||
"gpg": "key",
|
||||
"p7b": "key",
|
||||
"pem": "key",
|
||||
"pfx": "key",
|
||||
"pgp": "key",
|
||||
"license": "key",
|
||||
"codeowners": "maintainers",
|
||||
"credits": "maintainers",
|
||||
"cmake": "makefile",
|
||||
"markdown": "md",
|
||||
"mkd": "md",
|
||||
"rdoc": "md",
|
||||
"readme": "md",
|
||||
"mli": "ml",
|
||||
"sml": "ml",
|
||||
"netcdf": "nc",
|
||||
"php3": "php",
|
||||
"php4": "php",
|
||||
"php5": "php",
|
||||
"phpt": "php",
|
||||
"phtml": "php",
|
||||
"gslides": "ppt",
|
||||
"pptx": "ppt",
|
||||
"pxd": "py",
|
||||
"pyc": "py",
|
||||
"pyx": "py",
|
||||
"whl": "py",
|
||||
"rdata": "r",
|
||||
"rds": "r",
|
||||
"rmd": "r",
|
||||
"gemfile": "rb",
|
||||
"gemspec": "rb",
|
||||
"guardfile": "rb",
|
||||
"procfile": "rb",
|
||||
"rakefile": "rb",
|
||||
"rspec": "rb",
|
||||
"rspec_parallel": "rb",
|
||||
"rspec_status": "rb",
|
||||
"ru": "rb",
|
||||
"erb": "rubydoc",
|
||||
"slim": "rubydoc",
|
||||
"awk": "shell",
|
||||
"bash": "shell",
|
||||
"bash_history": "shell",
|
||||
"bash_profile": "shell",
|
||||
"bashrc": "shell",
|
||||
"csh": "shell",
|
||||
"fish": "shell",
|
||||
"ksh": "shell",
|
||||
"sh": "shell",
|
||||
"zsh": "shell",
|
||||
"zsh-theme": "shell",
|
||||
"zshrc": "shell",
|
||||
"plpgsql": "sql",
|
||||
"plsql": "sql",
|
||||
"psql": "sql",
|
||||
"tsql": "sql",
|
||||
"sl3": "sqlite3",
|
||||
"stylus": "styl",
|
||||
"cls": "tex",
|
||||
"avi": "video",
|
||||
"flv": "video",
|
||||
"m2v": "video",
|
||||
"mkv": "video",
|
||||
"mov": "video",
|
||||
"mp4": "video",
|
||||
"mpeg": "video",
|
||||
"mpg": "video",
|
||||
"ogm": "video",
|
||||
"ogv": "video",
|
||||
"vob": "video",
|
||||
"webm": "video",
|
||||
"vimrc": "vim",
|
||||
"bat": "windows",
|
||||
"cmd": "windows",
|
||||
"exe": "windows",
|
||||
"csv": "xls",
|
||||
"gsheet": "xls",
|
||||
"xlsx": "xls",
|
||||
"svelte": "xml",
|
||||
"plist": "xml",
|
||||
"xul": "xml",
|
||||
"yaml": "yml",
|
||||
"7z": "zip",
|
||||
"Z": "zip",
|
||||
"bz2": "zip",
|
||||
"gz": "zip",
|
||||
"lzma": "zip",
|
||||
"par": "zip",
|
||||
"rar": "zip",
|
||||
"tar": "zip",
|
||||
"tc": "zip",
|
||||
"tgz": "zip",
|
||||
"txz": "zip",
|
||||
"xz": "zip",
|
||||
"z": "zip",
|
||||
}
|
||||
|
||||
var folders = map[string]string{
|
||||
".atom": "\ue764",
|
||||
".aws": "\ue7ad",
|
||||
".docker": "\ue7b0",
|
||||
".gem": "\ue21e",
|
||||
".git": "\ue5fb",
|
||||
".git-credential-cache": "\ue5fb",
|
||||
".github": "\ue5fd",
|
||||
".npm": "\ue5fa",
|
||||
".nvm": "\ue718",
|
||||
".rvm": "\ue21e",
|
||||
".Trash": "\uf1f8",
|
||||
".vscode": "\ue70c",
|
||||
".vim": "\ue62b",
|
||||
"config": "\ue5fc",
|
||||
"folder": "\uf07c",
|
||||
"hidden": "\uf023",
|
||||
"node_modules": "\ue5fa",
|
||||
}
|
||||
|
||||
var otherIcons = map[string]string{
|
||||
"link": "\uf0c1",
|
||||
"linkDir": "\uf0c1",
|
||||
"brokenLink": "\uf127",
|
||||
"device": "\uf0a0",
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 135 KiB |
Binary file not shown.
Before Width: | Height: | Size: 135 KiB |
Binary file not shown.
Before Width: | Height: | Size: 100 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
Before Width: | Height: | Size: 57 KiB |
@ -1,46 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package lsgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/willf/pad"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getOwnerAndGroup(fileInfo *os.FileInfo) (string, string) {
|
||||
statT := (*fileInfo).Sys().(*syscall.Stat_t)
|
||||
uid := fmt.Sprint(statT.Uid)
|
||||
gid := fmt.Sprint(statT.Gid)
|
||||
owner, err := user.LookupId(uid)
|
||||
var ownerName string
|
||||
if err == nil {
|
||||
ownerName = owner.Username
|
||||
} else {
|
||||
ownerName = uid
|
||||
}
|
||||
|
||||
group, err := user.LookupGroupId(gid)
|
||||
var groupName string
|
||||
if err == nil {
|
||||
groupName = group.Name
|
||||
} else {
|
||||
groupName = gid
|
||||
}
|
||||
return ownerName, groupName
|
||||
}
|
||||
|
||||
func deviceNumbers(absPath string) string {
|
||||
stat := syscall.Stat_t{}
|
||||
err := syscall.Stat(absPath, &stat)
|
||||
check(err)
|
||||
major := strconv.FormatInt(int64(unix.Major(uint64(stat.Rdev))), 10)
|
||||
minor := strconv.FormatInt(int64(unix.Minor(uint64(stat.Rdev))), 10)
|
||||
return pad.Left(strings.Join([]string{major, minor}, ","), 7, " ") + " " + Reset
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package lsgo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/willf/pad"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
libadvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||
procGetFileSecurity = libadvapi32.NewProc("GetFileSecurityW")
|
||||
procGetSecurityDescriptorOwner = libadvapi32.NewProc("GetSecurityDescriptorOwner")
|
||||
)
|
||||
|
||||
func getOwnerAndGroup(fileInfo *os.FileInfo) (string, string) {
|
||||
path := (*fileInfo).Name()
|
||||
|
||||
var needed uint32
|
||||
procGetFileSecurity.Call(
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
|
||||
0x00000001, /* OWNER_SECURITY_INFORMATION */
|
||||
0,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&needed)))
|
||||
buf := make([]byte, needed)
|
||||
r1, _, err := procGetFileSecurity.Call(
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
|
||||
0x00000001, /* OWNER_SECURITY_INFORMATION */
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(needed),
|
||||
uintptr(unsafe.Pointer(&needed)))
|
||||
if r1 == 0 && err != nil {
|
||||
return "", ""
|
||||
}
|
||||
var ownerDefaulted uint32
|
||||
var sid *syscall.SID
|
||||
r1, _, err = procGetSecurityDescriptorOwner.Call(
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&sid)),
|
||||
uintptr(unsafe.Pointer(&ownerDefaulted)))
|
||||
if r1 == 0 && err != nil {
|
||||
return "", ""
|
||||
}
|
||||
uid, gid, _, err := sid.LookupAccount("")
|
||||
if r1 == 0 && err != nil {
|
||||
return "", ""
|
||||
}
|
||||
return uid, gid
|
||||
}
|
||||
|
||||
func deviceNumbers(absPath string) string {
|
||||
stat := syscall.Stat_t{}
|
||||
err := syscall.Stat(absPath, &stat)
|
||||
check(err)
|
||||
major := strconv.FormatInt(int64(windows.Major(uint64(stat.Rdev))), 10)
|
||||
minor := strconv.FormatInt(int64(windows.Minor(uint64(stat.Rdev))), 10)
|
||||
return pad.Left(strings.Join([]string{major, minor}, ","), 7, " ") + " " + Reset
|
||||
}
|
650
lsgo/lsgo.go
650
lsgo/lsgo.go
@ -1,650 +0,0 @@
|
||||
package lsgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/acarl005/textcol"
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
"github.com/willf/pad"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
// DisplayItem wraps the file stat info and string to be printed
|
||||
type DisplayItem struct {
|
||||
display string
|
||||
info os.FileInfo
|
||||
basename string
|
||||
ext string
|
||||
link *LinkInfo
|
||||
}
|
||||
|
||||
// LinkInfo wraps link stat info and whether the link points to valid file
|
||||
type LinkInfo struct {
|
||||
path string
|
||||
info os.FileInfo
|
||||
broken bool
|
||||
}
|
||||
|
||||
var (
|
||||
// True is a helper varable to help make pointers to `true`
|
||||
True = true
|
||||
sizeUnits = []string{"B", "K", "M", "G", "T"}
|
||||
dateFormat = "02.Jan'06" // uses the "reference time" https://golang.org/pkg/time/#Time.Format
|
||||
timeFormat = "15:04"
|
||||
start int64 // keep track of execution time
|
||||
stdout = colorable.NewColorableStdout() // write to this to allow ANSI color codes to be compatible on Windows
|
||||
)
|
||||
|
||||
// func LsGo(cmd *cobra.Command, args []string) {
|
||||
func LsGo() error {
|
||||
var err error
|
||||
|
||||
textcol.Output = stdout
|
||||
|
||||
start = time.Now().UnixNano()
|
||||
// auto-generate help text for the command with -h
|
||||
kingpin.CommandLine.HelpFlag.Short('h')
|
||||
|
||||
// parse the arguments and populate the struct
|
||||
kingpin.Parse()
|
||||
argsPostParse()
|
||||
|
||||
// separate the directories from the regular files
|
||||
dirs := []string{}
|
||||
files := []os.FileInfo{}
|
||||
for _, pathStr := range *args.paths {
|
||||
var fileStat os.FileInfo
|
||||
fileStat, err = os.Stat(pathStr)
|
||||
if err != nil && strings.Contains(err.Error(), "no such file or directory") {
|
||||
printErrorHeader(err, prettifyPath(pathStr))
|
||||
continue
|
||||
} else {
|
||||
check(err)
|
||||
}
|
||||
if fileStat.IsDir() {
|
||||
dirs = append(dirs, pathStr)
|
||||
} else {
|
||||
files = append(files, fileStat)
|
||||
}
|
||||
}
|
||||
|
||||
// list files first
|
||||
if len(files) > 0 {
|
||||
pwd := os.Getenv("PWD")
|
||||
listFiles(pwd, &files, true)
|
||||
}
|
||||
|
||||
// then list the contents of each directory
|
||||
for i, dir := range dirs {
|
||||
// print a blank line between directories, but not before the first one
|
||||
if i > 0 {
|
||||
fmt.Fprintln(stdout, "")
|
||||
}
|
||||
listDir(dir)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func listDir(pathStr string) {
|
||||
items, err := ioutil.ReadDir(pathStr)
|
||||
// if we couldn't read the folder, print a "header" with error message and use error-looking colors
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "permission denied") {
|
||||
printErrorHeader(err, prettifyPath(pathStr))
|
||||
return
|
||||
}
|
||||
check(err)
|
||||
}
|
||||
|
||||
// filter by the regexp if one was passed
|
||||
if len(*args.find) > 0 {
|
||||
filteredItems := []os.FileInfo{}
|
||||
for _, fileInfo := range items {
|
||||
re, err := regexp.Compile(*args.find)
|
||||
check(err)
|
||||
if re.MatchString(fileInfo.Name()) {
|
||||
filteredItems = append(filteredItems, fileInfo)
|
||||
}
|
||||
}
|
||||
items = filteredItems
|
||||
}
|
||||
|
||||
if !(len(*args.find) > 0 && len(items) == 0) &&
|
||||
!(len(*args.paths) == 1 && (*args.paths)[0] == "." && !*args.recurse) {
|
||||
printFolderHeader(pathStr)
|
||||
}
|
||||
|
||||
if len(items) > 0 {
|
||||
listFiles(pathStr, &items, false)
|
||||
}
|
||||
|
||||
if *args.recurse {
|
||||
for _, item := range items {
|
||||
if item.IsDir() && (item.Name()[0] != '.' || *args.all) {
|
||||
fmt.Fprintln(stdout, "") // put a blank line between directories
|
||||
listDir(path.Join(pathStr, item.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listFiles(parentDir string, items *[]os.FileInfo, forceDotfiles bool) {
|
||||
absPath, err := filepath.Abs(parentDir)
|
||||
check(err)
|
||||
|
||||
// collect all the contents here
|
||||
files := []*DisplayItem{}
|
||||
dirs := []*DisplayItem{}
|
||||
|
||||
// to help with formatting, we need to know the length of the longest name to add appropriate padding
|
||||
longestOwnerName := 0
|
||||
longestGroupName := 0
|
||||
if *args.owner {
|
||||
for _, fileInfo := range *items {
|
||||
owner, group := getOwnerAndGroup(&fileInfo)
|
||||
longestOwnerName = max(longestOwnerName, len(owner))
|
||||
longestGroupName = max(longestGroupName, len(group))
|
||||
}
|
||||
}
|
||||
|
||||
for _, fileInfo := range *items {
|
||||
// if this is a dotfile (hidden file)
|
||||
if fileInfo.Name()[0] == '.' {
|
||||
// we can skip everything with this file if we aren't using the `all` option
|
||||
if !*args.all && !forceDotfiles {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
basename, ext := splitExt(fileInfo.Name())
|
||||
|
||||
displayItem := DisplayItem{
|
||||
info: fileInfo,
|
||||
ext: ext,
|
||||
basename: basename,
|
||||
}
|
||||
|
||||
// read some info about linked file if this item is a symlink
|
||||
if fileInfo.Mode()&os.ModeSymlink != 0 {
|
||||
getLinkInfo(&displayItem, absPath)
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0 &&
|
||||
displayItem.link.info != nil &&
|
||||
displayItem.link.info.IsDir()) {
|
||||
if *args.files {
|
||||
continue
|
||||
} else {
|
||||
dirs = append(dirs, &displayItem)
|
||||
}
|
||||
} else {
|
||||
if *args.dirs {
|
||||
continue
|
||||
} else {
|
||||
files = append(files, &displayItem)
|
||||
}
|
||||
}
|
||||
|
||||
owner, group := getOwnerAndGroup(&fileInfo)
|
||||
ownerColor, groupColor := getOwnerAndGroupColors(owner, group)
|
||||
|
||||
if *args.perms {
|
||||
displayItem.display += permString(fileInfo, ownerColor, groupColor)
|
||||
}
|
||||
|
||||
if *args.owner {
|
||||
paddedOwner := pad.Right(owner, longestOwnerName, " ")
|
||||
ownerInfo := []string{Reset + ownerColor + paddedOwner}
|
||||
if !*args.nogroup {
|
||||
paddedGroup := pad.Right(group, longestGroupName, " ")
|
||||
ownerInfo = append(ownerInfo, groupColor+paddedGroup)
|
||||
}
|
||||
ownerInfo = append(ownerInfo, Reset)
|
||||
displayItem.display += strings.Join(ownerInfo, " ")
|
||||
}
|
||||
|
||||
if *args.bytes {
|
||||
if fileInfo.Mode()&os.ModeDevice != 0 {
|
||||
displayItem.display += deviceNumbers(path.Join(absPath, fileInfo.Name()))
|
||||
} else {
|
||||
displayItem.display += sizeString(fileInfo.Size())
|
||||
}
|
||||
}
|
||||
|
||||
if *args.mdate {
|
||||
displayItem.display += timeString(fileInfo.ModTime())
|
||||
}
|
||||
|
||||
displayItem.display += nameString(&displayItem)
|
||||
|
||||
if *args.links && fileInfo.Mode()&os.ModeSymlink != 0 {
|
||||
displayItem.display += linkString(&displayItem, absPath)
|
||||
}
|
||||
}
|
||||
|
||||
if *args.sortTime {
|
||||
sort.Sort(ByTime(dirs))
|
||||
sort.Sort(ByTime(files))
|
||||
if *args.backwards {
|
||||
reverse(dirs)
|
||||
reverse(files)
|
||||
}
|
||||
}
|
||||
|
||||
if *args.sortSize {
|
||||
sort.Sort(BySize(files))
|
||||
if *args.backwards {
|
||||
reverse(files)
|
||||
}
|
||||
}
|
||||
|
||||
if *args.sortKind {
|
||||
sort.Sort(ByKind(files))
|
||||
if *args.backwards {
|
||||
reverse(files)
|
||||
}
|
||||
}
|
||||
|
||||
// combine the items together again after sorting
|
||||
allItems := append(dirs, files...)
|
||||
|
||||
// if using "long" display, just print one item per line
|
||||
if *args.bytes || *args.mdate || *args.owner || *args.perms || *args.long {
|
||||
for _, item := range allItems {
|
||||
fmt.Fprintln(stdout, item.display)
|
||||
}
|
||||
} else {
|
||||
// but if not, try to format in columns, link `ls` would
|
||||
strs := []string{}
|
||||
for _, item := range allItems {
|
||||
strs = append(strs, item.display)
|
||||
}
|
||||
textcol.PrintColumns(&strs, 2)
|
||||
}
|
||||
|
||||
if *args.stats {
|
||||
printStats(len(files), len(dirs))
|
||||
}
|
||||
}
|
||||
|
||||
func getLinkInfo(item *DisplayItem, absPath string) {
|
||||
fullPath := path.Join(absPath, item.info.Name())
|
||||
linkPath, err1 := os.Readlink(fullPath)
|
||||
check(err1)
|
||||
|
||||
linkFullPath := linkPath
|
||||
if linkPath[0] != '/' {
|
||||
linkFullPath = path.Join(absPath, linkPath)
|
||||
}
|
||||
|
||||
linkInfo, err2 := os.Stat(linkFullPath)
|
||||
if *args.linkRel {
|
||||
linkRel, _ := filepath.Rel(absPath, linkPath)
|
||||
if linkRel != "" && len(linkRel) <= len(linkPath) {
|
||||
// i prefer the look of these relative paths prepended with ./
|
||||
if linkRel[0] != '.' {
|
||||
linkPath = "./" + linkRel
|
||||
} else {
|
||||
linkPath = linkRel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
link := LinkInfo{
|
||||
path: linkPath,
|
||||
}
|
||||
item.link = &link
|
||||
if linkInfo != nil {
|
||||
link.info = linkInfo
|
||||
} else if strings.Contains(err2.Error(), "no such file or directory") {
|
||||
link.broken = true
|
||||
} else if !strings.Contains(err2.Error(), "permission denied") {
|
||||
check(err2)
|
||||
}
|
||||
}
|
||||
|
||||
func nameString(item *DisplayItem) string {
|
||||
mode := item.info.Mode()
|
||||
name := item.info.Name()
|
||||
if mode&os.ModeDir != 0 {
|
||||
return dirString(item)
|
||||
} else if mode&os.ModeSymlink != 0 {
|
||||
if !item.link.broken && item.link.info.IsDir() {
|
||||
color := ConfigColor["link"]["nameDir"]
|
||||
if *args.nerdfont {
|
||||
var linkIcon string
|
||||
if item.link.broken {
|
||||
linkIcon = otherIcons["brokenLink"]
|
||||
} else {
|
||||
linkIcon = otherIcons["linkDir"]
|
||||
}
|
||||
return color + linkIcon + " " + name + " " + Reset
|
||||
} else if *args.icons {
|
||||
return color + "🔗 " + name + " " + Reset
|
||||
} else {
|
||||
return color + " " + name + " " + Reset
|
||||
}
|
||||
} else {
|
||||
color := ConfigColor["link"]["name"]
|
||||
if *args.nerdfont {
|
||||
var linkIcon string
|
||||
if item.link.broken {
|
||||
linkIcon = otherIcons["brokenLink"]
|
||||
} else {
|
||||
linkIcon = otherIcons["link"]
|
||||
}
|
||||
return color + linkIcon + " " + name + " " + Reset
|
||||
} else if *args.icons {
|
||||
return color + "🔗 " + name + " " + Reset
|
||||
} else {
|
||||
return color + name + " " + Reset
|
||||
}
|
||||
}
|
||||
} else if mode&os.ModeDevice != 0 {
|
||||
color := ConfigColor["device"]["name"]
|
||||
if *args.nerdfont {
|
||||
return color + otherIcons["device"] + " " + name + " " + Reset
|
||||
} else if *args.icons {
|
||||
return color + "💽 " + name + " " + Reset
|
||||
} else {
|
||||
return color + " " + name + " " + Reset
|
||||
}
|
||||
} else if mode&os.ModeNamedPipe != 0 {
|
||||
return ConfigColor["pipe"]["name"] + " " + name + " " + Reset
|
||||
} else if mode&os.ModeSocket != 0 {
|
||||
return ConfigColor["socket"]["name"] + " " + name + " " + Reset
|
||||
}
|
||||
return fileString(item)
|
||||
}
|
||||
|
||||
func linkString(item *DisplayItem, absPath string) string {
|
||||
colors := ConfigColor["link"]
|
||||
displayStrings := []string{colors["arrow"] + "►"}
|
||||
if item.link.info == nil && item.link.broken {
|
||||
displayStrings = append(displayStrings, colors["broken"]+item.link.path+Reset)
|
||||
} else if item.link.info != nil {
|
||||
linkname, linkext := splitExt(item.link.path)
|
||||
displayItem := DisplayItem{
|
||||
info: item.link.info,
|
||||
basename: linkname,
|
||||
ext: linkext,
|
||||
}
|
||||
displayStrings = append(displayStrings, nameString(&displayItem))
|
||||
} else {
|
||||
displayStrings = append(displayStrings, item.link.path)
|
||||
}
|
||||
return strings.Join(displayStrings, " ")
|
||||
}
|
||||
|
||||
func fileString(item *DisplayItem) string {
|
||||
key := strings.ToLower(item.ext)
|
||||
// figure out which color to choose
|
||||
colors := FileColor["_default"]
|
||||
alias, hasAlias := FileAliases[key]
|
||||
if hasAlias {
|
||||
key = alias
|
||||
}
|
||||
betterColor, hasBetterColor := FileColor[key]
|
||||
if hasBetterColor {
|
||||
colors = betterColor
|
||||
}
|
||||
|
||||
ext := item.ext
|
||||
if ext != "" {
|
||||
ext = "." + ext
|
||||
}
|
||||
|
||||
// in some cases files have icons if front
|
||||
// if nerd font enabled, then it'll be a file-specific icon, or if its an executable script, a little shell icon
|
||||
// if the regular --icons flag is used instead, then it will show a ">_" only if the file is executable
|
||||
icon := ""
|
||||
executable := isExecutableScript(item)
|
||||
if *args.nerdfont {
|
||||
if executable {
|
||||
icon = colors[0] + getIconForFile("", "shell") + " "
|
||||
} else {
|
||||
icon = colors[0] + getIconForFile(item.basename, item.ext) + " "
|
||||
}
|
||||
} else if *args.icons {
|
||||
if executable {
|
||||
icon = BgGray(1) + FgRGB(0, 5, 0) + ">_" + Reset + " "
|
||||
}
|
||||
}
|
||||
displayStrings := []string{icon, colors[0], item.basename, colors[1], ext, Reset}
|
||||
return strings.Join(displayStrings, "")
|
||||
}
|
||||
|
||||
// check for executable permissions
|
||||
func isExecutableScript(item *DisplayItem) bool {
|
||||
if item.info.Mode()&0111 != 0 && item.info.Mode().IsRegular() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dirString(item *DisplayItem) string {
|
||||
colors := ConfigColor["dir"]
|
||||
if item.basename == "" {
|
||||
colors = ConfigColor[".dir"]
|
||||
}
|
||||
displayStrings := []string{colors["name"]}
|
||||
icon := ""
|
||||
if *args.icons {
|
||||
displayStrings = append(displayStrings, "📂 ")
|
||||
} else if *args.nerdfont {
|
||||
icon = getIconForFolder(item.info.Name()) + " "
|
||||
displayStrings = append(displayStrings, icon)
|
||||
} else {
|
||||
displayStrings = append(displayStrings, " ")
|
||||
}
|
||||
ext := item.ext
|
||||
if ext != "" {
|
||||
ext = "." + ext
|
||||
}
|
||||
displayStrings = append(displayStrings, item.basename, colors["ext"], ext, " ", Reset)
|
||||
return strings.Join(displayStrings, "")
|
||||
}
|
||||
|
||||
func rwxString(mode os.FileMode, i uint, color string) string {
|
||||
bits := mode >> (i * 3)
|
||||
coloredStrings := []string{color}
|
||||
if bits&4 != 0 {
|
||||
coloredStrings = append(coloredStrings, "r")
|
||||
} else {
|
||||
coloredStrings = append(coloredStrings, "-")
|
||||
}
|
||||
if bits&2 != 0 {
|
||||
coloredStrings = append(coloredStrings, "w")
|
||||
} else {
|
||||
coloredStrings = append(coloredStrings, "-")
|
||||
}
|
||||
if i == 0 && mode&os.ModeSticky != 0 {
|
||||
if bits&1 != 0 {
|
||||
coloredStrings = append(coloredStrings, "t")
|
||||
} else {
|
||||
coloredStrings = append(coloredStrings, "T")
|
||||
}
|
||||
} else {
|
||||
if bits&1 != 0 {
|
||||
coloredStrings = append(coloredStrings, "x")
|
||||
} else {
|
||||
coloredStrings = append(coloredStrings, "-")
|
||||
}
|
||||
}
|
||||
return strings.Join(coloredStrings, "")
|
||||
}
|
||||
|
||||
// generates the permissions string, ya know like "drwxr-xr-x" and stuff like that
|
||||
func permString(info os.FileInfo, ownerColor string, groupColor string) string {
|
||||
defaultColor := PermsColor["other"]["_default"]
|
||||
|
||||
// info.Mode().String() does not produce the same output as `ls`, so we must build that string manually
|
||||
mode := info.Mode()
|
||||
// this "type" is not the file extension, but type as far as the OS is concerned
|
||||
filetype := "-"
|
||||
if mode&os.ModeDir != 0 {
|
||||
filetype = "d"
|
||||
} else if mode&os.ModeSymlink != 0 {
|
||||
filetype = "l"
|
||||
} else if mode&os.ModeDevice != 0 {
|
||||
if mode&os.ModeCharDevice == 0 {
|
||||
filetype = "b" // block device
|
||||
} else {
|
||||
filetype = "c" // character device
|
||||
}
|
||||
} else if mode&os.ModeNamedPipe != 0 {
|
||||
filetype = "p"
|
||||
} else if mode&os.ModeSocket != 0 {
|
||||
filetype = "s"
|
||||
}
|
||||
coloredStrings := []string{defaultColor, filetype}
|
||||
coloredStrings = append(coloredStrings, rwxString(mode, 2, ownerColor))
|
||||
coloredStrings = append(coloredStrings, rwxString(mode, 1, groupColor))
|
||||
coloredStrings = append(coloredStrings, rwxString(mode, 0, defaultColor), Reset, Reset)
|
||||
return strings.Join(coloredStrings, " ")
|
||||
}
|
||||
|
||||
func sizeString(size int64) string {
|
||||
sizeFloat := float64(size)
|
||||
for i, unit := range sizeUnits {
|
||||
base := math.Pow(1024, float64(i))
|
||||
if sizeFloat < base*1024 {
|
||||
var sizeStr string
|
||||
if i == 0 {
|
||||
sizeStr = strconv.FormatInt(size, 10)
|
||||
} else {
|
||||
sizeStr = fmt.Sprintf("%.2f", sizeFloat/base)
|
||||
}
|
||||
return SizeColor[unit] + pad.Left(sizeStr, 6, " ") + unit + " " + Reset
|
||||
}
|
||||
}
|
||||
return strconv.Itoa(int(size))
|
||||
}
|
||||
|
||||
func timeString(modtime time.Time) string {
|
||||
dateStr := modtime.Format(dateFormat)
|
||||
timeStr := modtime.Format(timeFormat)
|
||||
hour, err := strconv.Atoi(timeStr[0:2])
|
||||
check(err)
|
||||
// generate a color based on the hour of the day. darkest around midnight and whitest around noon
|
||||
timeColor := 14 - int(8*math.Cos(math.Pi*float64(hour)/12))
|
||||
colored := []string{FgGray(22) + dateStr, FgGray(timeColor) + timeStr, Reset}
|
||||
return strings.Join(colored, " ")
|
||||
}
|
||||
|
||||
// when we list out any subdirectories, print those paths conspicuously above the contents
|
||||
// this helps with visual separation
|
||||
func printFolderHeader(pathStr string) {
|
||||
colors := ConfigColor["folderHeader"]
|
||||
headerString := colors["arrow"] + "►" + colors["main"] + " "
|
||||
prettyPath := prettifyPath(pathStr)
|
||||
|
||||
if prettyPath == "/" {
|
||||
headerString += "/"
|
||||
} else {
|
||||
folders := strings.Split(prettyPath, "/")
|
||||
coloredFolders := make([]string, 0, len(folders))
|
||||
for i, folder := range folders {
|
||||
if i == len(folders)-1 { // different color for the last folder in the path
|
||||
coloredFolders = append(coloredFolders, colors["lastFolder"]+folder)
|
||||
} else {
|
||||
coloredFolders = append(coloredFolders, colors["main"]+folder)
|
||||
}
|
||||
}
|
||||
headerString += strings.Join(coloredFolders, colors["slash"]+"/")
|
||||
}
|
||||
|
||||
fmt.Fprintln(stdout, headerString+" "+Reset)
|
||||
}
|
||||
|
||||
func printErrorHeader(err error, pathStr string) {
|
||||
fmt.Fprintln(stdout, ConfigColor["folderHeader"]["error"]+"► "+pathStr+Reset)
|
||||
fmt.Fprintln(stdout, err.Error())
|
||||
}
|
||||
|
||||
func prettifyPath(pathStr string) string {
|
||||
prettyPath, err := filepath.Abs(pathStr)
|
||||
check(err)
|
||||
pwd := os.Getenv("PWD")
|
||||
home := os.Getenv("HOME")
|
||||
|
||||
if strings.HasPrefix(prettyPath, pwd) {
|
||||
prettyPath = "." + prettyPath[len(pwd):]
|
||||
} else if strings.HasPrefix(prettyPath, home) {
|
||||
prettyPath = "~" + prettyPath[len(home):]
|
||||
}
|
||||
return prettyPath
|
||||
}
|
||||
|
||||
func getOwnerAndGroupColors(owner string, group string) (string, string) {
|
||||
if owner == os.Getenv("USER") {
|
||||
owner = "_self"
|
||||
}
|
||||
ownerColor := PermsColor["user"][owner]
|
||||
if ownerColor == "" {
|
||||
ownerColor = PermsColor["user"]["_default"]
|
||||
}
|
||||
groupColor := PermsColor["group"][group]
|
||||
if groupColor == "" {
|
||||
groupColor = PermsColor["group"]["_default"]
|
||||
}
|
||||
return ownerColor, groupColor
|
||||
}
|
||||
|
||||
func printStats(numFiles, numDirs int) {
|
||||
colors := ConfigColor["stats"]
|
||||
end := time.Now().UnixNano()
|
||||
microSeconds := (end - start) / int64(time.Microsecond)
|
||||
milliSeconds := float64(microSeconds) / 1000
|
||||
statStrings := []string{
|
||||
colors["text"],
|
||||
colors["number"] + strconv.Itoa(numDirs),
|
||||
colors["text"] + "dirs",
|
||||
colors["number"] + strconv.Itoa(numFiles),
|
||||
colors["text"] + "files",
|
||||
colors["ms"] + fmt.Sprintf("%.2f", milliSeconds),
|
||||
colors["text"] + "ms",
|
||||
Reset,
|
||||
}
|
||||
fmt.Fprintln(stdout, strings.Join(statStrings, " "))
|
||||
}
|
||||
|
||||
func splitExt(filename string) (basepath, ext string) {
|
||||
basename := filepath.Base(filename)
|
||||
if basename[0] == '.' {
|
||||
ext = basename[1:]
|
||||
basepath = filename[:len(filename)-len(ext)-1]
|
||||
} else {
|
||||
ext = filepath.Ext(filename)
|
||||
basepath = filename[:len(filename)-len(ext)]
|
||||
if ext != "" {
|
||||
ext = ext[1:]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Go doesn't provide a `Max` function for ints like it does for floats (wtf?)
|
||||
func max(a int, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
67
lsgo/sort.go
67
lsgo/sort.go
@ -1,67 +0,0 @@
|
||||
package lsgo
|
||||
|
||||
// BySize tells `sort.Sort` how to sort by file size
|
||||
type BySize []*DisplayItem
|
||||
|
||||
func (s BySize) Less(i, j int) bool {
|
||||
return s[i].info.Size() < s[j].info.Size()
|
||||
}
|
||||
func (s BySize) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s BySize) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// ByTime tells `sort.Sort` how to sort by last modified time
|
||||
type ByTime []*DisplayItem
|
||||
|
||||
func (s ByTime) Less(i, j int) bool {
|
||||
return s[i].info.ModTime().Unix() < s[j].info.ModTime().Unix()
|
||||
}
|
||||
func (s ByTime) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s ByTime) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// ByKind tells `sort.Sort` how to sort by file extension
|
||||
type ByKind []*DisplayItem
|
||||
|
||||
func (s ByKind) Less(i, j int) bool {
|
||||
var kindi, kindj string
|
||||
if s[i].basename == "" {
|
||||
kindi = "."
|
||||
} else if s[i].ext == "" {
|
||||
kindi = "0"
|
||||
} else {
|
||||
kindi = s[i].ext
|
||||
}
|
||||
if s[j].basename == "" {
|
||||
kindj = "."
|
||||
} else if s[j].ext == "" {
|
||||
kindj = "0"
|
||||
} else {
|
||||
kindj = s[j].ext
|
||||
}
|
||||
if kindi == kindj {
|
||||
if kindi == "." {
|
||||
return s[i].ext < s[j].ext
|
||||
}
|
||||
return s[i].basename < s[j].basename
|
||||
}
|
||||
return kindi < kindj
|
||||
}
|
||||
func (s ByKind) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s ByKind) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func reverse(s []*DisplayItem) {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
}
|
@ -1,692 +0,0 @@
|
||||
package mmGit
|
||||
|
||||
import (
|
||||
"GoSungrow/Only"
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
func (z *Git) Connect() error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
var ok bool
|
||||
ok, z.Error = IsDirExists(z.RepoDir)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
if ok {
|
||||
z.Error = z.Open()
|
||||
break
|
||||
}
|
||||
|
||||
z.Error = z.Clone()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) Open() error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
z.repo, z.Error = git.PlainOpen(z.RepoDir)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
z.worktree, z.Error = z.repo.Worktree()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
var ref *plumbing.Reference
|
||||
ref, z.Error = z.repo.Head()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if ref.Hash().IsZero() {
|
||||
z.Error = errors.New("invalid HEAD reference")
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("Git opened\n\trepo: %s\n\tdir: %s\n", z.RepoUrl, z.RepoDir)
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) Clone() error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
var ok bool
|
||||
ok, z.Error = IsDirExists(z.RepoDir)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
if ok {
|
||||
z.Error = errors.New(fmt.Sprintf("Cannot clone - directory '%s' already exists.", z.RepoDir))
|
||||
break
|
||||
}
|
||||
|
||||
// CONTEXT: Provide Ctrl-C capability as well as operation timeouts.
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go func() {
|
||||
<-stop
|
||||
fmt.Println("\nCanceling operation...")
|
||||
cancel()
|
||||
}()
|
||||
// CONTEXT: Provide Ctrl-C capability as well as operation timeouts.
|
||||
|
||||
pk := z.GetSshAuth()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
options := &git.CloneOptions {
|
||||
URL: z.RepoUrl,
|
||||
Auth: pk,
|
||||
RemoteName: "",
|
||||
ReferenceName: "",
|
||||
SingleBranch: false,
|
||||
NoCheckout: false,
|
||||
Depth: 0,
|
||||
RecurseSubmodules: 0,
|
||||
Progress: os.Stdout,
|
||||
Tags: 0,
|
||||
InsecureSkipTLS: false,
|
||||
CABundle: nil,
|
||||
}
|
||||
z.repo, z.Error = git.PlainCloneContext(ctx, z.RepoDir, false, options)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
z.worktree, z.Error = z.repo.Worktree()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
var ref *plumbing.Reference
|
||||
ref, z.Error = z.repo.Head()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if ref.Hash().IsZero() {
|
||||
z.Error = errors.New("invalid HEAD reference")
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("Git cloned\n\trepo: %s\n\tdir: %s\n", z.RepoUrl, z.RepoDir)
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
// GetSshAuth: Gitlab keys need to be created with at least 3072 bits.
|
||||
// ssh-keygen -t rsa -b 3072 -C 'root@everywhere' -f gitlab_rsa -N ''
|
||||
func (z *Git) GetSshAuth() *ssh.PublicKeys {
|
||||
var pk *ssh.PublicKeys
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
var u *user.User
|
||||
u, z.Error = user.Current()
|
||||
|
||||
paths := []string {
|
||||
z.KeyFile,
|
||||
filepath.Join(u.HomeDir, ".ssh", "id_rsa"),
|
||||
}
|
||||
|
||||
var path string
|
||||
for _, path = range paths {
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
z.Error = checkKeyFile(path)
|
||||
if z.Error != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// Try without password first.
|
||||
var password string
|
||||
pk, z.Error = ssh.NewPublicKeysFromFile("git", path, password)
|
||||
if z.Error == nil {
|
||||
fmt.Printf("AUTH: %v\n", pk)
|
||||
break
|
||||
}
|
||||
|
||||
// Then with password.
|
||||
password = getPassword("ApiPassword: ")
|
||||
pk, z.Error = ssh.NewPublicKeysFromFile("git", path, password)
|
||||
if z.Error == nil {
|
||||
fmt.Printf("AUTH: %v\n", pk)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return pk
|
||||
}
|
||||
|
||||
func checkKeyFile(path string) error {
|
||||
var err error
|
||||
|
||||
for range Only.Once {
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var fi os.FileInfo
|
||||
fi, err = os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
err = errors.New("SSH publickey file is a directory")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// techEcho() - turns terminal echo on or off.
|
||||
func termEcho(on bool) {
|
||||
// Common settings and variables for both stty calls.
|
||||
attrs := syscall.ProcAttr{
|
||||
Dir: "",
|
||||
Env: []string{},
|
||||
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
|
||||
Sys: nil}
|
||||
var ws syscall.WaitStatus
|
||||
cmd := "echo"
|
||||
if on == false {
|
||||
cmd = "-echo"
|
||||
}
|
||||
|
||||
// Enable/disable echoing.
|
||||
pid, err := syscall.ForkExec(
|
||||
"/bin/stty",
|
||||
[]string{"stty", cmd},
|
||||
&attrs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Wait for the stty process to complete.
|
||||
_, err = syscall.Wait4(pid, &ws, 0, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// getPassword - Prompt for password.
|
||||
func getPassword(prompt string) string {
|
||||
fmt.Print(prompt)
|
||||
|
||||
// Catch a ^C interrupt.
|
||||
// Make sure that we reset term echo before exiting.
|
||||
signalChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChannel, os.Interrupt)
|
||||
go func() {
|
||||
for _ = range signalChannel {
|
||||
fmt.Println("\n^C interrupt.")
|
||||
termEcho(true)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// Echo is disabled, now grab the data.
|
||||
termEcho(false) // disable terminal echo
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
text, err := reader.ReadString('\n')
|
||||
termEcho(true) // always re-enable terminal echo
|
||||
fmt.Println("")
|
||||
if err != nil {
|
||||
// The terminal has been reset, go ahead and exit.
|
||||
fmt.Println("ERROR:", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
return strings.TrimSpace(text)
|
||||
}
|
||||
|
||||
//func (z *Git) setContext() error {
|
||||
//
|
||||
// for range Only.Once {
|
||||
// if z.IsNotOk() {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// stop := make(chan os.Signal, 1)
|
||||
// signal.Notify(stop, os.Interrupt)
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel() // cancel when we are finished consuming integers
|
||||
//
|
||||
// go func() {
|
||||
// <-stop
|
||||
// Warning("\nSignal detected, canceling operation...")
|
||||
// cancel()
|
||||
// }()
|
||||
//
|
||||
// var auth ssh.AuthMethod
|
||||
// auth, z.Error = ssh.DefaultAuthBuilder("admin-mickh")
|
||||
// if z.Error != nil {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// z.repo, z.Error = git.PlainClone(z.RepoDir, false, &git.CloneOptions {
|
||||
// URL: z.RepoUrl,
|
||||
// Auth: auth,
|
||||
// })
|
||||
// if z.Error != nil {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// var ref *plumbing.Reference
|
||||
// ref, z.Error = z.repo.Head()
|
||||
// if z.Error != nil {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// var commit *object.Commit
|
||||
// commit, z.Error = z.repo.CommitObject(ref.Hash())
|
||||
// if z.Error != nil {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// fmt.Println(commit)
|
||||
// }
|
||||
//
|
||||
// return z.Error
|
||||
//}
|
||||
|
||||
func (z *Git) SaveFile(fn string, data []byte) error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
//z.worktree, z.Error = z.repo.Worktree()
|
||||
//if z.Error != nil {
|
||||
// break
|
||||
//}
|
||||
//
|
||||
//var fh fs.File
|
||||
//var fi os.FileInfo
|
||||
//fi, z.Error = z.fs.Stat(fn)
|
||||
//if errors.Is(z.Error, os.ErrNotExist) {
|
||||
// // Create new file
|
||||
// fh, z.Error = z.fs.Create(fn)
|
||||
//} else {
|
||||
// // Open file
|
||||
// fh, z.Error = z.fs.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0664)
|
||||
//}
|
||||
|
||||
fh, err := os.OpenFile(filepath.Join(z.RepoDir, fn), os.O_RDWR|os.O_CREATE, 0664)
|
||||
if err != nil {
|
||||
z.Error = err
|
||||
break
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
fmt.Printf("Saved file '%s'\n", fn)
|
||||
_, z.Error = fh.Write(data)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Run git status before adding the file to the worktree
|
||||
//fmt.Println(z.worktree.Status())
|
||||
|
||||
// git add $filePath
|
||||
_, z.Error = z.worktree.Add(fn)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
//// Run git status after the file has been added adding to the worktree
|
||||
//fmt.Println(z.worktree.Status())
|
||||
//
|
||||
//// git commit -m $message
|
||||
//msg := fmt.Sprintf("Updated file '%s'", fn)
|
||||
//_, z.Error = z.worktree.Commit(msg, &git.CommitOptions{})
|
||||
//if z.Error != nil {
|
||||
// break
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) Status() error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
var status git.Status
|
||||
status, z.Error = z.worktree.Status()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if status.String() != "" {
|
||||
fmt.Printf("Status of Git\n\trepo: %s\n\tdir: %s\n%s\n",
|
||||
z.RepoUrl,
|
||||
z.RepoDir,
|
||||
status.String(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) Add(path string) error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
|
||||
fmt.Printf("Adding to Git\n\trepo: %s\n\tdir: %s\n", z.RepoUrl, z.RepoDir)
|
||||
|
||||
_, z.Error = z.worktree.Add(path)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
z.Error = z.Status()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//PrintError(z.Error)
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) Commit(msg string, args ...interface{}) error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
z.Error = z.Add(".")
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
cn := &object.Signature {
|
||||
Name: os.Getenv("USERNAME"),
|
||||
Email: "",
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
fmt.Printf("Committing Git\n\trepo: %s\n\tdir: %s\n", z.RepoUrl, z.RepoDir)
|
||||
// Similar to git commit -m $message
|
||||
var ph plumbing.Hash
|
||||
msg := fmt.Sprintf(msg, args...)
|
||||
ph, z.Error = z.worktree.Commit(msg, &git.CommitOptions{
|
||||
All: false,
|
||||
Author: cn,
|
||||
Committer: cn,
|
||||
Parents: nil,
|
||||
SignKey: nil,
|
||||
})
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Similar to git show -s
|
||||
var obj *object.Commit
|
||||
obj, z.Error = z.repo.CommitObject(ph)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if obj.String() != "" {
|
||||
fmt.Printf("Status of Git\n\trepo: %s\n\tdir: %s\n%s\n",
|
||||
z.RepoUrl,
|
||||
z.RepoDir,
|
||||
obj.String(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//PrintError(z.Error)
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) Pull() error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
pk := z.GetSshAuth()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("Pulling Git\n\trepo: %s\n\tdir: %s\n", z.RepoUrl, z.RepoDir)
|
||||
z.Error = z.worktree.Pull(&git.PullOptions {
|
||||
RemoteName: "",
|
||||
ReferenceName: "",
|
||||
SingleBranch: false,
|
||||
Depth: 0,
|
||||
Auth: pk,
|
||||
RecurseSubmodules: 0,
|
||||
Progress: os.Stdout,
|
||||
Force: false,
|
||||
InsecureSkipTLS: false,
|
||||
CABundle: nil,
|
||||
})
|
||||
if z.Error.Error() == "already up-to-date" {
|
||||
z.Error = nil
|
||||
break
|
||||
}
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//PrintError(z.Error)
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) Push() error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
z.Error = z.Commit("Updated")
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
pk := z.GetSshAuth()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("Pushing Git\n\trepo: %s\n\tdir: %s\n", z.RepoUrl, z.RepoDir)
|
||||
z.Error = z.repo.Push(&git.PushOptions{
|
||||
RemoteName: "",
|
||||
RefSpecs: nil,
|
||||
Auth: pk,
|
||||
Progress: os.Stdout,
|
||||
Prune: false,
|
||||
Force: false,
|
||||
InsecureSkipTLS: false,
|
||||
CABundle: nil,
|
||||
RequireRemoteRefs: nil,
|
||||
})
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//PrintError(z.Error)
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) Diff(path string) error {
|
||||
|
||||
for range Only.Once {
|
||||
var c []CommitDiffs
|
||||
|
||||
c, z.Error = z.GetDiffs(path)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if len(c) < 2 {
|
||||
fmt.Printf("Not enough revisions to compare.\n")
|
||||
break
|
||||
}
|
||||
|
||||
f1 := fmt.Sprintf("%s-%s", path, c[0].Hash)
|
||||
f1, z.Error = WriteTempFile(f1, c[0].Contents)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
f2 := fmt.Sprintf("%s-%s", path, c[1].Hash)
|
||||
f2, z.Error = WriteTempFile(f2, c[1].Contents)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if z.DiffCmd == "" {
|
||||
z.DiffCmd = "tkdiff"
|
||||
}
|
||||
z.DiffCmd, z.Error = exec.LookPath(z.DiffCmd)
|
||||
|
||||
cmd := exec.Command(z.DiffCmd, f1, f2)
|
||||
|
||||
var out []byte
|
||||
out, z.Error = cmd.Output()
|
||||
//if z.Error != nil {
|
||||
// break
|
||||
//}
|
||||
|
||||
fmt.Printf("# %s\n", cmd.String())
|
||||
|
||||
fmt.Println(string(out))
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//PrintError(z.Error)
|
||||
return z.Error
|
||||
}
|
||||
|
||||
type CommitDiffs struct {
|
||||
Hash string
|
||||
Contents string
|
||||
}
|
||||
|
||||
func (z *Git) GetDiffs(path string) ([]CommitDiffs, error) {
|
||||
var ret []CommitDiffs
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("Diff Git\n\trepo: %s\n\tdir: %s\n", z.RepoUrl, z.RepoDir)
|
||||
ref, _ := z.repo.Head()
|
||||
//fmt.Printf("ref '%s'\n", ref.String())
|
||||
|
||||
commit, _ := z.repo.CommitObject(ref.Hash())
|
||||
//fmt.Printf("commit '%s'\n", commit.String())
|
||||
|
||||
var comm []*object.Commit
|
||||
commitIter, _ := z.repo.Log(&git.LogOptions{From: commit.Hash})
|
||||
|
||||
_ = commitIter.ForEach(func(c *object.Commit) error {
|
||||
comm = append(comm, c)
|
||||
return nil
|
||||
})
|
||||
|
||||
var lastHash string
|
||||
//for k := 0; k < len(comm)-1; k++ {
|
||||
for k, _ := range comm {
|
||||
fmt.Printf("# Commit number[%d]: %s", k, comm[k].Hash)
|
||||
f2, _ := comm[k].File(path)
|
||||
if f2 == nil {
|
||||
fmt.Println(" - no path")
|
||||
continue
|
||||
}
|
||||
|
||||
fc, _ := f2.Contents()
|
||||
hs := GetHash(fc)
|
||||
if hs == lastHash {
|
||||
fmt.Println(" - no change")
|
||||
continue
|
||||
}
|
||||
lastHash = hs
|
||||
|
||||
ret = append(ret, CommitDiffs{
|
||||
Hash: comm[k].Hash.String(),
|
||||
Contents: fc,
|
||||
})
|
||||
fmt.Println(" - changed")
|
||||
}
|
||||
}
|
||||
|
||||
return ret, z.Error
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package mmGit
|
||||
|
||||
import (
|
||||
"GoSungrow/Only"
|
||||
"fmt"
|
||||
memfs "github.com/go-git/go-billy/v5/memfs"
|
||||
git "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||
memory "github.com/go-git/go-git/v5/storage/memory"
|
||||
"os"
|
||||
//"net/http"
|
||||
)
|
||||
|
||||
func (z *Git) MemConnect() error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
//auth := &http.BasicAuth {
|
||||
// ApiUsername: z.ApiUsername,
|
||||
// ApiPassword: z.ApiPassword,
|
||||
//}
|
||||
|
||||
var auth ssh.AuthMethod
|
||||
auth, z.Error = ssh.DefaultAuthBuilder("admin-mickh")
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
//if z.Error = r.Push(&git.PushOptions{Auth: sshAuth}); err != nil {
|
||||
// log.Error().Err(err).Msg("Push err")
|
||||
// os.Exit(1)
|
||||
//}
|
||||
//if z.Error = r.Push(&git.PushOptions{Auth: sshAuth}); err != nil {
|
||||
// log.Error().Err(err).Msg("Push err")
|
||||
// os.Exit(1)
|
||||
//}
|
||||
|
||||
//s := fmt.Sprintf("%s/.ssh/id_rsa", os.Getenv("HOME"))
|
||||
//sshKey, err = ioutil.FileRead(s)
|
||||
//signer, err := ssh.ParsePrivateKey([]byte(sshKey))
|
||||
//auth = &gitssh.PublicKeys{User: "git", Signer: signer}
|
||||
|
||||
z.storer = memory.NewStorage()
|
||||
z.fs = memfs.New()
|
||||
|
||||
z.repo, z.Error = git.Clone(z.storer, z.fs, &git.CloneOptions{
|
||||
URL: z.RepoUrl,
|
||||
Auth: auth,
|
||||
})
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) MemSaveFile(fn string, data []byte) error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
z.worktree, z.Error = z.repo.Worktree()
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
//var fh fs.File
|
||||
//var fi os.FileInfo
|
||||
//fi, z.Error = z.fs.Stat(fn)
|
||||
//if errors.Is(z.Error, os.ErrNotExist) {
|
||||
// // Create new file
|
||||
// fh, z.Error = z.fs.Create(fn)
|
||||
//} else {
|
||||
// // Open file
|
||||
// fh, z.Error = z.fs.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0664)
|
||||
//}
|
||||
|
||||
fh, err := z.fs.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0664)
|
||||
if err != nil {
|
||||
z.Error = err
|
||||
break
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
_, z.Error = fh.Write(data)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Run git status before adding the file to the worktree
|
||||
fmt.Println(z.worktree.Status())
|
||||
|
||||
// git add $filePath
|
||||
_, z.Error = z.worktree.Add(fn)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Run git status after the file has been added adding to the worktree
|
||||
fmt.Println(z.worktree.Status())
|
||||
|
||||
// git commit -m $message
|
||||
msg := fmt.Sprintf("Updated file '%s'", fn)
|
||||
_, z.Error = z.worktree.Commit(msg, &git.CommitOptions{})
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) MemCommit(msg string, args ...interface{}) error {
|
||||
|
||||
for range Only.Once {
|
||||
if z.IsNotOk() {
|
||||
break
|
||||
}
|
||||
|
||||
// Similar to git commit -m $message
|
||||
msg := fmt.Sprintf(msg, args...)
|
||||
_, z.Error = z.worktree.Commit(msg, &git.CommitOptions{})
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
159
mmGit/struct.go
159
mmGit/struct.go
@ -1,159 +0,0 @@
|
||||
package mmGit
|
||||
|
||||
import (
|
||||
"GoSungrow/Only"
|
||||
"errors"
|
||||
"github.com/go-git/go-billy/v5"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
)
|
||||
|
||||
|
||||
type Git struct {
|
||||
Username string
|
||||
Password string
|
||||
KeyFile string
|
||||
Token string
|
||||
RepoUrl string
|
||||
RepoDir string
|
||||
DiffCmd string
|
||||
|
||||
repo *git.Repository
|
||||
|
||||
modeMemory bool
|
||||
worktree *git.Worktree
|
||||
storer *memory.Storage
|
||||
fs billy.Filesystem
|
||||
|
||||
Error error
|
||||
}
|
||||
|
||||
func New() *Git {
|
||||
var ret Git
|
||||
|
||||
for range Only.Once {
|
||||
}
|
||||
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (z *Git) SetAuth(username string, password string) error {
|
||||
|
||||
for range Only.Once {
|
||||
if username == "" {
|
||||
z.Error = errors.New("username empty")
|
||||
break
|
||||
}
|
||||
z.Username = username
|
||||
|
||||
if password == "" {
|
||||
z.Error = errors.New("password empty")
|
||||
break
|
||||
}
|
||||
z.Password = password
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) SetKeyFile(path string) error {
|
||||
|
||||
for range Only.Once {
|
||||
if path == "" {
|
||||
break
|
||||
}
|
||||
|
||||
z.Error = checkKeyFile(path)
|
||||
if z.Error != nil {
|
||||
break
|
||||
}
|
||||
|
||||
z.KeyFile = path
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) SetToken(t string) error {
|
||||
|
||||
for range Only.Once {
|
||||
if t == "" {
|
||||
break
|
||||
}
|
||||
|
||||
z.Token = t
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) SetRepo(repo string) error {
|
||||
|
||||
for range Only.Once {
|
||||
if repo == "" {
|
||||
z.Error = errors.New("repo empty")
|
||||
break
|
||||
}
|
||||
z.RepoUrl = repo
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) SetDir(dir string) error {
|
||||
|
||||
for range Only.Once {
|
||||
if dir == "" {
|
||||
z.Error = errors.New("dir empty")
|
||||
break
|
||||
}
|
||||
z.RepoDir = dir
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) SetDiffCmd(cmd string) error {
|
||||
|
||||
for range Only.Once {
|
||||
if cmd == "" {
|
||||
cmd = "tkdiff"
|
||||
}
|
||||
z.DiffCmd = cmd
|
||||
}
|
||||
|
||||
return z.Error
|
||||
}
|
||||
|
||||
func (z *Git) IsOk() bool {
|
||||
var ok bool
|
||||
|
||||
for range Only.Once {
|
||||
//if z.ApiUsername == "" {
|
||||
// z.Error = errors.New("username empty")
|
||||
// break
|
||||
//}
|
||||
//
|
||||
//if z.ApiPassword == "" {
|
||||
// z.Error = errors.New("password empty")
|
||||
// break
|
||||
//}
|
||||
|
||||
if z.RepoUrl == "" {
|
||||
z.Error = errors.New("repo empty")
|
||||
break
|
||||
}
|
||||
|
||||
if z.RepoDir == "" {
|
||||
z.Error = errors.New("repo dir empty")
|
||||
break
|
||||
}
|
||||
|
||||
ok = true
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
func (z *Git) IsNotOk() bool {
|
||||
return !z.IsOk()
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package mmGit
|
||||
|
||||
import (
|
||||
"GoSungrow/Only"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
|
||||
func IsDirExists(path string) (bool, error) {
|
||||
var ok bool
|
||||
var err error
|
||||
|
||||
for range Only.Once {
|
||||
if path == "" {
|
||||
err = errors.New("empty git dir path")
|
||||
break
|
||||
}
|
||||
|
||||
var fi os.FileInfo
|
||||
fi, err = os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
err = errors.New("git path is not a dir")
|
||||
break
|
||||
}
|
||||
|
||||
ok = true
|
||||
}
|
||||
|
||||
return ok, err
|
||||
}
|
||||
|
||||
func PrintError(err error) {
|
||||
if err == nil {
|
||||
fmt.Println("OK")
|
||||
return
|
||||
}
|
||||
fmt.Printf("\nERROR: %s\n", err)
|
||||
}
|
||||
|
||||
func GetHash(data string) string {
|
||||
var ret string
|
||||
|
||||
for range Only.Once {
|
||||
h := md5.New()
|
||||
h.Write([]byte(data))
|
||||
ret = hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func WriteTempFile(path string, data string) (string, error) {
|
||||
var fn string
|
||||
var err error
|
||||
|
||||
for range Only.Once {
|
||||
fn = fmt.Sprintf("%s/%s", os.TempDir(), path)
|
||||
fmt.Printf("Writing file %s ...", fn)
|
||||
var fh *os.File
|
||||
fh, err = os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0664)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
break
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
_, err = fh.Write([]byte(data))
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
break
|
||||
}
|
||||
fmt.Println("OK")
|
||||
|
||||
//fmt.Printf("%v\n", data)
|
||||
}
|
||||
|
||||
return fn, err
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user