diff --git a/bluez/profile/device/types.go b/bluez/profile/device/types.go new file mode 100644 index 00000000..4628b642 --- /dev/null +++ b/bluez/profile/device/types.go @@ -0,0 +1,8 @@ +package device + +import "github.com/godbus/dbus/v5" + +type SetsItem struct { + Object dbus.ObjectPath + Dict map[string]byte +} diff --git a/gen/generator/generator_tpl.go b/gen/generator/generator_tpl.go index 6e837cda..18328ebc 100644 --- a/gen/generator/generator_tpl.go +++ b/gen/generator/generator_tpl.go @@ -64,8 +64,13 @@ func ErrorsTemplate(filename string, apis []*types.ApiGroup) error { } for i, err := range errors { + base := err[strings.LastIndex(err, ".")+1:] + if strings.Contains(err, "obex") { + base = "Obex" + base + } errorsList.List[i] = types.BluezError{ - Name: strings.Replace(err, "org.bluez.Error.", "", 1), + Name: err, + Base: base, } } diff --git a/gen/generator/generator_util.go b/gen/generator/generator_util.go index 0c1db2cf..dd90b715 100644 --- a/gen/generator/generator_util.go +++ b/gen/generator/generator_util.go @@ -6,12 +6,13 @@ import ( "strings" "text/template" + "github.com/muka/go-bluetooth/gen/override" "github.com/muka/go-bluetooth/gen/types" ) var TplPath = "gen/generator/tpl/%s.go.tpl" -//rename variable name to avoid collision with Go languages +// rename variable name to avoid collision with Go languages func renameReserved(varname string) string { switch varname { case "type": @@ -61,9 +62,17 @@ func prepareDocs(src string, skipFirstComment bool, leftpad int) string { } func getApiPackage(apiGroup *types.ApiGroup) string { - apiName := strings.Replace(apiGroup.FileName, "-api.txt", "", -1) - apiName = strings.Replace(apiName, "-", "_", -1) - apiName = strings.Replace(apiName, " [experimental]", "", -1) + apiName, ok := override.MapFile(apiGroup.FileName) + if !ok { + apiName = apiGroup.FileName + } + apiName = strings.ReplaceAll(apiName, "-api.txt", "") + apiName = strings.ReplaceAll(apiName, "_api.txt", "") + apiName = strings.ReplaceAll(apiName, "org.bluez.", "") + apiName = strings.ReplaceAll(apiName, ".rst", "") + apiName = strings.ReplaceAll(apiName, "-", "_") + apiName = strings.ReplaceAll(apiName, " [experimental]", "") + apiName = strings.ToLower(apiName) return apiName } diff --git a/gen/generator/tpl/errors.go.tpl b/gen/generator/tpl/errors.go.tpl index 18f70a2c..a1418334 100644 --- a/gen/generator/tpl/errors.go.tpl +++ b/gen/generator/tpl/errors.go.tpl @@ -8,10 +8,10 @@ import ( var ( {{- range .List }} - // {{.Name}} map to org.bluez.Error.{{.Name}} - Err{{.Name}} = dbus.Error{ - Name: "org.bluez.Error.{{.Name}}", - Body: []interface{}{"{{.Name}}"}, + // {{.Base}} map to {{.Name}} + Err{{.Base}} = dbus.Error{ + Name: "{{.Name}}", + Body: []interface{}{"{{.Base}}"}, } {{- end }} ) diff --git a/gen/override/filename.go b/gen/override/filename.go new file mode 100644 index 00000000..db2ec7a9 --- /dev/null +++ b/gen/override/filename.go @@ -0,0 +1,65 @@ +package override + +var filesMap = map[string]string{ + "org.bluez.Adapter.rst": "adapter-api.txt", + "org.bluez.AdminPolicySet.rst": "admin-policy-api.txt", + "org.bluez.AdminPolicyStatus.rst": "admin-policy-api.txt", + "org.bluez.AdvertisementMonitor.rst": "advertisement-monitor-api.txt", + "org.bluez.AdvertisementMonitorManager.rst": "advertisement-monitor-api.txt", + "org.bluez.LEAdvertisement.rst": "advertising-api.txt", + "org.bluez.LEAdvertisingManager.rst": "advertising-api.txt", + "org.bluez.AgentManager.rst": "agent-api.txt", + "org.bluez.Agent.rst": "agent-api.txt", + "org.bluez.Battery.rst": "battery-api.txt", + "org.bluez.BatteryProviderManager.rst": "battery-api.txt", + "org.bluez.BatteryProvider.rst": "battery-api.txt", + "org.bluez.Device.rst": "device-api.txt", + "org.bluez.GattService.rst": "gatt-api.txt", + "org.bluez.GattCharacteristic.rst": "gatt-api.txt", + "org.bluez.GattDescriptor.rst": "gatt-api.txt", + "org.bluez.GattProfile.rst": "gatt-api.txt", + "org.bluez.GattManager.rst": "gatt-api.txt", + "org.bluez.HealthManager.rst": "health-api.txt", + "org.bluez.HealthDevice.rst": "health-api.txt", + "org.bluez.HealthChannel.rst": "health-api.txt", + "org.bluez.Input.rst": "input-api.txt", + "org.bluez.Media.rst": "media-api.txt", + "org.bluez.MediaControl.rst": "media-api.txt", + "org.bluez.MediaPlayer.rst": "media-api.txt", + "org.bluez.MediaFolder.rst": "media-api.txt", + "org.bluez.MediaItem.rst": "media-api.txt", + "org.bluez.MediaEndpoint.rst": "media-api.txt", + "org.bluez.MediaTransport.rst": "media-api.txt", + "org.bluez.mesh.Network.rst": "mesh-api.txt", + "org.bluez.mesh.Node.rst": "mesh-api.txt", + "org.bluez.mesh.Management.rst": "mesh-api.txt", + "org.bluez.mesh.Application.rst": "mesh-api.txt", + "org.bluez.mesh.Element.rst": "mesh-api.txt", + "org.bluez.mesh.Attention.rst": "mesh-api.txt", + "org.bluez.mesh.Provisioner.rst": "mesh-api.txt", + "org.bluez.mesh.ProvisionAgent.rst": "mesh-api.txt", + "org.bluez.Network.rst": "network-api.txt", + "org.bluez.NetworkServer.rst": "network-api.txt", + "org.bluez.obex.AgentManager.rst": "obex-agent-api.txt", + "org.bluez.obex.Agent.rst": "obex-agent-api.txt", + "org.bluez.obex.Client.rst": "obex-api.txt", + "org.bluez.obex.Session.rst": "obex-api.txt", + "org.bluez.obex.Transfer.rst": "obex-api.txt", + "org.bluez.obex.ObjectPush.rst": "obex-api.txt", + "org.bluez.obex.FileTransfer.rst": "obex-api.txt", + "org.bluez.obex.PhonebookAccess.rst": "obex-api.txt", + "org.bluez.obex.Synchronization.rst": "obex-api.txt", + "org.bluez.obex.MessageAccess.rst": "obex-api.txt", + "org.bluez.obex.Message.rst": "obex-api.txt", + "org.bluez.ProfileManager.rst": "profile-api.txt", + "org.bluez.Profile.rst": "profile-api.txt", + "org.bluez.SimAccess.rst": "sap-api.txt", + "org.bluez.ThermometerManager.rst": "thermometer-api.txt", + "org.bluez.Thermometer.rst": "thermometer-api.txt", + "org.bluez.ThermometerWatcher.rst": "thermometer-api.txt", +} + +func MapFile(rawfile string) (string, bool) { + res, ok := filesMap[rawfile] + return res, ok +} diff --git a/gen/override/properties.go b/gen/override/properties.go index 88923148..2f3a4c62 100644 --- a/gen/override/properties.go +++ b/gen/override/properties.go @@ -11,6 +11,10 @@ var PropertyTypes = map[string]map[string]string{ "org.bluez.Device1": { "ServiceData": "map[string]interface{}", "ManufacturerData": "map[uint16]interface{}", + "Sets": "[]SetsItem", + }, + "org.bluez.DeviceSet1": { + "Devices": "[]dbus.ObjectPath", }, "org.bluez.GattCharacteristic1": { "Value": "[]byte `dbus:\"emit\"`", diff --git a/gen/parser/api_group.go b/gen/parser/api_group.go index 5ca81fc7..3b82c514 100644 --- a/gen/parser/api_group.go +++ b/gen/parser/api_group.go @@ -4,6 +4,7 @@ import ( "fmt" "path/filepath" "regexp" + "strings" "github.com/muka/go-bluetooth/gen/filters" "github.com/muka/go-bluetooth/gen/types" @@ -32,7 +33,229 @@ func NewApiGroupParser(debug bool, filtersList []filters.Filter) ApiGroupParser // Parse load a documentation file and parse the content func (g *ApiGroupParser) Parse(srcFile string) (*types.ApiGroup, error) { + if strings.HasSuffix(srcFile, ".txt") { // nolint: gocritic + return g.parseTXT(srcFile) + } else if strings.HasSuffix(srcFile, ".rst") { + return g.parseRST(srcFile) + } else { + log.Errorf("Unknown file type for %s", srcFile) + return nil, fmt.Errorf("Unknown file type.") + } +} + +func (g *ApiGroupParser) getSection(raw, sectionName string, divider rune) (out string, err error) { + pattern := fmt.Sprintf("(?ms)%s\\n%c+\\n(.*?)\\n((Methods|Signals|Properties)\\n[-`]+\\n|\\z)", sectionName, divider) + re := regexp.MustCompile(pattern) + matches := re.FindStringSubmatch(raw) + if len(matches) > 0 { + return strings.Trim(matches[1], " \t\n"), nil + } + + return "", nil +} + +func (g *ApiGroupParser) parseFlags(raw string) (flags []types.Flag, err error) { + for _, f := range strings.Split(raw, ", ") { + var flag types.Flag = 0 + switch f { + case "readonly": + fallthrough + case "read-only": + // log.Printf("f: %v", f) + flag = types.FlagReadOnly + case "writeonly": + fallthrough + case "write-only": + // log.Printf("f: %v", f) + flag = types.FlagWriteOnly + case "readwrite": + fallthrough + case "read-write": + fallthrough + case "read/write": + // log.Printf("f: %v", f) + flag = types.FlagReadWrite + case "experimental": + fallthrough + case "Experimental": + // log.Printf("f: %v", f) + flag = types.FlagExperimental + case "optional": + // log.Printf("f: %v", f) + flag = types.FlagOptional + default: + log.Warnf("Unknown flag %s", f) + } + if flag != 0 { + flags = append(flags, flag) + } + } + + return flags, nil +} + +func (g *ApiGroupParser) parseRST(srcFile string) (*types.ApiGroup, error) { + var err error + apiGroup := g.model + + if g.debug { + log.Debugf("------------------- Parsing %s -------------------", srcFile) + } + + apiGroup.FileName = filepath.Base(srcFile) + apiGroup.Api = make([]*types.Api, 0) + + rawBytes, err := util.ReadFile(srcFile) + if err != nil { + return apiGroup, err + } + + raw := string(rawBytes) + + re := regexp.MustCompile(`-+\n([^\n]+)\n-+\n`) + matches := re.FindStringSubmatch(string(raw)) + + g.model.Name = matches[1] + + section, err := g.getSection(raw, "Description", '=') + if err != nil { + return apiGroup, err + } else if section != "" { + // log.Printf(" RST Description: %+#v", section) + g.model.Description = strings.Trim(section, " \t\n") + } + + api := &types.Api{} + + section, err = g.getSection(raw, "Interface", '=') + if err != nil { + return apiGroup, err + } else if section != "" { + re = regexp.MustCompile(`[:;]?Service[:;]?\s+(.*?)\n`) + matches = re.FindStringSubmatch(section) + api.Service = matches[1] + // log.Printf("Service Match: %+#v", matches) + + re = regexp.MustCompile(`[:;]?Interface[:;]?\s+([^ \n]+)`) + matches = re.FindStringSubmatch(section) + api.Interface = matches[1] + // log.Printf("Interface Match: %+#v", matches) + + re = regexp.MustCompile(`[:;]?Object path[:;]?\s+([^\n]+)`) + matches = re.FindStringSubmatch(section) + api.ObjectPath = matches[1] + // log.Printf("ObjectPath Match: %+#v", matches) + + api.Title = g.model.Name + } + + section, err = g.getSection(raw, "Methods", '-') + switch { + case err != nil: + return apiGroup, err + case section != "": + // log.Printf("Methods Section: %s", section) + + re = regexp.MustCompile("(?ms)^(([^\\s`][^\n]*)\\s(\\w*)\\(([^\n)]*)\\))([^\\n\\r]*)\\n`+\\n(\\n(\\t[^\\n]*[\\n\\r]+|[\\n\\r]+)+)") + methodMatches := re.FindAllStringSubmatch(section, -1) + for _, x := range methodMatches { + method := &types.Method{} + method.Name = x[3] + method.ReturnType = x[2] + method.Docs = strings.Trim(x[6], " \t\n") + // log.Printf(" RST Method: %s - %s - %s, %+#v", x[1], x[2], x[3], x) + + if x[4] != "" { + for _, arg := range strings.Split(x[4], ", ") { + // log.Printf(" RST Method Arg: %+#v", arg) + v := strings.Split(arg, " ") + if len(v) == 1 { // FIXME: This is a horrible hack to deal with org.bluez.Profile.rst -> NewConnection missing a type for the fd argument. + method.Args = append(method.Args, types.Arg{Name: arg, Type: "int32"}) + } else { + method.Args = append(method.Args, types.Arg{Name: v[1], Type: v[0]}) + } + } + } else { + method.Args = []types.Arg{} + } + + re = regexp.MustCompile("Possible errors:\n\n((?:\t:.*:\n)+)") + errors := re.FindStringSubmatch(method.Docs) + // log.Printf(" RST Errors: %+#v", errors) + if errors != nil { + re = regexp.MustCompile("\t:(.*):\n") + errs := re.FindAllStringSubmatch(errors[1], -1) + // log.Printf(" RST Errors: %#v", errs) + for _, x := range errs { + method.Errors = append(method.Errors, x[1]) + } + } else { + method.Errors = []string{} + } + + // log.Printf(" RST Method: %+#v", method) + api.Methods = append(api.Methods, method) + } + default: + api.Methods = []*types.Method{} + } + + api.Signals = []*types.Method{} + + section, err = g.getSection(raw, "Properties", '-') + switch { + case err != nil: + return apiGroup, err + case section != "": + // log.Printf("Properties Section: %s", section) + + // re := regexp.MustCompile("(?:\\n|^)((.*?) (\\w+)(?: \\[([a-z, -]+)\\])?)\\n`+\\n(\\n(\\t[^\\n]*[\\n\\r]+|[\\n\\r]+)+)") + // re := regexp.MustCompile("(?:\\n|^)((.*?) (\\w+)(?: \\[([a-z, -]+)\\])?)\\n`+\\n((\\n(\\t[^\\n]*[\\n\\r]+|[\\n\\r]+))+)") + re := regexp.MustCompile( + "(?ms:^)((.*?)" + + " (\\w+)" + + "(?: \\[([a-z, -]+)\\])?" + + ")(?: \\(Default:.*\\))?\\n`+\\n+" + + + "(" + + "(?:" + + "\\t.+" + + "|" + + "\\n" + + ")*" + + ")") + // re := regexp.MustCompile("(?:\\n|^)((.*?) (\\w+)(?: \\[([a-z, -]+)\\])?)\\n`+") + matches := re.FindAllStringSubmatch(section, -1) + // log.Printf(" RST Properties: %+#v", matches) + + for _, x := range matches { + property := &types.Property{} + property.Name = x[3] + property.Type = x[2] + property.Docs = strings.Trim(x[5], " \t\n") + // property.Docs = x[5] + // log.Printf(" RST Property: %s - %s - %s - %s, %+#v", x[1], x[2], x[3], x[4], x) + + if x[4] != "" { + property.Flags, err = g.parseFlags(x[4]) + if err != nil { + return apiGroup, err + } + } + + // log.Printf(" RST Property: %+#v", property) + api.Properties = append(api.Properties, property) + } + default: + api.Properties = []*types.Property{} + } + + // One API per file for the RST files. + apiGroup.Api = append(apiGroup.Api, api) + return apiGroup, nil +} +func (g *ApiGroupParser) parseTXT(srcFile string) (*types.ApiGroup, error) { var err error apiGroup := g.model diff --git a/gen/types/generator.go b/gen/types/generator.go index 374c2838..a5203f33 100644 --- a/gen/types/generator.go +++ b/gen/types/generator.go @@ -2,6 +2,7 @@ package types type BluezError struct { Name string + Base string Error string } diff --git a/gen/util/util.go b/gen/util/util.go index bfeb91a1..78d8a937 100644 --- a/gen/util/util.go +++ b/gen/util/util.go @@ -30,7 +30,6 @@ func ListFiles(dir string) ([]string, error) { } err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if info.IsDir() { return nil } @@ -39,7 +38,7 @@ func ListFiles(dir string) ([]string, error) { return nil } - if !strings.HasSuffix(path, "-api.txt") { + if !strings.HasSuffix(path, "-api.txt") && !strings.HasPrefix(filepath.Base(path), "org.bluez.") { return nil } @@ -81,7 +80,7 @@ func Exists(name string) bool { return true } -//GetGitVersion return the docs git version +// GetGitVersion return the docs git version func GetGitVersion(docsDir string) (string, error) { cmd := exec.Command("git", "describe") cmd.Dir = docsDir